From be39a79f2e68ae9826f0bd404857a9869630b47a Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Mon, 21 Jan 2019 16:14:51 +0100 Subject: [PATCH 01/18] feat xcode: scan the directory for project files --- cmd/xcode.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/cmd/xcode.go b/cmd/xcode.go index 0ea4125c..06d69c81 100644 --- a/cmd/xcode.go +++ b/cmd/xcode.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "os" + "path" "path/filepath" "strings" @@ -81,12 +82,23 @@ func scanXcodeProject(cmd *cobra.Command, args []string) error { projectPath := paramXcodeProjectFilePath if projectPath == "" { - askText := `Please drag-and-drop your Xcode Project (` + colorstring.Green(".xcodeproj") + `) or Workspace (` + colorstring.Green(".xcworkspace") + `) file, + + log.Infof("Scan the directory for project files") + projpth, err := scanForProjectFiles() + if err != nil { + log.Printf("Failed: %s", err) + fmt.Println() + + log.Infof("Provide the project file manually") + askText := `Please drag-and-drop your Xcode Project (` + colorstring.Green(".xcodeproj") + `) or Workspace (` + colorstring.Green(".xcworkspace") + `) file, the one you usually open in Xcode, then hit Enter. (Note: if you have a Workspace file you should most likely use that)` - projpth, err := goinp.AskForPath(askText) - if err != nil { - return fmt.Errorf("failed to read input: %s", err) + projpth, err = goinp.AskForPath(askText) + if err != nil { + return fmt.Errorf("failed to read input: %s", err) + } + } else { + log.Printf("Found one project file: %s.", path.Base(projpth)) } projectPath = strings.Trim(strings.TrimSpace(projpth), "'\"") @@ -156,3 +168,46 @@ the one you usually open in Xcode, then hit Enter. printFinished(provProfilesUploaded, certsUploaded) return nil } + +// Scans the root dir for project files +// If there is a .xcworkspace file in the root dir it will return it's paths +// If there is a .xcodeproject file in the root dir it will return it's paths +// If non of them in the root dir, then it will return an error +func scanForProjectFiles() (string, error) { + root, err := os.Getwd() + if err != nil { + return "", err + } + + workspacePattern := filepath.Join(root, "*.xcworkspace") + workspacePaths, err := filepath.Glob(workspacePattern) + if err != nil { + return "", err + } + + switch len(workspacePaths) { + case 0: + // Search for .xcodeproj + break + case 1: + return workspacePaths[0], nil + default: + return "", fmt.Errorf("multiple .xcworkspace files found in the root (%s), directory: %s", root, strings.Join(workspacePaths, "\n")) + } + + projectPattern := filepath.Join(root, "*.xcodeproj") + projectPaths, err := filepath.Glob(projectPattern) + if err != nil { + return "", err + } + + switch len(projectPaths) { + case 0: + return "", fmt.Errorf("no .xcworkspace or .xcodeproject file found in directory: %s", root) + case 1: + return projectPaths[0], nil + default: + return "", fmt.Errorf("multiple .xcworkspace files found in the root (%s), directory: %s", root, strings.Join(workspacePaths, "\n")) + } + +} From f50d1abcdfbdfd5a5fd5c9c5d4c784a114fc626a Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Mon, 21 Jan 2019 16:23:15 +0100 Subject: [PATCH 02/18] typo --- cmd/xcode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/xcode.go b/cmd/xcode.go index 06d69c81..79af54cf 100644 --- a/cmd/xcode.go +++ b/cmd/xcode.go @@ -172,7 +172,7 @@ the one you usually open in Xcode, then hit Enter. // Scans the root dir for project files // If there is a .xcworkspace file in the root dir it will return it's paths // If there is a .xcodeproject file in the root dir it will return it's paths -// If non of them in the root dir, then it will return an error +// If none of them in the root dir, then it will return an error func scanForProjectFiles() (string, error) { root, err := os.Getwd() if err != nil { From 9a2efe47b86eaed25ca8170a21effdb4d7608e56 Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Mon, 21 Jan 2019 17:31:57 +0100 Subject: [PATCH 03/18] moved the scanForProjectFiles() to cmd/utils.go --- cmd/utils.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ cmd/xcode.go | 45 +-------------------------------------------- 2 files changed, 47 insertions(+), 44 deletions(-) create mode 100644 cmd/utils.go diff --git a/cmd/utils.go b/cmd/utils.go new file mode 100644 index 00000000..baa509ab --- /dev/null +++ b/cmd/utils.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +// ProjectType ... +type ProjectType string + +const ( + // iOS + xcodeWorkspace ProjectType = "*.xcworkspace" + xcodeProject ProjectType = "*.xcodeproj" +) + +// Scans the root dir for the provided project files +// If none of them in the root dir, then it will return an error +func scanForProjectFiles(projectFiles []ProjectType) (string, error) { + root, err := os.Getwd() + if err != nil { + return "", err + } + + for _, projectFile := range projectFiles { + pathPattern := filepath.Join(root, string(projectFile)) + paths, err := filepath.Glob(pathPattern) + if err != nil { + return "", err + } + + switch len(paths) { + case 0: + continue + case 1: + return paths[0], nil + default: + return "", fmt.Errorf("multiple project file (%s) found in the root (%s) directory: %s", projectFile, root, strings.Join(paths, "\n")) + } + } + + return "", fmt.Errorf("no project file found: %s", root) + +} diff --git a/cmd/xcode.go b/cmd/xcode.go index 79af54cf..cbf932df 100644 --- a/cmd/xcode.go +++ b/cmd/xcode.go @@ -84,7 +84,7 @@ func scanXcodeProject(cmd *cobra.Command, args []string) error { if projectPath == "" { log.Infof("Scan the directory for project files") - projpth, err := scanForProjectFiles() + projpth, err := scanForProjectFiles([]ProjectType{xcodeWorkspace, xcodeProject}) if err != nil { log.Printf("Failed: %s", err) fmt.Println() @@ -168,46 +168,3 @@ the one you usually open in Xcode, then hit Enter. printFinished(provProfilesUploaded, certsUploaded) return nil } - -// Scans the root dir for project files -// If there is a .xcworkspace file in the root dir it will return it's paths -// If there is a .xcodeproject file in the root dir it will return it's paths -// If none of them in the root dir, then it will return an error -func scanForProjectFiles() (string, error) { - root, err := os.Getwd() - if err != nil { - return "", err - } - - workspacePattern := filepath.Join(root, "*.xcworkspace") - workspacePaths, err := filepath.Glob(workspacePattern) - if err != nil { - return "", err - } - - switch len(workspacePaths) { - case 0: - // Search for .xcodeproj - break - case 1: - return workspacePaths[0], nil - default: - return "", fmt.Errorf("multiple .xcworkspace files found in the root (%s), directory: %s", root, strings.Join(workspacePaths, "\n")) - } - - projectPattern := filepath.Join(root, "*.xcodeproj") - projectPaths, err := filepath.Glob(projectPattern) - if err != nil { - return "", err - } - - switch len(projectPaths) { - case 0: - return "", fmt.Errorf("no .xcworkspace or .xcodeproject file found in directory: %s", root) - case 1: - return projectPaths[0], nil - default: - return "", fmt.Errorf("multiple .xcworkspace files found in the root (%s), directory: %s", root, strings.Join(workspacePaths, "\n")) - } - -} From f53835b0931c0d8cca65837b715c186c44cb3d20 Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Mon, 4 Feb 2019 12:53:29 +0100 Subject: [PATCH 04/18] project dir scan for the project files like in the scanner --- cmd/utils.go | 225 ++++++++++++++++++++++++++++++++++++++++++++++----- cmd/xcode.go | 14 +++- 2 files changed, 215 insertions(+), 24 deletions(-) diff --git a/cmd/utils.go b/cmd/utils.go index baa509ab..b85ba2b2 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -4,43 +4,224 @@ import ( "fmt" "os" "path/filepath" - "strings" + + "github.com/bitrise-core/bitrise-init/utility" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-tools/go-xcode/xcodeproj" ) -// ProjectType ... -type ProjectType string +// IDEType ... +type IDEType string const ( + xcodeIDE IDEType = "iOS" + + // // iOS - xcodeWorkspace ProjectType = "*.xcworkspace" - xcodeProject ProjectType = "*.xcodeproj" + embeddedWorkspacePathPattern = `.+\.xcodeproj/.+\.xcworkspace` + + gitDirName = ".git" + podsDirName = "Pods" + carthageDirName = "Carthage" + cordovaLibDirName = "CordovaLib" + + frameworkExt = ".framework" + + // + // Xamarin + solutionExtension = ".sln" + componentsDirName = "Components" + + // NodeModulesDirName ... + NodeModulesDirName = "node_modules" + + solutionConfigurationStart = "GlobalSection(SolutionConfigurationPlatforms) = preSolution" + solutionConfigurationEnd = "EndGlobalSection" ) // Scans the root dir for the provided project files -// If none of them in the root dir, then it will return an error -func scanForProjectFiles(projectFiles []ProjectType) (string, error) { - root, err := os.Getwd() +func scanForProjectFiles(ideType IDEType) ([]string, error) { + searchDir, err := os.Getwd() if err != nil { - return "", err + return nil, err + } + + fileList, err := utility.ListPathInDirSortedByComponents(searchDir, false) + if err != nil { + return nil, fmt.Errorf("failed to search for files in (%s), error: %s", searchDir, err) + } + + var paths []string + { + if ideType == xcodeIDE { + paths, err = FilterRelevantWorkspaceFiles(fileList) + if err != nil { + return nil, fmt.Errorf("failed to search for solution files, error: %s", err) + } + + if len(paths) == 0 { + paths, err = FilterRelevantProjectFiles(fileList) + if err != nil { + return nil, fmt.Errorf("failed to search for solution files, error: %s", err) + } + } + } else { + paths, err = FilterSolutionFiles(fileList) + if err != nil { + return nil, fmt.Errorf("failed to search for solution files, error: %s", err) + } + } + } - for _, projectFile := range projectFiles { - pathPattern := filepath.Join(root, string(projectFile)) - paths, err := filepath.Glob(pathPattern) - if err != nil { - return "", err + if len(paths) == 0 { + return nil, fmt.Errorf("no project file found: %s", searchDir) + + } + return paths, nil +} + +// +// iOS + +// XcodeProjectType ... +type XcodeProjectType string + +// AllowXcodeProjExtFilter ... +var AllowXcodeProjExtFilter = utility.ExtensionFilter(xcodeproj.XCodeProjExt, true) + +// AllowXCWorkspaceExtFilter ... +var AllowXCWorkspaceExtFilter = utility.ExtensionFilter(xcodeproj.XCWorkspaceExt, true) + +// AllowIsDirectoryFilter ... +var AllowIsDirectoryFilter = utility.IsDirectoryFilter(true) + +// ForbidEmbeddedWorkspaceRegexpFilter ... +var ForbidEmbeddedWorkspaceRegexpFilter = utility.RegexpFilter(embeddedWorkspacePathPattern, false) + +// ForbidGitDirComponentFilter ... +var ForbidGitDirComponentFilter = utility.ComponentFilter(gitDirName, false) + +// ForbidPodsDirComponentFilter ... +var ForbidPodsDirComponentFilter = utility.ComponentFilter(podsDirName, false) + +// ForbidCarthageDirComponentFilter ... +var ForbidCarthageDirComponentFilter = utility.ComponentFilter(carthageDirName, false) + +// ForbidCordovaLibDirComponentFilter ... +var ForbidCordovaLibDirComponentFilter = utility.ComponentFilter(cordovaLibDirName, false) + +// ForbidFramworkComponentWithExtensionFilter ... +var ForbidFramworkComponentWithExtensionFilter = utility.ComponentWithExtensionFilter(frameworkExt, false) + +// ForbidNodeModulesComponentFilter ... +var ForbidNodeModulesComponentFilter = utility.ComponentFilter(NodeModulesDirName, false) + +// AllowIphoneosSDKFilter ... +var AllowIphoneosSDKFilter = SDKFilter("iphoneos", true) + +// AllowMacosxSDKFilter ... +var AllowMacosxSDKFilter = SDKFilter("macosx", true) + +// SDKFilter ... +func SDKFilter(sdk string, allowed bool) utility.FilterFunc { + return func(pth string) (bool, error) { + found := false + + projectFiles := []string{} + + if xcodeproj.IsXCodeProj(pth) { + projectFiles = append(projectFiles, pth) + } else if xcodeproj.IsXCWorkspace(pth) { + projects, err := xcodeproj.WorkspaceProjectReferences(pth) + if err != nil { + return false, err + } + + for _, project := range projects { + exist, err := pathutil.IsPathExists(project) + if err != nil { + return false, err + } + if !exist { + continue + } + projectFiles = append(projectFiles, project) + + } + } else { + return false, fmt.Errorf("Not Xcode project nor workspace file: %s", pth) } - switch len(paths) { - case 0: - continue - case 1: - return paths[0], nil - default: - return "", fmt.Errorf("multiple project file (%s) found in the root (%s) directory: %s", projectFile, root, strings.Join(paths, "\n")) + for _, projectFile := range projectFiles { + pbxprojPth := filepath.Join(projectFile, "project.pbxproj") + projectSDKs, err := xcodeproj.GetBuildConfigSDKs(pbxprojPth) + if err != nil { + return false, err + } + + for _, projectSDK := range projectSDKs { + if projectSDK == sdk { + found = true + break + } + } } + + return (allowed == found), nil + } +} + +// FilterRelevantProjectFiles ... +func FilterRelevantProjectFiles(fileList []string, projectTypes ...XcodeProjectType) ([]string, error) { + filters := []utility.FilterFunc{ + AllowXcodeProjExtFilter, + AllowIsDirectoryFilter, + ForbidEmbeddedWorkspaceRegexpFilter, + ForbidGitDirComponentFilter, + ForbidPodsDirComponentFilter, + ForbidCarthageDirComponentFilter, + ForbidFramworkComponentWithExtensionFilter, + ForbidCordovaLibDirComponentFilter, + ForbidNodeModulesComponentFilter, + } + + return utility.FilterPaths(fileList, filters...) +} + +// FilterRelevantWorkspaceFiles ... +func FilterRelevantWorkspaceFiles(fileList []string, projectTypes ...XcodeProjectType) ([]string, error) { + filters := []utility.FilterFunc{ + AllowXCWorkspaceExtFilter, + AllowIsDirectoryFilter, + ForbidEmbeddedWorkspaceRegexpFilter, + ForbidGitDirComponentFilter, + ForbidPodsDirComponentFilter, + ForbidCarthageDirComponentFilter, + ForbidFramworkComponentWithExtensionFilter, + ForbidCordovaLibDirComponentFilter, + ForbidNodeModulesComponentFilter, } - return "", fmt.Errorf("no project file found: %s", root) + return utility.FilterPaths(fileList, filters...) +} + +// +// Xamarin + +var allowSolutionExtensionFilter = utility.ExtensionFilter(solutionExtension, true) +var forbidComponentsSolutionFilter = utility.ComponentFilter(componentsDirName, false) +var forbidNodeModulesDirComponentFilter = utility.ComponentFilter(NodeModulesDirName, false) + +// FilterSolutionFiles ... +func FilterSolutionFiles(fileList []string) ([]string, error) { + files, err := utility.FilterPaths(fileList, + allowSolutionExtensionFilter, + forbidComponentsSolutionFilter, + forbidNodeModulesDirComponentFilter) + if err != nil { + return []string{}, err + } + return files, nil } diff --git a/cmd/xcode.go b/cmd/xcode.go index cbf932df..6b2755ba 100644 --- a/cmd/xcode.go +++ b/cmd/xcode.go @@ -83,8 +83,9 @@ func scanXcodeProject(cmd *cobra.Command, args []string) error { projectPath := paramXcodeProjectFilePath if projectPath == "" { + var projpth string log.Infof("Scan the directory for project files") - projpth, err := scanForProjectFiles([]ProjectType{xcodeWorkspace, xcodeProject}) + projPaths, err := scanForProjectFiles(xcodeIDE) if err != nil { log.Printf("Failed: %s", err) fmt.Println() @@ -98,7 +99,16 @@ the one you usually open in Xcode, then hit Enter. return fmt.Errorf("failed to read input: %s", err) } } else { - log.Printf("Found one project file: %s.", path.Base(projpth)) + if len(projPaths) == 1 { + log.Printf("Found one project file: %s.", path.Base(projPaths[0])) + projpth = projPaths[0] + } else { + log.Printf("Found multiple project file: %s.", path.Base(projpth)) + projpth, err = goinp.SelectFromStringsWithDefault("Select the Scheme you usually use in Xcode", 1, projPaths) + if err != nil { + return fmt.Errorf("failed to select Scheme: %s", err) + } + } } projectPath = strings.Trim(strings.TrimSpace(projpth), "'\"") From 6ff2fe7c3c94a32305ac2ae58f3931737dff2729 Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Mon, 4 Feb 2019 13:08:40 +0100 Subject: [PATCH 05/18] clean --- cmd/utils.go | 178 ++------------------------------------------------- 1 file changed, 5 insertions(+), 173 deletions(-) diff --git a/cmd/utils.go b/cmd/utils.go index b85ba2b2..4a32131e 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -3,11 +3,10 @@ package cmd import ( "fmt" "os" - "path/filepath" + "github.com/bitrise-core/bitrise-init/scanners/ios" + "github.com/bitrise-core/bitrise-init/scanners/xamarin" "github.com/bitrise-core/bitrise-init/utility" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-tools/go-xcode/xcodeproj" ) // IDEType ... @@ -15,28 +14,6 @@ type IDEType string const ( xcodeIDE IDEType = "iOS" - - // - // iOS - embeddedWorkspacePathPattern = `.+\.xcodeproj/.+\.xcworkspace` - - gitDirName = ".git" - podsDirName = "Pods" - carthageDirName = "Carthage" - cordovaLibDirName = "CordovaLib" - - frameworkExt = ".framework" - - // - // Xamarin - solutionExtension = ".sln" - componentsDirName = "Components" - - // NodeModulesDirName ... - NodeModulesDirName = "node_modules" - - solutionConfigurationStart = "GlobalSection(SolutionConfigurationPlatforms) = preSolution" - solutionConfigurationEnd = "EndGlobalSection" ) // Scans the root dir for the provided project files @@ -54,19 +31,19 @@ func scanForProjectFiles(ideType IDEType) ([]string, error) { var paths []string { if ideType == xcodeIDE { - paths, err = FilterRelevantWorkspaceFiles(fileList) + paths, err = ios.FilterRelevantWorkspaceFiles(fileList) if err != nil { return nil, fmt.Errorf("failed to search for solution files, error: %s", err) } if len(paths) == 0 { - paths, err = FilterRelevantProjectFiles(fileList) + paths, err = ios.FilterRelevantProjectFiles(fileList) if err != nil { return nil, fmt.Errorf("failed to search for solution files, error: %s", err) } } } else { - paths, err = FilterSolutionFiles(fileList) + paths, err = xamarin.FilterSolutionFiles(fileList) if err != nil { return nil, fmt.Errorf("failed to search for solution files, error: %s", err) } @@ -80,148 +57,3 @@ func scanForProjectFiles(ideType IDEType) ([]string, error) { } return paths, nil } - -// -// iOS - -// XcodeProjectType ... -type XcodeProjectType string - -// AllowXcodeProjExtFilter ... -var AllowXcodeProjExtFilter = utility.ExtensionFilter(xcodeproj.XCodeProjExt, true) - -// AllowXCWorkspaceExtFilter ... -var AllowXCWorkspaceExtFilter = utility.ExtensionFilter(xcodeproj.XCWorkspaceExt, true) - -// AllowIsDirectoryFilter ... -var AllowIsDirectoryFilter = utility.IsDirectoryFilter(true) - -// ForbidEmbeddedWorkspaceRegexpFilter ... -var ForbidEmbeddedWorkspaceRegexpFilter = utility.RegexpFilter(embeddedWorkspacePathPattern, false) - -// ForbidGitDirComponentFilter ... -var ForbidGitDirComponentFilter = utility.ComponentFilter(gitDirName, false) - -// ForbidPodsDirComponentFilter ... -var ForbidPodsDirComponentFilter = utility.ComponentFilter(podsDirName, false) - -// ForbidCarthageDirComponentFilter ... -var ForbidCarthageDirComponentFilter = utility.ComponentFilter(carthageDirName, false) - -// ForbidCordovaLibDirComponentFilter ... -var ForbidCordovaLibDirComponentFilter = utility.ComponentFilter(cordovaLibDirName, false) - -// ForbidFramworkComponentWithExtensionFilter ... -var ForbidFramworkComponentWithExtensionFilter = utility.ComponentWithExtensionFilter(frameworkExt, false) - -// ForbidNodeModulesComponentFilter ... -var ForbidNodeModulesComponentFilter = utility.ComponentFilter(NodeModulesDirName, false) - -// AllowIphoneosSDKFilter ... -var AllowIphoneosSDKFilter = SDKFilter("iphoneos", true) - -// AllowMacosxSDKFilter ... -var AllowMacosxSDKFilter = SDKFilter("macosx", true) - -// SDKFilter ... -func SDKFilter(sdk string, allowed bool) utility.FilterFunc { - return func(pth string) (bool, error) { - found := false - - projectFiles := []string{} - - if xcodeproj.IsXCodeProj(pth) { - projectFiles = append(projectFiles, pth) - } else if xcodeproj.IsXCWorkspace(pth) { - projects, err := xcodeproj.WorkspaceProjectReferences(pth) - if err != nil { - return false, err - } - - for _, project := range projects { - exist, err := pathutil.IsPathExists(project) - if err != nil { - return false, err - } - if !exist { - continue - } - projectFiles = append(projectFiles, project) - - } - } else { - return false, fmt.Errorf("Not Xcode project nor workspace file: %s", pth) - } - - for _, projectFile := range projectFiles { - pbxprojPth := filepath.Join(projectFile, "project.pbxproj") - projectSDKs, err := xcodeproj.GetBuildConfigSDKs(pbxprojPth) - if err != nil { - return false, err - } - - for _, projectSDK := range projectSDKs { - if projectSDK == sdk { - found = true - break - } - } - } - - return (allowed == found), nil - } -} - -// FilterRelevantProjectFiles ... -func FilterRelevantProjectFiles(fileList []string, projectTypes ...XcodeProjectType) ([]string, error) { - filters := []utility.FilterFunc{ - AllowXcodeProjExtFilter, - AllowIsDirectoryFilter, - ForbidEmbeddedWorkspaceRegexpFilter, - ForbidGitDirComponentFilter, - ForbidPodsDirComponentFilter, - ForbidCarthageDirComponentFilter, - ForbidFramworkComponentWithExtensionFilter, - ForbidCordovaLibDirComponentFilter, - ForbidNodeModulesComponentFilter, - } - - return utility.FilterPaths(fileList, filters...) -} - -// FilterRelevantWorkspaceFiles ... -func FilterRelevantWorkspaceFiles(fileList []string, projectTypes ...XcodeProjectType) ([]string, error) { - filters := []utility.FilterFunc{ - AllowXCWorkspaceExtFilter, - AllowIsDirectoryFilter, - ForbidEmbeddedWorkspaceRegexpFilter, - ForbidGitDirComponentFilter, - ForbidPodsDirComponentFilter, - ForbidCarthageDirComponentFilter, - ForbidFramworkComponentWithExtensionFilter, - ForbidCordovaLibDirComponentFilter, - ForbidNodeModulesComponentFilter, - } - - return utility.FilterPaths(fileList, filters...) -} - -// -// Xamarin - -var allowSolutionExtensionFilter = utility.ExtensionFilter(solutionExtension, true) -var forbidComponentsSolutionFilter = utility.ComponentFilter(componentsDirName, false) -var forbidNodeModulesDirComponentFilter = utility.ComponentFilter(NodeModulesDirName, false) - -// FilterSolutionFiles ... -func FilterSolutionFiles(fileList []string) ([]string, error) { - files, err := utility.FilterPaths(fileList, - allowSolutionExtensionFilter, - forbidComponentsSolutionFilter, - forbidNodeModulesDirComponentFilter) - if err != nil { - return []string{}, err - } - - return files, nil -} From 6fd9ac46a62589fe5931b18f8cec327252884972 Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Mon, 4 Feb 2019 13:09:06 +0100 Subject: [PATCH 06/18] deps: scanner/xamarin & scanner/ios --- Gopkg.lock | 73 +- Gopkg.toml | 4 + .../bitrise-core/bitrise-init/.gitignore | 7 + .../bitrise-core/bitrise-init/Dockerfile | 3 + .../bitrise-core/bitrise-init/Gopkg.lock | 223 ++ .../bitrise-core/bitrise-init/Gopkg.toml | 51 + .../bitrise-core/bitrise-init/README.md | 52 + .../bitrise-init/_scripts/set_version.sh | 20 + .../_tests/integration/android_test.go | 545 +++ .../_tests/integration/cordova_test.go | 208 ++ .../_tests/integration/fastlane_test.go | 182 + .../_tests/integration/flutter_test.go | 1576 ++++++++ .../bitrise-init/_tests/integration/helper.go | 66 + .../_tests/integration/ionic_test.go | 99 + .../_tests/integration/ios_test.go | 573 +++ .../_tests/integration/mac_test.go | 133 + .../_tests/integration/manual_config_test.go | 1699 +++++++++ .../integration/reactnative_expo_test.go | 446 +++ .../_tests/integration/reactnative_test.go | 299 ++ .../_tests/integration/xamarin_test.go | 281 ++ .../bitrise-core/bitrise-init/bitrise.yml | 155 + .../bitrise-core/bitrise-init/cli/cli.go | 72 + .../bitrise-core/bitrise-init/cli/config.go | 185 + .../bitrise-init/cli/manual_config.go | 132 + .../bitrise-core/bitrise-init/cli/version.go | 79 + .../bitrise-init/docker-compose.yml | 7 + .../bitrise-core/bitrise-init/gows.yml | 1 + .../bitrise-core/bitrise-init/main.go | 10 + .../bitrise-init/models/configbuilder.go | 94 + .../bitrise-init/models/model_test.go | 390 ++ .../bitrise-init/models/models.go | 29 + .../bitrise-init/models/option.go | 205 ++ .../bitrise-init/models/workflowbuilder.go | 25 + .../bitrise-init/output/output.go | 120 + .../bitrise-init/release_config.yml | 13 + .../bitrise-init/scanner/config.go | 231 ++ .../bitrise-init/scanner/manual_config.go | 38 + .../bitrise-init/scanner/utils.go | 151 + .../bitrise-init/scanners/android/android.go | 123 + .../bitrise-init/scanners/android/const.go | 34 + .../bitrise-init/scanners/android/utility.go | 187 + .../bitrise-init/scanners/cordova/cordova.go | 405 +++ .../scanners/cordova/cordova_test.go | 37 + .../bitrise-init/scanners/cordova/utility.go | 47 + .../scanners/fastlane/fastlane.go | 242 ++ .../scanners/fastlane/fastlane_test.go | 149 + .../fastlane/fastlane_test_file_contents.go | 310 ++ .../bitrise-init/scanners/fastlane/utility.go | 104 + .../bitrise-init/scanners/flutter/flutter.go | 471 +++ .../bitrise-init/scanners/ionic/ionic.go | 406 +++ .../bitrise-init/scanners/ios/gemutil.go | 50 + .../bitrise-init/scanners/ios/gemutil_test.go | 210 ++ .../bitrise-init/scanners/ios/ios.go | 72 + .../bitrise-init/scanners/ios/podfile.go | 306 ++ .../bitrise-init/scanners/ios/podfile_test.go | 609 ++++ .../bitrise-init/scanners/ios/rubyscript.go | 82 + .../scanners/ios/rubyscript_test.go | 23 + .../bitrise-init/scanners/ios/utility.go | 645 ++++ .../bitrise-init/scanners/ios/utility_test.go | 57 + .../bitrise-init/scanners/ios/xcodeproj.go | 272 ++ .../scanners/ios/xcodeproj_test.go | 189 + .../scanners/ios/xcodeproj_test_files.go | 1289 +++++++ .../bitrise-init/scanners/macos/macos.go | 70 + .../reactnative-expo/reactnative-expo.go | 785 ++++ .../scanners/reactnative/reactnative.go | 501 +++ .../scanners/reactnative/utility.go | 56 + .../bitrise-init/scanners/scanners.go | 115 + .../bitrise-init/scanners/xamarin/utility.go | 82 + .../bitrise-init/scanners/xamarin/xamarin.go | 322 ++ .../scanners/xamarin/xamarin_test.go | 38 + .../bitrise-core/bitrise-init/steps/const.go | 262 ++ .../bitrise-core/bitrise-init/steps/steps.go | 283 ++ .../bitrise-init/toolscanner/toolscanner.go | 56 + .../toolscanner/toolscanner_test.go | 170 + .../bitrise-init/utility/sortable_path.go | 90 + .../utility/sortable_path_test.go | 106 + .../bitrise-init/utility/utility.go | 202 ++ .../bitrise-init/utility/utility_test.go | 306 ++ .../bitrise-init/version/build.go | 7 + .../bitrise-init/version/version.go | 4 + .../bitrise-io/bitrise/.codeclimate.yml | 41 + .../github.com/bitrise-io/bitrise/.gitignore | 9 + .../bitrise-io/bitrise/CHANGELOG.md | 3155 +++++++++++++++++ .../github.com/bitrise-io/bitrise/Dockerfile | 34 + .../bitrise-io/bitrise/Godeps/Godeps.json | 135 + .../bitrise-io/bitrise/Godeps/Readme | 5 + vendor/github.com/bitrise-io/bitrise/LICENSE | 22 + .../github.com/bitrise-io/bitrise/README.md | 120 + .../bitrise-io/bitrise/_docs/README.md | 15 + .../bitrise/_docs/bitrise-yml-format-spec.md | 152 + .../bitrise/_docs/cli-how-to-guide.md | 141 + .../bitrise/_docs/cli-introduction.md | 22 + .../bitrise/_docs/cli-react-native.md | 27 + .../bitrise/_docs/cli-share-guide.md | 18 + .../bitrise/_docs/images/success.gif | Bin 0 -> 960828 bytes .../_docs/step-development-guideline.md | 118 + .../bitrise-io/bitrise/_examples/README.md | 4 + .../experimentals/before-after/bitrise.yml | 73 + .../experimentals/create-new-step/bitrise.yml | 18 + .../experimentals/dependencies/bitrise.yml | 103 + .../experimentals/middleman/bitrise.yml | 24 + .../experimentals/middleman/v2.bitrise.yml | 40 + .../experimentals/templates/bitrise.yml | 131 + .../experimentals/timestamp-gen/bitrise.yml | 22 + .../experimentals/trigger-map/bitrise.yml | 58 + .../upload_download_bitrise_io/.gitignore | 2 + .../upload_download_bitrise_io/bitrise.yml | 32 + .../bitrise/_examples/tutorials/README.md | 21 + .../bitrise.yml | 46 + .../tutorials/inputs-outputs-envs/bitrise.yml | 43 + .../tutorials/react-native/bitrise.yml | 36 + .../tutorials/steps-and-workflows/bitrise.yml | 139 + .../steps-timestamp/LICENSE | 22 + .../steps-timestamp/README.md | 2 + .../steps-timestamp/_scripts/ci.sh | 25 + .../steps-timestamp/step.go | 41 + .../steps-timestamp/step.sh | 12 + .../steps-timestamp/step.yml | 17 + .../bitrise-io/bitrise/_lessons/README.md | 22 + .../bitrise/_lessons/lesson1_steps/.gitignore | 2 + .../bitrise/_lessons/lesson1_steps/README.md | 37 + .../_lessons/lesson1_steps/bitrise.yml | 91 + .../_lessons/lesson2_workflow/.gitignore | 2 + .../_lessons/lesson2_workflow/README.md | 88 + .../_lessons/lesson2_workflow/bitrise.yml | 53 + .../lesson3_input_output_env/.gitignore | 2 + .../lesson3_input_output_env/README.md | 19 + .../lesson3_input_output_env/bitrise.yml | 39 + .../_lessons/lesson4_errors/.gitignore | 2 + .../bitrise/_lessons/lesson4_errors/README.md | 17 + .../_lessons/lesson4_errors/bitrise.yml | 51 + .../_lessons/lesson5_complex_wf/.gitignore | 2 + .../_lessons/lesson5_complex_wf/README.md | 133 + .../_lessons/lesson5_complex_wf/bitrise.yml | 148 + .../_lessons/lesson6_triggers/.gitignore | 2 + .../_lessons/lesson6_triggers/README.md | 31 + .../_lessons/lesson6_triggers/bitrise.yml | 97 + .../bitrise/_scripts/build_tools_in_docker.sh | 36 + .../bitrise-io/bitrise/_scripts/ci.sh | 22 + .../create_release_with_docker_compose.sh | 13 + .../bitrise/_scripts/get_version.go | 42 + .../bitrise/_scripts/go_install_tools.sh | 35 + .../bitrise/_scripts/set_version.sh | 20 + .../bitrise/_tests/Dockerfile-min-env-debian | 9 + .../bitrise/_tests/Dockerfile-min-env-ubuntu | 9 + .../bitrise-io/bitrise/_tests/bitrise.json | 82 + .../bitrise-io/bitrise/_tests/bitrise.yml | 61 + .../bitrise/_tests/brew_publish.yml | 65 + .../bash_toolkit_step_template/.gitignore | 1 + .../bash_toolkit_step_template/LICENSE | 22 + .../bash_toolkit_step_template/README.md | 19 + .../bash_toolkit_step_template/bitrise.yml | 71 + .../bash_toolkit_step_template/step.sh | 7 + .../bash_toolkit_step_template/step.yml | 59 + .../bash_toolkit_step_template/step_entry.sh | 21 + .../_tests/integration/envstore_test.go | 19 + .../integration/envstore_test_bitrise.yml | 56 + .../_tests/integration/exit_code_test.go | 72 + .../integration/exit_code_test_bitrise.yml | 51 + .../_tests/integration/global_flag_test.go | 163 + .../integration/global_flag_test_bitrise.yml | 34 + .../integration/global_flag_test_secrets.yml | 4 + .../go_toolkit_step_template/.gitignore | 1 + .../go_toolkit_step_template/LICENSE | 22 + .../go_toolkit_step_template/README.md | 19 + .../go_toolkit_step_template/bitrise.yml | 71 + .../go_toolkit_step_template/main.go | 7 + .../go_toolkit_step_template/step.yml | 59 + .../bitrise/_tests/integration/helper.go | 25 + .../integration/invalid_command_test.go | 16 + .../_tests/integration/json_params_test.go | 112 + .../integration/json_params_test_bitrise.yml | 15 + .../_tests/integration/new_trigger_test.go | 141 + .../integration/new_trigger_test_bitrise.yml | 29 + .../_tests/integration/output_alias_test.go | 24 + .../integration/output_alias_test_bitrise.yml | 89 + .../integration/step_template/.gitignore | 1 + .../_tests/integration/step_template/LICENSE | 22 + .../integration/step_template/README.md | 19 + .../integration/step_template/bitrise.yml | 71 + .../_tests/integration/step_template/step.sh | 21 + .../_tests/integration/step_template/step.yml | 56 + .../_tests/integration/step_template_test.go | 36 + .../_tests/integration/trigger_check_test.go | 72 + .../trigger_check_test_bitrise.yml | 19 + .../trigger_check_test_empty_bitrise.yml | 12 + .../trigger_check_test_secrets.yml | 2 + .../_tests/integration/trigger_params_test.go | 52 + .../trigger_params_test_bitrise.yml | 26 + .../_tests/integration/validate_test.go | 214 ++ .../_tests/integration/version_test.go | 35 + .../github.com/bitrise-io/bitrise/bitrise.yml | 352 ++ .../bitrise/bitrise/dependencies.go | 420 +++ .../bitrise-io/bitrise/bitrise/print.go | 614 ++++ .../bitrise-io/bitrise/bitrise/print_test.go | 389 ++ .../bitrise-io/bitrise/bitrise/setup.go | 184 + .../bitrise/bitrise/template_util.go | 90 + .../bitrise/bitrise/template_utils_test.go | 408 +++ .../bitrise-io/bitrise/bitrise/util.go | 675 ++++ .../bitrise-io/bitrise/bitrise/util_test.go | 498 +++ .../github.com/bitrise-io/bitrise/cli/cli.go | 163 + .../bitrise-io/bitrise/cli/commands.go | 237 ++ .../bitrise-io/bitrise/cli/export.go | 78 + .../bitrise-io/bitrise/cli/flags.go | 192 + .../github.com/bitrise-io/bitrise/cli/help.go | 59 + .../github.com/bitrise-io/bitrise/cli/init.go | 50 + .../bitrise-io/bitrise/cli/normalize.go | 50 + .../bitrise-io/bitrise/cli/plugin.go | 24 + .../bitrise-io/bitrise/cli/plugin_delete.go | 58 + .../bitrise-io/bitrise/cli/plugin_info.go | 106 + .../bitrise-io/bitrise/cli/plugin_install.go | 75 + .../bitrise-io/bitrise/cli/plugin_list.go | 77 + .../bitrise-io/bitrise/cli/plugin_update.go | 100 + .../github.com/bitrise-io/bitrise/cli/run.go | 237 ++ .../bitrise-io/bitrise/cli/run_test.go | 1482 ++++++++ .../bitrise/cli/run_trigger_params.go | 138 + .../bitrise/cli/run_trigger_params_test.go | 483 +++ .../bitrise-io/bitrise/cli/run_util.go | 981 +++++ .../bitrise-io/bitrise/cli/run_util_test.go | 463 +++ .../bitrise-io/bitrise/cli/setup.go | 68 + .../bitrise-io/bitrise/cli/share.go | 16 + .../bitrise-io/bitrise/cli/share_audit.go | 16 + .../bitrise-io/bitrise/cli/share_create.go | 28 + .../bitrise-io/bitrise/cli/share_finish.go | 16 + .../bitrise-io/bitrise/cli/share_start.go | 21 + .../bitrise-io/bitrise/cli/step_info.go | 119 + .../bitrise-io/bitrise/cli/step_list.go | 73 + .../bitrise-io/bitrise/cli/tools.go | 37 + .../bitrise-io/bitrise/cli/trigger.go | 163 + .../bitrise-io/bitrise/cli/trigger_check.go | 221 ++ .../bitrise/cli/trigger_check_test.go | 639 ++++ .../bitrise-io/bitrise/cli/validate.go | 239 ++ .../bitrise-io/bitrise/cli/version.go | 63 + .../bitrise-io/bitrise/cli/workflow_list.go | 129 + .../bitrise-io/bitrise/configs/configs.go | 159 + .../bitrise/configs/configs_test.go | 30 + .../bitrise-io/bitrise/configs/paths.go | 203 ++ .../bitrise-io/bitrise/configs/paths_test.go | 104 + vendor/github.com/bitrise-io/bitrise/deps.go | 9 + .../bitrise-io/bitrise/docker-compose.yml | 4 + vendor/github.com/bitrise-io/bitrise/gows.yml | 1 + vendor/github.com/bitrise-io/bitrise/main.go | 7 + .../bitrise-io/bitrise/models/models.go | 123 + .../bitrise/models/models_methods.go | 982 +++++ .../bitrise/models/models_methods_test.go | 1567 ++++++++ .../bitrise-io/bitrise/output/output.go | 64 + .../bitrise-io/bitrise/plugins/events.go | 72 + .../bitrise-io/bitrise/plugins/git.go | 221 ++ .../bitrise-io/bitrise/plugins/git_test.go | 89 + .../bitrise-io/bitrise/plugins/install.go | 289 ++ .../bitrise/plugins/install_test.go | 223 ++ .../bitrise/plugins/model_methods_test.go | 368 ++ .../bitrise-io/bitrise/plugins/models.go | 60 + .../bitrise/plugins/models_methods.go | 344 ++ .../bitrise-io/bitrise/plugins/paths.go | 186 + .../bitrise-io/bitrise/plugins/plugins.go | 162 + .../bitrise/plugins/plugins_test.go | 54 + .../bitrise-io/bitrise/plugins/run.go | 188 + .../bitrise-io/bitrise/plugins/run_test.go | 24 + .../bitrise-io/bitrise/release_config.yml | 41 + .../bitrise-io/bitrise/toolkits/bash.go | 77 + .../bitrise-io/bitrise/toolkits/golang.go | 387 ++ .../bitrise/toolkits/golang_test.go | 78 + .../bitrise-io/bitrise/toolkits/toolkit.go | 82 + .../bitrise/toolkits/toolkit_test.go | 1 + .../bitrise-io/bitrise/tools/tools.go | 361 ++ .../bitrise-io/bitrise/tools/tools_test.go | 113 + .../bitrise-io/bitrise/utils/utils.go | 16 + .../bitrise-io/bitrise/version/build.go | 7 + .../bitrise/version/tool_version.go | 87 + .../bitrise-io/bitrise/version/version.go | 4 + .../github.com/bitrise-io/envman/.gitignore | 6 + .../github.com/bitrise-io/envman/Dockerfile | 50 + .../github.com/bitrise-io/envman/Gopkg.lock | 121 + .../github.com/bitrise-io/envman/Gopkg.toml | 23 + vendor/github.com/bitrise-io/envman/LICENSE | 22 + vendor/github.com/bitrise-io/envman/README.md | 196 + .../envman/_tests/integration/add_test.go | 141 + .../envman/_tests/integration/helper.go | 7 + .../github.com/bitrise-io/envman/bitrise.yml | 97 + .../github.com/bitrise-io/envman/cli/add.go | 225 ++ .../bitrise-io/envman/cli/add_test.go | 76 + .../github.com/bitrise-io/envman/cli/clear.go | 34 + .../github.com/bitrise-io/envman/cli/cli.go | 96 + .../bitrise-io/envman/cli/commands.go | 72 + .../github.com/bitrise-io/envman/cli/flags.go | 127 + .../github.com/bitrise-io/envman/cli/init.go | 27 + .../github.com/bitrise-io/envman/cli/print.go | 95 + .../bitrise-io/envman/cli/print_test.go | 32 + .../github.com/bitrise-io/envman/cli/run.go | 102 + .../bitrise-io/envman/cli/run_test.go | 237 ++ .../bitrise-io/envman/cli/version.go | 46 + .../bitrise-io/envman/docker-compose.yml | 4 + .../bitrise-io/envman/envman/configs.go | 99 + .../bitrise-io/envman/envman/configs_test.go | 51 + .../bitrise-io/envman/envman/util.go | 254 ++ .../bitrise-io/envman/envman/util_test.go | 125 + vendor/github.com/bitrise-io/envman/gows.yml | 1 + vendor/github.com/bitrise-io/envman/main.go | 7 + .../bitrise-io/envman/models/models.go | 31 + .../envman/models/models_methods.go | 328 ++ .../envman/models/models_methods_test.go | 492 +++ .../bitrise-io/envman/output/output.go | 64 + .../bitrise-io/envman/version/build.go | 7 + .../bitrise-io/envman/version/version.go | 4 + .../github.com/bitrise-io/stepman/.gitignore | 6 + .../github.com/bitrise-io/stepman/Dockerfile | 31 + .../github.com/bitrise-io/stepman/Gopkg.lock | 152 + .../github.com/bitrise-io/stepman/Gopkg.toml | 27 + vendor/github.com/bitrise-io/stepman/LICENSE | 22 + .../github.com/bitrise-io/stepman/README.md | 35 + .../stepman/_tests/integration/helper.go | 23 + .../stepman/_tests/integration/setup_test.go | 60 + .../_tests/integration/step_info_test.go | 286 ++ .../_tests/integration/test-step/.gitignore | 1 + .../_tests/integration/test-step/LICENSE | 22 + .../_tests/integration/test-step/README.md | 93 + .../_tests/integration/test-step/bitrise.yml | 84 + .../_tests/integration/test-step/step.sh | 21 + .../_tests/integration/test-step/step.yml | 58 + .../stepman/_tests/integration/update_test.go | 52 + .../_tests/integration/version_test.go | 34 + .../github.com/bitrise-io/stepman/bitrise.yml | 99 + .../bitrise-io/stepman/cli/activate.go | 138 + .../bitrise-io/stepman/cli/audit.go | 164 + .../bitrise-io/stepman/cli/audit_test.go | 41 + .../github.com/bitrise-io/stepman/cli/cli.go | 67 + .../bitrise-io/stepman/cli/collections.go | 107 + .../bitrise-io/stepman/cli/commands.go | 177 + .../bitrise-io/stepman/cli/delete_steplib.go | 35 + .../bitrise-io/stepman/cli/download.go | 80 + .../bitrise-io/stepman/cli/export.go | 147 + .../bitrise-io/stepman/cli/flags.go | 168 + .../github.com/bitrise-io/stepman/cli/help.go | 26 + .../bitrise-io/stepman/cli/setup.go | 58 + .../bitrise-io/stepman/cli/share.go | 188 + .../bitrise-io/stepman/cli/share_audit.go | 46 + .../bitrise-io/stepman/cli/share_create.go | 238 ++ .../stepman/cli/share_create_test.go | 23 + .../bitrise-io/stepman/cli/share_finish.go | 81 + .../bitrise-io/stepman/cli/share_start.go | 128 + .../bitrise-io/stepman/cli/step_info.go | 194 + .../bitrise-io/stepman/cli/step_list.go | 117 + .../bitrise-io/stepman/cli/update.go | 35 + .../bitrise-io/stepman/cli/version.go | 82 + .../bitrise-io/stepman/docker-compose.yml | 4 + vendor/github.com/bitrise-io/stepman/gows.yml | 1 + vendor/github.com/bitrise-io/stepman/main.go | 7 + .../bitrise-io/stepman/models/models.go | 189 + .../stepman/models/models_methods.go | 342 ++ .../stepman/models/models_methods_test.go | 467 +++ .../bitrise-io/stepman/models/models_test.go | 70 + .../bitrise-io/stepman/stepman/library.go | 130 + .../bitrise-io/stepman/stepman/paths.go | 248 ++ .../bitrise-io/stepman/stepman/util.go | 427 +++ .../bitrise-io/stepman/stepman/util_test.go | 28 + .../bitrise-io/stepman/version/build.go | 7 + .../bitrise-io/stepman/version/version.go | 4 + vendor/gopkg.in/yaml.v2/.travis.yml | 12 + vendor/gopkg.in/yaml.v2/LICENSE | 201 ++ vendor/gopkg.in/yaml.v2/LICENSE.libyaml | 31 + vendor/gopkg.in/yaml.v2/NOTICE | 13 + vendor/gopkg.in/yaml.v2/README.md | 133 + vendor/gopkg.in/yaml.v2/apic.go | 739 ++++ vendor/gopkg.in/yaml.v2/decode.go | 775 ++++ vendor/gopkg.in/yaml.v2/decode_test.go | 1334 +++++++ vendor/gopkg.in/yaml.v2/emitterc.go | 1685 +++++++++ vendor/gopkg.in/yaml.v2/encode.go | 390 ++ vendor/gopkg.in/yaml.v2/encode_test.go | 625 ++++ .../gopkg.in/yaml.v2/example_embedded_test.go | 41 + vendor/gopkg.in/yaml.v2/go.mod | 5 + vendor/gopkg.in/yaml.v2/parserc.go | 1095 ++++++ vendor/gopkg.in/yaml.v2/readerc.go | 412 +++ vendor/gopkg.in/yaml.v2/resolve.go | 258 ++ vendor/gopkg.in/yaml.v2/scannerc.go | 2696 ++++++++++++++ vendor/gopkg.in/yaml.v2/sorter.go | 113 + vendor/gopkg.in/yaml.v2/suite_test.go | 12 + vendor/gopkg.in/yaml.v2/writerc.go | 26 + vendor/gopkg.in/yaml.v2/yaml.go | 466 +++ vendor/gopkg.in/yaml.v2/yamlh.go | 738 ++++ vendor/gopkg.in/yaml.v2/yamlprivateh.go | 173 + 381 files changed, 64245 insertions(+), 10 deletions(-) create mode 100644 vendor/github.com/bitrise-core/bitrise-init/.gitignore create mode 100644 vendor/github.com/bitrise-core/bitrise-init/Dockerfile create mode 100644 vendor/github.com/bitrise-core/bitrise-init/Gopkg.lock create mode 100644 vendor/github.com/bitrise-core/bitrise-init/Gopkg.toml create mode 100644 vendor/github.com/bitrise-core/bitrise-init/README.md create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_scripts/set_version.sh create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/android_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/cordova_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/fastlane_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/flutter_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/helper.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ionic_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ios_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/mac_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/manual_config_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_expo_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/xamarin_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/bitrise.yml create mode 100644 vendor/github.com/bitrise-core/bitrise-init/cli/cli.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/cli/config.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/cli/manual_config.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/cli/version.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/docker-compose.yml create mode 100644 vendor/github.com/bitrise-core/bitrise-init/gows.yml create mode 100644 vendor/github.com/bitrise-core/bitrise-init/main.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/models/configbuilder.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/models/model_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/models/models.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/models/option.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/models/workflowbuilder.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/output/output.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/release_config.yml create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanner/config.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanner/manual_config.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanner/utils.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/android/android.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/android/const.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/android/utility.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/utility.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test_file_contents.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/utility.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/flutter/flutter.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ionic/ionic.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/gemutil.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/gemutil_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/ios.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/podfile.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/podfile_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/rubyscript.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/rubyscript_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/utility.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/utility_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj_test_files.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/macos/macos.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative-expo/reactnative-expo.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/reactnative.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/utility.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/scanners.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/utility.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/xamarin.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/xamarin_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/steps/const.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/steps/steps.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/utility/sortable_path.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/utility/sortable_path_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/utility/utility.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/utility/utility_test.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/version/build.go create mode 100644 vendor/github.com/bitrise-core/bitrise-init/version/version.go create mode 100644 vendor/github.com/bitrise-io/bitrise/.codeclimate.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/.gitignore create mode 100644 vendor/github.com/bitrise-io/bitrise/CHANGELOG.md create mode 100644 vendor/github.com/bitrise-io/bitrise/Dockerfile create mode 100644 vendor/github.com/bitrise-io/bitrise/Godeps/Godeps.json create mode 100644 vendor/github.com/bitrise-io/bitrise/Godeps/Readme create mode 100644 vendor/github.com/bitrise-io/bitrise/LICENSE create mode 100644 vendor/github.com/bitrise-io/bitrise/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/bitrise-yml-format-spec.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/cli-how-to-guide.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/cli-introduction.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/cli-react-native.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/cli-share-guide.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/images/success.gif create mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/step-development-guideline.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/before-after/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/create-new-step/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/dependencies/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/v2.bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/templates/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/timestamp-gen/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/trigger-map/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/.gitignore create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/errors-force-run-and-skippable/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/inputs-outputs-envs/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/react-native/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/LICENSE create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/_scripts/ci.sh create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.go create mode 100755 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.sh create mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/.gitignore create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/.gitignore create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/.gitignore create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/.gitignore create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/.gitignore create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/.gitignore create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_scripts/build_tools_in_docker.sh create mode 100644 vendor/github.com/bitrise-io/bitrise/_scripts/ci.sh create mode 100644 vendor/github.com/bitrise-io/bitrise/_scripts/create_release_with_docker_compose.sh create mode 100644 vendor/github.com/bitrise-io/bitrise/_scripts/get_version.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_scripts/go_install_tools.sh create mode 100644 vendor/github.com/bitrise-io/bitrise/_scripts/set_version.sh create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/Dockerfile-min-env-debian create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/Dockerfile-min-env-ubuntu create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/bitrise.json create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/brew_publish.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/.gitignore create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/LICENSE create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/step.sh create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/step.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/step_entry.sh create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/envstore_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/envstore_test_bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/exit_code_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/exit_code_test_bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/global_flag_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/global_flag_test_bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/global_flag_test_secrets.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/go_toolkit_step_template/.gitignore create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/go_toolkit_step_template/LICENSE create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/go_toolkit_step_template/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/go_toolkit_step_template/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/go_toolkit_step_template/main.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/go_toolkit_step_template/step.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/helper.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/invalid_command_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/json_params_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/json_params_test_bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/new_trigger_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/new_trigger_test_bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/output_alias_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/output_alias_test_bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template/.gitignore create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template/LICENSE create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template/README.md create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template/step.sh create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template/step.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/trigger_check_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/trigger_check_test_bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/trigger_check_test_empty_bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/trigger_check_test_secrets.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/trigger_params_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/trigger_params_test_bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/validate_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/version_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/dependencies.go create mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/print.go create mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/print_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/setup.go create mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/template_util.go create mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/template_utils_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/util.go create mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/util_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/cli.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/commands.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/export.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/flags.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/help.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/init.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/normalize.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/plugin.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/plugin_delete.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/plugin_info.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/plugin_install.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/plugin_list.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/plugin_update.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/run.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/run_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/run_util.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/run_util_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/setup.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/share.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/share_audit.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/share_create.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/share_finish.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/share_start.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/step_info.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/step_list.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/tools.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/trigger.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/trigger_check.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/trigger_check_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/validate.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/version.go create mode 100644 vendor/github.com/bitrise-io/bitrise/cli/workflow_list.go create mode 100644 vendor/github.com/bitrise-io/bitrise/configs/configs.go create mode 100644 vendor/github.com/bitrise-io/bitrise/configs/configs_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/configs/paths.go create mode 100644 vendor/github.com/bitrise-io/bitrise/configs/paths_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/deps.go create mode 100644 vendor/github.com/bitrise-io/bitrise/docker-compose.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/gows.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/main.go create mode 100644 vendor/github.com/bitrise-io/bitrise/models/models.go create mode 100644 vendor/github.com/bitrise-io/bitrise/models/models_methods.go create mode 100644 vendor/github.com/bitrise-io/bitrise/models/models_methods_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/output/output.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/events.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/git.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/git_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/install.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/install_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/model_methods_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/models.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/models_methods.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/paths.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/plugins.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/plugins_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/run.go create mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/run_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/release_config.yml create mode 100644 vendor/github.com/bitrise-io/bitrise/toolkits/bash.go create mode 100644 vendor/github.com/bitrise-io/bitrise/toolkits/golang.go create mode 100644 vendor/github.com/bitrise-io/bitrise/toolkits/golang_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/toolkits/toolkit.go create mode 100644 vendor/github.com/bitrise-io/bitrise/toolkits/toolkit_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/tools/tools.go create mode 100644 vendor/github.com/bitrise-io/bitrise/tools/tools_test.go create mode 100644 vendor/github.com/bitrise-io/bitrise/utils/utils.go create mode 100644 vendor/github.com/bitrise-io/bitrise/version/build.go create mode 100644 vendor/github.com/bitrise-io/bitrise/version/tool_version.go create mode 100644 vendor/github.com/bitrise-io/bitrise/version/version.go create mode 100644 vendor/github.com/bitrise-io/envman/.gitignore create mode 100644 vendor/github.com/bitrise-io/envman/Dockerfile create mode 100644 vendor/github.com/bitrise-io/envman/Gopkg.lock create mode 100644 vendor/github.com/bitrise-io/envman/Gopkg.toml create mode 100644 vendor/github.com/bitrise-io/envman/LICENSE create mode 100644 vendor/github.com/bitrise-io/envman/README.md create mode 100644 vendor/github.com/bitrise-io/envman/_tests/integration/add_test.go create mode 100644 vendor/github.com/bitrise-io/envman/_tests/integration/helper.go create mode 100644 vendor/github.com/bitrise-io/envman/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/envman/cli/add.go create mode 100644 vendor/github.com/bitrise-io/envman/cli/add_test.go create mode 100644 vendor/github.com/bitrise-io/envman/cli/clear.go create mode 100644 vendor/github.com/bitrise-io/envman/cli/cli.go create mode 100644 vendor/github.com/bitrise-io/envman/cli/commands.go create mode 100644 vendor/github.com/bitrise-io/envman/cli/flags.go create mode 100644 vendor/github.com/bitrise-io/envman/cli/init.go create mode 100644 vendor/github.com/bitrise-io/envman/cli/print.go create mode 100644 vendor/github.com/bitrise-io/envman/cli/print_test.go create mode 100644 vendor/github.com/bitrise-io/envman/cli/run.go create mode 100644 vendor/github.com/bitrise-io/envman/cli/run_test.go create mode 100644 vendor/github.com/bitrise-io/envman/cli/version.go create mode 100644 vendor/github.com/bitrise-io/envman/docker-compose.yml create mode 100644 vendor/github.com/bitrise-io/envman/envman/configs.go create mode 100644 vendor/github.com/bitrise-io/envman/envman/configs_test.go create mode 100644 vendor/github.com/bitrise-io/envman/envman/util.go create mode 100644 vendor/github.com/bitrise-io/envman/envman/util_test.go create mode 100644 vendor/github.com/bitrise-io/envman/gows.yml create mode 100644 vendor/github.com/bitrise-io/envman/main.go create mode 100644 vendor/github.com/bitrise-io/envman/models/models.go create mode 100644 vendor/github.com/bitrise-io/envman/models/models_methods.go create mode 100644 vendor/github.com/bitrise-io/envman/models/models_methods_test.go create mode 100644 vendor/github.com/bitrise-io/envman/output/output.go create mode 100644 vendor/github.com/bitrise-io/envman/version/build.go create mode 100644 vendor/github.com/bitrise-io/envman/version/version.go create mode 100644 vendor/github.com/bitrise-io/stepman/.gitignore create mode 100644 vendor/github.com/bitrise-io/stepman/Dockerfile create mode 100644 vendor/github.com/bitrise-io/stepman/Gopkg.lock create mode 100644 vendor/github.com/bitrise-io/stepman/Gopkg.toml create mode 100644 vendor/github.com/bitrise-io/stepman/LICENSE create mode 100644 vendor/github.com/bitrise-io/stepman/README.md create mode 100644 vendor/github.com/bitrise-io/stepman/_tests/integration/helper.go create mode 100644 vendor/github.com/bitrise-io/stepman/_tests/integration/setup_test.go create mode 100644 vendor/github.com/bitrise-io/stepman/_tests/integration/step_info_test.go create mode 100755 vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/.gitignore create mode 100755 vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/LICENSE create mode 100755 vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/README.md create mode 100755 vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/bitrise.yml create mode 100755 vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.sh create mode 100755 vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.yml create mode 100644 vendor/github.com/bitrise-io/stepman/_tests/integration/update_test.go create mode 100644 vendor/github.com/bitrise-io/stepman/_tests/integration/version_test.go create mode 100644 vendor/github.com/bitrise-io/stepman/bitrise.yml create mode 100644 vendor/github.com/bitrise-io/stepman/cli/activate.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/audit.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/audit_test.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/cli.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/collections.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/commands.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/delete_steplib.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/download.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/export.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/flags.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/help.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/setup.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/share.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/share_audit.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/share_create.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/share_create_test.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/share_finish.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/share_start.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/step_info.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/step_list.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/update.go create mode 100644 vendor/github.com/bitrise-io/stepman/cli/version.go create mode 100644 vendor/github.com/bitrise-io/stepman/docker-compose.yml create mode 100644 vendor/github.com/bitrise-io/stepman/gows.yml create mode 100644 vendor/github.com/bitrise-io/stepman/main.go create mode 100644 vendor/github.com/bitrise-io/stepman/models/models.go create mode 100644 vendor/github.com/bitrise-io/stepman/models/models_methods.go create mode 100644 vendor/github.com/bitrise-io/stepman/models/models_methods_test.go create mode 100644 vendor/github.com/bitrise-io/stepman/models/models_test.go create mode 100644 vendor/github.com/bitrise-io/stepman/stepman/library.go create mode 100644 vendor/github.com/bitrise-io/stepman/stepman/paths.go create mode 100644 vendor/github.com/bitrise-io/stepman/stepman/util.go create mode 100644 vendor/github.com/bitrise-io/stepman/stepman/util_test.go create mode 100644 vendor/github.com/bitrise-io/stepman/version/build.go create mode 100644 vendor/github.com/bitrise-io/stepman/version/version.go create mode 100644 vendor/gopkg.in/yaml.v2/.travis.yml create mode 100644 vendor/gopkg.in/yaml.v2/LICENSE create mode 100644 vendor/gopkg.in/yaml.v2/LICENSE.libyaml create mode 100644 vendor/gopkg.in/yaml.v2/NOTICE create mode 100644 vendor/gopkg.in/yaml.v2/README.md create mode 100644 vendor/gopkg.in/yaml.v2/apic.go create mode 100644 vendor/gopkg.in/yaml.v2/decode.go create mode 100644 vendor/gopkg.in/yaml.v2/decode_test.go create mode 100644 vendor/gopkg.in/yaml.v2/emitterc.go create mode 100644 vendor/gopkg.in/yaml.v2/encode.go create mode 100644 vendor/gopkg.in/yaml.v2/encode_test.go create mode 100644 vendor/gopkg.in/yaml.v2/example_embedded_test.go create mode 100644 vendor/gopkg.in/yaml.v2/go.mod create mode 100644 vendor/gopkg.in/yaml.v2/parserc.go create mode 100644 vendor/gopkg.in/yaml.v2/readerc.go create mode 100644 vendor/gopkg.in/yaml.v2/resolve.go create mode 100644 vendor/gopkg.in/yaml.v2/scannerc.go create mode 100644 vendor/gopkg.in/yaml.v2/sorter.go create mode 100644 vendor/gopkg.in/yaml.v2/suite_test.go create mode 100644 vendor/gopkg.in/yaml.v2/writerc.go create mode 100644 vendor/gopkg.in/yaml.v2/yaml.go create mode 100644 vendor/gopkg.in/yaml.v2/yamlh.go create mode 100644 vendor/gopkg.in/yaml.v2/yamlprivateh.go diff --git a/Gopkg.lock b/Gopkg.lock index c29ba291..22eb9a84 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,19 +1,52 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + digest = "1:020a8f7482fc2908fb66380a25d675c750310fc269045690d7a14e40831c1b8b" + name = "github.com/bitrise-core/bitrise-init" + packages = [ + "models", + "scanners/ios", + "scanners/xamarin", + "steps", + "utility", + ] + pruneopts = "" + revision = "d2ea1b27c9c491e1f41ab3106e99e100296cceb0" + version = "1.8.0" + +[[projects]] + digest = "1:5f5022227806c631673aac91c13ca5b0e9f754f4a6c61634ea874f9451ed93b8" + name = "github.com/bitrise-io/bitrise" + packages = ["models"] + pruneopts = "" + revision = "4ff9e54c15d46ddd638030202326ee02734e8b82" + version = "1.8.0" + [[projects]] branch = "master" - digest = "1:2aa540c5a392849eaa3c5a81aee4e2a31025a5bfcf00c43351a97e830cf63d2a" + digest = "1:aad9e0eeac37ffe5409673ca6d1160fca6786408cf904fba456b888bee6dba42" + name = "github.com/bitrise-io/envman" + packages = ["models"] + pruneopts = "" + revision = "41ea1b6f422eabd86189caf7da0c633208ba4760" + +[[projects]] + branch = "master" + digest = "1:5306f1051f67906b51a08c34bfb52fafd0f0634b275dc5b92aa368a3aeb573a7" name = "github.com/bitrise-io/go-utils" packages = [ "colorstring", "command", + "command/rubyscript", "errorutil", "fileutil", "log", + "parseutil", "pathutil", "pkcs12", "pkcs12/internal/rc2", + "pointers", "progress", "retry", "sliceutil", @@ -24,15 +57,23 @@ [[projects]] branch = "master" - digest = "1:fb3cc9c055818a84aba159da735a24efce77453a904e7742141f2aa0359a6ae6" + digest = "1:6825c56bedbe125a302e7516f731fa841ce9db05f873dbc5745ee807a8c3d3a2" name = "github.com/bitrise-io/goinp" packages = ["goinp"] pruneopts = "" revision = "f451b19ba4d087d8272d411ad91af5f4218fd517" +[[projects]] + digest = "1:d5755753303235de52ed9ec42f9ec0fa6d1021e205e69794975390cc3d50bbad" + name = "github.com/bitrise-io/stepman" + packages = ["models"] + pruneopts = "" + revision = "076579c0004ea94d98322d3df5d5d21b47412b1c" + version = "0.11.0" + [[projects]] branch = "master" - digest = "1:189a7e7bbda8a5f93eb4931155d9dc88c30a050dd316f8f613a05a799e36c559" + digest = "1:5a10928d8c90f5f52d5b126c507db75f6a330fbd8d69f669737688a894875ff9" name = "github.com/bitrise-tools/go-xamarin" packages = [ "analyzers/project", @@ -51,7 +92,7 @@ [[projects]] branch = "master" - digest = "1:f3eb815b9c0b11d53813a09340e6fbdf6151aefc6b780a83b5a80471a189c11a" + digest = "1:165bebf2fc00916648e90c424a19105b0d3afc72ead558e7ae7a56edcde7524c" name = "github.com/bitrise-tools/go-xcode" packages = [ "certificateutil", @@ -62,12 +103,13 @@ "profileutil", "utility", "xcarchive", + "xcodeproj", ] pruneopts = "" revision = "07db8cf760f5213241e12c0595158aedce96669a" [[projects]] - digest = "1:0a39ec8bf5629610a4bc7873a92039ee509246da3cef1a0ea60f1ed7e5f9cea5" + digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b" name = "github.com/davecgh/go-spew" packages = ["spew"] pruneopts = "" @@ -116,7 +158,7 @@ [[projects]] branch = "master" - digest = "1:04f9cbdaf9ddfd1fa3e4851a69a1b0bc254a034a0542c0134568de2f8abdc185" + digest = "1:348b9440fef689f042a46a46daf154b997e464db696c56019603d2c6d3eac0ae" name = "github.com/spf13/cobra" packages = ["."] pruneopts = "" @@ -132,7 +174,7 @@ [[projects]] branch = "master" - digest = "1:bc2a12c8863e1080226b7bc69192efd6c37aaa9b85cec508b0a8f54fabb9bd9f" + digest = "1:c587772fb8ad29ad4db67575dad25ba17a51f072ff18a22b4f0257a4d9c24f75" name = "github.com/stretchr/testify" packages = [ "assert", @@ -143,7 +185,7 @@ [[projects]] branch = "master" - digest = "1:e79c3d6b195477b429f017aaa02af135249bbdff31c9acbd8a93bb37a4ef7d3b" + digest = "1:6ef14be530be39b6b9d75d54ce1d546ae9231e652d9e3eef198cbb19ce8ed3e7" name = "golang.org/x/crypto" packages = ["ssh/terminal"] pruneopts = "" @@ -151,7 +193,7 @@ [[projects]] branch = "master" - digest = "1:880f5ae3ffdd8741fe179f0f70b46df57fcdd746f3a0e9d0e189bb6a241535e4" + digest = "1:19f072f12708aaafef9064b49432953e3f813a98fcb356b30831cf2b0f5272b3" name = "golang.org/x/sys" packages = [ "unix", @@ -160,9 +202,17 @@ pruneopts = "" revision = "1b2967e3c290b7c545b3db0deeda16e9be4f98a2" +[[projects]] + digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d" + name = "gopkg.in/yaml.v2" + packages = ["."] + pruneopts = "" + revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" + version = "v2.2.2" + [[projects]] branch = "master" - digest = "1:cb91f4514dcec0fbc8e1af274856c50553f8088770c05982b76369759426c087" + digest = "1:5e44963f3ff639a4fd8a03522f38614b223467fd040beda2b7122cbf75ede030" name = "howett.net/plist" packages = ["."] pruneopts = "" @@ -172,6 +222,9 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ + "github.com/bitrise-core/bitrise-init/scanners/ios", + "github.com/bitrise-core/bitrise-init/scanners/xamarin", + "github.com/bitrise-core/bitrise-init/utility", "github.com/bitrise-io/go-utils/colorstring", "github.com/bitrise-io/go-utils/command", "github.com/bitrise-io/go-utils/fileutil", diff --git a/Gopkg.toml b/Gopkg.toml index 9e3048cf..2ada5d85 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -25,3 +25,7 @@ [[constraint]] name = "github.com/stretchr/testify" branch = "master" + +[[constraint]] + name = "github.com/bitrise-core/bitrise-init" + version = "1.8.0" diff --git a/vendor/github.com/bitrise-core/bitrise-init/.gitignore b/vendor/github.com/bitrise-core/bitrise-init/.gitignore new file mode 100644 index 00000000..c1b6fd28 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/.gitignore @@ -0,0 +1,7 @@ +_scan_result/ +_defaults/ +_tmp/ +_bin/ +.bitrise* +.gows* +bitrise-init \ No newline at end of file diff --git a/vendor/github.com/bitrise-core/bitrise-init/Dockerfile b/vendor/github.com/bitrise-core/bitrise-init/Dockerfile new file mode 100644 index 00000000..03dc075e --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/Dockerfile @@ -0,0 +1,3 @@ +FROM quay.io/bitriseio/android + +WORKDIR /bitrise/go/src/github.com/bitrise-core/bitrise-init \ No newline at end of file diff --git a/vendor/github.com/bitrise-core/bitrise-init/Gopkg.lock b/vendor/github.com/bitrise-core/bitrise-init/Gopkg.lock new file mode 100644 index 00000000..c18e20ca --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/Gopkg.lock @@ -0,0 +1,223 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:87c2e02fb01c27060ccc5ba7c5a407cc91147726f8f40b70cceeedbc52b1f3a8" + name = "github.com/Sirupsen/logrus" + packages = ["."] + pruneopts = "UT" + revision = "e1e72e9de974bd926e5c56f83753fba2df402ce5" + version = "v1.3.0" + +[[projects]] + digest = "1:7e26f568122298ee1065b6e264380f1f5387eff732cfc93ad860b6265f9063f9" + name = "github.com/bitrise-io/bitrise" + packages = ["models"] + pruneopts = "UT" + revision = "4ff9e54c15d46ddd638030202326ee02734e8b82" + version = "1.8.0" + +[[projects]] + branch = "master" + digest = "1:0bdbc9f9b2bbc084d3bb353d8e94d53ee0495f9d434baaddb5e0f07fb88c966a" + name = "github.com/bitrise-io/envman" + packages = ["models"] + pruneopts = "UT" + revision = "41ea1b6f422eabd86189caf7da0c633208ba4760" + +[[projects]] + branch = "master" + digest = "1:6ff15f23762c225970c8624e1ef1b6a8861aa5d1ef4f10702ea158bf24d3ce4a" + name = "github.com/bitrise-io/go-utils" + packages = [ + "colorstring", + "command", + "command/git", + "command/rubyscript", + "errorutil", + "fileutil", + "log", + "parseutil", + "pathutil", + "pointers", + "sliceutil", + ] + pruneopts = "UT" + revision = "2a09aab8380d7842750328aebd5671bcccea89c8" + +[[projects]] + branch = "master" + digest = "1:f9749f95e34724e0bfb71a3bf0fa00e0e303d42f1de172bf134f013fa1e9dbb7" + name = "github.com/bitrise-io/goinp" + packages = ["goinp"] + pruneopts = "UT" + revision = "f451b19ba4d087d8272d411ad91af5f4218fd517" + +[[projects]] + digest = "1:34e5041861da39ff37f982c3cdfd46c0b0b46836d136151e895be7bf8fb50d86" + name = "github.com/bitrise-io/stepman" + packages = ["models"] + pruneopts = "UT" + revision = "076579c0004ea94d98322d3df5d5d21b47412b1c" + version = "0.11.0" + +[[projects]] + branch = "master" + digest = "1:fd63ae4d0b8c60e868266de6575ac33ba5c4ca8705267a35b2824590f24c9a47" + name = "github.com/bitrise-tools/go-xcode" + packages = [ + "plistutil", + "xcodeproj", + ] + pruneopts = "UT" + revision = "b278b67ed298829932d08fbe05e7590bca753bdf" + +[[projects]] + branch = "master" + digest = "1:8208679f767666bb99d70139954a6f5988375c8bcdce77b6ece5c63e3fcf1f1b" + name = "github.com/bitrise-tools/xcode-project" + packages = [ + "serialized", + "xcodebuild", + "xcodeproj", + "xcscheme", + "xcworkspace", + ] + pruneopts = "UT" + revision = "489a630c4fa828115a6265700eb61c3839aad9a7" + +[[projects]] + digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" + name = "github.com/davecgh/go-spew" + packages = ["spew"] + pruneopts = "UT" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" + +[[projects]] + digest = "1:2e3c336fc7fde5c984d2841455a658a6d626450b1754a854b3b32e7a8f49a07a" + name = "github.com/google/go-cmp" + packages = [ + "cmp", + "cmp/internal/diff", + "cmp/internal/function", + "cmp/internal/value", + ] + pruneopts = "UT" + revision = "3af367b6b30c263d47e8895973edcca9a49cf029" + version = "v0.2.0" + +[[projects]] + digest = "1:0a69a1c0db3591fcefb47f115b224592c8dfa4368b7ba9fae509d5e16cdc95c8" + name = "github.com/konsorten/go-windows-terminal-sequences" + packages = ["."] + pruneopts = "UT" + revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" + version = "v1.0.1" + +[[projects]] + digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b" + name = "github.com/pkg/errors" + packages = ["."] + pruneopts = "UT" + revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" + version = "v0.8.1" + +[[projects]] + digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + pruneopts = "UT" + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + digest = "1:0e792eea6c96ec55ff302ef33886acbaa5006e900fefe82689e88d96439dcd84" + name = "github.com/ryanuber/go-glob" + packages = ["."] + pruneopts = "UT" + revision = "572520ed46dbddaed19ea3d9541bdd0494163693" + version = "v0.1" + +[[projects]] + digest = "1:5da8ce674952566deae4dbc23d07c85caafc6cfa815b0b3e03e41979cedb8750" + name = "github.com/stretchr/testify" + packages = [ + "assert", + "require", + ] + pruneopts = "UT" + revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" + version = "v1.3.0" + +[[projects]] + digest = "1:b24d38b282bacf9791408a080f606370efa3d364e4b5fd9ba0f7b87786d3b679" + name = "github.com/urfave/cli" + packages = ["."] + pruneopts = "UT" + revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" + version = "v1.20.0" + +[[projects]] + branch = "master" + digest = "1:38f553aff0273ad6f367cb0a0f8b6eecbaef8dc6cb8b50e57b6a81c1d5b1e332" + name = "golang.org/x/crypto" + packages = ["ssh/terminal"] + pruneopts = "UT" + revision = "ff983b9c42bc9fbf91556e191cc8efb585c16908" + +[[projects]] + branch = "master" + digest = "1:72f402ba458cb14ed7964c8b9a38d992f27834b3cf3479f3b08ea9e5334811b3" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows", + ] + pruneopts = "UT" + revision = "770c60269bf0ef965e9e7ac8bedcb6bca2a1cefd" + +[[projects]] + digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" + name = "gopkg.in/yaml.v2" + packages = ["."] + pruneopts = "UT" + revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" + version = "v2.2.2" + +[[projects]] + branch = "master" + digest = "1:c10265d5a71326618d37e97169eddb3582f78e8ac7dcf87403b4cf619efd519a" + name = "howett.net/plist" + packages = ["."] + pruneopts = "UT" + revision = "591f970eefbbeb04d7b37f334a0c4c3256e32876" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/Sirupsen/logrus", + "github.com/bitrise-io/bitrise/models", + "github.com/bitrise-io/envman/models", + "github.com/bitrise-io/go-utils/colorstring", + "github.com/bitrise-io/go-utils/command", + "github.com/bitrise-io/go-utils/command/git", + "github.com/bitrise-io/go-utils/errorutil", + "github.com/bitrise-io/go-utils/fileutil", + "github.com/bitrise-io/go-utils/log", + "github.com/bitrise-io/go-utils/pathutil", + "github.com/bitrise-io/go-utils/pointers", + "github.com/bitrise-io/go-utils/sliceutil", + "github.com/bitrise-io/goinp/goinp", + "github.com/bitrise-io/stepman/models", + "github.com/bitrise-tools/go-xcode/xcodeproj", + "github.com/bitrise-tools/xcode-project/serialized", + "github.com/bitrise-tools/xcode-project/xcworkspace", + "github.com/google/go-cmp/cmp", + "github.com/stretchr/testify/require", + "github.com/urfave/cli", + "gopkg.in/yaml.v2", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/bitrise-core/bitrise-init/Gopkg.toml b/vendor/github.com/bitrise-core/bitrise-init/Gopkg.toml new file mode 100644 index 00000000..4df52f0f --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/Gopkg.toml @@ -0,0 +1,51 @@ +[[constraint]] + name = "github.com/Sirupsen/logrus" + version = "1.3.0" + +[[constraint]] + name = "github.com/bitrise-io/bitrise" + version = "1.6.2" + +[[constraint]] + branch = "master" + name = "github.com/bitrise-io/envman" + +[[constraint]] + branch = "master" + name = "github.com/bitrise-io/go-utils" + +[[constraint]] + branch = "master" + name = "github.com/bitrise-io/goinp" + +[[constraint]] + name = "github.com/bitrise-io/stepman" + version = "0.11.0" + +[[constraint]] + branch = "master" + name = "github.com/bitrise-tools/go-xcode" + +[[constraint]] + branch = "master" + name = "github.com/bitrise-tools/xcode-project" + +[[constraint]] + name = "github.com/google/go-cmp" + version = "0.2.0" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.3.0" + +[[constraint]] + name = "github.com/urfave/cli" + version = "1.20.0" + +[[constraint]] + name = "gopkg.in/yaml.v2" + version = "2.2.2" + +[prune] + go-tests = true + unused-packages = true diff --git a/vendor/github.com/bitrise-core/bitrise-init/README.md b/vendor/github.com/bitrise-core/bitrise-init/README.md new file mode 100644 index 00000000..304db846 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/README.md @@ -0,0 +1,52 @@ +# Bitrise Init Tool + +Initialize bitrise config, step template or plugin template + +## How to build this project +Project is written in [Go](https://golang.org/) language and +uses [dep](github.com/golang/dep/cmd/dep) as dependency management tool. + +You can build this project using sequence of `go` commands or refer to [bitrise.yml](./bitrise.yml) file, +which contains workflows for this project. + +You can run `bitrise` workflows on your local machine using [bitrise CLI](https://www.bitrise.io/cli). + +Before you start, make sure +- `$HOME/go/bin` (or `$GOPATH/bin` in case of custom go workspace) is added to `$PATH` +- `Ruby >= 2.2.2` version is installed +- `bundler` gem installed + +**How to build the project using bitrise workflows** + +Please check available workflows in [bitrise.yml](./bitrise.yml). +`bitrise --ci run ci` will execute `ci` workflow which consists of `prepare/build/run tests` stages. + +**How to build the project using Go commands** +- `go build` command builds the project and generates `bitrise-init` binary at `$HOME/go/bin/bitrise-init` (or `$GOPATH/bin/bitrise-init` in case of custom go workspace). +- `go test ./...` command runs unit tests in every project folder/subfolder. +- `go test -v ./_tests/integration/...` command runs integration tests. This command requires `INTEGRATION_TEST_BINARY_PATH=$HOME/go/bin/bitrise-init` (or `INTEGRATION_TEST_BINARY_PATH=$GOPATH/bin/bitrise-init` in case of custom go workspace) environment variable. + +## How to release new bitrise-init version + +- update the step versions in steps/const.go +- bump `version` in version/version.go +- commit these changes & open PR +- merge to master +- create tag with the new version +- test the generated release and its binaries + +__Update manual config on website__ + +- use the generated binaries in `./_bin/` directory to generate the manual config by calling: `BIN_PATH --ci manual-config` this will generate the manual.config.yml at: `CURRENT_DIR/_defaults/result.yml` +- throw the generated `result.yml` to the frontend team, to update the manual-config on the website +- once they put the new config in the website project, check the git changes to make sure, everything looks great + +__Update the [project-scanner step](https://github.com/bitrise-steplib/steps-project-scanner)__ + +- update bitrise-init dependency +- share a new version into the steplib (check the [README.md](https://github.com/bitrise-steplib/steps-project-scanner/blob/master/README.md)) + +__Update the [bitrise init plugin]((https://github.com/bitrise-core/bitrise-plugins-init))__ + +- update bitrise-init dependency +- release a new version (check the [README.md](https://github.com/bitrise-core/bitrise-plugins-init/blob/master/README.md)) \ No newline at end of file diff --git a/vendor/github.com/bitrise-core/bitrise-init/_scripts/set_version.sh b/vendor/github.com/bitrise-core/bitrise-init/_scripts/set_version.sh new file mode 100644 index 00000000..901c1438 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/_scripts/set_version.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -x + +version_file_path="$1" +if [ ! -f "$version_file_path" ] ; then + echo " [!] version_file_path not provided, or file doesn't exist at path: $version_file_path" + exit 1 +fi +versionNumber=$next_version +if [[ "$versionNumber" == "" ]] ; then + echo " [!] versionNumber not provided" + exit 1 +fi + +cat >"${version_file_path}" <No Gradle Wrapper (gradlew) found. \nUsing a Gradle Wrapper (gradlew) + is required, as the wrapper is what makes sure\nthat the right Gradle version + is installed and used for the build. More info/guide: https://docs.gradle.org/current/userguide/gradle_wrapper.html" +errors: + general: + - No known platform detected +` + +var sampleAppsAndroid22Versions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.InstallMissingAndroidToolsVersion, + steps.ChangeAndroidVersionCodeAndVersionNameVersion, + steps.AndroidLintVersion, + steps.AndroidUnitTestVersion, + steps.AndroidBuildVersion, + steps.SignAPKVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.InstallMissingAndroidToolsVersion, + steps.AndroidLintVersion, + steps.AndroidUnitTestVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, +} + +var sampleAppsAndroid22ResultYML = fmt.Sprintf(`options: + android: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + .: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + "": + config: android-config +configs: + android: + android-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: android + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + description: | + ## How to get a signed APK + + This workflow contains the **Sign APK** step. To sign your APK all you have to do is to: + + 1. Click on **Code Signing** tab + 1. Find the **ANDROID KEYSTORE FILE** section + 1. Click or drop your file on the upload file field + 1. Fill the displayed 3 input fields: + 1. **Keystore password** + 1. **Keystore alias** + 1. **Private key password** + 1. Click on **[Save metadata]** button + + That's it! From now on, **Sign APK** step will receive your uploaded files. + + ## To run this workflow + + If you want to run this workflow manually: + + 1. Open the app's build list page + 2. Click on **[Start/Schedule a Build]** button + 3. Select **deploy** in **Workflow** dropdown input + 4. Click **[Start Build]** button + + Or if you need this workflow to be started by a GIT event: + + 1. Click on **Triggers** tab + 2. Setup your desired event (push/tag/pull) and select **deploy** workflow + 3. Click on **[Done]** and then **[Save]** buttons + + The next change in your repository that matches any of your trigger map event will start **deploy** workflow. + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - change-android-versioncode-and-versionname@%s: + inputs: + - build_gradle_path: $PROJECT_LOCATION/$MODULE/build.gradle + - android-lint@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - android-unit-test@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - android-build@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - sign-apk@%s: + run_if: '{{getenv "BITRISEIO_ANDROID_KEYSTORE_URL" | ne ""}}' + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - android-lint@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - android-unit-test@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} +warnings: + android: [] +`, sampleAppsAndroid22Versions...) + +var androidNonExecutableGradlewVersions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.InstallMissingAndroidToolsVersion, + steps.ChangeAndroidVersionCodeAndVersionNameVersion, + steps.AndroidLintVersion, + steps.AndroidUnitTestVersion, + steps.AndroidBuildVersion, + steps.SignAPKVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.InstallMissingAndroidToolsVersion, + steps.AndroidLintVersion, + steps.AndroidUnitTestVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, +} + +var androidNonExecutableGradlewResultYML = fmt.Sprintf(`options: + android: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + .: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + "": + config: android-config +configs: + android: + android-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: android + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + description: | + ## How to get a signed APK + + This workflow contains the **Sign APK** step. To sign your APK all you have to do is to: + + 1. Click on **Code Signing** tab + 1. Find the **ANDROID KEYSTORE FILE** section + 1. Click or drop your file on the upload file field + 1. Fill the displayed 3 input fields: + 1. **Keystore password** + 1. **Keystore alias** + 1. **Private key password** + 1. Click on **[Save metadata]** button + + That's it! From now on, **Sign APK** step will receive your uploaded files. + + ## To run this workflow + + If you want to run this workflow manually: + + 1. Open the app's build list page + 2. Click on **[Start/Schedule a Build]** button + 3. Select **deploy** in **Workflow** dropdown input + 4. Click **[Start Build]** button + + Or if you need this workflow to be started by a GIT event: + + 1. Click on **Triggers** tab + 2. Setup your desired event (push/tag/pull) and select **deploy** workflow + 3. Click on **[Done]** and then **[Save]** buttons + + The next change in your repository that matches any of your trigger map event will start **deploy** workflow. + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - change-android-versioncode-and-versionname@%s: + inputs: + - build_gradle_path: $PROJECT_LOCATION/$MODULE/build.gradle + - android-lint@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - android-unit-test@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - android-build@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - sign-apk@%s: + run_if: '{{getenv "BITRISEIO_ANDROID_KEYSTORE_URL" | ne ""}}' + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - android-lint@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - android-unit-test@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} +warnings: + android: [] +`, androidNonExecutableGradlewVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/cordova_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/cordova_test.go new file mode 100644 index 00000000..cb9df9f7 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/cordova_test.go @@ -0,0 +1,208 @@ +package integration + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestCordova(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__cordova__") + require.NoError(t, err) + + t.Log("sample-apps-cordova-with-jasmine") + { + sampleAppDir := filepath.Join(tmpDir, "sample-apps-cordova-with-jasmine") + sampleAppURL := "https://github.com/bitrise-samples/sample-apps-cordova-with-jasmine.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(sampleAppsCordovaWithJasmineResultYML), strings.TrimSpace(result)) + } + + t.Log("sample-apps-cordova-with-karma-jasmine") + { + sampleAppDir := filepath.Join(tmpDir, "sample-apps-cordova-with-karma-jasmine") + sampleAppURL := "https://github.com/bitrise-samples/sample-apps-cordova-with-karma-jasmine.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(sampleAppsCordovaWithKarmaJasmineResultYML), strings.TrimSpace(result)) + } +} + +var sampleAppsCordovaWithJasmineVersions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.NpmVersion, + steps.JasmineTestRunnerVersion, + steps.GenerateCordovaBuildConfigVersion, + steps.CordovaArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.JasmineTestRunnerVersion, + steps.DeployToBitriseIoVersion, +} + +var sampleAppsCordovaWithJasmineResultYML = fmt.Sprintf(`options: + cordova: + title: Platform to use in cordova-cli commands + env_key: CORDOVA_PLATFORM + value_map: + android: + config: cordova-config + ios: + config: cordova-config + ios,android: + config: cordova-config +configs: + cordova: + cordova-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: cordova + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - npm@%s: + inputs: + - command: install + - jasmine-runner@%s: {} + - generate-cordova-build-configuration@%s: {} + - cordova-archive@%s: + inputs: + - platform: $CORDOVA_PLATFORM + - target: emulator + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - command: install + - jasmine-runner@%s: {} + - deploy-to-bitrise-io@%s: {} +warnings: + cordova: [] +`, sampleAppsCordovaWithJasmineVersions...) + +var sampleAppsCordovaWithKarmaJasmineVersions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.NpmVersion, + steps.KarmaJasmineTestRunnerVersion, + steps.GenerateCordovaBuildConfigVersion, + steps.CordovaArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.KarmaJasmineTestRunnerVersion, + steps.DeployToBitriseIoVersion, +} + +var sampleAppsCordovaWithKarmaJasmineResultYML = fmt.Sprintf(`options: + cordova: + title: Platform to use in cordova-cli commands + env_key: CORDOVA_PLATFORM + value_map: + android: + config: cordova-config + ios: + config: cordova-config + ios,android: + config: cordova-config +configs: + cordova: + cordova-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: cordova + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - npm@%s: + inputs: + - command: install + - karma-jasmine-runner@%s: {} + - generate-cordova-build-configuration@%s: {} + - cordova-archive@%s: + inputs: + - platform: $CORDOVA_PLATFORM + - target: emulator + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - command: install + - karma-jasmine-runner@%s: {} + - deploy-to-bitrise-io@%s: {} +warnings: + cordova: [] + `, sampleAppsCordovaWithKarmaJasmineVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/fastlane_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/fastlane_test.go new file mode 100644 index 00000000..56b2db35 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/fastlane_test.go @@ -0,0 +1,182 @@ +package integration + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestFastlane(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("fastlane") + require.NoError(t, err) + + t.Log("fastlane") + { + sampleAppDir := filepath.Join(tmpDir, "__fastlane__") + sampleAppURL := "https://github.com/bitrise-samples/fastlane.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(fastlaneResultYML), strings.TrimSpace(result)) + } +} + +var fastlaneVersions = []interface{}{ + // fastlane + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FastlaneVersion, + steps.DeployToBitriseIoVersion, + + // ios + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XcodeTestVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XcodeTestVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, +} + +var fastlaneResultYML = fmt.Sprintf(`options: + fastlane: + title: Project type + value_map: + ios: + title: Working directory + env_key: FASTLANE_WORK_DIR + value_map: + BitriseFastlaneSample: + title: Fastlane lane + env_key: FASTLANE_LANE + value_map: + ios test: + config: fastlane-config_ios + ios: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + BitriseFastlaneSample/BitriseFastlaneSample.xcodeproj: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + BitriseFastlaneSample: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: ios-test-config + app-store: + config: ios-test-config + development: + config: ios-test-config + enterprise: + config: ios-test-config +configs: + fastlane: + fastlane-config_ios: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: ios + app: + envs: + - FASTLANE_XCODE_LIST_TIMEOUT: "120" + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - fastlane@%s: + inputs: + - lane: $FASTLANE_LANE + - work_dir: $FASTLANE_WORK_DIR + - deploy-to-bitrise-io@%s: {} + ios: + ios-test-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: ios + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - xcode-test@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - xcode-test@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} +warnings: + fastlane: [] + ios: [] +`, fastlaneVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/flutter_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/flutter_test.go new file mode 100644 index 00000000..9ba529db --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/flutter_test.go @@ -0,0 +1,1576 @@ +package integration + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestFlutter(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__flutter__") + require.NoError(t, err) + + t.Log("sample-apps-flutter-ios-android") + { + sampleAppDir := filepath.Join(tmpDir, "sample-apps-flutter-ios-android") + sampleAppURL := "https://github.com/bitrise-samples/sample-apps-flutter-ios-android.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + + validateConfigExpectation(t, "sample-apps-flutter-ios-android", strings.TrimSpace(flutterSampleAppResultYML), strings.TrimSpace(result), flutterSampleAppVersions...) + } + + t.Log("sample-apps-flutter-ios-android-package") + { + sampleAppDir := filepath.Join(tmpDir, "sample-apps-flutter-ios-android-package") + sampleAppURL := "https://github.com/bitrise-samples/sample-apps-flutter-ios-android-package.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + + validateConfigExpectation(t, "sample-apps-flutter-ios-android-package", strings.TrimSpace(flutterSamplePackageResultYML), strings.TrimSpace(result), flutterSamplePackageVersions...) + } + + t.Log("sample-apps-flutter-ios-android-plugin") + { + sampleAppDir := filepath.Join(tmpDir, "sample-apps-flutter-ios-android-plugin") + sampleAppURL := "https://github.com/bitrise-samples/sample-apps-flutter-ios-android-plugin.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + + validateConfigExpectation(t, "sample-apps-flutter-ios-android-plugin", strings.TrimSpace(flutterSamplePluginResultYML), strings.TrimSpace(result), flutterSamplePluginVersions...) + } +} + +var flutterSampleAppVersions = []interface{}{ + // flutter-config + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-app-android + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterBuildVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-app-both + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-app-ios + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-test + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-test-app-android + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.FlutterBuildVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-test-app-both + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-test-app-ios + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, +} + +var flutterSampleAppResultYML = fmt.Sprintf(`options: + flutter: + title: Project Location + env_key: BITRISE_FLUTTER_PROJECT_LOCATION + value_map: + .: + title: Run tests found in the project + value_map: + "no": + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + ios/Runner.xcworkspace: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + Runner: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: flutter-config-app-both + app-store: + config: flutter-config-app-both + development: + config: flutter-config-app-both + enterprise: + config: flutter-config-app-both + "yes": + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + ios/Runner.xcworkspace: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + Runner: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: flutter-config-test-app-both + app-store: + config: flutter-config-test-app-both + development: + config: flutter-config-test-app-both + enterprise: + config: flutter-config-test-app-both +configs: + flutter: + flutter-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-app-android: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: android + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-app-both: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: both + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-app-ios: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: ios + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test-app-android: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: android + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test-app-both: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: both + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test-app-ios: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: ios + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} +warnings: + flutter: [] +`, flutterSampleAppVersions...) + +var flutterSamplePackageVersions = []interface{}{ + // flutter-config + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-app-android + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterBuildVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-app-both + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-app-ios + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-test + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-test-app-android + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.FlutterBuildVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-test-app-both + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-test-app-ios + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, +} + +var flutterSamplePackageResultYML = fmt.Sprintf(`options: + flutter: + title: Project Location + env_key: BITRISE_FLUTTER_PROJECT_LOCATION + value_map: + .: + title: Run tests found in the project + value_map: + "no": + config: flutter-config + "yes": + config: flutter-config-test +configs: + flutter: + flutter-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-app-android: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: android + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-app-both: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: both + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-app-ios: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: ios + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test-app-android: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: android + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test-app-both: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: both + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test-app-ios: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: ios + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} +warnings: + flutter: [] +`, flutterSamplePackageVersions...) + +var flutterSamplePluginVersions = []interface{}{ + // flutter-config + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-app-android + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterBuildVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-app-both + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-app-ios + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-test + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-test-app-android + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.FlutterBuildVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-test-app-both + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + // flutter-config-test-app-ios + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, +} + +var flutterSamplePluginResultYML = fmt.Sprintf(`options: + flutter: + title: Project Location + env_key: BITRISE_FLUTTER_PROJECT_LOCATION + value_map: + .: + config: flutter-config-app-android + example: + title: Run tests found in the project + value_map: + "no": + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + example/ios/Runner.xcworkspace: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + Runner: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: flutter-config-app-both + app-store: + config: flutter-config-app-both + development: + config: flutter-config-app-both + enterprise: + config: flutter-config-app-both + "yes": + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + example/ios/Runner.xcworkspace: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + Runner: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: flutter-config-test-app-both + app-store: + config: flutter-config-test-app-both + development: + config: flutter-config-test-app-both + enterprise: + config: flutter-config-test-app-both +configs: + flutter: + flutter-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-app-android: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: android + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-app-both: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: both + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-app-ios: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: ios + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test-app-android: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: android + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test-app-both: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: both + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test-app-ios: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: ios + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} +warnings: + flutter: [] +`, flutterSamplePluginVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/helper.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/helper.go new file mode 100644 index 00000000..0c3a06cc --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/helper.go @@ -0,0 +1,66 @@ +package integration + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/log" + + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func binPath() string { + return os.Getenv("INTEGRATION_TEST_BINARY_PATH") +} + +func replaceVersions(str string, versions ...interface{}) (string, error) { + for _, f := range versions { + if format, ok := f.(string); ok { + beforeCount := strings.Count(str, format) + if beforeCount < 1 { + return "", fmt.Errorf("format's original value not found") + } + str = strings.Replace(str, format, "%s", 1) + + afterCount := strings.Count(str, format) + if beforeCount-1 != afterCount { + return "", fmt.Errorf("failed to extract all versions") + } + } + } + return str, nil +} + +func validateConfigExpectation(t *testing.T, ID, expected, actual string, versions ...interface{}) { + if !assert.ObjectsAreEqual(expected, actual) { + s, err := replaceVersions(actual, versions...) + require.NoError(t, err) + fmt.Println("---------------------") + fmt.Println("Actual config format:") + fmt.Println("---------------------") + fmt.Println(s) + fmt.Println("---------------------") + + _, err = exec.LookPath("opendiff") + if err == nil { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__diffs__") + require.NoError(t, err) + expPth := filepath.Join(tmpDir, ID+"-expected.yml") + actPth := filepath.Join(tmpDir, ID+"-actual.yml") + require.NoError(t, fileutil.WriteStringToFile(expPth, expected)) + require.NoError(t, fileutil.WriteStringToFile(actPth, actual)) + require.NoError(t, exec.Command("opendiff", expPth, actPth).Start()) + t.FailNow() + return + } + log.Warnf("opendiff not installed, unable to open config diff") + t.FailNow() + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ionic_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ionic_test.go new file mode 100644 index 00000000..9d9f334d --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ionic_test.go @@ -0,0 +1,99 @@ +package integration + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestIonic(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__ionic__") + require.NoError(t, err) + + t.Log("ionic-2") + { + sampleAppDir := filepath.Join(tmpDir, "ionic-2") + sampleAppURL := "https://github.com/bitrise-samples/ionic-2.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(ionic2ResultYML), strings.TrimSpace(result)) + } +} + +var ionic2Versions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.NpmVersion, + steps.GenerateCordovaBuildConfigVersion, + steps.IonicArchiveVersion, + steps.DeployToBitriseIoVersion, +} + +var ionic2ResultYML = fmt.Sprintf(`options: + ionic: + title: Directory of Ionic Config.xml + env_key: IONIC_WORK_DIR + value_map: + cutePuppyPics: + title: Platform to use in ionic-cli commands + env_key: IONIC_PLATFORM + value_map: + android: + config: ionic-config + ios: + config: ionic-config + ios,android: + config: ionic-config +configs: + ionic: + ionic-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: ionic + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - npm@%s: + inputs: + - workdir: $IONIC_WORK_DIR + - command: install + - generate-cordova-build-configuration@%s: {} + - ionic-archive@%s: + inputs: + - platform: $IONIC_PLATFORM + - target: emulator + - workdir: $IONIC_WORK_DIR + - deploy-to-bitrise-io@%s: {} +warnings: + ionic: [] +`, ionic2Versions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ios_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ios_test.go new file mode 100644 index 00000000..d48090e6 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ios_test.go @@ -0,0 +1,573 @@ +package integration + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestIOS(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__ios__") + require.NoError(t, err) + + t.Log("ios-no-shared-schemes") + { + sampleAppDir := filepath.Join(tmpDir, "ios-no-shared-scheme") + sampleAppURL := "https://github.com/bitrise-samples/ios-no-shared-schemes.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(iosNoSharedSchemesResultYML), strings.TrimSpace(result)) + } + + t.Log("ios-cocoapods-at-root") + { + sampleAppDir := filepath.Join(tmpDir, "ios-cocoapods-at-root") + sampleAppURL := "https://github.com/bitrise-samples/ios-cocoapods-at-root.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(iosCocoapodsAtRootResultYML), strings.TrimSpace(result)) + } + + t.Log("sample-apps-ios-watchkit") + { + sampleAppDir := filepath.Join(tmpDir, "sample-apps-ios-watchkit") + sampleAppURL := "https://github.com/bitrise-io/sample-apps-ios-watchkit.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(sampleAppsIosWatchkitResultYML), strings.TrimSpace(result)) + } + + t.Log("sample-apps-carthage") + { + // + sampleAppDir := filepath.Join(tmpDir, "sample-apps-carthage") + sampleAppURL := "https://github.com/bitrise-samples/sample-apps-carthage.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(sampleAppsCarthageResultYML), strings.TrimSpace(result)) + } +} + +var iosNoSharedSchemesVersions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.RecreateUserSchemesVersion, + steps.XcodeTestVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.RecreateUserSchemesVersion, + steps.XcodeTestVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, +} + +var iosNoSharedSchemesResultYML = fmt.Sprintf(`options: + ios: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + BitriseXcode7Sample.xcodeproj: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + BitriseXcode7Sample: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: ios-test-missing-shared-schemes-config + app-store: + config: ios-test-missing-shared-schemes-config + development: + config: ios-test-missing-shared-schemes-config + enterprise: + config: ios-test-missing-shared-schemes-config +configs: + ios: + ios-test-missing-shared-schemes-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: ios + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - recreate-user-schemes@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - xcode-test@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - recreate-user-schemes@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - xcode-test@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} +warnings: + ios: + - |- + No shared schemes found for project: BitriseXcode7Sample.xcodeproj. + Automatically generated schemes may differ from the ones in your project. + Make sure to share your schemes for the expected behaviour. +`, iosNoSharedSchemesVersions...) + +var iosCocoapodsAtRootVersions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.CocoapodsInstallVersion, + steps.XcodeTestVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.CocoapodsInstallVersion, + steps.XcodeTestVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, +} + +var iosCocoapodsAtRootResultYML = fmt.Sprintf(`options: + ios: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + iOSMinimalCocoaPodsSample.xcworkspace: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + iOSMinimalCocoaPodsSample: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: ios-pod-test-config + app-store: + config: ios-pod-test-config + development: + config: ios-pod-test-config + enterprise: + config: ios-pod-test-config +configs: + ios: + ios-pod-test-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: ios + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - cocoapods-install@%s: {} + - xcode-test@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - cocoapods-install@%s: {} + - xcode-test@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} +warnings: + ios: [] +`, iosCocoapodsAtRootVersions...) + +var sampleAppsIosWatchkitVersions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XcodeTestVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XcodeTestVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, +} + +var sampleAppsIosWatchkitResultYML = fmt.Sprintf(`options: + ios: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + watch-test.xcodeproj: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + Complication - watch-test WatchKit App: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: ios-config + app-store: + config: ios-config + development: + config: ios-config + enterprise: + config: ios-config + Glance - watch-test WatchKit App: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: ios-config + app-store: + config: ios-config + development: + config: ios-config + enterprise: + config: ios-config + Notification - watch-test WatchKit App: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: ios-config + app-store: + config: ios-config + development: + config: ios-config + enterprise: + config: ios-config + watch-test: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: ios-test-config + app-store: + config: ios-test-config + development: + config: ios-test-config + enterprise: + config: ios-test-config + watch-test WatchKit App: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: ios-config + app-store: + config: ios-config + development: + config: ios-config + enterprise: + config: ios-config +configs: + ios: + ios-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: ios + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + ios-test-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: ios + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - xcode-test@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - xcode-test@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} +warnings: + ios: [] +`, sampleAppsIosWatchkitVersions...) + +var sampleAppsCarthageVersions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.CarthageVersion, + steps.XcodeTestVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.CarthageVersion, + steps.XcodeTestVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, +} + +var sampleAppsCarthageResultYML = fmt.Sprintf(`options: + ios: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + sample-apps-carthage.xcodeproj: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + sample-apps-carthage: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: ios-carthage-test-config + app-store: + config: ios-carthage-test-config + development: + config: ios-carthage-test-config + enterprise: + config: ios-carthage-test-config +configs: + ios: + ios-carthage-test-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: ios + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - carthage@%s: + inputs: + - carthage_command: bootstrap + - xcode-test@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - carthage@%s: + inputs: + - carthage_command: bootstrap + - xcode-test@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} +warnings: + ios: [] +`, sampleAppsCarthageVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/mac_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/mac_test.go new file mode 100644 index 00000000..89636fcf --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/mac_test.go @@ -0,0 +1,133 @@ +package integration + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestMacOS(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__macos__") + require.NoError(t, err) + + t.Log("sample-apps-osx-10-11") + { + sampleAppDir := filepath.Join(tmpDir, "sample-apps-osx-10-11") + sampleAppURL := "https://github.com/bitrise-samples/sample-apps-osx-10-11.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(sampleAppsOSX1011ResultYML), strings.TrimSpace(result)) + } +} + +var sampleAppsOSX1011Versions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XcodeTestMacVersion, + steps.XcodeArchiveMacVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XcodeTestMacVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, +} + +var sampleAppsOSX1011ResultYML = fmt.Sprintf(`options: + macos: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + sample-apps-osx-10-11.xcodeproj: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + sample-apps-osx-10-11: + title: |- + Application export method + NOTE: `+"`none`"+` means: Export a copy of the application without re-signing. + env_key: BITRISE_EXPORT_METHOD + value_map: + app-store: + config: macos-test-config + developer-id: + config: macos-test-config + development: + config: macos-test-config + none: + config: macos-test-config +configs: + macos: + macos-test-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: macos + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - xcode-test-mac@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - xcode-archive-mac@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - xcode-test-mac@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} +warnings: + macos: [] +`, sampleAppsOSX1011Versions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/manual_config_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/manual_config_test.go new file mode 100644 index 00000000..be841d23 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/manual_config_test.go @@ -0,0 +1,1699 @@ +package integration + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestManualConfig(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__manual-config__") + require.NoError(t, err) + + t.Log("manual-config") + { + manualConfigDir := filepath.Join(tmpDir, "manual-config") + require.NoError(t, os.MkdirAll(manualConfigDir, 0777)) + fmt.Printf("manualConfigDir: %s\n", manualConfigDir) + + cmd := command.New(binPath(), "--ci", "manual-config", "--output-dir", manualConfigDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(manualConfigDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + + validateConfigExpectation(t, "manual-config", strings.TrimSpace(customConfigResultYML), strings.TrimSpace(result), customConfigVersions...) + } +} + +var customConfigVersions = []interface{}{ + // android + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.InstallMissingAndroidToolsVersion, + steps.ChangeAndroidVersionCodeAndVersionNameVersion, + steps.AndroidLintVersion, + steps.AndroidUnitTestVersion, + steps.AndroidBuildVersion, + steps.SignAPKVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.InstallMissingAndroidToolsVersion, + steps.AndroidLintVersion, + steps.AndroidUnitTestVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + // cordova + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.NpmVersion, + steps.GenerateCordovaBuildConfigVersion, + steps.CordovaArchiveVersion, + steps.DeployToBitriseIoVersion, + + // fastlane + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FastlaneVersion, + steps.DeployToBitriseIoVersion, + + // flutter + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterBuildVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.DeployToBitriseIoVersion, + + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.FlutterBuildVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.FlutterBuildVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.FlutterInstallVersion, + steps.FlutterAnalyzeVersion, + steps.FlutterTestVersion, + steps.DeployToBitriseIoVersion, + + // ionic + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.NpmVersion, + steps.GenerateCordovaBuildConfigVersion, + steps.IonicArchiveVersion, + steps.DeployToBitriseIoVersion, + + // ios + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.RecreateUserSchemesVersion, + steps.CocoapodsInstallVersion, + steps.XcodeTestVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.RecreateUserSchemesVersion, + steps.CocoapodsInstallVersion, + steps.XcodeTestVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + // macos + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.RecreateUserSchemesVersion, + steps.CocoapodsInstallVersion, + steps.XcodeTestMacVersion, + steps.XcodeArchiveMacVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.CachePullVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.RecreateUserSchemesVersion, + steps.CocoapodsInstallVersion, + steps.XcodeTestMacVersion, + steps.DeployToBitriseIoVersion, + steps.CachePushVersion, + + // other + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.DeployToBitriseIoVersion, + + // react native + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.InstallMissingAndroidToolsVersion, + steps.AndroidBuildVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.NpmVersion, + steps.DeployToBitriseIoVersion, + + // react native expo with expo kit + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.ExpoDetachVersion, + steps.InstallMissingAndroidToolsVersion, + steps.AndroidBuildVersion, + steps.CertificateAndProfileInstallerVersion, + steps.CocoapodsInstallVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.NpmVersion, + steps.DeployToBitriseIoVersion, + + // react native expo (plain) + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.ExpoDetachVersion, + steps.InstallMissingAndroidToolsVersion, + steps.AndroidBuildVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.NpmVersion, + steps.DeployToBitriseIoVersion, + + // xamarin + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XamarinUserManagementVersion, + steps.NugetRestoreVersion, + steps.XamarinComponentsRestoreVersion, + steps.XamarinArchiveVersion, + steps.DeployToBitriseIoVersion, +} + +var customConfigResultYML = fmt.Sprintf(`options: + android: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + _: + title: Module + env_key: MODULE + value_map: + _: + title: Variant + env_key: VARIANT + value_map: + "": + config: default-android-config + cordova: + title: Directory of Cordova Config.xml + env_key: CORDOVA_WORK_DIR + value_map: + _: + title: Platform to use in cordova-cli commands + env_key: CORDOVA_PLATFORM + value_map: + android: + config: default-cordova-config + ios: + config: default-cordova-config + ios,android: + config: default-cordova-config + fastlane: + title: Working directory + env_key: FASTLANE_WORK_DIR + value_map: + _: + title: Fastlane lane + env_key: FASTLANE_LANE + value_map: + _: + config: default-fastlane-config + flutter: + title: Project Location + env_key: BITRISE_FLUTTER_PROJECT_LOCATION + value_map: + _: + title: Run tests found in the project + value_map: + "no": + title: Platform + value_map: + android: + config: flutter-config-app-android + both: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + _: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + _: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: flutter-config-app-both + app-store: + config: flutter-config-app-both + development: + config: flutter-config-app-both + enterprise: + config: flutter-config-app-both + ios: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + _: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + _: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: flutter-config-app-ios + app-store: + config: flutter-config-app-ios + development: + config: flutter-config-app-ios + enterprise: + config: flutter-config-app-ios + none: + config: flutter-config + "yes": + title: Platform + value_map: + android: + config: flutter-config-test-app-android + both: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + _: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + _: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: flutter-config-test-app-both + app-store: + config: flutter-config-test-app-both + development: + config: flutter-config-test-app-both + enterprise: + config: flutter-config-test-app-both + ios: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + _: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + _: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: flutter-config-test-app-ios + app-store: + config: flutter-config-test-app-ios + development: + config: flutter-config-test-app-ios + enterprise: + config: flutter-config-test-app-ios + none: + config: flutter-config-test + ionic: + title: Directory of Ionic Config.xml + env_key: IONIC_WORK_DIR + value_map: + _: + title: Platform to use in ionic-cli commands + env_key: IONIC_PLATFORM + value_map: + android: + config: default-ionic-config + ios: + config: default-ionic-config + ios,android: + config: default-ionic-config + ios: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + _: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + _: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: default-ios-config + app-store: + config: default-ios-config + development: + config: default-ios-config + enterprise: + config: default-ios-config + macos: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + _: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + _: + title: |- + Application export method + NOTE: `+"`none`"+` means: Export a copy of the application without re-signing. + env_key: BITRISE_EXPORT_METHOD + value_map: + app-store: + config: default-macos-config + developer-id: + config: default-macos-config + development: + config: default-macos-config + none: + config: default-macos-config + react-native: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + _: + title: Module + env_key: MODULE + value_map: + _: + title: Variant + env_key: VARIANT + value_map: + "": + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + _: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + _: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: default-react-native-config + app-store: + config: default-react-native-config + development: + config: default-react-native-config + enterprise: + config: default-react-native-config + react-native-expo: + title: Project uses Expo Kit (any js file imports expo dependency)? + env_key: USES_EXPO_KIT + value_map: + "no": + title: The iOS project path generated ny the 'expo eject' process + env_key: BITRISE_PROJECT_PATH + value_map: + _: + title: The iOS scheme name generated by the 'expo eject' process + env_key: BITRISE_SCHEME + value_map: + _: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + title: Project root directory (the directory of the project app.json/package.json + file) + env_key: WORKDIR + value_map: + _: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + config: react-native-expo-plain-default-config + app-store: + title: Project root directory (the directory of the project app.json/package.json + file) + env_key: WORKDIR + value_map: + _: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + config: react-native-expo-plain-default-config + development: + title: Project root directory (the directory of the project app.json/package.json + file) + env_key: WORKDIR + value_map: + _: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + config: react-native-expo-plain-default-config + enterprise: + title: Project root directory (the directory of the project app.json/package.json + file) + env_key: WORKDIR + value_map: + _: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + config: react-native-expo-plain-default-config + "yes": + title: The iOS workspace path generated ny the 'expo eject' process + env_key: BITRISE_PROJECT_PATH + value_map: + _: + title: The iOS scheme name generated by the 'expo eject' process + env_key: BITRISE_SCHEME + value_map: + _: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + title: Project root directory (the directory of the project app.json/package.json + file) + env_key: WORKDIR + value_map: + _: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + title: Expo username + env_key: EXPO_USERNAME + value_map: + _: + title: Expo password + env_key: EXPO_PASSWORD + value_map: + _: + config: react-native-expo-expo-kit-default-config + app-store: + title: Project root directory (the directory of the project app.json/package.json + file) + env_key: WORKDIR + value_map: + _: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + title: Expo username + env_key: EXPO_USERNAME + value_map: + _: + title: Expo password + env_key: EXPO_PASSWORD + value_map: + _: + config: react-native-expo-expo-kit-default-config + development: + title: Project root directory (the directory of the project app.json/package.json + file) + env_key: WORKDIR + value_map: + _: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + title: Expo username + env_key: EXPO_USERNAME + value_map: + _: + title: Expo password + env_key: EXPO_PASSWORD + value_map: + _: + config: react-native-expo-expo-kit-default-config + enterprise: + title: Project root directory (the directory of the project app.json/package.json + file) + env_key: WORKDIR + value_map: + _: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + title: Expo username + env_key: EXPO_USERNAME + value_map: + _: + title: Expo password + env_key: EXPO_PASSWORD + value_map: + _: + config: react-native-expo-expo-kit-default-config + xamarin: + title: Path to the Xamarin Solution file + env_key: BITRISE_PROJECT_PATH + value_map: + _: + title: Xamarin solution configuration + env_key: BITRISE_XAMARIN_CONFIGURATION + value_map: + _: + title: Xamarin solution platform + env_key: BITRISE_XAMARIN_PLATFORM + value_map: + _: + config: default-xamarin-config +configs: + android: + default-android-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: android + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + description: | + ## How to get a signed APK + + This workflow contains the **Sign APK** step. To sign your APK all you have to do is to: + + 1. Click on **Code Signing** tab + 1. Find the **ANDROID KEYSTORE FILE** section + 1. Click or drop your file on the upload file field + 1. Fill the displayed 3 input fields: + 1. **Keystore password** + 1. **Keystore alias** + 1. **Private key password** + 1. Click on **[Save metadata]** button + + That's it! From now on, **Sign APK** step will receive your uploaded files. + + ## To run this workflow + + If you want to run this workflow manually: + + 1. Open the app's build list page + 2. Click on **[Start/Schedule a Build]** button + 3. Select **deploy** in **Workflow** dropdown input + 4. Click **[Start Build]** button + + Or if you need this workflow to be started by a GIT event: + + 1. Click on **Triggers** tab + 2. Setup your desired event (push/tag/pull) and select **deploy** workflow + 3. Click on **[Done]** and then **[Save]** buttons + + The next change in your repository that matches any of your trigger map event will start **deploy** workflow. + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - change-android-versioncode-and-versionname@%s: + inputs: + - build_gradle_path: $PROJECT_LOCATION/$MODULE/build.gradle + - android-lint@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - android-unit-test@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - android-build@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - sign-apk@%s: + run_if: '{{getenv "BITRISEIO_ANDROID_KEYSTORE_URL" | ne ""}}' + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - android-lint@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - android-unit-test@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + cordova: + default-cordova-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: cordova + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - npm@%s: + inputs: + - command: install + - workdir: $CORDOVA_WORK_DIR + - generate-cordova-build-configuration@%s: {} + - cordova-archive@%s: + inputs: + - workdir: $CORDOVA_WORK_DIR + - platform: $CORDOVA_PLATFORM + - target: emulator + - deploy-to-bitrise-io@%s: {} + fastlane: + default-fastlane-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: other + app: + envs: + - FASTLANE_XCODE_LIST_TIMEOUT: "120" + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - fastlane@%s: + inputs: + - lane: $FASTLANE_LANE + - work_dir: $FASTLANE_WORK_DIR + - deploy-to-bitrise-io@%s: {} + flutter: + flutter-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-app-android: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: android + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-app-both: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: both + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-app-ios: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: ios + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test-app-android: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: android + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test-app-both: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: both + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + flutter-config-test-app-ios: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: flutter + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-build@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - platform: ios + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - flutter-installer@%s: {} + - flutter-analyze@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - flutter-test@%s: + inputs: + - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION + - deploy-to-bitrise-io@%s: {} + ionic: + default-ionic-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: ionic + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - npm@%s: + inputs: + - command: install + - workdir: $IONIC_WORK_DIR + - generate-cordova-build-configuration@%s: {} + - ionic-archive@%s: + inputs: + - workdir: $IONIC_WORK_DIR + - platform: $IONIC_PLATFORM + - target: emulator + - deploy-to-bitrise-io@%s: {} + ios: + default-ios-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: ios + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - recreate-user-schemes@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - cocoapods-install@%s: {} + - xcode-test@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - recreate-user-schemes@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - cocoapods-install@%s: {} + - xcode-test@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + macos: + default-macos-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: macos + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - recreate-user-schemes@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - cocoapods-install@%s: {} + - xcode-test-mac@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - xcode-archive-mac@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - cache-pull@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - recreate-user-schemes@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - cocoapods-install@%s: {} + - xcode-test-mac@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - deploy-to-bitrise-io@%s: {} + - cache-push@%s: {} + other: + other-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: other + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - deploy-to-bitrise-io@%s: {} + react-native: + default-react-native-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: react-native + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - command: install + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - android-build@%s: + inputs: + - project_location: $PROJECT_LOCATION + - certificate-and-profile-installer@%s: {} + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - command: install + - npm@%s: + inputs: + - command: test + - deploy-to-bitrise-io@%s: {} + react-native-expo: + default-react-native-expo-expo-kit-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: react-native-expo + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + description: "## Configure Android part of the deploy workflow\n\nTo generate + a signed APK:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. + Add **Sign APK step right after Android Build step**\n1. Click on **Code Signing** + tab\n1. Find the **ANDROID KEYSTORE FILE** section\n1. Click or drop your file + on the upload file field\n1. Fill the displayed 3 input fields:\n1. **Keystore + password**\n1. **Keystore alias**\n1. **Private key password**\n1. Click on + **[Save metadata]** button\n\nThat's it! From now on, **Sign APK** step will + receive your uploaded files.\n\n## Configure iOS part of the deploy workflow\n\nTo + generate IPA:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. + Click on **Code Signing** tab\n1. Find the **PROVISIONING PROFILE** section\n1. + Click or drop your file on the upload file field\n1. Find the **CODE SIGNING + IDENTITY** section\n1. Click or drop your file on the upload file field\n1. + Click on **Workflows** tab\n1. Select deploy workflow\n1. Select **Xcode Archive + & Export for iOS** step\n1. Open **Force Build Settings** input group\n1. Specify + codesign settings\nSet **Force code signing with Development Team**, **Force + code signing with Code Signing Identity** \nand **Force code signing with Provisioning + Profile** inputs regarding to the uploaded codesigning files\n1. Specify manual + codesign style\nIf the codesigning files, are generated manually on the Apple + Developer Portal, \nyou need to explicitly specify to use manual coedsign settings + \ \n(as ejected rn projects have xcode managed codesigning turned on). \nTo + do so, add 'CODE_SIGN_STYLE=\"Manual\"' to 'Additional options for xcodebuild + call' input\n\n## To run this workflow\n\nIf you want to run this workflow manually:\n\n1. + Open the app's build list page\n2. Click on **[Start/Schedule a Build]** button\n3. + Select **deploy** in **Workflow** dropdown input\n4. Click **[Start Build]** + button\n\nOr if you need this workflow to be started by a GIT event:\n\n1. Click + on **Triggers** tab\n2. Setup your desired event (push/tag/pull) and select + **deploy** workflow\n3. Click on **[Done]** and then **[Save]** buttons\n\nThe + next change in your repository that matches any of your trigger map event will + start **deploy** workflow.\n" + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - workdir: $WORKDIR + - command: install + - expo-detach@%s: + inputs: + - project_path: $WORKDIR + - user_name: $EXPO_USERNAME + - password: $EXPO_PASSWORD + - run_publish: "yes" + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - android-build@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - certificate-and-profile-installer@%s: {} + - cocoapods-install@%s: {} + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - workdir: $WORKDIR + - command: install + - npm@%s: + inputs: + - workdir: $WORKDIR + - command: test + - deploy-to-bitrise-io@%s: {} + default-react-native-expo-plain-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: react-native-expo + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + description: "## Configure Android part of the deploy workflow\n\nTo generate + a signed APK:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. + Add **Sign APK step right after Android Build step**\n1. Click on **Code Signing** + tab\n1. Find the **ANDROID KEYSTORE FILE** section\n1. Click or drop your file + on the upload file field\n1. Fill the displayed 3 input fields:\n1. **Keystore + password**\n1. **Keystore alias**\n1. **Private key password**\n1. Click on + **[Save metadata]** button\n\nThat's it! From now on, **Sign APK** step will + receive your uploaded files.\n\n## Configure iOS part of the deploy workflow\n\nTo + generate IPA:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. + Click on **Code Signing** tab\n1. Find the **PROVISIONING PROFILE** section\n1. + Click or drop your file on the upload file field\n1. Find the **CODE SIGNING + IDENTITY** section\n1. Click or drop your file on the upload file field\n1. + Click on **Workflows** tab\n1. Select deploy workflow\n1. Select **Xcode Archive + & Export for iOS** step\n1. Open **Force Build Settings** input group\n1. Specify + codesign settings\nSet **Force code signing with Development Team**, **Force + code signing with Code Signing Identity** \nand **Force code signing with Provisioning + Profile** inputs regarding to the uploaded codesigning files\n1. Specify manual + codesign style\nIf the codesigning files, are generated manually on the Apple + Developer Portal, \nyou need to explicitly specify to use manual coedsign settings + \ \n(as ejected rn projects have xcode managed codesigning turned on). \nTo + do so, add 'CODE_SIGN_STYLE=\"Manual\"' to 'Additional options for xcodebuild + call' input\n\n## To run this workflow\n\nIf you want to run this workflow manually:\n\n1. + Open the app's build list page\n2. Click on **[Start/Schedule a Build]** button\n3. + Select **deploy** in **Workflow** dropdown input\n4. Click **[Start Build]** + button\n\nOr if you need this workflow to be started by a GIT event:\n\n1. Click + on **Triggers** tab\n2. Setup your desired event (push/tag/pull) and select + **deploy** workflow\n3. Click on **[Done]** and then **[Save]** buttons\n\nThe + next change in your repository that matches any of your trigger map event will + start **deploy** workflow.\n" + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - workdir: $WORKDIR + - command: install + - expo-detach@%s: + inputs: + - project_path: $WORKDIR + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - android-build@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - certificate-and-profile-installer@%s: {} + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - workdir: $WORKDIR + - command: install + - npm@%s: + inputs: + - workdir: $WORKDIR + - command: test + - deploy-to-bitrise-io@%s: {} + xamarin: + default-xamarin-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: xamarin + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - xamarin-user-management@%s: + run_if: .IsCI + - nuget-restore@%s: {} + - xamarin-components-restore@%s: {} + - xamarin-archive@%s: + inputs: + - xamarin_solution: $BITRISE_PROJECT_PATH + - xamarin_configuration: $BITRISE_XAMARIN_CONFIGURATION + - xamarin_platform: $BITRISE_XAMARIN_PLATFORM + - deploy-to-bitrise-io@%s: {} +`, customConfigVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_expo_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_expo_test.go new file mode 100644 index 00000000..7406f9dc --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_expo_test.go @@ -0,0 +1,446 @@ +package integration + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestReactNativeExpoWithExpoKit(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__reactnative_expo_with_expo_kit__") + require.NoError(t, err) + + t.Log("BitriseExpoKit") + { + sampleAppDir := filepath.Join(tmpDir, "BitriseExpoKit") + sampleAppURL := "https://github.com/bitrise-samples/BitriseExpoKit.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + + validateConfigExpectation(t, "BitriseExpoKit", strings.TrimSpace(bitriseExpoKitResultYML), strings.TrimSpace(result), bitriseExpoKitVersions...) + } +} + +func TestReactNativeExpo(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__reactnative_expo__") + require.NoError(t, err) + + t.Log("BitriseCRNA") + { + sampleAppDir := filepath.Join(tmpDir, "BitriseCRNA") + sampleAppURL := "https://github.com/bitrise-samples/BitriseCRNA.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + + validateConfigExpectation(t, "BitriseCRNA", strings.TrimSpace(bitriseCRNAResultYML), strings.TrimSpace(result), bitriseCRNAVersions...) + } +} + +var bitriseCRNAVersions = []interface{}{ + models.FormatVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.ExpoDetachVersion, + steps.InstallMissingAndroidToolsVersion, + steps.AndroidBuildVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, +} + +var bitriseCRNAResultYML = fmt.Sprintf(`options: + react-native-expo: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + ios/BitriseCRNA.xcodeproj: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + BitriseCRNA: + title: iOS Development team + env_key: BITRISE_IOS_DEVELOPMENT_TEAM + value_map: + _: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + config: react-native-expo-config + app-store: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + config: react-native-expo-config + development: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + config: react-native-expo-config + enterprise: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + config: react-native-expo-config +configs: + react-native-expo: + react-native-expo-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: react-native-expo + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + description: "## Configure Android part of the deploy workflow\n\nTo generate + a signed APK:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. + Add **Sign APK step right after Android Build step**\n1. Click on **Code Signing** + tab\n1. Find the **ANDROID KEYSTORE FILE** section\n1. Click or drop your file + on the upload file field\n1. Fill the displayed 3 input fields:\n1. **Keystore + password**\n1. **Keystore alias**\n1. **Private key password**\n1. Click on + **[Save metadata]** button\n\nThat's it! From now on, **Sign APK** step will + receive your uploaded files.\n\n## Configure iOS part of the deploy workflow\n\nTo + generate IPA:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. + Click on **Code Signing** tab\n1. Find the **PROVISIONING PROFILE** section\n1. + Click or drop your file on the upload file field\n1. Find the **CODE SIGNING + IDENTITY** section\n1. Click or drop your file on the upload file field\n1. + Click on **Workflows** tab\n1. Select deploy workflow\n1. Select **Xcode Archive + & Export for iOS** step\n1. Open **Force Build Settings** input group\n1. Specify + codesign settings\nSet **Force code signing with Development Team**, **Force + code signing with Code Signing Identity** \nand **Force code signing with Provisioning + Profile** inputs regarding to the uploaded codesigning files\n1. Specify manual + codesign style\nIf the codesigning files, are generated manually on the Apple + Developer Portal, \nyou need to explicitly specify to use manual coedsign settings + \ \n(as ejected rn projects have xcode managed codesigning turned on). \nTo + do so, add 'CODE_SIGN_STYLE=\"Manual\"' to 'Additional options for xcodebuild + call' input\n\n## To run this workflow\n\nIf you want to run this workflow manually:\n\n1. + Open the app's build list page\n2. Click on **[Start/Schedule a Build]** button\n3. + Select **deploy** in **Workflow** dropdown input\n4. Click **[Start Build]** + button\n\nOr if you need this workflow to be started by a GIT event:\n\n1. Click + on **Triggers** tab\n2. Setup your desired event (push/tag/pull) and select + **deploy** workflow\n3. Click on **[Done]** and then **[Save]** buttons\n\nThe + next change in your repository that matches any of your trigger map event will + start **deploy** workflow.\n" + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - command: install + - expo-detach@%s: + inputs: + - project_path: ./ + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - android-build@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - certificate-and-profile-installer@%s: {} + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - configuration: Release + - export_method: $BITRISE_EXPORT_METHOD + - force_team_id: $BITRISE_IOS_DEVELOPMENT_TEAM + - xcodebuild_options: -UseModernBuildSystem=NO + - deploy-to-bitrise-io@%s: {} +warnings: + react-native-expo: [] +`, bitriseCRNAVersions...) + +var bitriseExpoKitVersions = []interface{}{ + models.FormatVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.ExpoDetachVersion, + steps.InstallMissingAndroidToolsVersion, + steps.AndroidBuildVersion, + steps.CertificateAndProfileInstallerVersion, + steps.CocoapodsInstallVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.NpmVersion, + steps.DeployToBitriseIoVersion, +} + +var bitriseExpoKitResultYML = fmt.Sprintf(`options: + react-native-expo: + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + ios/bitriseexpokit.xcworkspace: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + bitriseexpokit: + title: iOS Development team + env_key: BITRISE_IOS_DEVELOPMENT_TEAM + value_map: + _: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + title: Expo username + env_key: EXPO_USERNAME + value_map: + _: + title: Expo password + env_key: EXPO_PASSWORD + value_map: + _: + config: react-native-expo-config + app-store: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + title: Expo username + env_key: EXPO_USERNAME + value_map: + _: + title: Expo password + env_key: EXPO_PASSWORD + value_map: + _: + config: react-native-expo-config + development: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + title: Expo username + env_key: EXPO_USERNAME + value_map: + _: + title: Expo password + env_key: EXPO_PASSWORD + value_map: + _: + config: react-native-expo-config + enterprise: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + ./android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + Release: + title: Expo username + env_key: EXPO_USERNAME + value_map: + _: + title: Expo password + env_key: EXPO_PASSWORD + value_map: + _: + config: react-native-expo-config +configs: + react-native-expo: + react-native-expo-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: react-native-expo + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + description: "## Configure Android part of the deploy workflow\n\nTo generate + a signed APK:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. + Add **Sign APK step right after Android Build step**\n1. Click on **Code Signing** + tab\n1. Find the **ANDROID KEYSTORE FILE** section\n1. Click or drop your file + on the upload file field\n1. Fill the displayed 3 input fields:\n1. **Keystore + password**\n1. **Keystore alias**\n1. **Private key password**\n1. Click on + **[Save metadata]** button\n\nThat's it! From now on, **Sign APK** step will + receive your uploaded files.\n\n## Configure iOS part of the deploy workflow\n\nTo + generate IPA:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. + Click on **Code Signing** tab\n1. Find the **PROVISIONING PROFILE** section\n1. + Click or drop your file on the upload file field\n1. Find the **CODE SIGNING + IDENTITY** section\n1. Click or drop your file on the upload file field\n1. + Click on **Workflows** tab\n1. Select deploy workflow\n1. Select **Xcode Archive + & Export for iOS** step\n1. Open **Force Build Settings** input group\n1. Specify + codesign settings\nSet **Force code signing with Development Team**, **Force + code signing with Code Signing Identity** \nand **Force code signing with Provisioning + Profile** inputs regarding to the uploaded codesigning files\n1. Specify manual + codesign style\nIf the codesigning files, are generated manually on the Apple + Developer Portal, \nyou need to explicitly specify to use manual coedsign settings + \ \n(as ejected rn projects have xcode managed codesigning turned on). \nTo + do so, add 'CODE_SIGN_STYLE=\"Manual\"' to 'Additional options for xcodebuild + call' input\n\n## To run this workflow\n\nIf you want to run this workflow manually:\n\n1. + Open the app's build list page\n2. Click on **[Start/Schedule a Build]** button\n3. + Select **deploy** in **Workflow** dropdown input\n4. Click **[Start Build]** + button\n\nOr if you need this workflow to be started by a GIT event:\n\n1. Click + on **Triggers** tab\n2. Setup your desired event (push/tag/pull) and select + **deploy** workflow\n3. Click on **[Done]** and then **[Save]** buttons\n\nThe + next change in your repository that matches any of your trigger map event will + start **deploy** workflow.\n" + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - command: install + - expo-detach@%s: + inputs: + - project_path: ./ + - user_name: $EXPO_USERNAME + - password: $EXPO_PASSWORD + - run_publish: "yes" + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - android-build@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - certificate-and-profile-installer@%s: {} + - cocoapods-install@%s: {} + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - configuration: Release + - export_method: $BITRISE_EXPORT_METHOD + - force_team_id: $BITRISE_IOS_DEVELOPMENT_TEAM + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - command: install + - npm@%s: + inputs: + - command: test + - deploy-to-bitrise-io@%s: {} +warnings: + react-native-expo: [] +`, bitriseExpoKitVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_test.go new file mode 100644 index 00000000..e1ef86ed --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_test.go @@ -0,0 +1,299 @@ +package integration + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestReactNative(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__reactnative__") + require.NoError(t, err) + + t.Log("sample-apps-react-native-ios-and-android") + { + sampleAppDir := filepath.Join(tmpDir, "sample-apps-react-native-ios-and-android") + sampleAppURL := "https://github.com/bitrise-samples/sample-apps-react-native-ios-and-android.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + + validateConfigExpectation(t, "sample-apps-react-native-ios-and-android", strings.TrimSpace(sampleAppsReactNativeIosAndAndroidResultYML), strings.TrimSpace(result), sampleAppsReactNativeIosAndAndroidVersions...) + } + + t.Log("sample-apps-react-native-subdir") + { + sampleAppDir := filepath.Join(tmpDir, "sample-apps-react-native-subdir") + sampleAppURL := "https://github.com/bitrise-samples/sample-apps-react-native-subdir.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + + validateConfigExpectation(t, "sample-apps-react-native-subdir", strings.TrimSpace(sampleAppsReactNativeSubdirResultYML), strings.TrimSpace(result), sampleAppsReactNativeSubdirVersions...) + } +} + +var sampleAppsReactNativeSubdirVersions = []interface{}{ + models.FormatVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.InstallMissingAndroidToolsVersion, + steps.AndroidBuildVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.NpmVersion, + steps.DeployToBitriseIoVersion, +} + +var sampleAppsReactNativeSubdirResultYML = fmt.Sprintf(`options: + react-native: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + project/android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + "": + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + project/ios/SampleAppsReactNativeAndroid.xcodeproj: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + SampleAppsReactNativeAndroid: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: react-native-android-ios-test-config + app-store: + config: react-native-android-ios-test-config + development: + config: react-native-android-ios-test-config + enterprise: + config: react-native-android-ios-test-config + SampleAppsReactNativeAndroid-tvOS: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: react-native-android-ios-test-config + app-store: + config: react-native-android-ios-test-config + development: + config: react-native-android-ios-test-config + enterprise: + config: react-native-android-ios-test-config +configs: + react-native: + react-native-android-ios-test-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: react-native + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - workdir: project + - command: install + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - android-build@%s: + inputs: + - project_location: $PROJECT_LOCATION + - certificate-and-profile-installer@%s: {} + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - workdir: project + - command: install + - npm@%s: + inputs: + - workdir: project + - command: test + - deploy-to-bitrise-io@%s: {} +warnings: + react-native: [] +`, sampleAppsReactNativeSubdirVersions...) + +var sampleAppsReactNativeIosAndAndroidVersions = []interface{}{ + models.FormatVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.InstallMissingAndroidToolsVersion, + steps.AndroidBuildVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.NpmVersion, + steps.NpmVersion, + steps.DeployToBitriseIoVersion, +} + +var sampleAppsReactNativeIosAndAndroidResultYML = fmt.Sprintf(`options: + react-native: + title: The root directory of an Android project + env_key: PROJECT_LOCATION + value_map: + android: + title: Module + env_key: MODULE + value_map: + app: + title: Variant + env_key: VARIANT + value_map: + "": + title: Project (or Workspace) path + env_key: BITRISE_PROJECT_PATH + value_map: + ios/SampleAppsReactNativeAndroid.xcodeproj: + title: Scheme name + env_key: BITRISE_SCHEME + value_map: + SampleAppsReactNativeAndroid: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: react-native-android-ios-test-config + app-store: + config: react-native-android-ios-test-config + development: + config: react-native-android-ios-test-config + enterprise: + config: react-native-android-ios-test-config + SampleAppsReactNativeAndroid-tvOS: + title: ipa export method + env_key: BITRISE_EXPORT_METHOD + value_map: + ad-hoc: + config: react-native-android-ios-test-config + app-store: + config: react-native-android-ios-test-config + development: + config: react-native-android-ios-test-config + enterprise: + config: react-native-android-ios-test-config +configs: + react-native: + react-native-android-ios-test-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: react-native + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + deploy: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - command: install + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - android-build@%s: + inputs: + - project_location: $PROJECT_LOCATION + - certificate-and-profile-installer@%s: {} + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - export_method: $BITRISE_EXPORT_METHOD + - configuration: Release + - deploy-to-bitrise-io@%s: {} + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - npm@%s: + inputs: + - command: install + - npm@%s: + inputs: + - command: test + - deploy-to-bitrise-io@%s: {} +warnings: + react-native: [] +`, sampleAppsReactNativeIosAndAndroidVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/xamarin_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/xamarin_test.go new file mode 100644 index 00000000..44379ff2 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/xamarin_test.go @@ -0,0 +1,281 @@ +package integration + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestXamarin(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__xamarin__") + require.NoError(t, err) + + t.Log("xamarin-sample-app") + { + sampleAppDir := filepath.Join(tmpDir, "xamarin-sample-app") + sampleAppURL := "https://github.com/bitrise-samples/xamarin-sample-app.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(xamarinSampleAppResultYML), strings.TrimSpace(result)) + } + + t.Log("sample-apps-xamarin-ios") + { + sampleAppDir := filepath.Join(tmpDir, "sample-apps-xamarin-ios") + sampleAppURL := "https://github.com/bitrise-io/sample-apps-xamarin-ios.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(sampleAppsXamarinIosResultYML), strings.TrimSpace(result)) + } + + t.Log("sample-apps-xamarin-android") + { + sampleAppDir := filepath.Join(tmpDir, "sample-apps-xamarin-android") + sampleAppURL := "https://github.com/bitrise-io/sample-apps-xamarin-android.git" + gitClone(t, sampleAppDir, sampleAppURL) + + cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + scanResultPth := filepath.Join(sampleAppDir, "result.yml") + + result, err := fileutil.ReadStringFromFile(scanResultPth) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(sampleAppsXamarinAndroidResultYML), strings.TrimSpace(result)) + } +} + +var xamarinSampleAppVersions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.XamarinUserManagementVersion, + steps.NugetRestoreVersion, + steps.XamarinComponentsRestoreVersion, + steps.XamarinArchiveVersion, + steps.DeployToBitriseIoVersion, +} + +var xamarinSampleAppResultYML = fmt.Sprintf(`options: + xamarin: + title: Path to the Xamarin Solution file + env_key: BITRISE_PROJECT_PATH + value_map: + XamarinSampleApp.sln: + title: Xamarin solution configuration + env_key: BITRISE_XAMARIN_CONFIGURATION + value_map: + Debug: + title: Xamarin solution platform + env_key: BITRISE_XAMARIN_PLATFORM + value_map: + Any CPU: + config: xamarin-nuget-components-config + iPhone: + config: xamarin-nuget-components-config + iPhoneSimulator: + config: xamarin-nuget-components-config + Release: + title: Xamarin solution platform + env_key: BITRISE_XAMARIN_PLATFORM + value_map: + Any CPU: + config: xamarin-nuget-components-config + iPhone: + config: xamarin-nuget-components-config + iPhoneSimulator: + config: xamarin-nuget-components-config +configs: + xamarin: + xamarin-nuget-components-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: xamarin + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - xamarin-user-management@%s: + run_if: .IsCI + - nuget-restore@%s: {} + - xamarin-components-restore@%s: {} + - xamarin-archive@%s: + inputs: + - xamarin_solution: $BITRISE_PROJECT_PATH + - xamarin_configuration: $BITRISE_XAMARIN_CONFIGURATION + - xamarin_platform: $BITRISE_XAMARIN_PLATFORM + - deploy-to-bitrise-io@%s: {} +warnings: + xamarin: [] +`, xamarinSampleAppVersions...) + +var sampleAppsXamarinIosVersions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.NugetRestoreVersion, + steps.XamarinArchiveVersion, + steps.DeployToBitriseIoVersion, +} + +var sampleAppsXamarinIosResultYML = fmt.Sprintf(`options: + xamarin: + title: Path to the Xamarin Solution file + env_key: BITRISE_PROJECT_PATH + value_map: + CreditCardValidator.iOS.sln: + title: Xamarin solution configuration + env_key: BITRISE_XAMARIN_CONFIGURATION + value_map: + Debug: + title: Xamarin solution platform + env_key: BITRISE_XAMARIN_PLATFORM + value_map: + Any CPU: + config: xamarin-nuget-config + iPhone: + config: xamarin-nuget-config + iPhoneSimulator: + config: xamarin-nuget-config + Release: + title: Xamarin solution platform + env_key: BITRISE_XAMARIN_PLATFORM + value_map: + Any CPU: + config: xamarin-nuget-config + iPhone: + config: xamarin-nuget-config + iPhoneSimulator: + config: xamarin-nuget-config +configs: + xamarin: + xamarin-nuget-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: xamarin + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - nuget-restore@%s: {} + - xamarin-archive@%s: + inputs: + - xamarin_solution: $BITRISE_PROJECT_PATH + - xamarin_configuration: $BITRISE_XAMARIN_CONFIGURATION + - xamarin_platform: $BITRISE_XAMARIN_PLATFORM + - deploy-to-bitrise-io@%s: {} +warnings: + xamarin: [] +`, sampleAppsXamarinIosVersions...) + +var sampleAppsXamarinAndroidVersions = []interface{}{ + models.FormatVersion, + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.ScriptVersion, + steps.CertificateAndProfileInstallerVersion, + steps.NugetRestoreVersion, + steps.XamarinArchiveVersion, + steps.DeployToBitriseIoVersion, +} + +var sampleAppsXamarinAndroidResultYML = fmt.Sprintf(`options: + xamarin: + title: Path to the Xamarin Solution file + env_key: BITRISE_PROJECT_PATH + value_map: + CreditCardValidator.Droid.sln: + title: Xamarin solution configuration + env_key: BITRISE_XAMARIN_CONFIGURATION + value_map: + Debug: + title: Xamarin solution platform + env_key: BITRISE_XAMARIN_PLATFORM + value_map: + Any CPU: + config: xamarin-nuget-config + Release: + title: Xamarin solution platform + env_key: BITRISE_XAMARIN_PLATFORM + value_map: + Any CPU: + config: xamarin-nuget-config +configs: + xamarin: + xamarin-nuget-config: | + format_version: "%s" + default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + project_type: xamarin + trigger_map: + - push_branch: '*' + workflow: primary + - pull_request_source_branch: '*' + workflow: primary + workflows: + primary: + steps: + - activate-ssh-key@%s: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@%s: {} + - script@%s: + title: Do anything with Script step + - certificate-and-profile-installer@%s: {} + - nuget-restore@%s: {} + - xamarin-archive@%s: + inputs: + - xamarin_solution: $BITRISE_PROJECT_PATH + - xamarin_configuration: $BITRISE_XAMARIN_CONFIGURATION + - xamarin_platform: $BITRISE_XAMARIN_PLATFORM + - deploy-to-bitrise-io@%s: {} +warnings: + xamarin: [] +`, sampleAppsXamarinAndroidVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/bitrise.yml b/vendor/github.com/bitrise-core/bitrise-init/bitrise.yml new file mode 100644 index 00000000..a3c84bcf --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/bitrise.yml @@ -0,0 +1,155 @@ +format_version: "5" +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + +workflows: + # ---------------------------------------------------------------- + # --- workflows for CI and testing + ci-docker: + steps: + - script: + inputs: + - content: |- + #!/bin/env bash + set -ex + docker-compose build + docker-compose run --rm app bitrise run ci + + ci: + after_run: + - go-test + - integration-test + + go-test: + steps: + - script: + title: Export go files to test + inputs: + - content: | + #!/usr/bin/env bash + set -ex + no_vendor_paths="$(go list ./... | grep -v vendor)" + envman add --key GOLIST_WITHOUT_VENDOR --value "$no_vendor_paths" + - script: + title: Err check + inputs: + - content: | + #!/usr/bin/env bash + set -ex + go get -u github.com/kisielk/errcheck + errcheck -asserts=true -blank=true $GOLIST_WITHOUT_VENDOR + - script: + title: Go lint + inputs: + - content: |- + #!/usr/bin/env bash + set -ex + go get -u github.com/golang/lint/golint + while read -r line; do + echo "-> Linting: $line" + golint_out="$(golint $line)" + if [[ "${golint_out}" != "" ]] ; then + echo "=> Golint issues found:" + echo "${golint_out}" + exit 1 + fi + done <<< "$GOLIST_WITHOUT_VENDOR" + - script: + title: Go test + inputs: + - content: go test ./... + + integration-test: + steps: + - script: + title: Go build + inputs: + - content: |- + #!/bin/bash + set -ex + + # build the new bitrise + current_dir=$(pwd) + current_bitrise_init=$current_dir/_tmp/ci-bin + go build -o $current_bitrise_init + + envman add --key CURRENT_BITRISE_INIT --value $current_bitrise_init + - script: + title: Run integration tests + inputs: + - content: |- + #!/bin/bash + echo "Running integration tests ..." + set -ex + + export INTEGRATION_TEST_BINARY_PATH="$CURRENT_BITRISE_INIT" + go test -v ./_tests/integration/... + + # ---------------------------------------------------------------- + # --- workflows for Utility + dep-update: + title: Do dependency update + steps: + - script: + title: Dependency update + inputs: + - content: |- + #!/bin/bash + set -ex + go get -u -v github.com/golang/dep/cmd/dep + dep ensure -v + dep ensure -v -update + + create-binaries: + title: Create binaries + description: | + Creates Linux and Darwin binaries + steps: + - script: + title: Create binaries + inputs: + - content: | + #!/bin/bash + set -e + set -x + + BIN_NAME="bitrise-init" + + echo + echo "Create final binaries" + echo " Build number: $BITRISE_BUILD_NUMBER" + + export ARCH=x86_64 + export GOARCH=amd64 + + # Create Darwin bin + export OS=Darwin + export GOOS=darwin + + DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" + echo " Create final Darwin binary at: $DEPLOY_PATH" + + version_package="github.com/bitrise-core/bitrise-init/version" + + go build \ + -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ + -o "$DEPLOY_PATH" + + envman add --key OSX_DEPLOY_PATH --value $DEPLOY_PATH + cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH + echo " Copy final Darwin binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" + + + # Create Linux binary + export OS=Linux + export GOOS=linux + + DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" + echo " Create final Linux binary at: $DEPLOY_PATH" + + go build \ + -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ + -o "$DEPLOY_PATH" + + envman add --key LINUX_DEPLOY_PATH --value $DEPLOY_PATH + cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH + echo " Copy final Linux binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" \ No newline at end of file diff --git a/vendor/github.com/bitrise-core/bitrise-init/cli/cli.go b/vendor/github.com/bitrise-core/bitrise-init/cli/cli.go new file mode 100644 index 00000000..18b19c9d --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/cli/cli.go @@ -0,0 +1,72 @@ +package cli + +import ( + "fmt" + "os" + "path" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-core/bitrise-init/version" + "github.com/urfave/cli" +) + +// Run ... +func Run() { + // Parse cl + cli.VersionPrinter = func(c *cli.Context) { + fmt.Println(c.App.Version) + } + + app := cli.NewApp() + + app.Name = path.Base(os.Args[0]) + app.Usage = "Bitrise Init Tool" + app.Version = version.VERSION + app.Author = "" + app.Email = "" + + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "loglevel, l", + Usage: "Log level (options: debug, info, warn, error, fatal, panic).", + EnvVar: "LOGLEVEL", + }, + cli.BoolFlag{ + Name: "ci", + Usage: "If true it indicates that we're used by another tool so don't require any user input!", + EnvVar: "CI", + }, + } + + app.Before = func(c *cli.Context) error { + log.SetFormatter(&log.TextFormatter{ + FullTimestamp: true, + ForceColors: true, + TimestampFormat: "15:04:05", + }) + + // Log level + logLevelStr := c.String("loglevel") + if logLevelStr == "" { + logLevelStr = "info" + } + + level, err := log.ParseLevel(logLevelStr) + if err != nil { + return err + } + log.SetLevel(level) + + return nil + } + + app.Commands = []cli.Command{ + versionCommand, + configCommand, + manualConfigCommand, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/cli/config.go b/vendor/github.com/bitrise-core/bitrise-init/cli/config.go new file mode 100644 index 00000000..050d0a99 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/cli/config.go @@ -0,0 +1,185 @@ +package cli + +import ( + "fmt" + "os" + "path" + "path/filepath" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/output" + "github.com/bitrise-core/bitrise-init/scanner" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/urfave/cli" +) + +const ( + defaultScanResultDir = "_scan_result" +) + +var configCommand = cli.Command{ + Name: "config", + Usage: "Generates a bitrise config files based on your project.", + Action: func(c *cli.Context) error { + if err := initConfig(c); err != nil { + log.TErrorf(err.Error()) + os.Exit(1) + } + return nil + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "dir", + Usage: "Directory to scan.", + Value: "./", + }, + cli.StringFlag{ + Name: "output-dir", + Usage: "Directory to save scan results.", + Value: "./_scan_result", + }, + cli.StringFlag{ + Name: "format", + Usage: "Output format, options [json, yaml].", + Value: "yaml", + }, + }, +} + +func writeScanResult(scanResult models.ScanResultModel, outputDir string, format output.Format) (string, error) { + pth := path.Join(outputDir, "result") + return output.WriteToFile(scanResult, format, pth) +} + +func initConfig(c *cli.Context) error { + // Config + isCI := c.GlobalBool("ci") + searchDir := c.String("dir") + outputDir := c.String("output-dir") + formatStr := c.String("format") + + if isCI { + log.TInfof(colorstring.Yellow("CI mode")) + } + log.TInfof(colorstring.Yellowf("scan dir: %s", searchDir)) + log.TInfof(colorstring.Yellowf("output dir: %s", outputDir)) + log.TInfof(colorstring.Yellowf("output format: %s", formatStr)) + fmt.Println() + + currentDir, err := pathutil.AbsPath("./") + if err != nil { + return fmt.Errorf("Failed to expand path (%s), error: %s", outputDir, err) + } + + if searchDir == "" { + searchDir = currentDir + } + searchDir, err = pathutil.AbsPath(searchDir) + if err != nil { + return fmt.Errorf("Failed to expand path (%s), error: %s", outputDir, err) + } + + if outputDir == "" { + outputDir = filepath.Join(currentDir, defaultScanResultDir) + } + outputDir, err = pathutil.AbsPath(outputDir) + if err != nil { + return fmt.Errorf("Failed to expand path (%s), error: %s", outputDir, err) + } + if exist, err := pathutil.IsDirExists(outputDir); err != nil { + return err + } else if !exist { + if err := os.MkdirAll(outputDir, 0700); err != nil { + return fmt.Errorf("Failed to create (%s), error: %s", outputDir, err) + } + } + + if formatStr == "" { + formatStr = output.YAMLFormat.String() + } + format, err := output.ParseFormat(formatStr) + if err != nil { + return fmt.Errorf("Failed to parse format (%s), error: %s", formatStr, err) + } + if format != output.JSONFormat && format != output.YAMLFormat { + return fmt.Errorf("Not allowed output format (%s), options: [%s, %s]", format.String(), output.YAMLFormat.String(), output.JSONFormat.String()) + } + // --- + + scanResult := scanner.Config(searchDir) + + platforms := []string{} + for platform := range scanResult.ScannerToOptionRoot { + platforms = append(platforms, platform) + } + + if len(platforms) == 0 { + cmd := command.New("which", "tree") + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + if err != nil || out == "" { + log.TErrorf("tree not installed, can not list files") + } else { + fmt.Println() + cmd := command.NewWithStandardOuts("tree", ".", "-L", "3") + log.TPrintf("$ %s", cmd.PrintableCommandArgs()) + if err := cmd.Run(); err != nil { + log.TErrorf("Failed to list files in current directory, error: %s", err) + } + } + + log.TInfof("Saving outputs:") + scanResult.AddError("general", "No known platform detected") + + outputPth, err := writeScanResult(scanResult, outputDir, format) + if err != nil { + return fmt.Errorf("Failed to write output, error: %s", err) + } + + log.TPrintf("scan result: %s", outputPth) + return fmt.Errorf("No known platform detected") + } + + // Write output to files + if isCI { + log.TInfof("Saving outputs:") + + outputPth, err := writeScanResult(scanResult, outputDir, format) + if err != nil { + return fmt.Errorf("Failed to write output, error: %s", err) + } + + log.TPrintf(" scan result: %s", outputPth) + return nil + } + // --- + + // Select option + log.TInfof("Collecting inputs:") + + config, err := scanner.AskForConfig(scanResult) + if err != nil { + return err + } + + if exist, err := pathutil.IsDirExists(outputDir); err != nil { + return err + } else if !exist { + if err := os.MkdirAll(outputDir, 0700); err != nil { + return fmt.Errorf("Failed to create (%s), error: %s", outputDir, err) + } + } + + pth := path.Join(outputDir, "bitrise.yml") + outputPth, err := output.WriteToFile(config, format, pth) + if err != nil { + return fmt.Errorf("Failed to print result, error: %s", err) + } + log.TInfof(" bitrise.yml template: %s", outputPth) + fmt.Println() + // --- + + return nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/cli/manual_config.go b/vendor/github.com/bitrise-core/bitrise-init/cli/manual_config.go new file mode 100644 index 00000000..d53c6648 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/cli/manual_config.go @@ -0,0 +1,132 @@ +package cli + +import ( + "fmt" + "os" + "path" + "path/filepath" + + "github.com/bitrise-core/bitrise-init/output" + "github.com/bitrise-core/bitrise-init/scanner" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/urfave/cli" +) + +const ( + defaultOutputDir = "_defaults" +) + +var manualConfigCommand = cli.Command{ + Name: "manual-config", + Usage: "Generates default bitrise config files.", + Action: func(c *cli.Context) error { + if err := initManualConfig(c); err != nil { + log.TErrorf(err.Error()) + os.Exit(1) + } + return nil + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "output-dir", + Usage: "Directory to save scan results.", + Value: "./_defaults", + }, + cli.StringFlag{ + Name: "format", + Usage: "Output format, options [json, yaml].", + Value: "yaml", + }, + }, +} + +func initManualConfig(c *cli.Context) error { + // Config + isCI := c.GlobalBool("ci") + outputDir := c.String("output-dir") + formatStr := c.String("format") + + if isCI { + log.TInfof(colorstring.Yellow("CI mode")) + } + log.TInfof(colorstring.Yellowf("output dir: %s", outputDir)) + log.TInfof(colorstring.Yellowf("output format: %s", formatStr)) + fmt.Println() + + currentDir, err := pathutil.AbsPath("./") + if err != nil { + return fmt.Errorf("Failed to get current directory, error: %s", err) + } + + if outputDir == "" { + outputDir = filepath.Join(currentDir, defaultOutputDir) + } + outputDir, err = pathutil.AbsPath(outputDir) + if err != nil { + return fmt.Errorf("Failed to expand path (%s), error: %s", outputDir, err) + } + if exist, err := pathutil.IsDirExists(outputDir); err != nil { + return err + } else if !exist { + if err := os.MkdirAll(outputDir, 0700); err != nil { + return fmt.Errorf("Failed to create (%s), error: %s", outputDir, err) + } + } + + if formatStr == "" { + formatStr = output.YAMLFormat.String() + } + format, err := output.ParseFormat(formatStr) + if err != nil { + return fmt.Errorf("Failed to parse format, err: %s", err) + } + if format != output.JSONFormat && format != output.YAMLFormat { + return fmt.Errorf("Not allowed output format (%v), options: [%s, %s]", format, output.YAMLFormat.String(), output.JSONFormat.String()) + } + // --- + + scanResult, err := scanner.ManualConfig() + if err != nil { + return err + } + + // Write output to files + if isCI { + log.TInfof(colorstring.Blue("Saving outputs:")) + + if err := os.MkdirAll(outputDir, 0700); err != nil { + return fmt.Errorf("Failed to create (%s), error: %s", outputDir, err) + } + + pth := path.Join(outputDir, "result") + outputPth, err := output.WriteToFile(scanResult, format, pth) + if err != nil { + return fmt.Errorf("Failed to print result, error: %s", err) + } + log.TInfof(" scan result: %s", colorstring.Blue(outputPth)) + + return nil + } + // --- + + // Select option + log.TInfof(colorstring.Blue("Collecting inputs:")) + + config, err := scanner.AskForConfig(scanResult) + if err != nil { + return err + } + + pth := path.Join(outputDir, "bitrise.yml") + outputPth, err := output.WriteToFile(config, format, pth) + if err != nil { + return fmt.Errorf("Failed to print result, error: %s", err) + } + log.TInfof(" bitrise.yml template: %s", colorstring.Blue(outputPth)) + fmt.Println() + // --- + + return nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/cli/version.go b/vendor/github.com/bitrise-core/bitrise-init/cli/version.go new file mode 100644 index 00000000..941b2935 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/cli/version.go @@ -0,0 +1,79 @@ +package cli + +import ( + "fmt" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-core/bitrise-init/output" + "github.com/bitrise-core/bitrise-init/version" + "github.com/urfave/cli" +) + +// VersionOutputModel ... +type VersionOutputModel struct { + Version string `json:"version" yaml:"version"` + BuildNumber string `json:"build_number" yaml:"build_number"` + Commit string `json:"commit" yaml:"commit"` +} + +var versionCommand = cli.Command{ + Name: "version", + Usage: "Prints the version", + Action: func(c *cli.Context) error { + if err := printVersion(c); err != nil { + log.Fatal(err) + } + return nil + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "Output format, options [raw, json, yaml].", + Value: "raw", + }, + cli.BoolFlag{ + Name: "full", + Usage: "Prints the build number as well.", + }, + }, +} + +func printVersion(c *cli.Context) error { + fullVersion := c.Bool("full") + formatStr := c.String("format") + + if formatStr == "" { + formatStr = output.YAMLFormat.String() + } + format, err := output.ParseFormat(formatStr) + if err != nil { + return fmt.Errorf("Failed to parse format, error: %s", err) + } + + versionOutput := VersionOutputModel{ + Version: version.VERSION, + } + + if fullVersion { + versionOutput.BuildNumber = version.BuildNumber + versionOutput.Commit = version.Commit + } + + var out interface{} + + if format == output.RawFormat { + if fullVersion { + out = fmt.Sprintf("version: %v\nbuild_number: %v\ncommit: %v\n", versionOutput.Version, versionOutput.BuildNumber, versionOutput.Commit) + } else { + out = fmt.Sprintf("%v\n", versionOutput.Version) + } + } else { + out = versionOutput + } + + if err := output.Print(out, format); err != nil { + return fmt.Errorf("Failed to print version, error: %s", err) + } + + return nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/docker-compose.yml b/vendor/github.com/bitrise-core/bitrise-init/docker-compose.yml new file mode 100644 index 00000000..4ce3712f --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/docker-compose.yml @@ -0,0 +1,7 @@ +app: + build: . + volumes: + - .:/bitrise/go/src/github.com/bitrise-core/bitrise-init + environment: + CI: "true" + BITRISE_SOURCE_DIR: /bitrise/go/src/github.com/bitrise-core/bitrise-init \ No newline at end of file diff --git a/vendor/github.com/bitrise-core/bitrise-init/gows.yml b/vendor/github.com/bitrise-core/bitrise-init/gows.yml new file mode 100644 index 00000000..4fd8e6f3 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/gows.yml @@ -0,0 +1 @@ +package_name: github.com/bitrise-core/bitrise-init diff --git a/vendor/github.com/bitrise-core/bitrise-init/main.go b/vendor/github.com/bitrise-core/bitrise-init/main.go new file mode 100644 index 00000000..53c81335 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/bitrise-core/bitrise-init/cli" + _ "github.com/bitrise-io/go-utils/command/git" +) + +func main() { + cli.Run() +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/models/configbuilder.go b/vendor/github.com/bitrise-core/bitrise-init/models/configbuilder.go new file mode 100644 index 00000000..fcb5ab09 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/models/configbuilder.go @@ -0,0 +1,94 @@ +package models + +import ( + "errors" + + bitriseModels "github.com/bitrise-io/bitrise/models" + envmanModels "github.com/bitrise-io/envman/models" +) + +// WorkflowID ... +type WorkflowID string + +const ( + // PrimaryWorkflowID ... + PrimaryWorkflowID WorkflowID = "primary" + // DeployWorkflowID ... + DeployWorkflowID WorkflowID = "deploy" + + // FormatVersion ... + FormatVersion = bitriseModels.Version + + defaultSteplibSource = "https://github.com/bitrise-io/bitrise-steplib.git" +) + +// ConfigBuilderModel ... +type ConfigBuilderModel struct { + workflowBuilderMap map[WorkflowID]*workflowBuilderModel +} + +// NewDefaultConfigBuilder ... +func NewDefaultConfigBuilder() *ConfigBuilderModel { + return &ConfigBuilderModel{ + workflowBuilderMap: map[WorkflowID]*workflowBuilderModel{ + PrimaryWorkflowID: newDefaultWorkflowBuilder(), + }, + } +} + +// AppendStepListItemsTo ... +func (builder *ConfigBuilderModel) AppendStepListItemsTo(workflow WorkflowID, items ...bitriseModels.StepListItemModel) { + workflowBuilder := builder.workflowBuilderMap[workflow] + if workflowBuilder == nil { + workflowBuilder = newDefaultWorkflowBuilder() + builder.workflowBuilderMap[workflow] = workflowBuilder + } + workflowBuilder.appendStepListItems(items...) +} + +// SetWorkflowDescriptionTo ... +func (builder *ConfigBuilderModel) SetWorkflowDescriptionTo(workflow WorkflowID, description string) { + workflowBuilder := builder.workflowBuilderMap[workflow] + if workflowBuilder == nil { + workflowBuilder = newDefaultWorkflowBuilder() + builder.workflowBuilderMap[workflow] = workflowBuilder + } + workflowBuilder.Description = description +} + +// Generate ... +func (builder *ConfigBuilderModel) Generate(projectType string, appEnvs ...envmanModels.EnvironmentItemModel) (bitriseModels.BitriseDataModel, error) { + primaryWorkflowBuilder, ok := builder.workflowBuilderMap[PrimaryWorkflowID] + if !ok || primaryWorkflowBuilder == nil || len(primaryWorkflowBuilder.Steps) == 0 { + return bitriseModels.BitriseDataModel{}, errors.New("primary workflow not defined") + } + + workflows := map[string]bitriseModels.WorkflowModel{} + for workflowID, workflowBuilder := range builder.workflowBuilderMap { + workflows[string(workflowID)] = workflowBuilder.generate() + } + + triggerMap := []bitriseModels.TriggerMapItemModel{ + bitriseModels.TriggerMapItemModel{ + PushBranch: "*", + WorkflowID: string(PrimaryWorkflowID), + }, + bitriseModels.TriggerMapItemModel{ + PullRequestSourceBranch: "*", + WorkflowID: string(PrimaryWorkflowID), + }, + } + + app := bitriseModels.AppModel{ + Environments: appEnvs, + } + + return bitriseModels.BitriseDataModel{ + FormatVersion: FormatVersion, + DefaultStepLibSource: defaultSteplibSource, + ProjectType: projectType, + TriggerMap: triggerMap, + Workflows: workflows, + App: app, + }, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/models/model_test.go b/vendor/github.com/bitrise-core/bitrise-init/models/model_test.go new file mode 100644 index 00000000..2587b2a5 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/models/model_test.go @@ -0,0 +1,390 @@ +package models + +import ( + "fmt" + "testing" + + "encoding/json" + + "github.com/stretchr/testify/require" +) + +func TestNewOption(t *testing.T) { + actual := NewOption("Project (or Workspace) path", "BITRISE_PROJECT_PATH") + expected := &OptionNode{ + Title: "Project (or Workspace) path", + EnvKey: "BITRISE_PROJECT_PATH", + ChildOptionMap: map[string]*OptionNode{}, + Components: []string{}, + } + + require.Equal(t, expected, actual) +} + +func TestGetValues(t *testing.T) { + option := OptionNode{ + ChildOptionMap: map[string]*OptionNode{}, + } + option.ChildOptionMap["assembleAndroidTest"] = &OptionNode{} + option.ChildOptionMap["assembleDebug"] = &OptionNode{} + option.ChildOptionMap["assembleRelease"] = &OptionNode{} + + values := option.GetValues() + + expectedMap := map[string]bool{ + "assembleAndroidTest": false, + "assembleDebug": false, + "assembleRelease": false, + } + + for _, value := range values { + delete(expectedMap, value) + } + + require.Equal(t, 0, len(expectedMap)) +} + +func TestLastOptions(t *testing.T) { + // 1. level + opt0 := NewOption("OPT0", "OPT0_KEY") + + // 2. level + opt01 := NewOption("OPT01", "OPT01_KEY") // has no child + opt01.AddConfig("test", nil) + opt0.AddOption("value1", opt01) + + opt02 := NewOption("OPT02", "OPT02_KEY") + opt0.AddOption("value2", opt02) + + // 3. level + opt021 := NewOption("OPT021", "OPT021_KEY") + opt02.AddOption("value1", opt021) + + // 4. level + opt0211 := NewOption("OPT0211", "OPT0211_KEY") // has no child + opt021.AddOption("value1", opt0211) + + opt0212 := NewOption("OPT0212", "OPT0212_KEY") + opt021.AddOption("value2", opt0212) + + // 5. level + opt02121 := NewOption("OPT02121", "OPT02121_KEY") // has no child + opt0212.AddOption("value1", opt02121) + + lastOptions := opt0.LastChilds() + require.Equal(t, true, len(lastOptions) == 3, fmt.Sprintf("%d", len(lastOptions))) + + optionsMap := map[string]bool{} + for _, opt := range lastOptions { + optionsMap[opt.Title] = true + } + + require.Equal(t, true, optionsMap["OPT01"]) + require.Equal(t, true, optionsMap["OPT0211"]) + require.Equal(t, true, optionsMap["OPT02121"]) + + { + optionJSON := `{ + "title": "Project (or Workspace) path", + "env_key": "BITRISE_PROJECT_PATH", + "value_map": { + "BitriseTest.xcodeproj": { + "title": "Scheme name", + "env_key": "BITRISE_SCHEME", + "value_map": { + "BitriseTest": { + "config": "ios-test-config" + }, + "BitriseTest-tvOS": { + "config": "ios-test-config" + } + } + } + } +}` + + var option OptionNode + require.NoError(t, json.Unmarshal([]byte(optionJSON), &option)) + + lastOptions := option.LastChilds() + optionsMap := map[string]bool{} + for _, opt := range lastOptions { + optionsMap[opt.String()] = true + } + + require.Equal(t, true, optionsMap[`{ + "title": "Scheme name", + "env_key": "BITRISE_SCHEME", + "value_map": { + "BitriseTest": { + "config": "ios-test-config" + }, + "BitriseTest-tvOS": { + "config": "ios-test-config" + } + } +}`]) + } + + { + optionJSON := `{ + "title": "Gradlew file path", + "env_key": "GRADLEW_PATH", + "value_map": { + "$HOME/Develop/react/AwesomeProject/android/gradlew": { + "title": "Path to the gradle file to use", + "env_key": "GRADLE_BUILD_FILE_PATH", + "value_map": { + "$HOME/Develop/react/AwesomeProject/android/build.gradle": { + "config": "android-config" + } + } + } + } +}` + + var option OptionNode + require.NoError(t, json.Unmarshal([]byte(optionJSON), &option)) + + lastOptions := option.LastChilds() + optionsMap := map[string]bool{} + for _, opt := range lastOptions { + optionsMap[opt.String()] = true + } + + require.Equal(t, true, optionsMap[`{ + "title": "Path to the gradle file to use", + "env_key": "GRADLE_BUILD_FILE_PATH", + "value_map": { + "$HOME/Develop/react/AwesomeProject/android/build.gradle": { + "config": "android-config" + } + } +}`]) + } + + { + optionJSON := `{ + "title": "project_dir", + "env_key": "PROJECT_DIR", + "value_map": { + "$HOME/Develop/react/AwesomeProject": { + "title": "Gradlew file path", + "env_key": "GRADLEW_PATH", + "value_map": { + "$HOME/Develop/react/AwesomeProject/android/gradlew": { + "title": "Path to the gradle file to use", + "env_key": "GRADLE_BUILD_FILE_PATH", + "value_map": { + "$HOME/Develop/react/AwesomeProject/android/build.gradle": {} + } + } + } + } + } +}` + + var option OptionNode + require.NoError(t, json.Unmarshal([]byte(optionJSON), &option)) + + lastOptions := option.LastChilds() + optionsMap := map[string]bool{} + for _, opt := range lastOptions { + optionsMap[opt.String()] = true + } + + require.Equal(t, true, optionsMap[`{ + "title": "Path to the gradle file to use", + "env_key": "GRADLE_BUILD_FILE_PATH", + "value_map": { + "$HOME/Develop/react/AwesomeProject/android/build.gradle": {} + } +}`]) + } +} + +func TestCopy(t *testing.T) { + // 1. level + opt0 := NewOption("OPT0", "OPT0_KEY") + + // 2. level + opt01 := NewOption("OPT01", "OPT01_KEY") + opt01.AddOption("value01", nil) + + opt0.AddOption("value1", opt01) + + opt02 := NewConfigOption("name") + opt0.AddConfig("value2", opt02) + + // make a copy + opt0Copy := opt0.Copy() + + // Ensure copy is the same + require.Equal(t, opt0.Title, opt0Copy.Title) + require.Equal(t, opt0.EnvKey, opt0Copy.EnvKey) + + opt01Copy := opt0Copy.ChildOptionMap["value1"] + require.Equal(t, opt01.Title, opt01Copy.Title) + require.Equal(t, opt01.EnvKey, opt01Copy.EnvKey) + require.Equal(t, 1, len(opt01Copy.ChildOptionMap)) + _, ok := opt01Copy.ChildOptionMap["value01"] + require.Equal(t, true, ok) + require.Equal(t, "", opt01Copy.Config) + + opt02Copy := opt0Copy.ChildOptionMap["value2"] + require.Equal(t, opt02.Title, opt02Copy.Title) + require.Equal(t, opt02.EnvKey, opt02Copy.EnvKey) + require.Equal(t, 0, len(opt02Copy.ChildOptionMap)) + require.Equal(t, "name", opt02Copy.Config) + + // Ensure copy is a new object + opt0Copy.Title = "OPT0_COPY" + require.Equal(t, "OPT0", opt0.Title) + + opt01Copy.Title = "OPT01_COPY" + require.Equal(t, "OPT01", opt01.Title) + + opt02Copy.Config = "name_copy" + require.Equal(t, "name", opt02.Config) +} + +func TestComponents(t *testing.T) { + // 1. level + opt0 := NewOption("OPT0", "OPT0_KEY") + + // 2. level + opt01 := NewOption("OPT01", "OPT01_KEY") // has no child + opt0.AddOption("value1", opt01) + + opt02 := NewOption("OPT02", "OPT02_KEY") + opt0.AddOption("value2", opt02) + + // 3. level + opt021 := NewOption("OPT021", "OPT021_KEY") + opt02.AddOption("value1", opt021) + + // 4. level + opt0211 := NewOption("OPT0211", "OPT0211_KEY") // has no child + opt021.AddOption("value1", opt0211) + + opt0212 := NewOption("OPT0212", "OPT0212_KEY") + opt021.AddOption("value2", opt0212) + + // 5. level + opt02121 := NewOption("OPT02121", "OPT02121_KEY") // has no child + opt0212.AddOption("value1", opt02121) + + require.Equal(t, []string{}, opt0.Components) + require.Equal(t, []string{"value1"}, opt01.Components) + require.Equal(t, []string{"value2"}, opt02.Components) + require.Equal(t, []string{"value2", "value1"}, opt021.Components) + require.Equal(t, []string{"value2", "value1", "value1"}, opt0211.Components) + require.Equal(t, []string{"value2", "value1", "value2"}, opt0212.Components) + require.Equal(t, []string{"value2", "value1", "value2", "value1"}, opt02121.Components) +} + +func TestHead(t *testing.T) { + // 1. level + opt0 := NewOption("OPT0", "OPT0_KEY") + + // 2. level + opt01 := NewOption("OPT01", "OPT01_KEY") // has no child + opt0.AddOption("value1", opt01) + + opt02 := NewOption("OPT02", "OPT02_KEY") + opt0.AddOption("value2", opt02) + + // 3. level + opt021 := NewOption("OPT021", "OPT021_KEY") + opt02.AddOption("value1", opt021) + + require.Equal(t, (*OptionNode)(nil), opt0.Head) + require.Equal(t, opt0, opt01.Head) + require.Equal(t, opt0, opt02.Head) + require.Equal(t, opt0, opt021.Head) +} + +func TestParent(t *testing.T) { + // 1. level + opt0 := NewOption("OPT0", "OPT0_KEY") + + // 2. level + opt01 := NewOption("OPT01", "OPT01_KEY") // has no child + opt0.AddOption("value1", opt01) + + opt02 := NewOption("OPT02", "OPT02_KEY") + opt0.AddOption("value2", opt02) + + // 3. level + opt021 := NewOption("OPT021", "OPT021_KEY") + opt02.AddOption("value1", opt021) + + { + parent, underKey, ok := opt0.Parent() + require.Equal(t, (*OptionNode)(nil), parent) + require.Equal(t, "", underKey) + require.Equal(t, false, ok) + } + + { + parent, underKey, ok := opt01.Parent() + require.Equal(t, opt0, parent) + require.Equal(t, "value1", underKey) + require.Equal(t, true, ok) + } + + { + parent, underKey, ok := opt02.Parent() + require.Equal(t, opt0, parent) + require.Equal(t, "value2", underKey) + require.Equal(t, true, ok) + } + + { + parent, underKey, ok := opt021.Parent() + require.Equal(t, opt02, parent) + require.Equal(t, "value1", underKey) + require.Equal(t, true, ok) + } +} + +func TestRemoveConfigs(t *testing.T) { + optionJSON := `{ + "title": "Project (or Workspace) path", + "env_key": "BITRISE_PROJECT_PATH", + "value_map": { + "BitriseTest.xcodeproj": { + "title": "Scheme name", + "env_key": "BITRISE_SCHEME", + "value_map": { + "BitriseTest": { + "config": "ios-test-config" + }, + "BitriseTest-tvOS": { + "config": "ios-test-config" + } + } + } + } +}` + + var option OptionNode + require.NoError(t, json.Unmarshal([]byte(optionJSON), &option)) + + option.RemoveConfigs() + + require.Equal(t, `{ + "title": "Project (or Workspace) path", + "env_key": "BITRISE_PROJECT_PATH", + "value_map": { + "BitriseTest.xcodeproj": { + "title": "Scheme name", + "env_key": "BITRISE_SCHEME", + "value_map": { + "BitriseTest": {}, + "BitriseTest-tvOS": {} + } + } + } +}`, option.String()) +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/models/models.go b/vendor/github.com/bitrise-core/bitrise-init/models/models.go new file mode 100644 index 00000000..db94cedf --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/models/models.go @@ -0,0 +1,29 @@ +package models + +// BitriseConfigMap ... +type BitriseConfigMap map[string]string + +// Warnings ... +type Warnings []string + +// Errors ... +type Errors []string + +// ScanResultModel ... +type ScanResultModel struct { + ScannerToOptionRoot map[string]OptionNode `json:"options,omitempty" yaml:"options,omitempty"` + ScannerToBitriseConfigMap map[string]BitriseConfigMap `json:"configs,omitempty" yaml:"configs,omitempty"` + ScannerToWarnings map[string]Warnings `json:"warnings,omitempty" yaml:"warnings,omitempty"` + ScannerToErrors map[string]Errors `json:"errors,omitempty" yaml:"errors,omitempty"` +} + +// AddError ... +func (result *ScanResultModel) AddError(platform string, errorMessage string) { + if result.ScannerToErrors == nil { + result.ScannerToErrors = map[string]Errors{} + } + if result.ScannerToErrors[platform] == nil { + result.ScannerToErrors[platform] = []string{} + } + result.ScannerToErrors[platform] = append(result.ScannerToErrors[platform], errorMessage) +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/models/option.go b/vendor/github.com/bitrise-core/bitrise-init/models/option.go new file mode 100644 index 00000000..4ceb6cc7 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/models/option.go @@ -0,0 +1,205 @@ +package models + +import ( + "encoding/json" + "fmt" +) + +// OptionNode ... +type OptionNode struct { + Title string `json:"title,omitempty" yaml:"title,omitempty"` + EnvKey string `json:"env_key,omitempty" yaml:"env_key,omitempty"` + + ChildOptionMap map[string]*OptionNode `json:"value_map,omitempty" yaml:"value_map,omitempty"` + Config string `json:"config,omitempty" yaml:"config,omitempty"` + + Components []string `json:"-" yaml:"-"` + Head *OptionNode `json:"-" yaml:"-"` +} + +// NewOption ... +func NewOption(title, envKey string) *OptionNode { + return &OptionNode{ + Title: title, + EnvKey: envKey, + ChildOptionMap: map[string]*OptionNode{}, + Components: []string{}, + } +} + +// NewConfigOption ... +func NewConfigOption(name string) *OptionNode { + return &OptionNode{ + ChildOptionMap: map[string]*OptionNode{}, + Config: name, + Components: []string{}, + } +} + +func (option *OptionNode) String() string { + bytes, err := json.MarshalIndent(option, "", "\t") + if err != nil { + return fmt.Sprintf("failed to marshal, error: %s", err) + } + return string(bytes) +} + +// IsConfigOption ... +func (option *OptionNode) IsConfigOption() bool { + return option.Config != "" +} + +// IsValueOption ... +func (option *OptionNode) IsValueOption() bool { + return option.Title != "" +} + +// IsEmpty ... +func (option *OptionNode) IsEmpty() bool { + return !option.IsValueOption() && !option.IsConfigOption() +} + +// AddOption ... +func (option *OptionNode) AddOption(forValue string, newOption *OptionNode) { + option.ChildOptionMap[forValue] = newOption + + if newOption != nil { + newOption.Components = append(option.Components, forValue) + + if option.Head == nil { + // first option's head is nil + newOption.Head = option + } else { + newOption.Head = option.Head + } + } +} + +// AddConfig ... +func (option *OptionNode) AddConfig(forValue string, newConfigOption *OptionNode) { + option.ChildOptionMap[forValue] = newConfigOption + + if newConfigOption != nil { + newConfigOption.Components = append(option.Components, forValue) + + if option.Head == nil { + // first option's head is nil + newConfigOption.Head = option + } else { + newConfigOption.Head = option.Head + } + } +} + +// Parent ... +func (option *OptionNode) Parent() (*OptionNode, string, bool) { + if option.Head == nil { + return nil, "", false + } + + parentComponents := option.Components[:len(option.Components)-1] + parentOption, ok := option.Head.Child(parentComponents...) + if !ok { + return nil, "", false + } + underKey := option.Components[len(option.Components)-1:][0] + return parentOption, underKey, true +} + +// Child ... +func (option *OptionNode) Child(components ...string) (*OptionNode, bool) { + currentOption := option + for _, component := range components { + childOption := currentOption.ChildOptionMap[component] + if childOption == nil { + return nil, false + } + currentOption = childOption + } + return currentOption, true +} + +// LastChilds ... +func (option *OptionNode) LastChilds() []*OptionNode { + lastOptions := []*OptionNode{} + + var walk func(*OptionNode) + walk = func(opt *OptionNode) { + if len(opt.ChildOptionMap) == 0 { + lastOptions = append(lastOptions, opt) + return + } + + for _, childOption := range opt.ChildOptionMap { + if childOption == nil { + lastOptions = append(lastOptions, opt) + return + } + + if childOption.IsConfigOption() { + lastOptions = append(lastOptions, opt) + return + } + + if childOption.IsEmpty() { + lastOptions = append(lastOptions, opt) + return + } + + walk(childOption) + } + } + + walk(option) + + return lastOptions +} + +// RemoveConfigs ... +func (option *OptionNode) RemoveConfigs() { + lastChilds := option.LastChilds() + for _, child := range lastChilds { + for _, child := range child.ChildOptionMap { + child.Config = "" + } + } +} + +// AttachToLastChilds ... +func (option *OptionNode) AttachToLastChilds(opt *OptionNode) { + childs := option.LastChilds() + for _, child := range childs { + values := child.GetValues() + for _, value := range values { + child.AddOption(value, opt) + } + } +} + +// Copy ... +func (option *OptionNode) Copy() *OptionNode { + bytes, err := json.Marshal(*option) + if err != nil { + return nil + } + + var optionCopy OptionNode + if err := json.Unmarshal(bytes, &optionCopy); err != nil { + return nil + } + + return &optionCopy +} + +// GetValues ... +func (option *OptionNode) GetValues() []string { + if option.Config != "" { + return []string{option.Config} + } + + values := []string{} + for value := range option.ChildOptionMap { + values = append(values, value) + } + return values +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/models/workflowbuilder.go b/vendor/github.com/bitrise-core/bitrise-init/models/workflowbuilder.go new file mode 100644 index 00000000..7b5288fd --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/models/workflowbuilder.go @@ -0,0 +1,25 @@ +package models + +import bitriseModels "github.com/bitrise-io/bitrise/models" + +type workflowBuilderModel struct { + Steps []bitriseModels.StepListItemModel + Description string +} + +func newDefaultWorkflowBuilder() *workflowBuilderModel { + return &workflowBuilderModel{ + Steps: []bitriseModels.StepListItemModel{}, + } +} + +func (builder *workflowBuilderModel) appendStepListItems(items ...bitriseModels.StepListItemModel) { + builder.Steps = append(builder.Steps, items...) +} + +func (builder *workflowBuilderModel) generate() bitriseModels.WorkflowModel { + return bitriseModels.WorkflowModel{ + Steps: builder.Steps, + Description: builder.Description, + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/output/output.go b/vendor/github.com/bitrise-core/bitrise-init/output/output.go new file mode 100644 index 00000000..8edc8f52 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/output/output.go @@ -0,0 +1,120 @@ +package output + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + + "gopkg.in/yaml.v2" + + "github.com/bitrise-io/go-utils/fileutil" +) + +// Format ... +type Format uint8 + +const ( + // RawFormat ... + RawFormat Format = iota + // JSONFormat ... + JSONFormat + // YAMLFormat ... + YAMLFormat +) + +// ParseFormat ... +func ParseFormat(format string) (Format, error) { + switch strings.ToLower(format) { + case "raw": + return RawFormat, nil + case "json": + return JSONFormat, nil + case "yaml": + return YAMLFormat, nil + } + + var f Format + return f, fmt.Errorf("not a valid format: %s", format) +} + +// String ... +func (format Format) String() string { + switch format { + case RawFormat: + return "raw" + case JSONFormat: + return "json" + case YAMLFormat: + return "yaml" + } + + return "unknown" +} + +// WriteToFile ... +func WriteToFile(a interface{}, format Format, pth string) (string, error) { + str := "" + ext := "" + + switch format { + case RawFormat: + str = fmt.Sprint(a) + ext = ".txt" + case JSONFormat: + bytes, err := json.MarshalIndent(a, "", "\t") + if err != nil { + return "", err + } + str = string(bytes) + ext = ".json" + case YAMLFormat: + bytes, err := yaml.Marshal(a) + if err != nil { + return "", err + } + str = string(bytes) + ext = ".yml" + default: + return "", fmt.Errorf("not a valid format: %s", format) + } + + fileExt := filepath.Ext(pth) + if fileExt != "" { + pth = strings.TrimSuffix(pth, fileExt) + } + pth = pth + ext + + if err := fileutil.WriteStringToFile(pth, str); err != nil { + return "", err + } + + return pth, nil +} + +// Print ... +func Print(a interface{}, format Format) error { + str := "" + + switch format { + case RawFormat: + str = fmt.Sprint(a) + case JSONFormat: + bytes, err := json.MarshalIndent(a, "", "\t") + if err != nil { + return err + } + str = string(bytes) + case YAMLFormat: + bytes, err := yaml.Marshal(a) + if err != nil { + return err + } + str = string(bytes) + default: + return fmt.Errorf("not a valid format: %s", format) + } + + fmt.Println(str) + return nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/release_config.yml b/vendor/github.com/bitrise-core/bitrise-init/release_config.yml new file mode 100644 index 00000000..4a1f737d --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/release_config.yml @@ -0,0 +1,13 @@ +release: + development_branch: master + release_branch: master +changelog: + path: CHANGELOG.md + content_template: |- + {{range .ContentItems}}### {{.EndTaggedCommit.Tag}} ({{.EndTaggedCommit.Date.Format "2006 Jan 02"}}) + + {{range .Commits}}* [{{firstChars .Hash 7}}] {{.Message}} + {{end}} + {{end}} + header_template: '## Changelog (Current version: {{.Version}})' + footer_template: 'Updated: {{.CurrentDate.Format "2006 Jan 02"}}' diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanner/config.go b/vendor/github.com/bitrise-core/bitrise-init/scanner/config.go new file mode 100644 index 00000000..5fbe8c91 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanner/config.go @@ -0,0 +1,231 @@ +package scanner + +import ( + "fmt" + "os" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/scanners" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/sliceutil" +) + +const otherProjectType = "other" + +type status int + +const ( + // in case DetectPlatform() returned error, or false + notDetected status = iota + // in case DetectPlatform() returned true, but Options() or Config() returned an error + detectedWithErrors + // in case DetectPlatform() returned true, Options() and Config() returned no error + detected +) + +type scannerOutput struct { + status status + + // can always be set + // warnings returned by DetectPlatform(), Options() + warnings models.Warnings + + // set if scanResultStatus is scanResultDetectedWithErrors + // errors returned by Config() + errors models.Errors + + // set if scanResultStatus is scanResultDetected + options models.OptionNode + configs models.BitriseConfigMap + excludedScanners []string +} + +// Config ... +func Config(searchDir string) models.ScanResultModel { + result := models.ScanResultModel{} + + // + // Setup + currentDir, err := os.Getwd() + if err != nil { + result.AddError("general", fmt.Sprintf("Failed to expand current directory path, error: %s", err)) + return result + } + + if searchDir == "" { + searchDir = currentDir + } else { + absScerach, err := pathutil.AbsPath(searchDir) + if err != nil { + result.AddError("general", fmt.Sprintf("Failed to expand path (%s), error: %s", searchDir, err)) + return result + } + searchDir = absScerach + } + + if searchDir != currentDir { + if err := os.Chdir(searchDir); err != nil { + result.AddError("general", fmt.Sprintf("Failed to change dir, to (%s), error: %s", searchDir, err)) + return result + } + defer func() { + if err := os.Chdir(currentDir); err != nil { + log.TWarnf("Failed to change dir, to (%s), error: %s", searchDir, err) + } + }() + } + // --- + + // + // Scan + log.TInfof(colorstring.Blue("Running scanners:")) + fmt.Println() + + // Collect scanner outputs, by scanner name + scannerToOutput := map[string]scannerOutput{} + { + projectScannerToOutputs := runScanners(scanners.ProjectScanners, searchDir) + detectedProjectTypes := getDetectedScannerNames(projectScannerToOutputs) + log.Printf("Detected project types: %s", detectedProjectTypes) + fmt.Println() + + // Project types are needed by tool scanners, to create decision tree on which project type + // to actually use in bitrise.yml + if len(detectedProjectTypes) == 0 { + detectedProjectTypes = []string{otherProjectType} + } + for _, toolScanner := range scanners.AutomationToolScanners { + toolScanner.(scanners.AutomationToolScanner).SetDetectedProjectTypes(detectedProjectTypes) + } + + toolScannerToOutputs := runScanners(scanners.AutomationToolScanners, searchDir) + detectedAutomationToolScanners := getDetectedScannerNames(toolScannerToOutputs) + log.Printf("Detected automation tools: %s", detectedAutomationToolScanners) + fmt.Println() + + // Merge project and tool scanner outputs + scannerToOutput = toolScannerToOutputs + for scanner, scannerOutput := range projectScannerToOutputs { + scannerToOutput[scanner] = scannerOutput + } + } + + scannerToWarnings := map[string]models.Warnings{} + scannerToErrors := map[string]models.Errors{} + scannerToOptions := map[string]models.OptionNode{} + scannerToConfigMap := map[string]models.BitriseConfigMap{} + for scanner, scannerOutput := range scannerToOutput { + // Currently the tests except an empty warning list if no warnings + // are created in the not detect case. + if scannerOutput.status == notDetected && len(scannerOutput.warnings) > 0 || + scannerOutput.status != notDetected { + scannerToWarnings[scanner] = scannerOutput.warnings + } + if len(scannerOutput.errors) > 0 && + (scannerOutput.status == detected || scannerOutput.status == detectedWithErrors) { + scannerToErrors[scanner] = scannerOutput.errors + } + if len(scannerOutput.configs) > 0 && scannerOutput.status == detected { + scannerToOptions[scanner] = scannerOutput.options + scannerToConfigMap[scanner] = scannerOutput.configs + } + } + return models.ScanResultModel{ + ScannerToOptionRoot: scannerToOptions, + ScannerToBitriseConfigMap: scannerToConfigMap, + ScannerToWarnings: scannerToWarnings, + ScannerToErrors: scannerToErrors, + } +} + +func runScanners(scannerList []scanners.ScannerInterface, searchDir string) map[string]scannerOutput { + scannerOutputs := map[string]scannerOutput{} + var excludedScannerNames []string + for _, scanner := range scannerList { + log.TInfof("Scanner: %s", colorstring.Blue(scanner.Name())) + if sliceutil.IsStringInSlice(scanner.Name(), excludedScannerNames) { + log.TWarnf("scanner is marked as excluded, skipping...") + fmt.Println() + continue + } + + log.TPrintf("+------------------------------------------------------------------------------+") + log.TPrintf("| |") + scannerOutput := runScanner(scanner, searchDir) + log.TPrintf("| |") + log.TPrintf("+------------------------------------------------------------------------------+") + fmt.Println() + + scannerOutputs[scanner.Name()] = scannerOutput + excludedScannerNames = append(excludedScannerNames, scannerOutput.excludedScanners...) + } + return scannerOutputs +} + +// Collect output of a specific scanner +func runScanner(detector scanners.ScannerInterface, searchDir string) scannerOutput { + var detectorWarnings models.Warnings + var detectorErrors []string + + if isDetect, err := detector.DetectPlatform(searchDir); err != nil { + log.TErrorf("Scanner failed, error: %s", err) + return scannerOutput{ + status: notDetected, + warnings: models.Warnings{err.Error()}, + } + } else if !isDetect { + return scannerOutput{ + status: notDetected, + } + } + + options, projectWarnings, err := detector.Options() + detectorWarnings = append(detectorWarnings, projectWarnings...) + + if err != nil { + log.TErrorf("Analyzer failed, error: %s", err) + // Error returned as a warning + detectorWarnings = append(detectorWarnings, err.Error()) + return scannerOutput{ + status: detectedWithErrors, + warnings: detectorWarnings, + } + } + + // Generate configs + configs, err := detector.Configs() + if err != nil { + log.TErrorf("Failed to generate config, error: %s", err) + detectorErrors = append(detectorErrors, err.Error()) + return scannerOutput{ + status: detectedWithErrors, + warnings: detectorWarnings, + errors: detectorErrors, + } + } + + scannerExcludedScanners := detector.ExcludedScannerNames() + if len(scannerExcludedScanners) > 0 { + log.TWarnf("Scanner will exclude scanners: %v", scannerExcludedScanners) + } + + return scannerOutput{ + status: detected, + warnings: detectorWarnings, + errors: detectorErrors, + options: options, + configs: configs, + excludedScanners: scannerExcludedScanners, + } +} + +func getDetectedScannerNames(scannerOutputs map[string]scannerOutput) (names []string) { + for scanner, scannerOutput := range scannerOutputs { + if scannerOutput.status == detected { + names = append(names, scanner) + } + } + return +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanner/manual_config.go b/vendor/github.com/bitrise-core/bitrise-init/scanner/manual_config.go new file mode 100644 index 00000000..2c917ef1 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanner/manual_config.go @@ -0,0 +1,38 @@ +package scanner + +import ( + "fmt" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/scanners" +) + +// ManualConfig ... +func ManualConfig() (models.ScanResultModel, error) { + scannerList := append(scanners.ProjectScanners, scanners.AutomationToolScanners...) + scannerToOptionRoot := map[string]models.OptionNode{} + scannerToBitriseConfigMap := map[string]models.BitriseConfigMap{} + + for _, scanner := range scannerList { + option := scanner.DefaultOptions() + scannerToOptionRoot[scanner.Name()] = option + + configs, err := scanner.DefaultConfigs() + if err != nil { + return models.ScanResultModel{}, fmt.Errorf("Failed create default configs, error: %s", err) + } + scannerToBitriseConfigMap[scanner.Name()] = configs + } + + customConfig, err := scanners.CustomConfig() + if err != nil { + return models.ScanResultModel{}, fmt.Errorf("Failed create default custom configs, error: %s", err) + } + + scannerToBitriseConfigMap[scanners.CustomProjectType] = customConfig + + return models.ScanResultModel{ + ScannerToOptionRoot: scannerToOptionRoot, + ScannerToBitriseConfigMap: scannerToBitriseConfigMap, + }, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanner/utils.go b/vendor/github.com/bitrise-core/bitrise-init/scanner/utils.go new file mode 100644 index 00000000..f9187e60 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanner/utils.go @@ -0,0 +1,151 @@ +package scanner + +import ( + "errors" + "fmt" + + yaml "gopkg.in/yaml.v2" + + "github.com/bitrise-core/bitrise-init/models" + bitriseModels "github.com/bitrise-io/bitrise/models" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/goinp/goinp" +) + +func askForOptionValue(option models.OptionNode) (string, string, error) { + optionValues := option.GetValues() + + selectedValue := "" + if len(optionValues) == 1 { + if optionValues[0] == "_" { + // provide option value + question := fmt.Sprintf("Provide: %s", option.Title) + answer, err := goinp.AskForString(question) + if err != nil { + return "", "", err + } + + selectedValue = answer + } else { + // auto select the only one value + selectedValue = optionValues[0] + } + } else { + // select from values + question := fmt.Sprintf("Select: %s", option.Title) + answer, err := goinp.SelectFromStrings(question, optionValues) + if err != nil { + return "", "", err + } + + selectedValue = answer + } + + return option.EnvKey, selectedValue, nil +} + +// AskForOptions ... +func AskForOptions(options models.OptionNode) (string, []envmanModels.EnvironmentItemModel, error) { + configPth := "" + appEnvs := []envmanModels.EnvironmentItemModel{} + + var walkDepth func(models.OptionNode) error + walkDepth = func(opt models.OptionNode) error { + optionEnvKey, selectedValue, err := askForOptionValue(opt) + if err != nil { + return fmt.Errorf("Failed to ask for value, error: %s", err) + } + + if opt.Title == "" { + // last option selected, config got + configPth = selectedValue + return nil + } else if optionEnvKey != "" { + // env's value selected + appEnvs = append(appEnvs, envmanModels.EnvironmentItemModel{ + optionEnvKey: selectedValue, + }) + } + + var nestedOptions *models.OptionNode + if len(opt.ChildOptionMap) == 1 { + // auto select the next option + for _, childOption := range opt.ChildOptionMap { + nestedOptions = childOption + break + } + } else { + // go to the next option, based on the selected value + childOptions, found := opt.ChildOptionMap[selectedValue] + if !found { + return nil + } + nestedOptions = childOptions + } + + return walkDepth(*nestedOptions) + } + + if err := walkDepth(options); err != nil { + return "", []envmanModels.EnvironmentItemModel{}, err + } + + if configPth == "" { + return "", nil, errors.New("no config selected") + } + + return configPth, appEnvs, nil +} + +// AskForConfig ... +func AskForConfig(scanResult models.ScanResultModel) (bitriseModels.BitriseDataModel, error) { + + // + // Select platform + platforms := []string{} + for platform := range scanResult.ScannerToOptionRoot { + platforms = append(platforms, platform) + } + + platform := "" + if len(platforms) == 0 { + return bitriseModels.BitriseDataModel{}, errors.New("no platform detected") + } else if len(platforms) == 1 { + platform = platforms[0] + } else { + var err error + platform, err = goinp.SelectFromStrings("Select platform", platforms) + if err != nil { + return bitriseModels.BitriseDataModel{}, err + } + } + // --- + + // + // Select config + options, ok := scanResult.ScannerToOptionRoot[platform] + if !ok { + return bitriseModels.BitriseDataModel{}, fmt.Errorf("invalid platform selected: %s", platform) + } + + configPth, appEnvs, err := AskForOptions(options) + if err != nil { + return bitriseModels.BitriseDataModel{}, err + } + // -- + + // + // Build config + configMap := scanResult.ScannerToBitriseConfigMap[platform] + configStr := configMap[configPth] + + var config bitriseModels.BitriseDataModel + if err := yaml.Unmarshal([]byte(configStr), &config); err != nil { + return bitriseModels.BitriseDataModel{}, fmt.Errorf("failed to unmarshal config, error: %s", err) + } + + config.App.Environments = append(config.App.Environments, appEnvs...) + // --- + + return config, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/android/android.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/android/android.go new file mode 100644 index 00000000..7559a2f0 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/android/android.go @@ -0,0 +1,123 @@ +package android + +import ( + "fmt" + "path/filepath" + + "gopkg.in/yaml.v2" + + "github.com/bitrise-core/bitrise-init/models" +) + +// Scanner ... +type Scanner struct { + SearchDir string + ProjectRoots []string + ExcludeTest bool +} + +// NewScanner ... +func NewScanner() *Scanner { + return &Scanner{} +} + +// Name ... +func (Scanner) Name() string { + return ScannerName +} + +// ExcludedScannerNames ... +func (*Scanner) ExcludedScannerNames() []string { + return nil +} + +// DetectPlatform ... +func (scanner *Scanner) DetectPlatform(searchDir string) (_ bool, err error) { + scanner.SearchDir = searchDir + + scanner.ProjectRoots, err = walkMultipleFiles(searchDir, "build.gradle", "settings.gradle") + if err != nil { + return false, fmt.Errorf("failed to search for build.gradle files, error: %s", err) + } + + return len(scanner.ProjectRoots) > 0, err +} + +// Options ... +func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { + projectLocationOption := models.NewOption(ProjectLocationInputTitle, ProjectLocationInputEnvKey) + warnings := models.Warnings{} + + for _, projectRoot := range scanner.ProjectRoots { + if err := checkGradlew(projectRoot); err != nil { + return models.OptionNode{}, warnings, err + } + + relProjectRoot, err := filepath.Rel(scanner.SearchDir, projectRoot) + if err != nil { + return models.OptionNode{}, warnings, err + } + + configOption := models.NewConfigOption(ConfigName) + moduleOption := models.NewOption(ModuleInputTitle, ModuleInputEnvKey) + variantOption := models.NewOption(VariantInputTitle, VariantInputEnvKey) + + projectLocationOption.AddOption(relProjectRoot, moduleOption) + moduleOption.AddOption("app", variantOption) + variantOption.AddConfig("", configOption) + } + + return *projectLocationOption, warnings, nil +} + +// DefaultOptions ... +func (scanner *Scanner) DefaultOptions() models.OptionNode { + projectLocationOption := models.NewOption(ProjectLocationInputTitle, ProjectLocationInputEnvKey) + moduleOption := models.NewOption(ModuleInputTitle, ModuleInputEnvKey) + variantOption := models.NewOption(VariantInputTitle, VariantInputEnvKey) + configOption := models.NewConfigOption(DefaultConfigName) + + projectLocationOption.AddOption("_", moduleOption) + moduleOption.AddOption("_", variantOption) + variantOption.AddConfig("", configOption) + + return *projectLocationOption +} + +// Configs ... +func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { + configBuilder := scanner.generateConfigBuilder() + + config, err := configBuilder.Generate(ScannerName) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + ConfigName: string(data), + }, nil +} + +// DefaultConfigs ... +func (scanner *Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { + configBuilder := scanner.generateConfigBuilder() + + config, err := configBuilder.Generate(ScannerName) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + DefaultConfigName: string(data), + }, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/android/const.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/android/const.go new file mode 100644 index 00000000..f5df5aee --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/android/const.go @@ -0,0 +1,34 @@ +package android + +const deployWorkflowDescription = `## How to get a signed APK + +This workflow contains the **Sign APK** step. To sign your APK all you have to do is to: + +1. Click on **Code Signing** tab +1. Find the **ANDROID KEYSTORE FILE** section +1. Click or drop your file on the upload file field +1. Fill the displayed 3 input fields: + 1. **Keystore password** + 1. **Keystore alias** + 1. **Private key password** +1. Click on **[Save metadata]** button + +That's it! From now on, **Sign APK** step will receive your uploaded files. + +## To run this workflow + +If you want to run this workflow manually: + +1. Open the app's build list page +2. Click on **[Start/Schedule a Build]** button +3. Select **deploy** in **Workflow** dropdown input +4. Click **[Start Build]** button + +Or if you need this workflow to be started by a GIT event: + +1. Click on **Triggers** tab +2. Setup your desired event (push/tag/pull) and select **deploy** workflow +3. Click on **[Done]** and then **[Save]** buttons + +The next change in your repository that matches any of your trigger map event will start **deploy** workflow. +` diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/android/utility.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/android/utility.go new file mode 100644 index 00000000..7f5ceb36 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/android/utility.go @@ -0,0 +1,187 @@ +package android + +import ( + "errors" + "os" + "path/filepath" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/pathutil" +) + +// Constants ... +const ( + ScannerName = "android" + ConfigName = "android-config" + DefaultConfigName = "default-android-config" + + ProjectLocationInputKey = "project_location" + ProjectLocationInputEnvKey = "PROJECT_LOCATION" + ProjectLocationInputTitle = "The root directory of an Android project" + + ModuleBuildGradlePathInputKey = "build_gradle_path" + + VariantInputKey = "variant" + VariantInputEnvKey = "VARIANT" + VariantInputTitle = "Variant" + + ModuleInputKey = "module" + ModuleInputEnvKey = "MODULE" + ModuleInputTitle = "Module" + + GradlewPathInputKey = "gradlew_path" + GradlewPathInputEnvKey = "GRADLEW_PATH" + GradlewPathInputTitle = "Gradlew file path" +) + +func walk(src string, fn func(path string, info os.FileInfo) error) error { + return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if path == src { + return nil + } + return fn(path, info) + }) +} + +func checkFiles(path string, files ...string) (bool, error) { + for _, file := range files { + exists, err := pathutil.IsPathExists(filepath.Join(path, file)) + if err != nil { + return false, err + } + if !exists { + return false, nil + } + } + return true, nil +} + +func walkMultipleFiles(searchDir string, files ...string) (matches []string, err error) { + match, err := checkFiles(searchDir, files...) + if err != nil { + return nil, err + } + if match { + matches = append(matches, searchDir) + } + return matches, walk(searchDir, func(path string, info os.FileInfo) error { + if err != nil { + return err + } + if info.IsDir() { + match, err := checkFiles(path, files...) + if err != nil { + return err + } + if match { + matches = append(matches, path) + } + } + return nil + }) +} + +func checkGradlew(projectDir string) error { + gradlewPth := filepath.Join(projectDir, "gradlew") + exist, err := pathutil.IsPathExists(gradlewPth) + if err != nil { + return err + } + if !exist { + return errors.New(`No Gradle Wrapper (gradlew) found. +Using a Gradle Wrapper (gradlew) is required, as the wrapper is what makes sure +that the right Gradle version is installed and used for the build. More info/guide: https://docs.gradle.org/current/userguide/gradle_wrapper.html`) + } + return nil +} + +func (scanner *Scanner) generateConfigBuilder() models.ConfigBuilderModel { + configBuilder := models.NewDefaultConfigBuilder() + + projectLocationEnv, gradlewPath, moduleEnv, variantEnv := "$"+ProjectLocationInputEnvKey, "$"+ProjectLocationInputEnvKey+"/gradlew", "$"+ModuleInputEnvKey, "$"+VariantInputEnvKey + + //-- primary + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(true)...) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.InstallMissingAndroidToolsStepListItem( + envmanModels.EnvironmentItemModel{GradlewPathInputKey: gradlewPath}, + )) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.AndroidLintStepListItem( + envmanModels.EnvironmentItemModel{ + ProjectLocationInputKey: projectLocationEnv, + }, + envmanModels.EnvironmentItemModel{ + ModuleInputKey: moduleEnv, + }, + envmanModels.EnvironmentItemModel{ + VariantInputKey: variantEnv, + }, + )) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.AndroidUnitTestStepListItem( + envmanModels.EnvironmentItemModel{ + ProjectLocationInputKey: projectLocationEnv, + }, + envmanModels.EnvironmentItemModel{ + ModuleInputKey: moduleEnv, + }, + envmanModels.EnvironmentItemModel{ + VariantInputKey: variantEnv, + }, + )) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(true)...) + + //-- deploy + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(true)...) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.InstallMissingAndroidToolsStepListItem( + envmanModels.EnvironmentItemModel{GradlewPathInputKey: gradlewPath}, + )) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.ChangeAndroidVersionCodeAndVersionNameStepListItem( + envmanModels.EnvironmentItemModel{ModuleBuildGradlePathInputKey: filepath.Join(projectLocationEnv, moduleEnv, "build.gradle")}, + )) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidLintStepListItem( + envmanModels.EnvironmentItemModel{ + ProjectLocationInputKey: projectLocationEnv, + }, + envmanModels.EnvironmentItemModel{ + ModuleInputKey: moduleEnv, + }, + envmanModels.EnvironmentItemModel{ + VariantInputKey: variantEnv, + }, + )) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidUnitTestStepListItem( + envmanModels.EnvironmentItemModel{ + ProjectLocationInputKey: projectLocationEnv, + }, + envmanModels.EnvironmentItemModel{ + ModuleInputKey: moduleEnv, + }, + envmanModels.EnvironmentItemModel{ + VariantInputKey: variantEnv, + }, + )) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( + envmanModels.EnvironmentItemModel{ + ProjectLocationInputKey: projectLocationEnv, + }, + envmanModels.EnvironmentItemModel{ + ModuleInputKey: moduleEnv, + }, + envmanModels.EnvironmentItemModel{ + VariantInputKey: variantEnv, + }, + )) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.SignAPKStepListItem()) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(true)...) + + configBuilder.SetWorkflowDescriptionTo(models.DeployWorkflowID, deployWorkflowDescription) + + return *configBuilder +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova.go new file mode 100644 index 00000000..2816e491 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova.go @@ -0,0 +1,405 @@ +package cordova + +import ( + "fmt" + "path/filepath" + "strings" + + yaml "gopkg.in/yaml.v2" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/scanners/android" + "github.com/bitrise-core/bitrise-init/scanners/ios" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-core/bitrise-init/utility" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" +) + +// ScannerName ... +const ScannerName = "cordova" + +const ( + configName = "cordova-config" + defaultConfigName = "default-cordova-config" +) + +// Step Inputs +const ( + workDirInputKey = "workdir" + workDirInputTitle = "Directory of Cordova Config.xml" + workDirInputEnvKey = "CORDOVA_WORK_DIR" +) + +const ( + platformInputKey = "platform" + platformInputTitle = "Platform to use in cordova-cli commands" + platformInputEnvKey = "CORDOVA_PLATFORM" +) + +const ( + targetInputKey = "target" + targetEmulator = "emulator" +) + +//------------------ +// ScannerInterface +//------------------ + +// Scanner ... +type Scanner struct { + cordovaConfigPth string + relCordovaConfigDir string + searchDir string + hasKarmaJasmineTest bool + hasJasmineTest bool +} + +// NewScanner ... +func NewScanner() *Scanner { + return &Scanner{} +} + +// Name ... +func (Scanner) Name() string { + return ScannerName +} + +// DetectPlatform ... +func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { + fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) + if err != nil { + return false, fmt.Errorf("failed to search for files in (%s), error: %s", searchDir, err) + } + + // Search for config.xml file + log.TInfof("Searching for config.xml file") + + configXMLPth, err := FilterRootConfigXMLFile(fileList) + if err != nil { + return false, fmt.Errorf("failed to search for config.xml file, error: %s", err) + } + + log.TPrintf("config.xml: %s", configXMLPth) + + if configXMLPth == "" { + log.TPrintf("platform not detected") + return false, nil + } + + widget, err := ParseConfigXML(configXMLPth) + if err != nil { + log.TPrintf("can not parse config.xml as a Cordova widget, error: %s", err) + log.TPrintf("platform not detected") + return false, nil + } + + // ensure it is a cordova widget + if !strings.Contains(widget.XMLNSCDV, "cordova.apache.org") { + log.TPrintf("config.xml propert: xmlns:cdv does not contain cordova.apache.org") + log.TPrintf("platform not detected") + return false, nil + } + + // ensure it is not an ionic project + projectBaseDir := filepath.Dir(configXMLPth) + + if exist, err := pathutil.IsPathExists(filepath.Join(projectBaseDir, "ionic.project")); err != nil { + return false, fmt.Errorf("failed to check if project is an ionic project, error: %s", err) + } else if exist { + log.TPrintf("ionic.project file found seems to be an ionic project") + return false, nil + } + + if exist, err := pathutil.IsPathExists(filepath.Join(projectBaseDir, "ionic.config.json")); err != nil { + return false, fmt.Errorf("failed to check if project is an ionic project, error: %s", err) + } else if exist { + log.TPrintf("ionic.config.json file found seems to be an ionic project") + return false, nil + } + + log.TSuccessf("Platform detected") + + scanner.cordovaConfigPth = configXMLPth + scanner.searchDir = searchDir + + return true, nil +} + +// ExcludedScannerNames ... +func (*Scanner) ExcludedScannerNames() []string { + return []string{ + string(ios.XcodeProjectTypeIOS), + string(ios.XcodeProjectTypeMacOS), + android.ScannerName, + } +} + +// Options ... +func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { + warnings := models.Warnings{} + projectRootDir := filepath.Dir(scanner.cordovaConfigPth) + + packagesJSONPth := filepath.Join(projectRootDir, "package.json") + packages, err := utility.ParsePackagesJSON(packagesJSONPth) + if err != nil { + return models.OptionNode{}, warnings, err + } + + // Search for karma/jasmine tests + log.TPrintf("Searching for karma/jasmine test") + + karmaTestDetected := false + + karmaJasmineDependencyFound := false + for dependency := range packages.Dependencies { + if strings.Contains(dependency, "karma-jasmine") { + karmaJasmineDependencyFound = true + } + } + if !karmaJasmineDependencyFound { + for dependency := range packages.DevDependencies { + if strings.Contains(dependency, "karma-jasmine") { + karmaJasmineDependencyFound = true + } + } + } + log.TPrintf("karma-jasmine dependency found: %v", karmaJasmineDependencyFound) + + if karmaJasmineDependencyFound { + karmaConfigJSONPth := filepath.Join(projectRootDir, "karma.conf.js") + if exist, err := pathutil.IsPathExists(karmaConfigJSONPth); err != nil { + return models.OptionNode{}, warnings, err + } else if exist { + karmaTestDetected = true + } + } + log.TPrintf("karma.conf.js found: %v", karmaTestDetected) + + scanner.hasKarmaJasmineTest = karmaTestDetected + // --- + + // Search for jasmine tests + jasminTestDetected := false + + if !karmaTestDetected { + log.TPrintf("Searching for jasmine test") + + jasmineDependencyFound := false + for dependency := range packages.Dependencies { + if strings.Contains(dependency, "jasmine") { + jasmineDependencyFound = true + break + } + } + if !jasmineDependencyFound { + for dependency := range packages.DevDependencies { + if strings.Contains(dependency, "jasmine") { + jasmineDependencyFound = true + break + } + } + } + log.TPrintf("jasmine dependency found: %v", jasmineDependencyFound) + + if jasmineDependencyFound { + jasmineConfigJSONPth := filepath.Join(projectRootDir, "spec", "support", "jasmine.json") + if exist, err := pathutil.IsPathExists(jasmineConfigJSONPth); err != nil { + return models.OptionNode{}, warnings, err + } else if exist { + jasminTestDetected = true + } + } + + log.TPrintf("jasmine.json found: %v", jasminTestDetected) + + scanner.hasJasmineTest = jasminTestDetected + } + // --- + + // Get relative config.xml dir + cordovaConfigDir := filepath.Dir(scanner.cordovaConfigPth) + relCordovaConfigDir, err := utility.RelPath(scanner.searchDir, cordovaConfigDir) + if err != nil { + return models.OptionNode{}, warnings, fmt.Errorf("Failed to get relative config.xml dir path, error: %s", err) + } + if relCordovaConfigDir == "." { + // config.xml placed in the search dir, no need to change-dir in the workflows + relCordovaConfigDir = "" + } + scanner.relCordovaConfigDir = relCordovaConfigDir + // --- + + // Options + var rootOption *models.OptionNode + + platforms := []string{"ios", "android", "ios,android"} + + if relCordovaConfigDir != "" { + rootOption = models.NewOption(workDirInputTitle, workDirInputEnvKey) + + projectTypeOption := models.NewOption(platformInputTitle, platformInputEnvKey) + rootOption.AddOption(relCordovaConfigDir, projectTypeOption) + + for _, platform := range platforms { + configOption := models.NewConfigOption(configName) + projectTypeOption.AddConfig(platform, configOption) + } + } else { + rootOption = models.NewOption(platformInputTitle, platformInputEnvKey) + + for _, platform := range platforms { + configOption := models.NewConfigOption(configName) + rootOption.AddConfig(platform, configOption) + } + } + // --- + + return *rootOption, warnings, nil +} + +// DefaultOptions ... +func (*Scanner) DefaultOptions() models.OptionNode { + workDirOption := models.NewOption(workDirInputTitle, workDirInputEnvKey) + + projectTypeOption := models.NewOption(platformInputTitle, platformInputEnvKey) + workDirOption.AddOption("_", projectTypeOption) + + platforms := []string{ + "ios", + "android", + "ios,android", + } + for _, platform := range platforms { + configOption := models.NewConfigOption(defaultConfigName) + projectTypeOption.AddConfig(platform, configOption) + } + + return *workDirOption +} + +// Configs ... +func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + + workdirEnvList := []envmanModels.EnvironmentItemModel{} + if scanner.relCordovaConfigDir != "" { + workdirEnvList = append(workdirEnvList, envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}) + } + + if scanner.hasJasmineTest || scanner.hasKarmaJasmineTest { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + + // CI + if scanner.hasKarmaJasmineTest { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.KarmaJasmineTestRunnerStepListItem(workdirEnvList...)) + } else if scanner.hasJasmineTest { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.JasmineTestRunnerStepListItem(workdirEnvList...)) + } + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + // CD + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + + if scanner.hasKarmaJasmineTest { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.KarmaJasmineTestRunnerStepListItem(workdirEnvList...)) + } else if scanner.hasJasmineTest { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.JasmineTestRunnerStepListItem(workdirEnvList...)) + } + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.GenerateCordovaBuildConfigStepListItem()) + + cordovaArchiveEnvs := []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{platformInputKey: "$" + platformInputEnvKey}, + envmanModels.EnvironmentItemModel{targetInputKey: targetEmulator}, + } + if scanner.relCordovaConfigDir != "" { + cordovaArchiveEnvs = append(cordovaArchiveEnvs, envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}) + } + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CordovaArchiveStepListItem(cordovaArchiveEnvs...)) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) + + config, err := configBuilder.Generate(ScannerName) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + configName: string(data), + }, nil + } + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.GenerateCordovaBuildConfigStepListItem()) + + cordovaArchiveEnvs := []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{platformInputKey: "$" + platformInputEnvKey}, + envmanModels.EnvironmentItemModel{targetInputKey: targetEmulator}, + } + if scanner.relCordovaConfigDir != "" { + cordovaArchiveEnvs = append(cordovaArchiveEnvs, envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}) + } + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CordovaArchiveStepListItem(cordovaArchiveEnvs...)) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + config, err := configBuilder.Generate(ScannerName) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + configName: string(data), + }, nil +} + +// DefaultConfigs ... +func (*Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem( + envmanModels.EnvironmentItemModel{"command": "install"}, + envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey})) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.GenerateCordovaBuildConfigStepListItem()) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CordovaArchiveStepListItem( + envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}, + envmanModels.EnvironmentItemModel{platformInputKey: "$" + platformInputEnvKey}, + envmanModels.EnvironmentItemModel{targetInputKey: targetEmulator})) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + config, err := configBuilder.Generate(ScannerName) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + defaultConfigName: string(data), + }, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova_test.go new file mode 100644 index 00000000..d756586b --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova_test.go @@ -0,0 +1,37 @@ +package cordova + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseConfigXMLContent(t *testing.T) { + widget, err := parseConfigXMLContent(testConfigXMLContent) + require.NoError(t, err) + require.Equal(t, "http://cordova.apache.org/ns/1.0", widget.XMLNSCDV) +} + +const testConfigXMLContent = ` + + CordovaOnBitrise + A sample Apache Cordova application that builds on Bitrise. + + + + + + + + + + + + + + + + + + +` diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/utility.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/utility.go new file mode 100644 index 00000000..700ff087 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/utility.go @@ -0,0 +1,47 @@ +package cordova + +import ( + "encoding/xml" + + "github.com/bitrise-core/bitrise-init/utility" + "github.com/bitrise-io/go-utils/fileutil" +) + +const configXMLBasePath = "config.xml" + +// WidgetModel ... +type WidgetModel struct { + XMLNSCDV string `xml:"xmlns cdv,attr"` +} + +func parseConfigXMLContent(content string) (WidgetModel, error) { + widget := WidgetModel{} + if err := xml.Unmarshal([]byte(content), &widget); err != nil { + return WidgetModel{}, err + } + return widget, nil +} + +// ParseConfigXML ... +func ParseConfigXML(pth string) (WidgetModel, error) { + content, err := fileutil.ReadStringFromFile(pth) + if err != nil { + return WidgetModel{}, err + } + return parseConfigXMLContent(content) +} + +// FilterRootConfigXMLFile ... +func FilterRootConfigXMLFile(fileList []string) (string, error) { + allowConfigXMLBaseFilter := utility.BaseFilter(configXMLBasePath, true) + configXMLs, err := utility.FilterPaths(fileList, allowConfigXMLBaseFilter) + if err != nil { + return "", err + } + + if len(configXMLs) == 0 { + return "", nil + } + + return configXMLs[0], nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane.go new file mode 100644 index 00000000..ba8beddf --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane.go @@ -0,0 +1,242 @@ +package fastlane + +import ( + "fmt" + + "gopkg.in/yaml.v2" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-core/bitrise-init/toolscanner" + "github.com/bitrise-core/bitrise-init/utility" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/log" +) + +const scannerName = "fastlane" + +const ( + unknownProjectType = "other" + fastlaneWorkflowID = scannerName +) + +const ( + configName = "fastlane-config" + defaultConfigName = "default-fastlane-config" +) + +// Step Inputs +const ( + laneInputKey = "lane" + laneInputTitle = "Fastlane lane" + laneInputEnvKey = "FASTLANE_LANE" +) + +const ( + workDirInputKey = "work_dir" + workDirInputTitle = "Working directory" + workDirInputEnvKey = "FASTLANE_WORK_DIR" +) + +const ( + fastlaneXcodeListTimeoutEnvKey = "FASTLANE_XCODE_LIST_TIMEOUT" + fastlaneXcodeListTimeoutEnvValue = "120" +) + +//------------------ +// ScannerInterface +//------------------ + +// Scanner ... +type Scanner struct { + Fastfiles []string + projectTypes []string +} + +// NewScanner ... +func NewScanner() *Scanner { + return &Scanner{} +} + +// Name ... +func (Scanner) Name() string { + return scannerName +} + +// DetectPlatform ... +func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { + fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) + if err != nil { + return false, fmt.Errorf("failed to search for files in (%s), error: %s", searchDir, err) + } + + // Search for Fastfile + log.TInfof("Searching for Fastfiles") + + fastfiles, err := FilterFastfiles(fileList) + if err != nil { + return false, fmt.Errorf("failed to search for Fastfile in (%s), error: %s", searchDir, err) + } + + scanner.Fastfiles = fastfiles + + log.TPrintf("%d Fastfiles detected", len(fastfiles)) + for _, file := range fastfiles { + log.TPrintf("- %s", file) + } + + if len(fastfiles) == 0 { + log.TPrintf("platform not detected") + return false, nil + } + + log.TSuccessf("Platform detected") + + return true, nil +} + +// ExcludedScannerNames ... +func (*Scanner) ExcludedScannerNames() []string { + return []string{} +} + +// Options ... +func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { + warnings := models.Warnings{} + + isValidFastfileFound := false + + // Inspect Fastfiles + + workDirOption := models.NewOption(workDirInputTitle, workDirInputEnvKey) + + for _, fastfile := range scanner.Fastfiles { + log.TInfof("Inspecting Fastfile: %s", fastfile) + + workDir := WorkDir(fastfile) + log.TPrintf("fastlane work dir: %s", workDir) + + lanes, err := InspectFastfile(fastfile) + if err != nil { + log.TWarnf("Failed to inspect Fastfile, error: %s", err) + warnings = append(warnings, fmt.Sprintf("Failed to inspect Fastfile (%s), error: %s", fastfile, err)) + continue + } + + log.TPrintf("%d lanes found", len(lanes)) + + if len(lanes) == 0 { + log.TWarnf("No lanes found") + warnings = append(warnings, fmt.Sprintf("No lanes found for Fastfile: %s", fastfile)) + continue + } + + isValidFastfileFound = true + + laneOption := models.NewOption(laneInputTitle, laneInputEnvKey) + workDirOption.AddOption(workDir, laneOption) + + for _, lane := range lanes { + log.TPrintf("- %s", lane) + + configOption := models.NewConfigOption(configName) + laneOption.AddConfig(lane, configOption) + } + } + + if !isValidFastfileFound { + log.TErrorf("No valid Fastfile found") + warnings = append(warnings, "No valid Fastfile found") + return models.OptionNode{}, warnings, nil + } + + // Add project_type property option to decision tree + optionWithProjectType := toolscanner.AddProjectTypeToOptions(*workDirOption, scanner.projectTypes) + + return optionWithProjectType, warnings, nil +} + +// DefaultOptions ... +func (*Scanner) DefaultOptions() models.OptionNode { + workDirOption := models.NewOption(workDirInputTitle, workDirInputEnvKey) + + laneOption := models.NewOption(laneInputTitle, laneInputEnvKey) + workDirOption.AddOption("_", laneOption) + + configOption := models.NewConfigOption(defaultConfigName) + laneOption.AddConfig("_", configOption) + + return *workDirOption +} + +// Configs ... +func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.FastlaneStepListItem( + envmanModels.EnvironmentItemModel{laneInputKey: "$" + laneInputEnvKey}, + envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}, + )) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + // Fill in project type later, from the list of detected project types + config, err := configBuilder.Generate(unknownProjectType, + envmanModels.EnvironmentItemModel{ + fastlaneXcodeListTimeoutEnvKey: fastlaneXcodeListTimeoutEnvValue, + }) + if err != nil { + return models.BitriseConfigMap{}, err + } + + // Create list of possible configs with project types + nameToConfigModel := toolscanner.AddProjectTypeToConfig(configName, config, scanner.projectTypes) + + nameToConfigString := map[string]string{} + for configName, config := range nameToConfigModel { + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + nameToConfigString[configName] = string(data) + } + return nameToConfigString, nil +} + +// DefaultConfigs ... +func (*Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.FastlaneStepListItem( + envmanModels.EnvironmentItemModel{laneInputKey: "$" + laneInputEnvKey}, + envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}, + )) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + config, err := configBuilder.Generate(unknownProjectType, envmanModels.EnvironmentItemModel{fastlaneXcodeListTimeoutEnvKey: fastlaneXcodeListTimeoutEnvValue}) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + defaultConfigName: string(data), + }, nil +} + +// AutomationToolScannerInterface + +// SetDetectedProjectTypes ... +func (scanner *Scanner) SetDetectedProjectTypes(detectedProjectTypes []string) { + scanner.projectTypes = detectedProjectTypes +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test.go new file mode 100644 index 00000000..a6799719 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test.go @@ -0,0 +1,149 @@ +package fastlane + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilterFastFiles(t *testing.T) { + + t.Log(`Contains "Fastfile" files`) + { + fileList := []string{ + "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-android/fastlane/Fastfile", + "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-android/Fastfile", + "path/to/my/gradlew/file", + "path/to/my", + } + + files, err := FilterFastfiles(fileList) + require.NoError(t, err) + require.Equal(t, 2, len(files)) + + // Also sorts "Fastfile" files by path components length + require.Equal(t, "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-android/Fastfile", files[0]) + require.Equal(t, "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-android/fastlane/Fastfile", files[1]) + } + + t.Log(`Do not contains "Fastfile" file`) + { + fileList := []string{ + "path/to/my/gradlew/build.", + "path/to/my/gradle", + } + + files, err := FilterFastfiles(fileList) + require.NoError(t, err) + require.Equal(t, 0, len(files)) + } +} + +func TestInspectFastFileContent(t *testing.T) { + lines := []string{ + " test ", + " lane ", + ":xcode", + + " lane :xcode do", + "lane :deploy do", + " lane :unit_tests do |params|", + + " private_lane :post_to_slack do |options|", + " private_lane :verify_xcode_version do", + } + content := strings.Join(lines, "\n") + + expectedLanes := []string{ + "xcode", + "deploy", + "unit_tests", + } + + lanes, err := inspectFastfileContent(content) + require.NoError(t, err) + require.Equal(t, expectedLanes, lanes) + + t.Log("ios test") + { + lanes, err := inspectFastfileContent(iosTesFastfileContent) + require.NoError(t, err) + + expectedLanes := []string{ + "ios test", + } + + require.Equal(t, expectedLanes, lanes) + } + + t.Log("experimental ios test") + { + lanes, err := inspectFastfileContent(complexIosTestFastFileContent) + require.NoError(t, err) + + expectedLanes := []string{ + "ios analyze", + "ios testAndPushBeta", + "ios submitAndPushToMaster", + "ios verifyTestPlatforms", + "ios verify", + "ios bumpPatch", + "ios bumpMinor", + "ios bumpMajor", + "ios bump", + "ios bumpAndTagBeta", + "ios bumpAndTagRelease", + "ios default_changelog", + "ios beta", + "ios store", + "ios dev", + } + + require.Equal(t, expectedLanes, lanes) + } +} + +func TestFastlaneWorkDir(t *testing.T) { + t.Log("Fastfile's dir, if Fastfile is NOT in fastlane dir") + { + expected := "." + actual := WorkDir("Fastfile") + require.Equal(t, expected, actual) + } + + t.Log("fastlane dir's parent, if Fastfile is in fastlane dir") + { + expected := "." + actual := WorkDir("fastlane/Fastfile") + require.Equal(t, expected, actual) + } + + t.Log("Fastfile's dir, if Fastfile is NOT in fastlane dir") + { + expected := "test" + actual := WorkDir("test/Fastfile") + require.Equal(t, expected, actual) + } + + t.Log("fastlane dir's parent, if Fastfile is in fastlane dir") + { + expected := "test" + actual := WorkDir("test/fastlane/Fastfile") + require.Equal(t, expected, actual) + } + + t.Log("Fastfile's dir, if Fastfile is NOT in fastlane dir") + { + expected := "my/app/test" + actual := WorkDir("my/app/test/Fastfile") + require.Equal(t, expected, actual) + } + + t.Log("fastlane dir's parent, if Fastfile is in fastlane dir") + { + expected := "my/app/test" + actual := WorkDir("my/app/test/fastlane/Fastfile") + require.Equal(t, expected, actual) + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test_file_contents.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test_file_contents.go new file mode 100644 index 00000000..5b9115b1 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test_file_contents.go @@ -0,0 +1,310 @@ +package fastlane + +const iosTesFastfileContent = `fastlane_version "1.49.0" + +default_platform :ios + + +platform :ios do + before_all do + # ENV["SLACK_URL"] = "https://hooks.slack.com/services/..." + + end + + desc "Runs all tests, archives app" + lane :test do + + match( + username: ENV['FASTLANE_USERNAME'], + app_identifier: "io.bitrise.BitriseFastlaneSample", + readonly: true, + type: "appstore" + ) + + scan( + scheme: "BitriseFastlaneSample", + destination: "name=iPhone 5s,OS=latest", + output_directory: ENV['BITRISE_DEPLOY_DIR'] + ) + + gym( + scheme: "BitriseFastlaneSample", + configuration: "Release", + output_directory: ENV['BITRISE_DEPLOY_DIR'], + output_name: "BitriseFastlaneSample.ipa", + use_legacy_build_api: "true" + ) + + crashlytics( + crashlytics_path: "./Crashlytics.framework", + api_token: ENV['CRASHLYTICS_API_TOKEN'], + build_secret: ENV['CRASHLYTICS_BUILD_SECRET'], + ipa_path: "#{ENV['BITRISE_DEPLOY_DIR']}/BitriseFastlaneSample.ipa" + ) + + snapshot + end + + after_all do |lane| + + end + + error do |lane, exception| + + end +end` + +const complexIosTestFastFileContent = `default_platform :ios + +platform :ios do + before_all do + # Set project for commit_version_bump, which seems to get confused by projects in other folders + ENV['FL_BUILD_NUMBER_PROJECT'] = "Wikipedia.xcodeproj" + ensure_git_status_clean unless ENV['FL_NO_ENSURE_CLEAN'] + end + + desc "Runs linting (and eventually static analysis)" + lane :analyze do + xcodebuild( + workspace: "Wikipedia.xcworkspace", + scheme: "Wikipedia", + configuration: "Debug", + sdk: 'iphonesimulator', + destination: 'platform=iOS Simulator,OS=9.3,name=iPhone 6', + analyze: true + ) + end + + desc "Runs tests, version, tag, and push to the beta branch" + lane :testAndPushBeta do + verifyTestPlatforms + bumpAndTagBeta + badge(dark: true, shield: get_badge_version_string, shield_no_resize: true) + beta + end + + desc "Runs tests, version, tag, and push to the beta branch" + lane :submitAndPushToMaster do + bumpAndTagRelease + store + end + + desc "Runs tests on the primary platforms and configurations" + lane :verifyTestPlatforms do + verify( + sim_os: 8.4, + scheme: "WikipediaRTL" + ) + verify( + sim_os: 8.4 + ) + verify( + scheme: "WikipediaRTL" + ) + verify( + junit: true + ) + end + + desc "Runs unit tests, optionally generating a JUnit report." + lane :verify do |options| + scheme = options[:scheme] || 'Wikipedia' + sim_os = options[:sim_os] || '9.3' + destination = "platform=iOS Simulator,name=iPhone 6,OS=#{sim_os}" + opts = { + :scheme => scheme, + :workspace => 'Wikipedia.xcworkspace', + :configuration => 'Debug', + :destination => destination, + :buildlog_path => './build', + :output_directory => './build/reports', + :output_style => 'basic', + :code_coverage => true, + :xcargs => "TRAVIS=#{ENV["TRAVIS"]}" + } + opts[:output_types] = options[:junit] ? "junit" : "" + scan(opts) + end + + desc "Increment the app version patch" + lane :bumpPatch do + increment_version_number( + bump_type: "patch" + ) + end + + desc "Increment the app version minor" + lane :bumpMinor do + increment_version_number( + bump_type: "minor" + ) + end + + desc "Increment the app version major" + lane :bumpMajor do + increment_version_number( + bump_type: "major" + ) + end + + desc "Increment the app's build number without committing the changes. Returns a string of the new, bumped version." + lane :bump do |options| + opt_build_num = options[:build_number] || ENV['BUILD_NUMBER'] + if opt_build_num then + increment_build_number(build_number: opt_build_num.to_i) + else + increment_build_number(build_number: get_build_number) + end + get_version_number + end + + desc "Increment the app's beta build number, add a tag, and push to the beta branch." + lane :bumpAndTagBeta do |options| + sh "git fetch" + sh "git checkout develop" + sh "git pull" + sh "git checkout beta" + sh "git merge develop" + + increment_build_number + + new_version = get_version_number + commit_version_bump + push_to_git_remote( + local_branch: 'beta', # optional, aliased by 'branch', default: 'master' + remote_branch: 'beta', # optional, default is set to local_branch + ) + + tag_name = "betas/#{new_version}" + add_git_tag(tag: tag_name) + sh "git push origin --tags" + + end + + desc "Increment the app's build number, add a tag, and push to the master branch." + lane :bumpAndTagRelease do |options| + sh "git fetch" + sh "git checkout release" + sh "git pull" + sh "git checkout master" + sh "git merge release" + + increment_build_number(build_number: get_release_build_number) + + new_version = get_version_number + commit_version_bump + push_to_git_remote( + local_branch: 'master', # optional, aliased by 'branch', default: 'master' + remote_branch: 'master', # optional, default is set to local_branch + ) + + tag_name = "releases/#{new_version}" + add_git_tag(tag: tag_name) + sh "git push origin --tags" + + end + + + desc "Returns a default changelog." + lane :default_changelog do + changelog = changelog_from_git_commits( + between: [ENV['GIT_PREVIOUS_SUCCESSFUL_COMMIT'] || "HEAD^^^^^", "HEAD"], + pretty: "- %s" + ) + # HAX: strip emoji from changelog + changelog = changelog.sub(/[\u{1F300}-\u{1F6FF}]/, '') + Actions.lane_context[SharedValues::FL_CHANGELOG] = changelog + puts changelog + changelog + end + + desc "Submit a new **Wikipedia Beta** build to Apple TestFlight for internal testing." + lane :beta do + + sigh( + adhoc: false, + force: true + ) + + gym( + configuration: "Beta", + scheme: "Wikipedia Beta" + ) + + # changelog = default_changelog + + hockey_beta_id = ENV["HOCKEY_BETA"] + raise "Must specifiy HockeyApp public identifier." unless hockey_beta_id.length > 0 + + hockey( + public_identifier: hockey_beta_id, + # notes: changelog, + notify: '0', # Means do not notify + status: '1', # Means do not make available for download + ) + + pilot( + skip_submission: false, + distribute_external: false + ) + + end + + desc "Submit a new App Store release candidate Apple TestFlight for internal testing." + lane :store do + sigh( + adhoc: false, + force: true + ) + + gym( + configuration: "Release", + scheme: "Wikipedia" + ) + + hockey_prod_id = ENV["HOCKEY_PRODUCTION"] + raise "Must specifiy HockeyApp public identifier." unless hockey_prod_id.length > 0 + + hockey( + public_identifier: hockey_prod_id, + notify: '0', # Means do not notify + status: '1', # Means do not make available for download + ) + + pilot( + skip_submission: false, + distribute_external: false + ) + + end + + + desc "Upload a developer build to Hockey." + lane :dev do + sigh( + adhoc: true, + # Fastlane has issues forcing AdHoc profiles + force: false + ) + + # force iTunes file sharing to be enabled (normally disabled for release builds) + ENV['WMF_FORCE_ITUNES_FILE_SHARING'] = '1' + # force debug menu to be shown + ENV['WMF_FORCE_DEBUG_MENU'] = '1' + + gym( + configuration: "AdHoc", + scheme: "Wikipedia AdHoc", + # both of these flags are required for ad hoc + export_method: 'ad-hoc', + use_legacy_build_api: true + ) + + hockey( + notes: default_changelog, + notify: '2', # Notify all testers + status: '2', # Make available for download + release_type: '2' # 'alpha' release type + ) + end +end` diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/utility.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/utility.go new file mode 100644 index 00000000..74f11d7d --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/utility.go @@ -0,0 +1,104 @@ +package fastlane + +import ( + "bufio" + "path/filepath" + "regexp" + "strings" + + "github.com/bitrise-core/bitrise-init/utility" + "github.com/bitrise-io/go-utils/fileutil" +) + +const ( + fastfileBasePath = "Fastfile" +) + +// FilterFastfiles ... +func FilterFastfiles(fileList []string) ([]string, error) { + allowFastfileBaseFilter := utility.BaseFilter(fastfileBasePath, true) + fastfiles, err := utility.FilterPaths(fileList, allowFastfileBaseFilter) + if err != nil { + return []string{}, err + } + + return utility.SortPathsByComponents(fastfiles) +} + +func inspectFastfileContent(content string) ([]string, error) { + commonLanes := []string{} + laneMap := map[string][]string{} + + // platform :ios do ... + platformSectionStartRegexp := regexp.MustCompile(`platform\s+:(?P.*)\s+do`) + platformSectionEndPattern := "end" + platform := "" + + // lane :test_and_snapshot do + laneRegexp := regexp.MustCompile(`^[\s]*lane\s+:(?P.*)\s+do`) + + reader := strings.NewReader(content) + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := strings.TrimRight(scanner.Text(), " ") + + if platform != "" && line == platformSectionEndPattern { + platform = "" + continue + } + + if platform == "" { + if match := platformSectionStartRegexp.FindStringSubmatch(line); len(match) == 2 { + platform = match[1] + continue + } + } + + if match := laneRegexp.FindStringSubmatch(line); len(match) == 2 { + lane := match[1] + + if platform != "" { + lanes, found := laneMap[platform] + if !found { + lanes = []string{} + } + lanes = append(lanes, lane) + laneMap[platform] = lanes + } else { + commonLanes = append(commonLanes, lane) + } + } + } + if err := scanner.Err(); err != nil { + return []string{}, err + } + + lanes := commonLanes + for platform, platformLanes := range laneMap { + for _, lane := range platformLanes { + lanes = append(lanes, platform+" "+lane) + } + } + + return lanes, nil +} + +// InspectFastfile ... +func InspectFastfile(fastFile string) ([]string, error) { + content, err := fileutil.ReadStringFromFile(fastFile) + if err != nil { + return []string{}, err + } + + return inspectFastfileContent(content) +} + +// WorkDir ... +func WorkDir(fastfilePth string) string { + dirPth := filepath.Dir(fastfilePth) + dirName := filepath.Base(dirPth) + if dirName == "fastlane" { + return filepath.Dir(dirPth) + } + return dirPth +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/flutter/flutter.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/flutter/flutter.go new file mode 100644 index 00000000..462c4b9e --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/flutter/flutter.go @@ -0,0 +1,471 @@ +package flutter + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/bitrise-io/go-utils/pathutil" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/scanners/android" + "github.com/bitrise-core/bitrise-init/scanners/ios" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-core/bitrise-init/utility" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-tools/xcode-project/xcworkspace" + yaml "gopkg.in/yaml.v2" +) + +const ( + scannerName = "flutter" + configName = "flutter-config" + projectLocationInputKey = "project_location" + platformInputKey = "platform" + defaultIOSConfiguration = "Release" + projectLocationInputEnvKey = "BITRISE_FLUTTER_PROJECT_LOCATION" + projectLocationInputTitle = "Project Location" + projectTypeInputEnvKey = "BITRISE_FLUTTER_PROJECT_TYPE" + projectTypeInputTitle = "Project Type" + testsInputTitle = "Run tests found in the project" + platformInputTitle = "Platform" +) + +var ( + projectTypes = []string{ + "app", + "plugin", + "package", + } + platforms = []string{ + "none", + "android", + "ios", + "both", + } +) + +//------------------ +// ScannerInterface +//------------------ + +// Scanner ... +type Scanner struct { + projects []project +} + +type project struct { + path string + xcodeProjectPaths map[string][]string + hasTest bool + hasIosProject bool + hasAndroidProject bool +} + +type pubspec struct { + Name string `yaml:"name"` +} + +// NewScanner ... +func NewScanner() *Scanner { + return &Scanner{} +} + +// Name ... +func (Scanner) Name() string { + return scannerName +} + +func findProjectLocations(searchDir string) ([]string, error) { + fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) + if err != nil { + return nil, err + } + + filters := []utility.FilterFunc{ + utility.BaseFilter("pubspec.yaml", true), + } + + paths, err := utility.FilterPaths(fileList, filters...) + if err != nil { + return nil, err + } + + for i, path := range paths { + paths[i] = filepath.Dir(path) + } + + return paths, nil +} + +func findWorkspaceLocations(projectLocation string) ([]string, error) { + fileList, err := utility.ListPathInDirSortedByComponents(projectLocation, true) + if err != nil { + return nil, err + } + + for i, file := range fileList { + fileList[i] = filepath.Join(projectLocation, file) + } + + filters := []utility.FilterFunc{ + ios.AllowXCWorkspaceExtFilter, + ios.AllowIsDirectoryFilter, + ios.ForbidEmbeddedWorkspaceRegexpFilter, + ios.ForbidGitDirComponentFilter, + ios.ForbidPodsDirComponentFilter, + ios.ForbidCarthageDirComponentFilter, + ios.ForbidFramworkComponentWithExtensionFilter, + ios.ForbidCordovaLibDirComponentFilter, + ios.ForbidNodeModulesComponentFilter, + } + + return utility.FilterPaths(fileList, filters...) +} + +// DetectPlatform ... +func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { + log.TInfof("Search for project(s)") + projectLocations, err := findProjectLocations(searchDir) + if err != nil { + return false, err + } + + log.TPrintf("Paths containing pubspec.yaml(%d):", len(projectLocations)) + for _, p := range projectLocations { + log.TPrintf("- %s", p) + } + log.TPrintf("") + + log.TInfof("Fetching pubspec.yaml files") +projects: + for _, projectLocation := range projectLocations { + var proj project + + pubspecPath := filepath.Join(projectLocation, "pubspec.yaml") + pubspecFile, err := os.Open(pubspecPath) + if err != nil { + log.TErrorf("Failed to open pubspec.yaml file at: %s, error: %s", pubspecPath, err) + return false, err + } + + var ps pubspec + if err := yaml.NewDecoder(pubspecFile).Decode(&ps); err != nil { + log.TErrorf("Failed to decode yaml pubspec.yaml file at: %s, error: %s", pubspecPath, err) + return false, err + } + + testsDirPath := filepath.Join(projectLocation, "test") + if exists, err := pathutil.IsDirExists(testsDirPath); err == nil && exists { + if files, err := ioutil.ReadDir(testsDirPath); err == nil && len(files) > 0 { + for _, file := range files { + if strings.HasSuffix(file.Name(), "_test.dart") { + proj.hasTest = true + break + } + } + } + } + + iosProjPath := filepath.Join(projectLocation, "ios", "Runner.xcworkspace") + if exists, err := pathutil.IsPathExists(iosProjPath); err == nil && exists { + proj.hasIosProject = true + } + + androidProjPath := filepath.Join(projectLocation, "android", "build.gradle") + if exists, err := pathutil.IsPathExists(androidProjPath); err == nil && exists { + proj.hasAndroidProject = true + } + + log.TPrintf("- Project name: %s", ps.Name) + log.TPrintf(" Path: %s", projectLocation) + log.TPrintf(" HasTest: %t", proj.hasTest) + log.TPrintf(" HasAndroidProject: %t", proj.hasAndroidProject) + log.TPrintf(" HasIosProject: %t", proj.hasIosProject) + + proj.path = projectLocation + + if proj.hasIosProject { + if workspaceLocations, err := findWorkspaceLocations(filepath.Join(projectLocation, "ios")); err != nil { + log.TWarnf("Failed to check path at: %s, error: %s", filepath.Join(projectLocation, "ios"), err) + } else { + log.TPrintf(" XCWorkspaces(%d):", len(workspaceLocations)) + + for _, workspaceLocation := range workspaceLocations { + log.TPrintf(" Path: %s", workspaceLocation) + ws, err := xcworkspace.Open(workspaceLocation) + if err != nil { + continue projects + } + schemeMap, err := ws.Schemes() + if err != nil { + continue projects + } + + proj.xcodeProjectPaths = map[string][]string{} + + for _, schemes := range schemeMap { + if len(schemes) > 0 { + log.TPrintf(" Schemes(%d):", len(schemes)) + } + for _, scheme := range schemes { + log.TPrintf(" - %s", scheme.Name) + proj.xcodeProjectPaths[workspaceLocation] = append(proj.xcodeProjectPaths[workspaceLocation], scheme.Name) + } + } + } + } + } + + scanner.projects = append(scanner.projects, proj) + } + + if len(scanner.projects) == 0 { + return false, nil + } + + return true, nil +} + +// ExcludedScannerNames ... +func (Scanner) ExcludedScannerNames() []string { + return []string{ + string(ios.XcodeProjectTypeIOS), + android.ScannerName, + } +} + +// Options ... +func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { + flutterProjectLocationOption := models.NewOption(projectLocationInputTitle, projectLocationInputEnvKey) + + for _, project := range scanner.projects { + if project.hasTest { + flutterProjectHasTestOption := models.NewOption(testsInputTitle, "") + flutterProjectLocationOption.AddOption(project.path, flutterProjectHasTestOption) + + for _, v := range []string{"yes", "no"} { + cfg := configName + if v == "yes" { + cfg += "-test" + } + + if project.hasIosProject || project.hasAndroidProject { + if project.hasIosProject { + projectPathOption := models.NewOption(ios.ProjectPathInputTitle, ios.ProjectPathInputEnvKey) + flutterProjectHasTestOption.AddOption(v, projectPathOption) + + for xcodeWorkspacePath, schemes := range project.xcodeProjectPaths { + schemeOption := models.NewOption(ios.SchemeInputTitle, ios.SchemeInputEnvKey) + projectPathOption.AddOption(xcodeWorkspacePath, schemeOption) + + for _, scheme := range schemes { + exportMethodOption := models.NewOption(ios.IosExportMethodInputTitle, ios.ExportMethodInputEnvKey) + schemeOption.AddOption(scheme, exportMethodOption) + + for _, exportMethod := range ios.IosExportMethods { + configOption := models.NewConfigOption(cfg + "-app-" + getBuildablePlatform(project.hasAndroidProject, project.hasIosProject)) + exportMethodOption.AddConfig(exportMethod, configOption) + } + } + } + } else { + configOption := models.NewConfigOption(cfg + "-app-" + getBuildablePlatform(project.hasAndroidProject, project.hasIosProject)) + flutterProjectHasTestOption.AddOption(v, configOption) + } + } else { + configOption := models.NewConfigOption(cfg) + flutterProjectHasTestOption.AddOption(v, configOption) + } + } + } else { + cfg := configName + + if project.hasIosProject || project.hasAndroidProject { + if project.hasIosProject { + projectPathOption := models.NewOption(ios.ProjectPathInputTitle, ios.ProjectPathInputEnvKey) + flutterProjectLocationOption.AddOption(project.path, projectPathOption) + + for xcodeWorkspacePath, schemes := range project.xcodeProjectPaths { + schemeOption := models.NewOption(ios.SchemeInputTitle, ios.SchemeInputEnvKey) + projectPathOption.AddOption(xcodeWorkspacePath, schemeOption) + + for _, scheme := range schemes { + exportMethodOption := models.NewOption(ios.IosExportMethodInputTitle, ios.ExportMethodInputEnvKey) + schemeOption.AddOption(scheme, exportMethodOption) + + for _, exportMethod := range ios.IosExportMethods { + configOption := models.NewConfigOption(cfg + "-app-" + getBuildablePlatform(project.hasAndroidProject, project.hasIosProject)) + exportMethodOption.AddConfig(exportMethod, configOption) + } + } + } + } else { + configOption := models.NewConfigOption(cfg + "-app-" + getBuildablePlatform(project.hasAndroidProject, project.hasIosProject)) + flutterProjectLocationOption.AddOption(project.path, configOption) + } + } else { + configOption := models.NewConfigOption(cfg) + flutterProjectLocationOption.AddOption(project.path, configOption) + } + } + } + + return *flutterProjectLocationOption, nil, nil +} + +func getBuildablePlatform(hasAndroidProject, hasIosProject bool) string { + switch { + case hasAndroidProject && !hasIosProject: + return "android" + case !hasAndroidProject && hasIosProject: + return "ios" + default: + return "both" + } +} + +// DefaultOptions ... +func (Scanner) DefaultOptions() models.OptionNode { + flutterProjectLocationOption := models.NewOption(projectLocationInputTitle, projectLocationInputEnvKey) + + flutterProjectHasTestOption := models.NewOption(testsInputTitle, "") + flutterProjectLocationOption.AddOption("_", flutterProjectHasTestOption) + + for _, v := range []string{"yes", "no"} { + cfg := configName + if v == "yes" { + cfg += "-test" + } + flutterPlatformOption := models.NewOption(platformInputTitle, "") + flutterProjectHasTestOption.AddOption(v, flutterPlatformOption) + + for _, platform := range platforms { + if platform != "none" { + if platform != "android" { + projectPathOption := models.NewOption(ios.ProjectPathInputTitle, ios.ProjectPathInputEnvKey) + flutterPlatformOption.AddOption(platform, projectPathOption) + + schemeOption := models.NewOption(ios.SchemeInputTitle, ios.SchemeInputEnvKey) + projectPathOption.AddOption("_", schemeOption) + + exportMethodOption := models.NewOption(ios.IosExportMethodInputTitle, ios.ExportMethodInputEnvKey) + schemeOption.AddOption("_", exportMethodOption) + + for _, exportMethod := range ios.IosExportMethods { + configOption := models.NewConfigOption(cfg + "-app-" + platform) + exportMethodOption.AddConfig(exportMethod, configOption) + } + } else { + configOption := models.NewConfigOption(cfg + "-app-" + platform) + flutterPlatformOption.AddConfig(platform, configOption) + } + } else { + configOption := models.NewConfigOption(cfg) + flutterPlatformOption.AddConfig(platform, configOption) + } + } + } + + return *flutterProjectLocationOption +} + +// Configs ... +func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { + return scanner.DefaultConfigs() +} + +// DefaultConfigs ... +func (scanner Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { + configs := models.BitriseConfigMap{} + + for _, variant := range []struct { + configID string + test bool + deploy bool + platform string + }{ + {test: false, deploy: false, configID: configName}, + {test: true, deploy: false, configID: configName + "-test"}, + {test: false, deploy: true, platform: "both", configID: configName + "-app-both"}, + {test: true, deploy: true, platform: "both", configID: configName + "-test-app-both"}, + {test: false, deploy: true, platform: "android", configID: configName + "-app-android"}, + {test: true, deploy: true, platform: "android", configID: configName + "-test-app-android"}, + {test: false, deploy: true, platform: "ios", configID: configName + "-app-ios"}, + {test: true, deploy: true, platform: "ios", configID: configName + "-test-app-ios"}, + } { + configBuilder := models.NewDefaultConfigBuilder() + + // primary + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.FlutterInstallStepListItem()) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.FlutterAnalyzeStepListItem( + envmanModels.EnvironmentItemModel{projectLocationInputKey: "$" + projectLocationInputEnvKey}, + )) + + if variant.test { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.FlutterTestStepListItem( + envmanModels.EnvironmentItemModel{projectLocationInputKey: "$" + projectLocationInputEnvKey}, + )) + } + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + // deploy + + if variant.deploy { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) + + if variant.platform != "android" { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + } + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.FlutterInstallStepListItem()) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.FlutterAnalyzeStepListItem( + envmanModels.EnvironmentItemModel{projectLocationInputKey: "$" + projectLocationInputEnvKey}, + )) + + if variant.test { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.FlutterTestStepListItem( + envmanModels.EnvironmentItemModel{projectLocationInputKey: "$" + projectLocationInputEnvKey}, + )) + } + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.FlutterBuildStepListItem( + envmanModels.EnvironmentItemModel{projectLocationInputKey: "$" + projectLocationInputEnvKey}, + envmanModels.EnvironmentItemModel{platformInputKey: variant.platform}, + )) + + if variant.platform != "android" { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem( + envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: defaultIOSConfiguration}, + )) + } + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) + } + + config, err := configBuilder.Generate(scannerName) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + configs[variant.configID] = string(data) + } + + return configs, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ionic/ionic.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ionic/ionic.go new file mode 100644 index 00000000..a1e5a0c1 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ionic/ionic.go @@ -0,0 +1,406 @@ +package ionic + +import ( + "fmt" + "path/filepath" + "strings" + + yaml "gopkg.in/yaml.v2" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/scanners/android" + "github.com/bitrise-core/bitrise-init/scanners/cordova" + "github.com/bitrise-core/bitrise-init/scanners/ios" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-core/bitrise-init/utility" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" +) + +const scannerName = "ionic" + +const ( + configName = "ionic-config" + defaultConfigName = "default-ionic-config" +) + +// Step Inputs +const ( + workDirInputKey = "workdir" + workDirInputTitle = "Directory of Ionic Config.xml" + workDirInputEnvKey = "IONIC_WORK_DIR" +) + +const ( + platformInputKey = "platform" + platformInputTitle = "Platform to use in ionic-cli commands" + platformInputEnvKey = "IONIC_PLATFORM" +) + +const ( + targetInputKey = "target" + targetEmulator = "emulator" +) + +//------------------ +// ScannerInterface +//------------------ + +// Scanner ... +type Scanner struct { + cordovaConfigPth string + relCordovaConfigDir string + searchDir string + hasKarmaJasmineTest bool + hasJasmineTest bool +} + +// NewScanner ... +func NewScanner() *Scanner { + return &Scanner{} +} + +// Name ... +func (Scanner) Name() string { + return scannerName +} + +// DetectPlatform ... +func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { + fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) + if err != nil { + return false, fmt.Errorf("failed to search for files in (%s), error: %s", searchDir, err) + } + + // Search for config.xml file + log.TInfof("Searching for config.xml file") + + configXMLPth, err := cordova.FilterRootConfigXMLFile(fileList) + if err != nil { + return false, fmt.Errorf("failed to search for config.xml file, error: %s", err) + } + + log.TPrintf("config.xml: %s", configXMLPth) + + if configXMLPth == "" { + log.TPrintf("platform not detected") + return false, nil + } + + widget, err := cordova.ParseConfigXML(configXMLPth) + if err != nil { + log.TPrintf("can not parse config.xml as a Cordova widget, error: %s", err) + log.TPrintf("platform not detected") + return false, nil + } + + // ensure it is a cordova widget + if !strings.Contains(widget.XMLNSCDV, "cordova.apache.org") { + log.TPrintf("config.xml propert: xmlns:cdv does not contain cordova.apache.org") + log.TPrintf("platform not detected") + return false, nil + } + + // ensure it is an ionic project + projectBaseDir := filepath.Dir(configXMLPth) + + ionicProjectExist, err := pathutil.IsPathExists(filepath.Join(projectBaseDir, "ionic.project")) + if err != nil { + return false, fmt.Errorf("failed to check if project is an ionic project, error: %s", err) + } + + ionicConfigExist, err := pathutil.IsPathExists(filepath.Join(projectBaseDir, "ionic.config.json")) + if err != nil { + return false, fmt.Errorf("failed to check if project is an ionic project, error: %s", err) + } + + if !ionicProjectExist && !ionicConfigExist { + log.Printf("no ionic.project file nor ionic.config.json found, seems to be a cordova project") + return false, nil + } + + log.TSuccessf("Platform detected") + + scanner.cordovaConfigPth = configXMLPth + scanner.searchDir = searchDir + + return true, nil +} + +// ExcludedScannerNames ... +func (Scanner) ExcludedScannerNames() []string { + return []string{ + string(ios.XcodeProjectTypeIOS), + string(ios.XcodeProjectTypeMacOS), + cordova.ScannerName, + android.ScannerName, + } +} + +// Options ... +func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { + warnings := models.Warnings{} + projectRootDir := filepath.Dir(scanner.cordovaConfigPth) + + packagesJSONPth := filepath.Join(projectRootDir, "package.json") + packages, err := utility.ParsePackagesJSON(packagesJSONPth) + if err != nil { + return models.OptionNode{}, warnings, err + } + + // Search for karma/jasmine tests + log.TPrintf("Searching for karma/jasmine test") + + karmaTestDetected := false + + karmaJasmineDependencyFound := false + for dependency := range packages.Dependencies { + if strings.Contains(dependency, "karma-jasmine") { + karmaJasmineDependencyFound = true + } + } + if !karmaJasmineDependencyFound { + for dependency := range packages.DevDependencies { + if strings.Contains(dependency, "karma-jasmine") { + karmaJasmineDependencyFound = true + } + } + } + log.TPrintf("karma-jasmine dependency found: %v", karmaJasmineDependencyFound) + + if karmaJasmineDependencyFound { + karmaConfigJSONPth := filepath.Join(projectRootDir, "karma.conf.js") + if exist, err := pathutil.IsPathExists(karmaConfigJSONPth); err != nil { + return models.OptionNode{}, warnings, err + } else if exist { + karmaTestDetected = true + } + } + log.TPrintf("karma.conf.js found: %v", karmaTestDetected) + + scanner.hasKarmaJasmineTest = karmaTestDetected + // --- + + // Search for jasmine tests + jasminTestDetected := false + + if !karmaTestDetected { + log.TPrintf("Searching for jasmine test") + + jasmineDependencyFound := false + for dependency := range packages.Dependencies { + if strings.Contains(dependency, "jasmine") { + jasmineDependencyFound = true + break + } + } + if !jasmineDependencyFound { + for dependency := range packages.DevDependencies { + if strings.Contains(dependency, "jasmine") { + jasmineDependencyFound = true + break + } + } + } + log.TPrintf("jasmine dependency found: %v", jasmineDependencyFound) + + if jasmineDependencyFound { + jasmineConfigJSONPth := filepath.Join(projectRootDir, "spec", "support", "jasmine.json") + if exist, err := pathutil.IsPathExists(jasmineConfigJSONPth); err != nil { + return models.OptionNode{}, warnings, err + } else if exist { + jasminTestDetected = true + } + } + + log.TPrintf("jasmine.json found: %v", jasminTestDetected) + + scanner.hasJasmineTest = jasminTestDetected + } + // --- + + // Get relative config.xml dir + cordovaConfigDir := filepath.Dir(scanner.cordovaConfigPth) + relCordovaConfigDir, err := utility.RelPath(scanner.searchDir, cordovaConfigDir) + if err != nil { + return models.OptionNode{}, warnings, fmt.Errorf("Failed to get relative config.xml dir path, error: %s", err) + } + if relCordovaConfigDir == "." { + // config.xml placed in the search dir, no need to change-dir in the workflows + relCordovaConfigDir = "" + } + scanner.relCordovaConfigDir = relCordovaConfigDir + // --- + + // Options + var rootOption *models.OptionNode + + platforms := []string{"ios", "android", "ios,android"} + + if relCordovaConfigDir != "" { + rootOption = models.NewOption(workDirInputTitle, workDirInputEnvKey) + + projectTypeOption := models.NewOption(platformInputTitle, platformInputEnvKey) + rootOption.AddOption(relCordovaConfigDir, projectTypeOption) + + for _, platform := range platforms { + configOption := models.NewConfigOption(configName) + projectTypeOption.AddConfig(platform, configOption) + } + } else { + rootOption = models.NewOption(platformInputTitle, platformInputEnvKey) + + for _, platform := range platforms { + configOption := models.NewConfigOption(configName) + rootOption.AddConfig(platform, configOption) + } + } + // --- + + return *rootOption, warnings, nil +} + +// DefaultOptions ... +func (Scanner) DefaultOptions() models.OptionNode { + workDirOption := models.NewOption(workDirInputTitle, workDirInputEnvKey) + + projectTypeOption := models.NewOption(platformInputTitle, platformInputEnvKey) + workDirOption.AddOption("_", projectTypeOption) + + platforms := []string{ + "ios", + "android", + "ios,android", + } + for _, platform := range platforms { + configOption := models.NewConfigOption(defaultConfigName) + projectTypeOption.AddConfig(platform, configOption) + } + + return *workDirOption +} + +// Configs ... +func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + + workdirEnvList := []envmanModels.EnvironmentItemModel{} + if scanner.relCordovaConfigDir != "" { + workdirEnvList = append(workdirEnvList, envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}) + } + + if scanner.hasJasmineTest || scanner.hasKarmaJasmineTest { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + + // CI + if scanner.hasKarmaJasmineTest { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.KarmaJasmineTestRunnerStepListItem(workdirEnvList...)) + } else if scanner.hasJasmineTest { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.JasmineTestRunnerStepListItem(workdirEnvList...)) + } + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + // CD + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + + if scanner.hasKarmaJasmineTest { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.KarmaJasmineTestRunnerStepListItem(workdirEnvList...)) + } else if scanner.hasJasmineTest { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.JasmineTestRunnerStepListItem(workdirEnvList...)) + } + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.GenerateCordovaBuildConfigStepListItem()) + + ionicArchiveEnvs := []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{platformInputKey: "$" + platformInputEnvKey}, + envmanModels.EnvironmentItemModel{targetInputKey: targetEmulator}, + } + if scanner.relCordovaConfigDir != "" { + ionicArchiveEnvs = append(ionicArchiveEnvs, envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}) + } + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.IonicArchiveStepListItem(ionicArchiveEnvs...)) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) + + config, err := configBuilder.Generate(scannerName) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + configName: string(data), + }, nil + } + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.GenerateCordovaBuildConfigStepListItem()) + + ionicArchiveEnvs := []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{platformInputKey: "$" + platformInputEnvKey}, + envmanModels.EnvironmentItemModel{targetInputKey: targetEmulator}, + } + if scanner.relCordovaConfigDir != "" { + ionicArchiveEnvs = append(ionicArchiveEnvs, envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}) + } + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.IonicArchiveStepListItem(ionicArchiveEnvs...)) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + config, err := configBuilder.Generate(scannerName) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + configName: string(data), + }, nil +} + +// DefaultConfigs ... +func (Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem( + envmanModels.EnvironmentItemModel{"command": "install"}, + envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey})) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.GenerateCordovaBuildConfigStepListItem()) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.IonicArchiveStepListItem( + envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}, + envmanModels.EnvironmentItemModel{platformInputKey: "$" + platformInputEnvKey}, + envmanModels.EnvironmentItemModel{targetInputKey: targetEmulator})) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + config, err := configBuilder.Generate(scannerName) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + defaultConfigName: string(data), + }, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/gemutil.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/gemutil.go new file mode 100644 index 00000000..0b1cbdaa --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/gemutil.go @@ -0,0 +1,50 @@ +package ios + +import ( + "fmt" + "regexp" + "strings" + + "github.com/bitrise-io/go-utils/fileutil" +) + +// GemVersionFromGemfileLockContent ... +func GemVersionFromGemfileLockContent(gem, content string) string { + relevantLines := []string{} + lines := strings.Split(content, "\n") + + specsStart := false + for _, line := range lines { + if strings.Contains(line, "specs:") { + specsStart = true + } + + trimmed := strings.Trim(line, " ") + if trimmed == "" { + specsStart = false + } + + if specsStart { + relevantLines = append(relevantLines, line) + } + } + + exp := regexp.MustCompile(fmt.Sprintf(`%s \((.+)\)`, gem)) + for _, line := range relevantLines { + match := exp.FindStringSubmatch(line) + if len(match) == 2 { + return match[1] + } + } + + return "" +} + +// GemVersionFromGemfileLock ... +func GemVersionFromGemfileLock(gem, gemfileLockPth string) (string, error) { + content, err := fileutil.ReadStringFromFile(gemfileLockPth) + if err != nil { + return "", err + } + return GemVersionFromGemfileLockContent(gem, content), nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/gemutil_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/gemutil_test.go new file mode 100644 index 00000000..a01294c7 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/gemutil_test.go @@ -0,0 +1,210 @@ +package ios + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGemVersionFromGemfileLockContent(t *testing.T) { + version := GemVersionFromGemfileLockContent("fastlane", gemfileLockContent) + require.Equal(t, "2.13.0", version) +} + +const gemfileLockContent = `GIT + remote: git://xyz.git + revision: xyz + branch: patch-1 + specs: + fastlane-xyz (1.0.2) +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (2.3.5) + activesupport (4.2.7.1) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + addressable (2.5.0) + public_suffix (~> 2.0, >= 2.0.2) + babosa (1.0.2) + claide (1.0.1) + cocoapods (1.1.1) + activesupport (>= 4.0.2, < 5) + claide (>= 1.0.1, < 2.0) + cocoapods-core (= 1.1.1) + cocoapods-deintegrate (>= 1.0.1, < 2.0) + cocoapods-downloader (>= 1.1.2, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-stats (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.1.1, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored (~> 1.2) + escape (~> 0.0.4) + fourflusher (~> 2.0.1) + gh_inspector (~> 1.0) + molinillo (~> 0.5.1) + nap (~> 1.0) + xcodeproj (>= 1.3.3, < 2.0) + cocoapods-core (1.1.1) + activesupport (>= 4.0.2, < 5) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + cocoapods-deintegrate (1.0.1) + cocoapods-downloader (1.1.3) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.0) + cocoapods-stats (1.0.0) + cocoapods-trunk (1.1.2) + nap (>= 0.8, < 2.0) + netrc (= 0.7.8) + cocoapods-try (1.1.0) + colored (1.2) + commander (4.4.3) + highline (~> 1.7.2) + domain_name (0.5.20161129) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.2.0) + escape (0.0.4) + excon (0.54.0) + faraday (0.11.0) + multipart-post (>= 1.2, < 3) + faraday-cookie_jar (0.0.6) + faraday (>= 0.7.4) + http-cookie (~> 1.0.0) + faraday_middleware (0.11.0.1) + faraday (>= 0.7.4, < 1.0) + fastimage (2.0.1) + addressable (~> 2) + fastlane (2.13.0) + activesupport (< 5) + addressable (>= 2.3, < 3.0.0) + babosa (>= 1.0.2, < 2.0.0) + bundler (>= 1.12.0, < 2.0.0) + colored + commander (>= 4.4.0, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + excon (>= 0.45.0, < 1.0.0) + faraday (~> 0.9) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 0.9) + fastimage (>= 1.6) + gh_inspector (>= 1.0.1, < 2.0.0) + google-api-client (~> 0.9.2) + highline (>= 1.7.2, < 2.0.0) + json (< 3.0.0) + mini_magick (~> 4.5.1) + multi_json + multi_xml (~> 0.5) + multipart-post (~> 2.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 1.1.0, < 2.0.0) + security (= 0.1.3) + slack-notifier (>= 1.3, < 2.0.0) + terminal-notifier (>= 1.6.2, < 2.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 0.20, < 2.0.0) + xcpretty (>= 0.2.4, < 1.0.0) + xcpretty-travis-formatter (>= 0.0.3) + fastlane-plugin-tpa (1.1.0) + fourflusher (2.0.1) + fuzzy_match (2.0.4) + gh_inspector (1.0.3) + google-api-client (0.9.26) + addressable (~> 2.3) + googleauth (~> 0.5) + httpclient (~> 2.7) + hurley (~> 0.1) + memoist (~> 0.11) + mime-types (>= 1.6) + representable (~> 2.3.0) + retriable (~> 2.0) + googleauth (0.5.1) + faraday (~> 0.9) + jwt (~> 1.4) + logging (~> 2.0) + memoist (~> 0.12) + multi_json (~> 1.11) + os (~> 0.9) + signet (~> 0.7) + highline (1.7.8) + http-cookie (1.0.3) + domain_name (~> 0.5) + httpclient (2.8.3) + hurley (0.2) + i18n (0.8.0) + json (1.8.6) + jwt (1.5.6) + little-plugger (1.1.4) + logging (2.1.0) + little-plugger (~> 1.1) + multi_json (~> 1.10) + memoist (0.15.0) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_magick (4.5.1) + minitest (5.10.1) + molinillo (0.5.5) + multi_json (1.12.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + nanaimo (0.2.3) + nap (1.1.0) + netrc (0.7.8) + os (0.9.6) + plist (3.2.0) + public_suffix (2.0.5) + representable (2.3.0) + uber (~> 0.0.7) + retriable (2.1.0) + rouge (1.11.1) + rubyzip (1.2.0) + security (0.1.3) + signet (0.7.3) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (~> 1.5) + multi_json (~> 1.10) + slack-notifier (1.5.1) + terminal-notifier (1.7.1) + terminal-table (1.7.3) + unicode-display_width (~> 1.1.1) + thread_safe (0.3.5) + tzinfo (1.2.2) + thread_safe (~> 0.1) + uber (0.0.15) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.2) + unicode-display_width (1.1.3) + word_wrap (1.0.0) + xcode-install (2.1.1) + claide (>= 0.9.1, < 1.1.0) + fastlane (>= 2.1.0, < 3.0.0) + xcodeproj (1.4.2) + CFPropertyList (~> 2.3.3) + activesupport (>= 3) + claide (>= 1.0.1, < 2.0) + colored (~> 1.2) + nanaimo (~> 0.2.3) + xcpretty (0.2.4) +> 1.8) + xcpretty-travis-formatter (0.0.4) + xcpretty (~> 0.2, >= 0.0.7) +PLATFORMS + ruby +DEPENDENCIES + cocoapods (~> 1.1.0) + dotenv (~> 2.0) + fastlane (~> 2.0) + fastlane-plugin-prepare_build_resources! + fastlane-plugin-tpa (~> 1.1.0) + xcode-install +BUNDLED WITH + 1.13.6` diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/ios.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/ios.go new file mode 100644 index 00000000..87224736 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/ios.go @@ -0,0 +1,72 @@ +package ios + +import "github.com/bitrise-core/bitrise-init/models" + +//------------------ +// ScannerInterface +//------------------ + +// Scanner ... +type Scanner struct { + SearchDir string + ConfigDescriptors []ConfigDescriptor +} + +// NewScanner ... +func NewScanner() *Scanner { + return &Scanner{} +} + +// Name ... +func (Scanner) Name() string { + return string(XcodeProjectTypeIOS) +} + +// DetectPlatform ... +func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { + scanner.SearchDir = searchDir + + detected, err := Detect(XcodeProjectTypeIOS, searchDir) + if err != nil { + return false, err + } + + return detected, nil +} + +// ExcludedScannerNames ... +func (Scanner) ExcludedScannerNames() []string { + return []string{} +} + +// Options ... +func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { + options, configDescriptors, warnings, err := GenerateOptions(XcodeProjectTypeIOS, scanner.SearchDir) + if err != nil { + return models.OptionNode{}, warnings, err + } + + scanner.ConfigDescriptors = configDescriptors + + return options, warnings, nil +} + +// DefaultOptions ... +func (Scanner) DefaultOptions() models.OptionNode { + return GenerateDefaultOptions(XcodeProjectTypeIOS) +} + +// Configs ... +func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { + return GenerateConfig(XcodeProjectTypeIOS, scanner.ConfigDescriptors, true) +} + +// DefaultConfigs ... +func (Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { + return GenerateDefaultConfig(XcodeProjectTypeIOS, true) +} + +// GetProjectType returns the project_type property used in a bitrise config +func (Scanner) GetProjectType() string { + return string(XcodeProjectTypeIOS) +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/podfile.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/podfile.go new file mode 100644 index 00000000..1fad919b --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/podfile.go @@ -0,0 +1,306 @@ +package ios + +import ( + "errors" + "fmt" + "path/filepath" + "strings" + + "encoding/json" + + "github.com/bitrise-core/bitrise-init/utility" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-tools/go-xcode/xcodeproj" +) + +const podfileBase = "Podfile" + +// AllowPodfileBaseFilter ... +var AllowPodfileBaseFilter = utility.BaseFilter(podfileBase, true) + +func getTargetDefinitionProjectMap(podfilePth, cocoapodsVersion string) (map[string]string, error) { + gemfileCocoapodsVersion := "" + if cocoapodsVersion != "" { + gemfileCocoapodsVersion = fmt.Sprintf(`, '%s'`, cocoapodsVersion) + } + + gemfileContent := fmt.Sprintf(`source 'https://rubygems.org' +gem 'cocoapods-core'%s +gem 'json' +`, gemfileCocoapodsVersion) + + // returns target - project map, if xcodeproj defined in the Podfile + // return empty string if no xcodeproj defined in the Podfile + rubyScriptContent := `require 'cocoapods-core' +require 'json' + +begin + podfile_path = ENV['PODFILE_PATH'] + podfile = Pod::Podfile.from_file(podfile_path) + targets = podfile.target_definitions + + puts "#{{}.to_json}" unless targets + + target_project_map = {} + targets.each do |name, target_definition| + next unless target_definition.user_project_path + target_project_map[name] = target_definition.user_project_path + end + + puts "#{{ :data => target_project_map }.to_json}" +rescue => e + puts "#{{ :error => e.to_s }.to_json}" +end +` + + absPodfilePth, err := filepath.Abs(podfilePth) + if err != nil { + return map[string]string{}, fmt.Errorf("failed to expand path (%s), error: %s", podfilePth, err) + } + + envs := []string{fmt.Sprintf("PODFILE_PATH=%s", absPodfilePth)} + podfileDir := filepath.Dir(absPodfilePth) + + out, err := runRubyScriptForOutput(rubyScriptContent, gemfileContent, podfileDir, envs) + if err != nil { + return map[string]string{}, fmt.Errorf("ruby script failed, error: %s", err) + } + + if out == "" { + return map[string]string{}, nil + } + + type targetDefinitionOutputModel struct { + Data map[string]string + Error string + } + + var targetDefinitionOutput targetDefinitionOutputModel + if err := json.Unmarshal([]byte(out), &targetDefinitionOutput); err != nil { + return map[string]string{}, fmt.Errorf("failed to parse target definition output, error: %s", err) + } + + if targetDefinitionOutput.Error != "" { + return map[string]string{}, fmt.Errorf("failed to read target defintion map, error: %s", targetDefinitionOutput.Error) + } + + return targetDefinitionOutput.Data, nil +} + +func getUserDefinedProjectRelavtivePath(podfilePth, cocoapodsVersion string) (string, error) { + targetProjectMap, err := getTargetDefinitionProjectMap(podfilePth, cocoapodsVersion) + if err != nil { + return "", fmt.Errorf("failed to get target definition map, error: %s", err) + } + + for target, project := range targetProjectMap { + if target == "Pods" { + return project, nil + } + } + return "", nil +} + +func getUserDefinedWorkspaceRelativePath(podfilePth, cocoapodsVersion string) (string, error) { + gemfileCocoapodsVersion := "" + if cocoapodsVersion != "" { + gemfileCocoapodsVersion = fmt.Sprintf(`, '%s'`, cocoapodsVersion) + } + + gemfileContent := fmt.Sprintf(`source 'https://rubygems.org' +gem 'cocoapods-core'%s +gem 'json' +`, gemfileCocoapodsVersion) + + // returns WORKSPACE_NAME.xcworkspace if user defined a workspace name + // returns empty struct {}, if no user defined workspace name exists in Podfile + rubyScriptContent := `require 'cocoapods-core' +require 'json' + +begin + podfile_path = ENV['PODFILE_PATH'] + podfile = Pod::Podfile.from_file(podfile_path) + pth = podfile.workspace_path + puts "#{{ :data => pth }.to_json}" +rescue => e + puts "#{{ :error => e.to_s }.to_json}" +end +` + absPodfilePth, err := filepath.Abs(podfilePth) + if err != nil { + return "", fmt.Errorf("failed to expand path (%s), error: %s", podfilePth, err) + } + + envs := []string{fmt.Sprintf("PODFILE_PATH=%s", absPodfilePth)} + podfileDir := filepath.Dir(absPodfilePth) + + out, err := runRubyScriptForOutput(rubyScriptContent, gemfileContent, podfileDir, envs) + if err != nil { + return "", fmt.Errorf("ruby script failed, error: %s", err) + } + + if out == "" { + return "", nil + } + + type workspacePathOutputModel struct { + Data string + Error string + } + + var workspacePathOutput workspacePathOutputModel + if err := json.Unmarshal([]byte(out), &workspacePathOutput); err != nil { + return "", fmt.Errorf("failed to parse workspace path output, error: %s", err) + } + + if workspacePathOutput.Error != "" { + return "", fmt.Errorf("failed to readworkspace path, error: %s", workspacePathOutput.Error) + } + + return workspacePathOutput.Data, nil +} + +// GetWorkspaceProjectMap ... +// If one project exists in the Podfile's directory, workspace name will be the project's name. +// If more then one project exists in the Podfile's directory, root 'xcodeproj/project' property have to be defined in the Podfile. +// Root 'xcodeproj/project' property will be mapped to the default cocoapods target (Pods). +// If workspace property defined in the Podfile, it will override the workspace name. +func GetWorkspaceProjectMap(podfilePth string, projects []string) (map[string]string, error) { + podfileDir := filepath.Dir(podfilePth) + + cocoapodsVersion := "" + + podfileLockPth := filepath.Join(podfileDir, "Podfile.lock") + if exist, err := pathutil.IsPathExists(podfileLockPth); err != nil { + return map[string]string{}, fmt.Errorf("failed to check if Podfile.lock exist, error: %s", err) + } else if !exist { + podfileLockPth = filepath.Join(podfileDir, "podfile.lock") + if exist, err := pathutil.IsPathExists(podfileLockPth); err != nil { + return map[string]string{}, fmt.Errorf("failed to check if podfile.lock exist, error: %s", err) + } else if !exist { + podfileLockPth = "" + } + } + + if podfileLockPth != "" { + version, err := GemVersionFromGemfileLock("cocoapods", podfileLockPth) + if err != nil { + return map[string]string{}, fmt.Errorf("failed to read cocoapods version from %s, error: %s", podfileLockPth, err) + } + cocoapodsVersion = version + } + + // fix podfile quotation + podfileContent, err := fileutil.ReadStringFromFile(podfilePth) + if err != nil { + return map[string]string{}, fmt.Errorf("failed to read podfile (%s), error: %s", podfilePth, err) + } + + podfileContent = strings.Replace(podfileContent, `‘`, `'`, -1) + podfileContent = strings.Replace(podfileContent, `’`, `'`, -1) + podfileContent = strings.Replace(podfileContent, `“`, `"`, -1) + podfileContent = strings.Replace(podfileContent, `”`, `"`, -1) + + if err := fileutil.WriteStringToFile(podfilePth, podfileContent); err != nil { + return map[string]string{}, fmt.Errorf("failed to apply Podfile quotation fix, error: %s", err) + } + // ---- + + projectRelPth, err := getUserDefinedProjectRelavtivePath(podfilePth, cocoapodsVersion) + if err != nil { + return map[string]string{}, fmt.Errorf("failed to get user defined project path, error: %s", err) + } + + if projectRelPth == "" { + projects, err := utility.FilterPaths(projects, utility.InDirectoryFilter(podfileDir, true)) + if err != nil { + return map[string]string{}, fmt.Errorf("failed to filter projects, error: %s", err) + } + + if len(projects) == 0 { + return map[string]string{}, errors.New("failed to determin workspace - project mapping: no explicit project specified and no project found in the Podfile's directory") + } else if len(projects) > 1 { + return map[string]string{}, errors.New("failed to determin workspace - project mapping: no explicit project specified and more than one project found in the Podfile's directory") + } + + projectRelPth = filepath.Base(projects[0]) + } + projectPth := filepath.Join(podfileDir, projectRelPth) + + if exist, err := pathutil.IsPathExists(projectPth); err != nil { + return map[string]string{}, fmt.Errorf("failed to check if path (%s) exists, error: %s", projectPth, err) + } else if !exist { + return map[string]string{}, fmt.Errorf("project not found at: %s", projectPth) + } + + workspaceRelPth, err := getUserDefinedWorkspaceRelativePath(podfilePth, cocoapodsVersion) + if err != nil { + return map[string]string{}, fmt.Errorf("failed to get user defined workspace path, error: %s", err) + } + + if workspaceRelPth == "" { + projectName := filepath.Base(strings.TrimSuffix(projectPth, ".xcodeproj")) + workspaceRelPth = projectName + ".xcworkspace" + } + workspacePth := filepath.Join(podfileDir, workspaceRelPth) + + return map[string]string{ + workspacePth: projectPth, + }, nil +} + +// MergePodWorkspaceProjectMap ... +// Previously we separated standalone projects and workspaces. +// But pod workspace-project map may define workspace which is not in the repository, but will be created by `pod install`. +// Related project should be found in the standalone projects list. +// We will create this workspace model, join the related project and remove this project from standlone projects. +// If workspace is in the repository, both workspace and project should be find in the input lists. +func MergePodWorkspaceProjectMap(podWorkspaceProjectMap map[string]string, standaloneProjects []xcodeproj.ProjectModel, workspaces []xcodeproj.WorkspaceModel) ([]xcodeproj.ProjectModel, []xcodeproj.WorkspaceModel, error) { + mergedStandaloneProjects := []xcodeproj.ProjectModel{} + mergedWorkspaces := []xcodeproj.WorkspaceModel{} + + for podWorkspaceFile, podProjectFile := range podWorkspaceProjectMap { + podWorkspace, found := FindWorkspaceInList(podWorkspaceFile, workspaces) + if found { + // Workspace found, this means workspace is in the repository. + podWorkspace.IsPodWorkspace = true + + // This case the project is already attached to the workspace. + _, found := FindProjectInList(podProjectFile, podWorkspace.Projects) + if !found { + return []xcodeproj.ProjectModel{}, []xcodeproj.WorkspaceModel{}, fmt.Errorf("pod workspace (%s) found, but assigned project (%s) project not", podWorkspaceFile, podProjectFile) + } + + // And the project is not standalone. + _, found = FindProjectInList(podProjectFile, standaloneProjects) + if found { + return []xcodeproj.ProjectModel{}, []xcodeproj.WorkspaceModel{}, fmt.Errorf("pod workspace (%s) found, but assigned project (%s) marked as standalone", podWorkspaceFile, podProjectFile) + } + + mergedStandaloneProjects = standaloneProjects + mergedWorkspaces = ReplaceWorkspaceInList(workspaces, podWorkspace) + } else { + // Workspace not found, this means workspace is not in the repository, + // but it will created by `pod install`. + podWorkspace = xcodeproj.WorkspaceModel{ + Pth: podWorkspaceFile, + Name: strings.TrimSuffix(filepath.Base(podWorkspaceFile), filepath.Ext(podWorkspaceFile)), + IsPodWorkspace: true, + } + + // This case the pod project was marked previously as standalone project. + podProject, found := FindProjectInList(podProjectFile, standaloneProjects) + if !found { + return []xcodeproj.ProjectModel{}, []xcodeproj.WorkspaceModel{}, fmt.Errorf("pod workspace (%s) will be generated by (%s) project, but it does not found", podWorkspaceFile, podProjectFile) + } + + podWorkspace.Projects = []xcodeproj.ProjectModel{podProject} + + mergedStandaloneProjects = RemoveProjectFromList(podProjectFile, standaloneProjects) + mergedWorkspaces = append(workspaces, podWorkspace) + } + } + + return mergedStandaloneProjects, mergedWorkspaces, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/podfile_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/podfile_test.go new file mode 100644 index 00000000..ce9a3a60 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/podfile_test.go @@ -0,0 +1,609 @@ +package ios + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-core/bitrise-init/utility" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-tools/go-xcode/xcodeproj" + "github.com/stretchr/testify/require" +) + +func TestAllowPodfileBaseFilter(t *testing.T) { + t.Log("abs path") + { + absPaths := []string{ + "/Users/bitrise/Test.txt", + "/Users/bitrise/.git/Podfile", + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Podfile", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Podfile", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Podfile", + } + + expectedPaths := []string{ + "/Users/bitrise/.git/Podfile", + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Podfile", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Podfile", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Podfile", + } + + actualPaths, err := utility.FilterPaths(absPaths, AllowPodfileBaseFilter) + require.NoError(t, err) + require.Equal(t, expectedPaths, actualPaths) + } + + t.Log("rel path") + { + relPaths := []string{ + ".", + "Test.txt", + ".git/Podfile", + "sample-apps-ios-cocoapods/Pods/Podfile", + "ios-no-shared-schemes/Carthage/Checkouts/Result/Podfile", + "ios-no-shared-schemes/test.framework/Checkouts/Result/Podfile", + } + + expectedPaths := []string{ + ".git/Podfile", + "sample-apps-ios-cocoapods/Pods/Podfile", + "ios-no-shared-schemes/Carthage/Checkouts/Result/Podfile", + "ios-no-shared-schemes/test.framework/Checkouts/Result/Podfile", + } + + actualPaths, err := utility.FilterPaths(relPaths, AllowPodfileBaseFilter) + require.NoError(t, err) + require.Equal(t, expectedPaths, actualPaths) + } +} + +func TestGetTargetDefinitionProjectMap(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__utility_test__") + require.NoError(t, err) + defer func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }() + + t.Log("xcodeproj defined") + { + tmpDir = filepath.Join(tmpDir, "xcodeproj_defined") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +project 'MyXcodeProject' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + expectedTargetDefinition := map[string]string{ + "Pods": "MyXcodeProject.xcodeproj", + } + actualTargetDefinition, err := getTargetDefinitionProjectMap(podfilePth, "") + require.NoError(t, err) + require.Equal(t, expectedTargetDefinition, actualTargetDefinition) + } + + t.Log("xcodeproj NOT defined") + { + tmpDir = filepath.Join(tmpDir, "xcodeproj_not_defined") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + expectedTargetDefinition := map[string]string{} + actualTargetDefinition, err := getTargetDefinitionProjectMap(podfilePth, "") + require.NoError(t, err) + require.Equal(t, expectedTargetDefinition, actualTargetDefinition) + } + + t.Log("cocoapods 0.38.0") + { + tmpDir = filepath.Join(tmpDir, "xcodeproj_not_defined") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' + +# pod 'Functional.m', '~> 1.0' + +# Add Kiwi as an exclusive dependency for the Test target +target :SampleAppWithCocoapodsTests, :exclusive => true do + pod 'Kiwi' +end + +# post_install do |installer_representation| +# installer_representation.project.targets.each do |target| +# target.build_configurations.each do |config| +# config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO' +# end +# end +# end` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + expectedTargetDefinition := map[string]string{} + actualTargetDefinition, err := getTargetDefinitionProjectMap(podfilePth, "0.38.0") + require.NoError(t, err) + require.Equal(t, expectedTargetDefinition, actualTargetDefinition) + } +} + +func TestGetUserDefinedProjectRelavtivePath(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__utility_test__") + require.NoError(t, err) + defer func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }() + + t.Log("xcodeproj defined") + { + tmpDir = filepath.Join(tmpDir, "xcodeproj_defined") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +project 'MyXcodeProject' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + expectedProject := "MyXcodeProject.xcodeproj" + actualProject, err := getUserDefinedProjectRelavtivePath(podfilePth, "") + require.NoError(t, err) + require.Equal(t, expectedProject, actualProject) + } + + t.Log("xcodeproj NOT defined") + { + tmpDir = filepath.Join(tmpDir, "xcodeproj_not_defined") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + expectedProject := "" + actualProject, err := getUserDefinedProjectRelavtivePath(podfilePth, "") + require.NoError(t, err) + require.Equal(t, expectedProject, actualProject) + } +} + +func TestGetUserDefinedWorkspaceRelativePath(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__utility_test__") + require.NoError(t, err) + defer func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }() + + t.Log("workspace defined") + { + tmpDir = filepath.Join(tmpDir, "workspace_defined") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +workspace 'MyWorkspace' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + expectedWorkspace := "MyWorkspace.xcworkspace" + actualWorkspace, err := getUserDefinedWorkspaceRelativePath(podfilePth, "") + require.NoError(t, err) + require.Equal(t, expectedWorkspace, actualWorkspace) + } + + t.Log("workspace NOT defined") + { + tmpDir = filepath.Join(tmpDir, "workspace_not_defined") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + expectedWorkspace := "" + actualWorkspace, err := getUserDefinedWorkspaceRelativePath(podfilePth, "") + require.NoError(t, err) + require.Equal(t, expectedWorkspace, actualWorkspace) + } +} + +func TestGetWorkspaceProjectMap(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__utility_test__") + require.NoError(t, err) + defer func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }() + + t.Log("0 project in Podfile's dir") + { + tmpDir = filepath.Join(tmpDir, "no_project") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{}) + require.Error(t, err) + require.Equal(t, 0, len(workspaceProjectMap)) + + require.NoError(t, os.RemoveAll(tmpDir)) + } + + t.Log("1 project in Podfile's dir") + { + tmpDir = filepath.Join(tmpDir, "one_project") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + project := "" + projectPth := filepath.Join(tmpDir, "project.xcodeproj") + require.NoError(t, fileutil.WriteStringToFile(projectPth, project)) + + workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{projectPth}) + require.NoError(t, err) + require.Equal(t, 1, len(workspaceProjectMap)) + + for workspace, project := range workspaceProjectMap { + workspaceBasename := filepath.Base(workspace) + workspaceName := strings.TrimSuffix(workspaceBasename, ".xcworkspace") + + projectBasename := filepath.Base(project) + projectName := strings.TrimSuffix(projectBasename, ".xcodeproj") + + require.Equal(t, "project", workspaceName, fmt.Sprintf("%v", workspaceProjectMap)) + require.Equal(t, "project", projectName, fmt.Sprintf("%v", workspaceProjectMap)) + } + + require.NoError(t, os.RemoveAll(tmpDir)) + } + + t.Log("Multiple project in Podfile's dir") + { + tmpDir = filepath.Join(tmpDir, "multiple_project") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + project1 := "" + project1Pth := filepath.Join(tmpDir, "project1.xcodeproj") + require.NoError(t, fileutil.WriteStringToFile(project1Pth, project1)) + + project2 := "" + project2Pth := filepath.Join(tmpDir, "project2.xcodeproj") + require.NoError(t, fileutil.WriteStringToFile(project2Pth, project2)) + + workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{project1Pth, project2Pth}) + require.Error(t, err) + require.Equal(t, 0, len(workspaceProjectMap)) + + require.NoError(t, os.RemoveAll(tmpDir)) + } + + t.Log("0 project in Podfile's dir + project defined in Podfile") + { + tmpDir = filepath.Join(tmpDir, "no_project_project_defined") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +project 'MyXcodeProject' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{}) + require.Error(t, err) + require.Equal(t, 0, len(workspaceProjectMap)) + + require.NoError(t, os.RemoveAll(tmpDir)) + } + + t.Log("1 project in Podfile's dir + project defined in Podfile") + { + tmpDir = filepath.Join(tmpDir, "one_project_project_defined") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +project 'project' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + project := "" + projectPth := filepath.Join(tmpDir, "project.xcodeproj") + require.NoError(t, fileutil.WriteStringToFile(projectPth, project)) + + workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{projectPth}) + require.NoError(t, err) + require.Equal(t, 1, len(workspaceProjectMap)) + + for workspace, project := range workspaceProjectMap { + workspaceBasename := filepath.Base(workspace) + workspaceName := strings.TrimSuffix(workspaceBasename, ".xcworkspace") + + projectBasename := filepath.Base(project) + projectName := strings.TrimSuffix(projectBasename, ".xcodeproj") + + require.Equal(t, "project", workspaceName, fmt.Sprintf("%v", workspaceProjectMap)) + require.Equal(t, "project", projectName, fmt.Sprintf("%v", workspaceProjectMap)) + } + + require.NoError(t, os.RemoveAll(tmpDir)) + } + + t.Log("Multiple project in Podfile's dir + project defined in Podfile") + { + tmpDir = filepath.Join(tmpDir, "multiple_project") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +project 'project1' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + project1 := "" + project1Pth := filepath.Join(tmpDir, "project1.xcodeproj") + require.NoError(t, fileutil.WriteStringToFile(project1Pth, project1)) + + project2 := "" + project2Pth := filepath.Join(tmpDir, "project2.xcodeproj") + require.NoError(t, fileutil.WriteStringToFile(project2Pth, project2)) + + workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{project1Pth, project2Pth}) + require.NoError(t, err) + require.Equal(t, 1, len(workspaceProjectMap)) + + for workspace, project := range workspaceProjectMap { + workspaceBasename := filepath.Base(workspace) + workspaceName := strings.TrimSuffix(workspaceBasename, ".xcworkspace") + + projectBasename := filepath.Base(project) + projectName := strings.TrimSuffix(projectBasename, ".xcodeproj") + + require.Equal(t, "project1", workspaceName, fmt.Sprintf("%v", workspaceProjectMap)) + require.Equal(t, "project1", projectName, fmt.Sprintf("%v", workspaceProjectMap)) + } + + require.NoError(t, os.RemoveAll(tmpDir)) + } + + t.Log("1 project in Podfile's dir + workspace defined in Podfile") + { + tmpDir = filepath.Join(tmpDir, "one_project") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +workspace 'MyWorkspace' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + project := "" + projectPth := filepath.Join(tmpDir, "project.xcodeproj") + require.NoError(t, fileutil.WriteStringToFile(projectPth, project)) + + workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{projectPth}) + require.NoError(t, err) + require.Equal(t, 1, len(workspaceProjectMap)) + + for workspace, project := range workspaceProjectMap { + workspaceBasename := filepath.Base(workspace) + workspaceName := strings.TrimSuffix(workspaceBasename, ".xcworkspace") + + projectBasename := filepath.Base(project) + projectName := strings.TrimSuffix(projectBasename, ".xcodeproj") + + require.Equal(t, "MyWorkspace", workspaceName, fmt.Sprintf("%v", workspaceProjectMap)) + require.Equal(t, "project", projectName, fmt.Sprintf("%v", workspaceProjectMap)) + } + + require.NoError(t, os.RemoveAll(tmpDir)) + } + + t.Log("Multiple project in Podfile's dir + workspace defined in Podfile") + { + tmpDir = filepath.Join(tmpDir, "multiple_project") + require.NoError(t, os.MkdirAll(tmpDir, 0777)) + + podfile := `platform :ios, '9.0' +project 'project1' +workspace 'MyWorkspace' +pod 'Alamofire', '~> 3.4' +` + podfilePth := filepath.Join(tmpDir, "Podfile") + require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) + + project1 := "" + project1Pth := filepath.Join(tmpDir, "project1.xcodeproj") + require.NoError(t, fileutil.WriteStringToFile(project1Pth, project1)) + + project2 := "" + project2Pth := filepath.Join(tmpDir, "project2.xcodeproj") + require.NoError(t, fileutil.WriteStringToFile(project2Pth, project2)) + + workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{project1Pth, project2Pth}) + require.NoError(t, err) + require.Equal(t, 1, len(workspaceProjectMap)) + + for workspace, project := range workspaceProjectMap { + workspaceBasename := filepath.Base(workspace) + workspaceName := strings.TrimSuffix(workspaceBasename, ".xcworkspace") + + projectBasename := filepath.Base(project) + projectName := strings.TrimSuffix(projectBasename, ".xcodeproj") + + require.Equal(t, "MyWorkspace", workspaceName, fmt.Sprintf("%v", workspaceProjectMap)) + require.Equal(t, "project1", projectName, fmt.Sprintf("%v", workspaceProjectMap)) + } + + require.NoError(t, os.RemoveAll(tmpDir)) + } +} + +func TestMergePodWorkspaceProjectMap(t *testing.T) { + t.Log("workspace is in the repository") + { + podWorkspaceMap := map[string]string{ + "MyWorkspace.xcworkspace": "MyXcodeProject.xcodeproj", + } + + standaloneProjects := []xcodeproj.ProjectModel{} + expectedStandaloneProjects := []xcodeproj.ProjectModel{} + + workspaces := []xcodeproj.WorkspaceModel{ + xcodeproj.WorkspaceModel{ + Pth: "MyWorkspace.xcworkspace", + Name: "MyWorkspace", + Projects: []xcodeproj.ProjectModel{ + xcodeproj.ProjectModel{ + Pth: "MyXcodeProject.xcodeproj", + }, + }, + }, + } + expectedWorkspaces := []xcodeproj.WorkspaceModel{ + xcodeproj.WorkspaceModel{ + Pth: "MyWorkspace.xcworkspace", + Name: "MyWorkspace", + Projects: []xcodeproj.ProjectModel{ + xcodeproj.ProjectModel{ + Pth: "MyXcodeProject.xcodeproj", + }, + }, + IsPodWorkspace: true, + }, + } + + mergedStandaloneProjects, mergedWorkspaces, err := MergePodWorkspaceProjectMap(podWorkspaceMap, standaloneProjects, workspaces) + require.NoError(t, err) + require.Equal(t, expectedStandaloneProjects, mergedStandaloneProjects) + require.Equal(t, expectedWorkspaces, mergedWorkspaces) + } + + t.Log("workspace is in the repository, but project not attached - ERROR") + { + podWorkspaceMap := map[string]string{ + "MyWorkspace.xcworkspace": "MyXcodeProject.xcodeproj", + } + + standaloneProjects := []xcodeproj.ProjectModel{} + + workspaces := []xcodeproj.WorkspaceModel{ + xcodeproj.WorkspaceModel{ + Pth: "MyWorkspace.xcworkspace", + Name: "MyWorkspace", + }, + } + + mergedStandaloneProjects, mergedWorkspaces, err := MergePodWorkspaceProjectMap(podWorkspaceMap, standaloneProjects, workspaces) + require.Error(t, err) + require.Equal(t, []xcodeproj.ProjectModel{}, mergedStandaloneProjects) + require.Equal(t, []xcodeproj.WorkspaceModel{}, mergedWorkspaces) + } + + t.Log("workspace is in the repository, but project is marged as standalon - ERROR") + { + podWorkspaceMap := map[string]string{ + "MyWorkspace.xcworkspace": "MyXcodeProject.xcodeproj", + } + + standaloneProjects := []xcodeproj.ProjectModel{ + xcodeproj.ProjectModel{ + Pth: "MyXcodeProject.xcodeproj", + }, + } + + workspaces := []xcodeproj.WorkspaceModel{ + xcodeproj.WorkspaceModel{ + Pth: "MyWorkspace.xcworkspace", + Name: "MyWorkspace", + }, + } + + mergedStandaloneProjects, mergedWorkspaces, err := MergePodWorkspaceProjectMap(podWorkspaceMap, standaloneProjects, workspaces) + require.Error(t, err) + require.Equal(t, []xcodeproj.ProjectModel{}, mergedStandaloneProjects) + require.Equal(t, []xcodeproj.WorkspaceModel{}, mergedWorkspaces) + } + + t.Log("workspace is gitignored") + { + podWorkspaceMap := map[string]string{ + "MyWorkspace.xcworkspace": "MyXcodeProject.xcodeproj", + } + + standaloneProjects := []xcodeproj.ProjectModel{ + xcodeproj.ProjectModel{ + Pth: "MyXcodeProject.xcodeproj", + }, + } + expectedStandaloneProjects := []xcodeproj.ProjectModel{} + + workspaces := []xcodeproj.WorkspaceModel{} + expectedWorkspaces := []xcodeproj.WorkspaceModel{ + xcodeproj.WorkspaceModel{ + Pth: "MyWorkspace.xcworkspace", + Name: "MyWorkspace", + Projects: []xcodeproj.ProjectModel{ + xcodeproj.ProjectModel{ + Pth: "MyXcodeProject.xcodeproj", + }, + }, + IsPodWorkspace: true, + }, + } + + mergedStandaloneProjects, mergedWorkspaces, err := MergePodWorkspaceProjectMap(podWorkspaceMap, standaloneProjects, workspaces) + require.NoError(t, err) + require.Equal(t, expectedStandaloneProjects, mergedStandaloneProjects) + require.Equal(t, expectedWorkspaces, mergedWorkspaces) + } + + t.Log("workspace is gitignored, but standalon project missing - ERROR") + { + podWorkspaceMap := map[string]string{ + "MyWorkspace.xcworkspace": "MyXcodeProject.xcodeproj", + } + + standaloneProjects := []xcodeproj.ProjectModel{} + + workspaces := []xcodeproj.WorkspaceModel{} + + mergedStandaloneProjects, mergedWorkspaces, err := MergePodWorkspaceProjectMap(podWorkspaceMap, standaloneProjects, workspaces) + require.Error(t, err) + require.Equal(t, []xcodeproj.ProjectModel{}, mergedStandaloneProjects) + require.Equal(t, []xcodeproj.WorkspaceModel{}, mergedWorkspaces) + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/rubyscript.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/rubyscript.go new file mode 100644 index 00000000..66ed83bb --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/rubyscript.go @@ -0,0 +1,82 @@ +package ios + +import ( + "errors" + "path" + + "os" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/errorutil" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" +) + +func runRubyScriptForOutput(scriptContent, gemfileContent, inDir string, withEnvs []string) (string, error) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__bitrise-init__") + if err != nil { + return "", err + } + defer func() { + if err := os.RemoveAll(tmpDir); err != nil { + log.TErrorf("Failed to remove tmp dir (%s), error: %s", tmpDir, err) + } + }() + + // Write Gemfile to file and install + if gemfileContent != "" { + gemfilePth := path.Join(tmpDir, "Gemfile") + if err := fileutil.WriteStringToFile(gemfilePth, gemfileContent); err != nil { + return "", err + } + + cmd := command.New("bundle", "install") + + if inDir != "" { + cmd.SetDir(inDir) + } + + withEnvs = append(withEnvs, "BUNDLE_GEMFILE="+gemfilePth) + cmd.AppendEnvs(withEnvs...) + + if out, err := cmd.RunAndReturnTrimmedCombinedOutput(); err != nil { + if errorutil.IsExitStatusError(err) { + return "", errors.New(out) + } + return "", err + } + } + + // Write script to file and run + rubyScriptPth := path.Join(tmpDir, "script.rb") + if err := fileutil.WriteStringToFile(rubyScriptPth, scriptContent); err != nil { + return "", err + } + + var cmd *command.Model + + if gemfileContent != "" { + cmd = command.New("bundle", "exec", "ruby", rubyScriptPth) + } else { + cmd = command.New("ruby", rubyScriptPth) + } + + if inDir != "" { + cmd.SetDir(inDir) + } + + if len(withEnvs) > 0 { + cmd.AppendEnvs(withEnvs...) + } + + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + if err != nil { + if errorutil.IsExitStatusError(err) { + return "", errors.New(out) + } + return "", err + } + + return out, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/rubyscript_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/rubyscript_test.go new file mode 100644 index 00000000..c0121d57 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/rubyscript_test.go @@ -0,0 +1,23 @@ +package ios + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRunRubyScriptForOutput(t *testing.T) { + gemfileContent := `source 'https://rubygems.org' +gem 'json' +` + + rubyScriptContent := `require 'json' + +puts "#{{ :test_key => 'test_value' }.to_json}" +` + + expectedOut := "{\"test_key\":\"test_value\"}" + actualOut, err := runRubyScriptForOutput(rubyScriptContent, gemfileContent, "", []string{}) + require.NoError(t, err) + require.Equal(t, expectedOut, actualOut) +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/utility.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/utility.go new file mode 100644 index 00000000..7559a5b6 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/utility.go @@ -0,0 +1,645 @@ +package ios + +import ( + "fmt" + + "gopkg.in/yaml.v2" + + "path/filepath" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-core/bitrise-init/utility" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-tools/go-xcode/xcodeproj" +) + +const ( + defaultConfigNameFormat = "default-%s-config" + configNameFormat = "%s%s-config" +) + +const ( + // ProjectPathInputKey ... + ProjectPathInputKey = "project_path" + // ProjectPathInputEnvKey ... + ProjectPathInputEnvKey = "BITRISE_PROJECT_PATH" + // ProjectPathInputTitle ... + ProjectPathInputTitle = "Project (or Workspace) path" +) + +const ( + // SchemeInputKey ... + SchemeInputKey = "scheme" + // SchemeInputEnvKey ... + SchemeInputEnvKey = "BITRISE_SCHEME" + // SchemeInputTitle ... + SchemeInputTitle = "Scheme name" +) + +const ( + // ExportMethodInputKey ... + ExportMethodInputKey = "export_method" + // ExportMethodInputEnvKey ... + ExportMethodInputEnvKey = "BITRISE_EXPORT_METHOD" + // IosExportMethodInputTitle ... + IosExportMethodInputTitle = "ipa export method" + // MacExportMethodInputTitle ... + MacExportMethodInputTitle = "Application export method\nNOTE: `none` means: Export a copy of the application without re-signing." +) + +// IosExportMethods ... +var IosExportMethods = []string{"app-store", "ad-hoc", "enterprise", "development"} + +// MacExportMethods ... +var MacExportMethods = []string{"app-store", "developer-id", "development", "none"} + +const ( + // ConfigurationInputKey ... + ConfigurationInputKey = "configuration" +) + +const ( + // CarthageCommandInputKey ... + CarthageCommandInputKey = "carthage_command" +) + +const cartfileBase = "Cartfile" +const cartfileResolvedBase = "Cartfile.resolved" + +// AllowCartfileBaseFilter ... +var AllowCartfileBaseFilter = utility.BaseFilter(cartfileBase, true) + +// ConfigDescriptor ... +type ConfigDescriptor struct { + HasPodfile bool + CarthageCommand string + HasTest bool + MissingSharedSchemes bool +} + +// NewConfigDescriptor ... +func NewConfigDescriptor(hasPodfile bool, carthageCommand string, hasXCTest bool, missingSharedSchemes bool) ConfigDescriptor { + return ConfigDescriptor{ + HasPodfile: hasPodfile, + CarthageCommand: carthageCommand, + HasTest: hasXCTest, + MissingSharedSchemes: missingSharedSchemes, + } +} + +// ConfigName ... +func (descriptor ConfigDescriptor) ConfigName(projectType XcodeProjectType) string { + qualifiers := "" + if descriptor.HasPodfile { + qualifiers += "-pod" + } + if descriptor.CarthageCommand != "" { + qualifiers += "-carthage" + } + if descriptor.HasTest { + qualifiers += "-test" + } + if descriptor.MissingSharedSchemes { + qualifiers += "-missing-shared-schemes" + } + return fmt.Sprintf(configNameFormat, string(projectType), qualifiers) +} + +// HasCartfileInDirectoryOf ... +func HasCartfileInDirectoryOf(pth string) bool { + dir := filepath.Dir(pth) + cartfilePth := filepath.Join(dir, cartfileBase) + exist, err := pathutil.IsPathExists(cartfilePth) + if err != nil { + return false + } + return exist +} + +// HasCartfileResolvedInDirectoryOf ... +func HasCartfileResolvedInDirectoryOf(pth string) bool { + dir := filepath.Dir(pth) + cartfileResolvedPth := filepath.Join(dir, cartfileResolvedBase) + exist, err := pathutil.IsPathExists(cartfileResolvedPth) + if err != nil { + return false + } + return exist +} + +// Detect ... +func Detect(projectType XcodeProjectType, searchDir string) (bool, error) { + fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) + if err != nil { + return false, err + } + + log.TInfof("Filter relevant Xcode project files") + + relevantXcodeprojectFiles, err := FilterRelevantProjectFiles(fileList, projectType) + if err != nil { + return false, err + } + + log.TPrintf("%d Xcode %s project files found", len(relevantXcodeprojectFiles), string(projectType)) + for _, xcodeprojectFile := range relevantXcodeprojectFiles { + log.TPrintf("- %s", xcodeprojectFile) + } + + if len(relevantXcodeprojectFiles) == 0 { + log.TPrintf("platform not detected") + return false, nil + } + + log.TSuccessf("Platform detected") + + return true, nil +} + +func printMissingSharedSchemesAndGenerateWarning(projectPth, defaultGitignorePth string, targets []xcodeproj.TargetModel) string { + isXcshareddataGitignored := false + if exist, err := pathutil.IsPathExists(defaultGitignorePth); err != nil { + log.TWarnf("Failed to check if .gitignore file exists at: %s, error: %s", defaultGitignorePth, err) + } else if exist { + isGitignored, err := utility.FileContains(defaultGitignorePth, "xcshareddata") + if err != nil { + log.TWarnf("Failed to check if xcshareddata gitignored, error: %s", err) + } else { + isXcshareddataGitignored = isGitignored + } + } + + log.TPrintf("") + log.TErrorf("No shared schemes found, adding recreate-user-schemes step...") + log.TErrorf("The newly generated schemes may differ from the ones in your project.") + + message := `No shared schemes found for project: ` + projectPth + `.` + "\n" + + if isXcshareddataGitignored { + log.TErrorf("Your gitignore file (%s) contains 'xcshareddata', maybe shared schemes are gitignored?", defaultGitignorePth) + log.TErrorf("If not, make sure to share your schemes, to have the expected behaviour.") + + message += `Your gitignore file (` + defaultGitignorePth + `) contains 'xcshareddata', maybe shared schemes are gitignored?` + "\n" + } else { + log.TErrorf("Make sure to share your schemes, to have the expected behaviour.") + } + + message += `Automatically generated schemes may differ from the ones in your project. +Make sure to share your schemes for the expected behaviour.` + + log.TPrintf("") + + log.TWarnf("%d user schemes will be generated", len(targets)) + for _, target := range targets { + log.TWarnf("- %s", target.Name) + } + + log.TPrintf("") + + return message +} + +func detectCarthageCommand(projectPth string) (string, string) { + carthageCommand := "" + warning := "" + + if HasCartfileInDirectoryOf(projectPth) { + if HasCartfileResolvedInDirectoryOf(projectPth) { + carthageCommand = "bootstrap" + } else { + dir := filepath.Dir(projectPth) + cartfilePth := filepath.Join(dir, "Cartfile") + + warning = fmt.Sprintf(`Cartfile found at (%s), but no Cartfile.resolved exists in the same directory. +It is strongly recommended to commit this file to your repository`, cartfilePth) + + carthageCommand = "update" + } + } + + return carthageCommand, warning +} + +// GenerateOptions ... +func GenerateOptions(projectType XcodeProjectType, searchDir string) (models.OptionNode, []ConfigDescriptor, models.Warnings, error) { + warnings := models.Warnings{} + + fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) + if err != nil { + return models.OptionNode{}, []ConfigDescriptor{}, models.Warnings{}, err + } + + // Separate workspaces and standalon projects + projectFiles, err := FilterRelevantProjectFiles(fileList, projectType) + if err != nil { + return models.OptionNode{}, []ConfigDescriptor{}, models.Warnings{}, err + } + + workspaceFiles, err := FilterRelevantWorkspaceFiles(fileList, projectType) + if err != nil { + return models.OptionNode{}, []ConfigDescriptor{}, models.Warnings{}, err + } + + standaloneProjects, workspaces, err := CreateStandaloneProjectsAndWorkspaces(projectFiles, workspaceFiles) + if err != nil { + return models.OptionNode{}, []ConfigDescriptor{}, models.Warnings{}, err + } + + exportMethodInputTitle := "" + exportMethods := []string{} + if projectType == XcodeProjectTypeIOS { + exportMethodInputTitle = IosExportMethodInputTitle + exportMethods = IosExportMethods + } else { + exportMethodInputTitle = MacExportMethodInputTitle + exportMethods = MacExportMethods + } + + // Create cocoapods workspace-project mapping + log.TInfof("Searching for Podfile") + + podfiles, err := FilterRelevantPodfiles(fileList) + if err != nil { + return models.OptionNode{}, []ConfigDescriptor{}, models.Warnings{}, err + } + + log.TPrintf("%d Podfiles detected", len(podfiles)) + + for _, podfile := range podfiles { + log.TPrintf("- %s", podfile) + + workspaceProjectMap, err := GetWorkspaceProjectMap(podfile, projectFiles) + if err != nil { + warning := fmt.Sprintf("Failed to determine cocoapods project-workspace mapping, error: %s", err) + warnings = append(warnings, warning) + log.Warnf(warning) + continue + } + + aStandaloneProjects, aWorkspaces, err := MergePodWorkspaceProjectMap(workspaceProjectMap, standaloneProjects, workspaces) + if err != nil { + warning := fmt.Sprintf("Failed to create cocoapods project-workspace mapping, error: %s", err) + warnings = append(warnings, warning) + log.Warnf(warning) + continue + } + + standaloneProjects = aStandaloneProjects + workspaces = aWorkspaces + } + + // Carthage + log.TInfof("Searching for Cartfile") + + cartfiles, err := FilterRelevantCartFile(fileList) + if err != nil { + return models.OptionNode{}, []ConfigDescriptor{}, models.Warnings{}, err + } + + log.TPrintf("%d Cartfiles detected", len(cartfiles)) + for _, file := range cartfiles { + log.TPrintf("- %s", file) + } + + // Create config descriptors & options + configDescriptors := []ConfigDescriptor{} + + defaultGitignorePth := filepath.Join(searchDir, ".gitignore") + + projectPathOption := models.NewOption(ProjectPathInputTitle, ProjectPathInputEnvKey) + + // Standalon Projects + for _, project := range standaloneProjects { + log.TInfof("Inspecting standalone project file: %s", project.Pth) + + schemeOption := models.NewOption(SchemeInputTitle, SchemeInputEnvKey) + projectPathOption.AddOption(project.Pth, schemeOption) + + carthageCommand, warning := detectCarthageCommand(project.Pth) + if warning != "" { + warnings = append(warnings, warning) + } + + log.TPrintf("%d shared schemes detected", len(project.SharedSchemes)) + + if len(project.SharedSchemes) == 0 { + message := printMissingSharedSchemesAndGenerateWarning(project.Pth, defaultGitignorePth, project.Targets) + if message != "" { + warnings = append(warnings, message) + } + + for _, target := range project.Targets { + + exportMethodOption := models.NewOption(exportMethodInputTitle, ExportMethodInputEnvKey) + schemeOption.AddOption(target.Name, exportMethodOption) + + for _, exportMethod := range exportMethods { + configDescriptor := NewConfigDescriptor(false, carthageCommand, target.HasXCTest, true) + configDescriptors = append(configDescriptors, configDescriptor) + + configOption := models.NewConfigOption(configDescriptor.ConfigName(projectType)) + exportMethodOption.AddConfig(exportMethod, configOption) + } + } + } else { + for _, scheme := range project.SharedSchemes { + log.TPrintf("- %s", scheme.Name) + + exportMethodOption := models.NewOption(exportMethodInputTitle, ExportMethodInputEnvKey) + schemeOption.AddOption(scheme.Name, exportMethodOption) + + for _, exportMethod := range exportMethods { + configDescriptor := NewConfigDescriptor(false, carthageCommand, scheme.HasXCTest, false) + configDescriptors = append(configDescriptors, configDescriptor) + + configOption := models.NewConfigOption(configDescriptor.ConfigName(projectType)) + exportMethodOption.AddConfig(exportMethod, configOption) + + } + } + } + } + + // Workspaces + for _, workspace := range workspaces { + log.TInfof("Inspecting workspace file: %s", workspace.Pth) + + schemeOption := models.NewOption(SchemeInputTitle, SchemeInputEnvKey) + projectPathOption.AddOption(workspace.Pth, schemeOption) + + carthageCommand, warning := detectCarthageCommand(workspace.Pth) + if warning != "" { + warnings = append(warnings, warning) + } + + sharedSchemes := workspace.GetSharedSchemes() + log.TPrintf("%d shared schemes detected", len(sharedSchemes)) + + if len(sharedSchemes) == 0 { + targets := workspace.GetTargets() + + message := printMissingSharedSchemesAndGenerateWarning(workspace.Pth, defaultGitignorePth, targets) + if message != "" { + warnings = append(warnings, message) + } + + for _, target := range targets { + exportMethodOption := models.NewOption(exportMethodInputTitle, ExportMethodInputEnvKey) + schemeOption.AddOption(target.Name, exportMethodOption) + + for _, exportMethod := range exportMethods { + configDescriptor := NewConfigDescriptor(workspace.IsPodWorkspace, carthageCommand, target.HasXCTest, true) + configDescriptors = append(configDescriptors, configDescriptor) + + configOption := models.NewConfigOption(configDescriptor.ConfigName(projectType)) + exportMethodOption.AddConfig(exportMethod, configOption) + } + } + } else { + for _, scheme := range sharedSchemes { + log.TPrintf("- %s", scheme.Name) + + exportMethodOption := models.NewOption(exportMethodInputTitle, ExportMethodInputEnvKey) + schemeOption.AddOption(scheme.Name, exportMethodOption) + + for _, exportMethod := range exportMethods { + configDescriptor := NewConfigDescriptor(workspace.IsPodWorkspace, carthageCommand, scheme.HasXCTest, false) + configDescriptors = append(configDescriptors, configDescriptor) + + configOption := models.NewConfigOption(configDescriptor.ConfigName(projectType)) + exportMethodOption.AddConfig(exportMethod, configOption) + } + } + } + } + + configDescriptors = RemoveDuplicatedConfigDescriptors(configDescriptors, projectType) + + if len(configDescriptors) == 0 { + log.TErrorf("No valid %s config found", string(projectType)) + return models.OptionNode{}, []ConfigDescriptor{}, warnings, fmt.Errorf("No valid %s config found", string(projectType)) + } + + return *projectPathOption, configDescriptors, warnings, nil +} + +// GenerateDefaultOptions ... +func GenerateDefaultOptions(projectType XcodeProjectType) models.OptionNode { + projectPathOption := models.NewOption(ProjectPathInputTitle, ProjectPathInputEnvKey) + + schemeOption := models.NewOption(SchemeInputTitle, SchemeInputEnvKey) + projectPathOption.AddOption("_", schemeOption) + + exportMethodInputTitle := "" + exportMethods := []string{} + if projectType == XcodeProjectTypeIOS { + exportMethodInputTitle = IosExportMethodInputTitle + exportMethods = IosExportMethods + } else { + exportMethodInputTitle = MacExportMethodInputTitle + exportMethods = MacExportMethods + } + + exportMethodOption := models.NewOption(exportMethodInputTitle, ExportMethodInputEnvKey) + schemeOption.AddOption("_", exportMethodOption) + + for _, exportMethod := range exportMethods { + configOption := models.NewConfigOption(fmt.Sprintf(defaultConfigNameFormat, string(projectType))) + exportMethodOption.AddConfig(exportMethod, configOption) + } + + return *projectPathOption +} + +// GenerateConfigBuilder ... +func GenerateConfigBuilder(projectType XcodeProjectType, hasPodfile, hasTest, missingSharedSchemes bool, carthageCommand string, isIncludeCache bool) models.ConfigBuilderModel { + configBuilder := models.NewDefaultConfigBuilder() + + // CI + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(isIncludeCache)...) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + if missingSharedSchemes { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.RecreateUserSchemesStepListItem( + envmanModels.EnvironmentItemModel{ProjectPathInputKey: "$" + ProjectPathInputEnvKey}, + )) + } + + if hasPodfile { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CocoapodsInstallStepListItem()) + } + + if carthageCommand != "" { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CarthageStepListItem( + envmanModels.EnvironmentItemModel{CarthageCommandInputKey: carthageCommand}, + )) + } + + xcodeStepInputModels := []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ProjectPathInputKey: "$" + ProjectPathInputEnvKey}, + envmanModels.EnvironmentItemModel{SchemeInputKey: "$" + SchemeInputEnvKey}, + } + xcodeArchiveStepInputModels := append(xcodeStepInputModels, envmanModels.EnvironmentItemModel{ExportMethodInputKey: "$" + ExportMethodInputEnvKey}) + + if hasTest { + switch projectType { + case XcodeProjectTypeIOS: + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XcodeTestStepListItem(xcodeStepInputModels...)) + case XcodeProjectTypeMacOS: + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XcodeTestMacStepListItem(xcodeStepInputModels...)) + } + } else { + switch projectType { + case XcodeProjectTypeIOS: + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XcodeArchiveStepListItem(xcodeArchiveStepInputModels...)) + case XcodeProjectTypeMacOS: + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XcodeArchiveMacStepListItem(xcodeArchiveStepInputModels...)) + } + } + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(isIncludeCache)...) + + if hasTest { + // CD + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(isIncludeCache)...) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + if missingSharedSchemes { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.RecreateUserSchemesStepListItem( + envmanModels.EnvironmentItemModel{ProjectPathInputKey: "$" + ProjectPathInputEnvKey}, + )) + } + + if hasPodfile { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CocoapodsInstallStepListItem()) + } + + if carthageCommand != "" { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CarthageStepListItem( + envmanModels.EnvironmentItemModel{CarthageCommandInputKey: carthageCommand}, + )) + } + + switch projectType { + case XcodeProjectTypeIOS: + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeTestStepListItem(xcodeStepInputModels...)) + case XcodeProjectTypeMacOS: + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeTestMacStepListItem(xcodeStepInputModels...)) + } + + switch projectType { + case XcodeProjectTypeIOS: + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem(xcodeArchiveStepInputModels...)) + case XcodeProjectTypeMacOS: + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveMacStepListItem(xcodeArchiveStepInputModels...)) + } + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(isIncludeCache)...) + } + + return *configBuilder +} + +// RemoveDuplicatedConfigDescriptors ... +func RemoveDuplicatedConfigDescriptors(configDescriptors []ConfigDescriptor, projectType XcodeProjectType) []ConfigDescriptor { + descritorNameMap := map[string]ConfigDescriptor{} + for _, descriptor := range configDescriptors { + name := descriptor.ConfigName(projectType) + descritorNameMap[name] = descriptor + } + + descriptors := []ConfigDescriptor{} + for _, descriptor := range descritorNameMap { + descriptors = append(descriptors, descriptor) + } + + return descriptors +} + +// GenerateConfig ... +func GenerateConfig(projectType XcodeProjectType, configDescriptors []ConfigDescriptor, isIncludeCache bool) (models.BitriseConfigMap, error) { + bitriseDataMap := models.BitriseConfigMap{} + for _, descriptor := range configDescriptors { + configBuilder := GenerateConfigBuilder(projectType, descriptor.HasPodfile, descriptor.HasTest, descriptor.MissingSharedSchemes, descriptor.CarthageCommand, isIncludeCache) + + config, err := configBuilder.Generate(string(projectType)) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + bitriseDataMap[descriptor.ConfigName(projectType)] = string(data) + } + + return bitriseDataMap, nil +} + +// GenerateDefaultConfig ... +func GenerateDefaultConfig(projectType XcodeProjectType, isIncludeCache bool) (models.BitriseConfigMap, error) { + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(isIncludeCache)...) + + // CI + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.RecreateUserSchemesStepListItem( + envmanModels.EnvironmentItemModel{ProjectPathInputKey: "$" + ProjectPathInputEnvKey}, + )) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CocoapodsInstallStepListItem()) + + xcodeTestStepInputModels := []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ProjectPathInputKey: "$" + ProjectPathInputEnvKey}, + envmanModels.EnvironmentItemModel{SchemeInputKey: "$" + SchemeInputEnvKey}, + } + xcodeArchiveStepInputModels := append(xcodeTestStepInputModels, envmanModels.EnvironmentItemModel{ExportMethodInputKey: "$" + ExportMethodInputEnvKey}) + + switch projectType { + case XcodeProjectTypeIOS: + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XcodeTestStepListItem(xcodeTestStepInputModels...)) + case XcodeProjectTypeMacOS: + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XcodeTestMacStepListItem(xcodeTestStepInputModels...)) + } + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(true)...) + + // CD + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(isIncludeCache)...) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.RecreateUserSchemesStepListItem( + envmanModels.EnvironmentItemModel{ProjectPathInputKey: "$" + ProjectPathInputEnvKey}, + )) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CocoapodsInstallStepListItem()) + + switch projectType { + case XcodeProjectTypeIOS: + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeTestStepListItem(xcodeTestStepInputModels...)) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem(xcodeArchiveStepInputModels...)) + case XcodeProjectTypeMacOS: + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeTestMacStepListItem(xcodeTestStepInputModels...)) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveMacStepListItem(xcodeArchiveStepInputModels...)) + } + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(true)...) + + config, err := configBuilder.Generate(string(projectType)) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + fmt.Sprintf(defaultConfigNameFormat, string(projectType)): string(data), + }, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/utility_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/utility_test.go new file mode 100644 index 00000000..f293b241 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/utility_test.go @@ -0,0 +1,57 @@ +package ios + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewConfigDescriptor(t *testing.T) { + descriptor := NewConfigDescriptor(false, "", false, true) + require.Equal(t, false, descriptor.HasPodfile) + require.Equal(t, false, descriptor.HasTest) + require.Equal(t, true, descriptor.MissingSharedSchemes) + require.Equal(t, "", descriptor.CarthageCommand) +} + +func TestConfigName(t *testing.T) { + { + descriptor := NewConfigDescriptor(false, "", false, false) + require.Equal(t, "ios-config", descriptor.ConfigName(XcodeProjectTypeIOS)) + } + + { + descriptor := NewConfigDescriptor(true, "", false, false) + require.Equal(t, "ios-pod-config", descriptor.ConfigName(XcodeProjectTypeIOS)) + } + + { + descriptor := NewConfigDescriptor(false, "bootsrap", false, false) + require.Equal(t, "ios-carthage-config", descriptor.ConfigName(XcodeProjectTypeIOS)) + } + + { + descriptor := NewConfigDescriptor(false, "", true, false) + require.Equal(t, "ios-test-config", descriptor.ConfigName(XcodeProjectTypeIOS)) + } + + { + descriptor := NewConfigDescriptor(false, "", false, true) + require.Equal(t, "ios-missing-shared-schemes-config", descriptor.ConfigName(XcodeProjectTypeIOS)) + } + + { + descriptor := NewConfigDescriptor(true, "bootstrap", false, false) + require.Equal(t, "ios-pod-carthage-config", descriptor.ConfigName(XcodeProjectTypeIOS)) + } + + { + descriptor := NewConfigDescriptor(true, "bootstrap", true, false) + require.Equal(t, "ios-pod-carthage-test-config", descriptor.ConfigName(XcodeProjectTypeIOS)) + } + + { + descriptor := NewConfigDescriptor(true, "bootstrap", true, true) + require.Equal(t, "ios-pod-carthage-test-missing-shared-schemes-config", descriptor.ConfigName(XcodeProjectTypeIOS)) + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj.go new file mode 100644 index 00000000..abd9e968 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj.go @@ -0,0 +1,272 @@ +package ios + +import ( + "path/filepath" + + "fmt" + + "github.com/bitrise-core/bitrise-init/scanners/xamarin" + "github.com/bitrise-core/bitrise-init/utility" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-tools/go-xcode/xcodeproj" +) + +const ( + embeddedWorkspacePathPattern = `.+\.xcodeproj/.+\.xcworkspace` + + gitDirName = ".git" + podsDirName = "Pods" + carthageDirName = "Carthage" + cordovaLibDirName = "CordovaLib" + + frameworkExt = ".framework" +) + +// XcodeProjectType ... +type XcodeProjectType string + +const ( + // XcodeProjectTypeIOS ... + XcodeProjectTypeIOS XcodeProjectType = "ios" + // XcodeProjectTypeMacOS ... + XcodeProjectTypeMacOS XcodeProjectType = "macos" +) + +// AllowXcodeProjExtFilter ... +var AllowXcodeProjExtFilter = utility.ExtensionFilter(xcodeproj.XCodeProjExt, true) + +// AllowXCWorkspaceExtFilter ... +var AllowXCWorkspaceExtFilter = utility.ExtensionFilter(xcodeproj.XCWorkspaceExt, true) + +// AllowIsDirectoryFilter ... +var AllowIsDirectoryFilter = utility.IsDirectoryFilter(true) + +// ForbidEmbeddedWorkspaceRegexpFilter ... +var ForbidEmbeddedWorkspaceRegexpFilter = utility.RegexpFilter(embeddedWorkspacePathPattern, false) + +// ForbidGitDirComponentFilter ... +var ForbidGitDirComponentFilter = utility.ComponentFilter(gitDirName, false) + +// ForbidPodsDirComponentFilter ... +var ForbidPodsDirComponentFilter = utility.ComponentFilter(podsDirName, false) + +// ForbidCarthageDirComponentFilter ... +var ForbidCarthageDirComponentFilter = utility.ComponentFilter(carthageDirName, false) + +// ForbidCordovaLibDirComponentFilter ... +var ForbidCordovaLibDirComponentFilter = utility.ComponentFilter(cordovaLibDirName, false) + +// ForbidFramworkComponentWithExtensionFilter ... +var ForbidFramworkComponentWithExtensionFilter = utility.ComponentWithExtensionFilter(frameworkExt, false) + +// ForbidNodeModulesComponentFilter ... +var ForbidNodeModulesComponentFilter = utility.ComponentFilter(xamarin.NodeModulesDirName, false) + +// AllowIphoneosSDKFilter ... +var AllowIphoneosSDKFilter = SDKFilter("iphoneos", true) + +// AllowMacosxSDKFilter ... +var AllowMacosxSDKFilter = SDKFilter("macosx", true) + +// SDKFilter ... +func SDKFilter(sdk string, allowed bool) utility.FilterFunc { + return func(pth string) (bool, error) { + found := false + + projectFiles := []string{} + + if xcodeproj.IsXCodeProj(pth) { + projectFiles = append(projectFiles, pth) + } else if xcodeproj.IsXCWorkspace(pth) { + projects, err := xcodeproj.WorkspaceProjectReferences(pth) + if err != nil { + return false, err + } + + for _, project := range projects { + exist, err := pathutil.IsPathExists(project) + if err != nil { + return false, err + } + if !exist { + continue + } + projectFiles = append(projectFiles, project) + + } + } else { + return false, fmt.Errorf("Not Xcode project nor workspace file: %s", pth) + } + + for _, projectFile := range projectFiles { + pbxprojPth := filepath.Join(projectFile, "project.pbxproj") + projectSDKs, err := xcodeproj.GetBuildConfigSDKs(pbxprojPth) + if err != nil { + return false, err + } + + for _, projectSDK := range projectSDKs { + if projectSDK == sdk { + found = true + break + } + } + } + + return (allowed == found), nil + } +} + +// FindWorkspaceInList ... +func FindWorkspaceInList(workspacePth string, workspaces []xcodeproj.WorkspaceModel) (xcodeproj.WorkspaceModel, bool) { + for _, workspace := range workspaces { + if workspace.Pth == workspacePth { + return workspace, true + } + } + return xcodeproj.WorkspaceModel{}, false +} + +// FindProjectInList ... +func FindProjectInList(projectPth string, projects []xcodeproj.ProjectModel) (xcodeproj.ProjectModel, bool) { + for _, project := range projects { + if project.Pth == projectPth { + return project, true + } + } + return xcodeproj.ProjectModel{}, false +} + +// RemoveProjectFromList ... +func RemoveProjectFromList(projectPth string, projects []xcodeproj.ProjectModel) []xcodeproj.ProjectModel { + newProjects := []xcodeproj.ProjectModel{} + for _, project := range projects { + if project.Pth != projectPth { + newProjects = append(newProjects, project) + } + } + return newProjects +} + +// ReplaceWorkspaceInList ... +func ReplaceWorkspaceInList(workspaces []xcodeproj.WorkspaceModel, workspace xcodeproj.WorkspaceModel) []xcodeproj.WorkspaceModel { + updatedWorkspaces := []xcodeproj.WorkspaceModel{} + for _, w := range workspaces { + if w.Pth == workspace.Pth { + updatedWorkspaces = append(updatedWorkspaces, workspace) + } else { + updatedWorkspaces = append(updatedWorkspaces, w) + } + } + return updatedWorkspaces +} + +// CreateStandaloneProjectsAndWorkspaces ... +func CreateStandaloneProjectsAndWorkspaces(projectFiles, workspaceFiles []string) ([]xcodeproj.ProjectModel, []xcodeproj.WorkspaceModel, error) { + workspaces := []xcodeproj.WorkspaceModel{} + for _, workspaceFile := range workspaceFiles { + workspace, err := xcodeproj.NewWorkspace(workspaceFile, projectFiles...) + if err != nil { + return []xcodeproj.ProjectModel{}, []xcodeproj.WorkspaceModel{}, err + } + workspaces = append(workspaces, workspace) + } + + standaloneProjects := []xcodeproj.ProjectModel{} + for _, projectFile := range projectFiles { + workspaceContains := false + for _, workspace := range workspaces { + _, found := FindProjectInList(projectFile, workspace.Projects) + if found { + workspaceContains = true + break + } + } + + if !workspaceContains { + project, err := xcodeproj.NewProject(projectFile) + if err != nil { + return []xcodeproj.ProjectModel{}, []xcodeproj.WorkspaceModel{}, err + } + standaloneProjects = append(standaloneProjects, project) + } + } + + return standaloneProjects, workspaces, nil +} + +// FilterRelevantProjectFiles ... +func FilterRelevantProjectFiles(fileList []string, projectTypes ...XcodeProjectType) ([]string, error) { + filters := []utility.FilterFunc{ + AllowXcodeProjExtFilter, + AllowIsDirectoryFilter, + ForbidEmbeddedWorkspaceRegexpFilter, + ForbidGitDirComponentFilter, + ForbidPodsDirComponentFilter, + ForbidCarthageDirComponentFilter, + ForbidFramworkComponentWithExtensionFilter, + ForbidCordovaLibDirComponentFilter, + ForbidNodeModulesComponentFilter, + } + + for _, projectType := range projectTypes { + switch projectType { + case XcodeProjectTypeIOS: + filters = append(filters, AllowIphoneosSDKFilter) + case XcodeProjectTypeMacOS: + filters = append(filters, AllowMacosxSDKFilter) + } + } + + return utility.FilterPaths(fileList, filters...) +} + +// FilterRelevantWorkspaceFiles ... +func FilterRelevantWorkspaceFiles(fileList []string, projectTypes ...XcodeProjectType) ([]string, error) { + filters := []utility.FilterFunc{ + AllowXCWorkspaceExtFilter, + AllowIsDirectoryFilter, + ForbidEmbeddedWorkspaceRegexpFilter, + ForbidGitDirComponentFilter, + ForbidPodsDirComponentFilter, + ForbidCarthageDirComponentFilter, + ForbidFramworkComponentWithExtensionFilter, + ForbidCordovaLibDirComponentFilter, + ForbidNodeModulesComponentFilter, + } + + for _, projectType := range projectTypes { + switch projectType { + case XcodeProjectTypeIOS: + filters = append(filters, AllowIphoneosSDKFilter) + case XcodeProjectTypeMacOS: + filters = append(filters, AllowMacosxSDKFilter) + } + } + + return utility.FilterPaths(fileList, filters...) +} + +// FilterRelevantPodfiles ... +func FilterRelevantPodfiles(fileList []string) ([]string, error) { + return utility.FilterPaths(fileList, + AllowPodfileBaseFilter, + ForbidGitDirComponentFilter, + ForbidPodsDirComponentFilter, + ForbidCarthageDirComponentFilter, + ForbidFramworkComponentWithExtensionFilter, + ForbidCordovaLibDirComponentFilter, + ForbidNodeModulesComponentFilter) +} + +// FilterRelevantCartFile ... +func FilterRelevantCartFile(fileList []string) ([]string, error) { + return utility.FilterPaths(fileList, + AllowCartfileBaseFilter, + ForbidGitDirComponentFilter, + ForbidPodsDirComponentFilter, + ForbidCarthageDirComponentFilter, + ForbidFramworkComponentWithExtensionFilter, + ForbidCordovaLibDirComponentFilter, + ForbidNodeModulesComponentFilter) +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj_test.go new file mode 100644 index 00000000..47c7bf7c --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj_test.go @@ -0,0 +1,189 @@ +package ios + +import ( + "os" + "path/filepath" + "testing" + + "github.com/bitrise-core/bitrise-init/utility" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestAllowXcodeProjExtFilter(t *testing.T) { + paths := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", + } + expectedFiltered := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", + } + actualFiltered, err := utility.FilterPaths(paths, AllowXcodeProjExtFilter) + require.NoError(t, err) + require.Equal(t, expectedFiltered, actualFiltered) +} + +func TestAllowXCWorkspaceExtFilter(t *testing.T) { + paths := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", + } + expectedFiltered := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + } + actualFiltered, err := utility.FilterPaths(paths, AllowXCWorkspaceExtFilter) + require.NoError(t, err) + require.Equal(t, expectedFiltered, actualFiltered) +} + +func TestForbidEmbeddedWorkspaceRegexpFilter(t *testing.T) { + paths := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", + } + expectedFiltered := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", + } + actualFiltered, err := utility.FilterPaths(paths, ForbidEmbeddedWorkspaceRegexpFilter) + require.NoError(t, err) + require.Equal(t, expectedFiltered, actualFiltered) +} + +func TestForbidGitDirComponentFilter(t *testing.T) { + paths := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", + } + expectedFiltered := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", + } + actualFiltered, err := utility.FilterPaths(paths, ForbidGitDirComponentFilter) + require.NoError(t, err) + require.Equal(t, expectedFiltered, actualFiltered) +} + +func TestForbidPodsDirComponentFilter(t *testing.T) { + paths := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", + } + expectedFiltered := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", + } + actualFiltered, err := utility.FilterPaths(paths, ForbidPodsDirComponentFilter) + require.NoError(t, err) + require.Equal(t, expectedFiltered, actualFiltered) +} + +func TestForbidCarthageDirComponentFilter(t *testing.T) { + paths := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", + } + expectedFiltered := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", + } + actualFiltered, err := utility.FilterPaths(paths, ForbidCarthageDirComponentFilter) + require.NoError(t, err) + require.Equal(t, expectedFiltered, actualFiltered) +} + +func TestForbidFramworkComponentWithExtensionFilter(t *testing.T) { + paths := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", + } + expectedFiltered := []string{ + "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", + "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", + "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", + } + actualFiltered, err := utility.FilterPaths(paths, ForbidFramworkComponentWithExtensionFilter) + require.NoError(t, err) + require.Equal(t, expectedFiltered, actualFiltered) +} + +func TestAllowIphoneosSDKFilter(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__xcodeproj_test__") + require.NoError(t, err) + defer func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }() + + iphoneosProject := filepath.Join(tmpDir, "iphoneos.xcodeproj") + require.NoError(t, os.MkdirAll(iphoneosProject, 0777)) + + iphoneosPbxprojPth := filepath.Join(iphoneosProject, "project.pbxproj") + require.NoError(t, fileutil.WriteStringToFile(iphoneosPbxprojPth, testIOSPbxprojContent)) + + macosxProject := filepath.Join(tmpDir, "macosx.xcodeproj") + require.NoError(t, os.MkdirAll(macosxProject, 0777)) + + macosxPbxprojPth := filepath.Join(macosxProject, "project.pbxproj") + require.NoError(t, fileutil.WriteStringToFile(macosxPbxprojPth, testMacOSPbxprojContent)) + + t.Log("iphoneos sdk") + { + paths := []string{ + iphoneosProject, + macosxProject, + } + expectedFiltered := []string{ + iphoneosProject, + } + actualFiltered, err := utility.FilterPaths(paths, AllowIphoneosSDKFilter) + require.NoError(t, err) + require.Equal(t, expectedFiltered, actualFiltered) + } + + t.Log("macosx sdk") + { + paths := []string{ + iphoneosProject, + macosxProject, + } + expectedFiltered := []string{ + macosxProject, + } + actualFiltered, err := utility.FilterPaths(paths, AllowMacosxSDKFilter) + require.NoError(t, err) + require.Equal(t, expectedFiltered, actualFiltered) + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj_test_files.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj_test_files.go new file mode 100644 index 00000000..81e4a63b --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj_test_files.go @@ -0,0 +1,1289 @@ +package ios + +const testMacOSPbxprojContent = `// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1302F7441D95BA4A005CE678 /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1302F7431D95BA4A005CE678 /* Session.swift */; }; + 130E6BBE1D95BBB4009D3C78 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138F9EE61D8E7ABC00515FCA /* Command.swift */; }; + 131A3D9A1D90543F002DAF99 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 131A3D961D9053BB002DAF99 /* Realm.framework */; }; + 131A3D9B1D90543F002DAF99 /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 131A3D961D9053BB002DAF99 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 131A3D9C1D90543F002DAF99 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 131A3D971D9053BB002DAF99 /* RealmSwift.framework */; }; + 131A3D9D1D90543F002DAF99 /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 131A3D971D9053BB002DAF99 /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 131A3DA61D9060AA002DAF99 /* Yaml.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 131A3DA11D90609E002DAF99 /* Yaml.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 131A3DA71D9060C0002DAF99 /* Yaml.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 131A3DA11D90609E002DAF99 /* Yaml.framework */; }; + 131A3DAB1D906BFA002DAF99 /* BitriseTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131A3DAA1D906BFA002DAF99 /* BitriseTool.swift */; }; + 131A3DAD1D906D54002DAF99 /* Bitrise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131A3DAC1D906D54002DAF99 /* Bitrise.swift */; }; + 131A3DAF1D906DAB002DAF99 /* Envman.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131A3DAE1D906DAB002DAF99 /* Envman.swift */; }; + 131A3DB11D906DBF002DAF99 /* Stepman.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131A3DB01D906DBF002DAF99 /* Stepman.swift */; }; + 131ACE731D93054B007E71E9 /* ToolManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131ACE721D93054B007E71E9 /* ToolManager.swift */; }; + 131ACE761D9323E3007E71E9 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131ACE751D9323E3007E71E9 /* Version.swift */; }; + 13A95E821D966F040061B54F /* BashSessionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A95E811D966F040061B54F /* BashSessionViewController.swift */; }; + 13AAE1BD1D8426FF00AEE66D /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AAE1BC1D8426FF00AEE66D /* FileManager.swift */; }; + 13B0B8F31D872E93006EA29C /* RealmManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B0B8F21D872E93006EA29C /* RealmManager.swift */; }; + 13B958CD1D89E87600D3310D /* SystemInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B958CC1D89E87600D3310D /* SystemInfoViewController.swift */; }; + 13B958D01D89EC7800D3310D /* RunViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B958CE1D89EC7800D3310D /* RunViewController.swift */; }; + 13C989811D8319600028BA2C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C989801D8319600028BA2C /* AppDelegate.swift */; }; + 13C989851D8319600028BA2C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13C989841D8319600028BA2C /* Assets.xcassets */; }; + 13C989881D8319600028BA2C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13C989861D8319600028BA2C /* Main.storyboard */; }; + 13C989931D8319600028BA2C /* BitriseStudioTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C989921D8319600028BA2C /* BitriseStudioTests.swift */; }; + 13C9899E1D8319600028BA2C /* BitriseStudioUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C9899D1D8319600028BA2C /* BitriseStudioUITests.swift */; }; + 13CA73D61D84A74800B1A323 /* AddProjectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CA73D51D84A74800B1A323 /* AddProjectViewController.swift */; }; + 13CA73DE1D84B67200B1A323 /* Project.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CA73DD1D84B67200B1A323 /* Project.swift */; }; + 13E3F5531D83477300AE7C20 /* ProjectsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C989821D8319600028BA2C /* ProjectsViewController.swift */; }; + 13FF5FCE1D9859EE008C7DFB /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FF5FCD1D9859EE008C7DFB /* Log.swift */; }; + 13FF5FD11D98620A008C7DFB /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FF5FD01D98620A008C7DFB /* String+Extensions.swift */; }; + 13FF5FD31D9862DA008C7DFB /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FF5FD21D9862DA008C7DFB /* Data+Extensions.swift */; }; + 13FF5FD51D9872EC008C7DFB /* Pipe+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FF5FD41D9872EC008C7DFB /* Pipe+Extensions.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 13C9898F1D8319600028BA2C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 13C989751D83195F0028BA2C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13C9897C1D83195F0028BA2C; + remoteInfo = BitriseStudio; + }; + 13C9899A1D8319600028BA2C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 13C989751D83195F0028BA2C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13C9897C1D83195F0028BA2C; + remoteInfo = BitriseStudio; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 131A3D9E1D90543F002DAF99 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 131A3D9D1D90543F002DAF99 /* RealmSwift.framework in Embed Frameworks */, + 131A3D9B1D90543F002DAF99 /* Realm.framework in Embed Frameworks */, + 131A3DA61D9060AA002DAF99 /* Yaml.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1302F7431D95BA4A005CE678 /* Session.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; }; + 131A3D961D9053BB002DAF99 /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Realm.framework; path = Framworks/Realm.framework; sourceTree = ""; }; + 131A3D971D9053BB002DAF99 /* RealmSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RealmSwift.framework; path = Framworks/RealmSwift.framework; sourceTree = ""; }; + 131A3DA11D90609E002DAF99 /* Yaml.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Yaml.framework; path = Framworks/Yaml.framework; sourceTree = ""; }; + 131A3DAA1D906BFA002DAF99 /* BitriseTool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitriseTool.swift; sourceTree = ""; }; + 131A3DAC1D906D54002DAF99 /* Bitrise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bitrise.swift; sourceTree = ""; }; + 131A3DAE1D906DAB002DAF99 /* Envman.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Envman.swift; sourceTree = ""; }; + 131A3DB01D906DBF002DAF99 /* Stepman.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stepman.swift; sourceTree = ""; }; + 131ACE721D93054B007E71E9 /* ToolManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolManager.swift; sourceTree = ""; }; + 131ACE751D9323E3007E71E9 /* Version.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; }; + 138F9EE61D8E7ABC00515FCA /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = ""; }; + 13A95E811D966F040061B54F /* BashSessionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BashSessionViewController.swift; sourceTree = ""; }; + 13AAE1BC1D8426FF00AEE66D /* FileManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = ""; }; + 13B0B8F21D872E93006EA29C /* RealmManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmManager.swift; sourceTree = ""; }; + 13B958CC1D89E87600D3310D /* SystemInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemInfoViewController.swift; sourceTree = ""; }; + 13B958CE1D89EC7800D3310D /* RunViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RunViewController.swift; sourceTree = ""; }; + 13C9897D1D8319600028BA2C /* BitriseStudio.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitriseStudio.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13C989801D8319600028BA2C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 13C989821D8319600028BA2C /* ProjectsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectsViewController.swift; sourceTree = ""; }; + 13C989841D8319600028BA2C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 13C989871D8319600028BA2C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 13C989891D8319600028BA2C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13C9898E1D8319600028BA2C /* BitriseStudioTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitriseStudioTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 13C989921D8319600028BA2C /* BitriseStudioTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitriseStudioTests.swift; sourceTree = ""; }; + 13C989941D8319600028BA2C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13C989991D8319600028BA2C /* BitriseStudioUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitriseStudioUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 13C9899D1D8319600028BA2C /* BitriseStudioUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitriseStudioUITests.swift; sourceTree = ""; }; + 13C9899F1D8319600028BA2C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13CA73D51D84A74800B1A323 /* AddProjectViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddProjectViewController.swift; sourceTree = ""; }; + 13CA73DD1D84B67200B1A323 /* Project.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Project.swift; sourceTree = ""; }; + 13FF5FCD1D9859EE008C7DFB /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; + 13FF5FD01D98620A008C7DFB /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; + 13FF5FD21D9862DA008C7DFB /* Data+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = ""; }; + 13FF5FD41D9872EC008C7DFB /* Pipe+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Pipe+Extensions.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13C9897A1D83195F0028BA2C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 131A3D9C1D90543F002DAF99 /* RealmSwift.framework in Frameworks */, + 131A3D9A1D90543F002DAF99 /* Realm.framework in Frameworks */, + 131A3DA71D9060C0002DAF99 /* Yaml.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C9898B1D8319600028BA2C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C989961D8319600028BA2C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1302F7421D95BA35005CE678 /* Bash */ = { + isa = PBXGroup; + children = ( + 1302F7431D95BA4A005CE678 /* Session.swift */, + 138F9EE61D8E7ABC00515FCA /* Command.swift */, + ); + name = Bash; + sourceTree = ""; + }; + 131ACE741D9323C1007E71E9 /* Version */ = { + isa = PBXGroup; + children = ( + 131ACE751D9323E3007E71E9 /* Version.swift */, + ); + name = Version; + sourceTree = ""; + }; + 13C989741D83195F0028BA2C = { + isa = PBXGroup; + children = ( + 13C9897F1D8319600028BA2C /* BitriseStudio */, + 13C989911D8319600028BA2C /* BitriseStudioTests */, + 13C9899C1D8319600028BA2C /* BitriseStudioUITests */, + 13C9897E1D8319600028BA2C /* Products */, + 13CA73D71D84B5C500B1A323 /* Frameworks */, + ); + sourceTree = ""; + }; + 13C9897E1D8319600028BA2C /* Products */ = { + isa = PBXGroup; + children = ( + 13C9897D1D8319600028BA2C /* BitriseStudio.app */, + 13C9898E1D8319600028BA2C /* BitriseStudioTests.xctest */, + 13C989991D8319600028BA2C /* BitriseStudioUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 13C9897F1D8319600028BA2C /* BitriseStudio */ = { + isa = PBXGroup; + children = ( + 13FF5FCF1D9861E3008C7DFB /* Extensions */, + 13FF5FCC1D9859DB008C7DFB /* Log */, + 1302F7421D95BA35005CE678 /* Bash */, + 131ACE741D9323C1007E71E9 /* Version */, + 13CA73DC1D84B63E00B1A323 /* Models */, + 13E3F5501D8342DD00AE7C20 /* Managers */, + 13E3F54D1D8341DF00AE7C20 /* Controllers */, + 13E3F54E1D83429900AE7C20 /* Supporting Files */, + 13E3F54F1D8342BC00AE7C20 /* Assets */, + 13C989801D8319600028BA2C /* AppDelegate.swift */, + ); + path = BitriseStudio; + sourceTree = ""; + }; + 13C989911D8319600028BA2C /* BitriseStudioTests */ = { + isa = PBXGroup; + children = ( + 13C989921D8319600028BA2C /* BitriseStudioTests.swift */, + 13C989941D8319600028BA2C /* Info.plist */, + ); + path = BitriseStudioTests; + sourceTree = ""; + }; + 13C9899C1D8319600028BA2C /* BitriseStudioUITests */ = { + isa = PBXGroup; + children = ( + 13C9899D1D8319600028BA2C /* BitriseStudioUITests.swift */, + 13C9899F1D8319600028BA2C /* Info.plist */, + ); + path = BitriseStudioUITests; + sourceTree = ""; + }; + 13CA73D71D84B5C500B1A323 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 131A3DA11D90609E002DAF99 /* Yaml.framework */, + 131A3D961D9053BB002DAF99 /* Realm.framework */, + 131A3D971D9053BB002DAF99 /* RealmSwift.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 13CA73DC1D84B63E00B1A323 /* Models */ = { + isa = PBXGroup; + children = ( + 13CA73DD1D84B67200B1A323 /* Project.swift */, + 131A3DAA1D906BFA002DAF99 /* BitriseTool.swift */, + 131A3DAC1D906D54002DAF99 /* Bitrise.swift */, + 131A3DAE1D906DAB002DAF99 /* Envman.swift */, + 131A3DB01D906DBF002DAF99 /* Stepman.swift */, + ); + name = Models; + sourceTree = ""; + }; + 13E3F54D1D8341DF00AE7C20 /* Controllers */ = { + isa = PBXGroup; + children = ( + 13C989861D8319600028BA2C /* Main.storyboard */, + 13C989821D8319600028BA2C /* ProjectsViewController.swift */, + 13CA73D51D84A74800B1A323 /* AddProjectViewController.swift */, + 13B958CC1D89E87600D3310D /* SystemInfoViewController.swift */, + 13B958CE1D89EC7800D3310D /* RunViewController.swift */, + 13A95E811D966F040061B54F /* BashSessionViewController.swift */, + ); + name = Controllers; + sourceTree = ""; + }; + 13E3F54E1D83429900AE7C20 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 13C989891D8319600028BA2C /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 13E3F54F1D8342BC00AE7C20 /* Assets */ = { + isa = PBXGroup; + children = ( + 13C989841D8319600028BA2C /* Assets.xcassets */, + ); + name = Assets; + sourceTree = ""; + }; + 13E3F5501D8342DD00AE7C20 /* Managers */ = { + isa = PBXGroup; + children = ( + 13AAE1BC1D8426FF00AEE66D /* FileManager.swift */, + 13B0B8F21D872E93006EA29C /* RealmManager.swift */, + 131ACE721D93054B007E71E9 /* ToolManager.swift */, + ); + name = Managers; + sourceTree = ""; + }; + 13FF5FCC1D9859DB008C7DFB /* Log */ = { + isa = PBXGroup; + children = ( + 13FF5FCD1D9859EE008C7DFB /* Log.swift */, + ); + name = Log; + sourceTree = ""; + }; + 13FF5FCF1D9861E3008C7DFB /* Extensions */ = { + isa = PBXGroup; + children = ( + 13FF5FD01D98620A008C7DFB /* String+Extensions.swift */, + 13FF5FD21D9862DA008C7DFB /* Data+Extensions.swift */, + 13FF5FD41D9872EC008C7DFB /* Pipe+Extensions.swift */, + ); + name = Extensions; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13C9897C1D83195F0028BA2C /* BitriseStudio */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13C989A21D8319600028BA2C /* Build configuration list for PBXNativeTarget "BitriseStudio" */; + buildPhases = ( + 13C989791D83195F0028BA2C /* Sources */, + 13C9897A1D83195F0028BA2C /* Frameworks */, + 13C9897B1D83195F0028BA2C /* Resources */, + 131A3D9E1D90543F002DAF99 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BitriseStudio; + productName = BitriseStudio; + productReference = 13C9897D1D8319600028BA2C /* BitriseStudio.app */; + productType = "com.apple.product-type.application"; + }; + 13C9898D1D8319600028BA2C /* BitriseStudioTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13C989A51D8319600028BA2C /* Build configuration list for PBXNativeTarget "BitriseStudioTests" */; + buildPhases = ( + 13C9898A1D8319600028BA2C /* Sources */, + 13C9898B1D8319600028BA2C /* Frameworks */, + 13C9898C1D8319600028BA2C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 13C989901D8319600028BA2C /* PBXTargetDependency */, + ); + name = BitriseStudioTests; + productName = BitriseStudioTests; + productReference = 13C9898E1D8319600028BA2C /* BitriseStudioTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13C989981D8319600028BA2C /* BitriseStudioUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13C989A81D8319600028BA2C /* Build configuration list for PBXNativeTarget "BitriseStudioUITests" */; + buildPhases = ( + 13C989951D8319600028BA2C /* Sources */, + 13C989961D8319600028BA2C /* Frameworks */, + 13C989971D8319600028BA2C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 13C9899B1D8319600028BA2C /* PBXTargetDependency */, + ); + name = BitriseStudioUITests; + productName = BitriseStudioUITests; + productReference = 13C989991D8319600028BA2C /* BitriseStudioUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 13C989751D83195F0028BA2C /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0800; + LastUpgradeCheck = 0810; + ORGANIZATIONNAME = "Krisztian Goedrei"; + TargetAttributes = { + 13C9897C1D83195F0028BA2C = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = 9NS44DLTN7; + ProvisioningStyle = Manual; + }; + 13C9898D1D8319600028BA2C = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = L935L4GU3F; + ProvisioningStyle = Automatic; + TestTargetID = 13C9897C1D83195F0028BA2C; + }; + 13C989981D8319600028BA2C = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = L935L4GU3F; + ProvisioningStyle = Automatic; + TestTargetID = 13C9897C1D83195F0028BA2C; + }; + }; + }; + buildConfigurationList = 13C989781D83195F0028BA2C /* Build configuration list for PBXProject "BitriseStudio" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 13C989741D83195F0028BA2C; + productRefGroup = 13C9897E1D8319600028BA2C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13C9897C1D83195F0028BA2C /* BitriseStudio */, + 13C9898D1D8319600028BA2C /* BitriseStudioTests */, + 13C989981D8319600028BA2C /* BitriseStudioUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 13C9897B1D83195F0028BA2C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C989851D8319600028BA2C /* Assets.xcassets in Resources */, + 13C989881D8319600028BA2C /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C9898C1D8319600028BA2C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C989971D8319600028BA2C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13C989791D83195F0028BA2C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13FF5FD11D98620A008C7DFB /* String+Extensions.swift in Sources */, + 131A3DB11D906DBF002DAF99 /* Stepman.swift in Sources */, + 13B0B8F31D872E93006EA29C /* RealmManager.swift in Sources */, + 13FF5FCE1D9859EE008C7DFB /* Log.swift in Sources */, + 131A3DAB1D906BFA002DAF99 /* BitriseTool.swift in Sources */, + 13E3F5531D83477300AE7C20 /* ProjectsViewController.swift in Sources */, + 130E6BBE1D95BBB4009D3C78 /* Command.swift in Sources */, + 13FF5FD51D9872EC008C7DFB /* Pipe+Extensions.swift in Sources */, + 131ACE731D93054B007E71E9 /* ToolManager.swift in Sources */, + 13A95E821D966F040061B54F /* BashSessionViewController.swift in Sources */, + 13C989811D8319600028BA2C /* AppDelegate.swift in Sources */, + 1302F7441D95BA4A005CE678 /* Session.swift in Sources */, + 131ACE761D9323E3007E71E9 /* Version.swift in Sources */, + 13B958CD1D89E87600D3310D /* SystemInfoViewController.swift in Sources */, + 13CA73D61D84A74800B1A323 /* AddProjectViewController.swift in Sources */, + 131A3DAD1D906D54002DAF99 /* Bitrise.swift in Sources */, + 13FF5FD31D9862DA008C7DFB /* Data+Extensions.swift in Sources */, + 13AAE1BD1D8426FF00AEE66D /* FileManager.swift in Sources */, + 13B958D01D89EC7800D3310D /* RunViewController.swift in Sources */, + 131A3DAF1D906DAB002DAF99 /* Envman.swift in Sources */, + 13CA73DE1D84B67200B1A323 /* Project.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C9898A1D8319600028BA2C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C989931D8319600028BA2C /* BitriseStudioTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C989951D8319600028BA2C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C9899E1D8319600028BA2C /* BitriseStudioUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 13C989901D8319600028BA2C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13C9897C1D83195F0028BA2C /* BitriseStudio */; + targetProxy = 13C9898F1D8319600028BA2C /* PBXContainerItemProxy */; + }; + 13C9899B1D8319600028BA2C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13C9897C1D83195F0028BA2C /* BitriseStudio */; + targetProxy = 13C9899A1D8319600028BA2C /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 13C989861D8319600028BA2C /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 13C989871D8319600028BA2C /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 13C989A01D8319600028BA2C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 13C989A11D8319600028BA2C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + }; + name = Release; + }; + 13C989A31D8319600028BA2C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Mac Developer: Some Dude (KYXQXCWE3G)"; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 9NS44DLTN7; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + "$(PROJECT_DIR)/Framworks", + ); + INFOPLIST_FILE = BitriseStudio/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseStudio; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "b17a1b90-9459-4620-9332-347d399f7cd9"; + PROVISIONING_PROFILE_SPECIFIER = "Mac Development Wildcard"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 13C989A41D8319600028BA2C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 9NS44DLTN7; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + "$(PROJECT_DIR)/Framworks", + ); + INFOPLIST_FILE = BitriseStudio/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseStudio; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "1bb807b8-a953-459e-85ca-c86d3fe13645"; + PROVISIONING_PROFILE_SPECIFIER = "Mac App-Store Wildcards"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + 13C989A61D8319600028BA2C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = L935L4GU3F; + INFOPLIST_FILE = BitriseStudioTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = bitrise.BitriseStudioTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BitriseStudio.app/Contents/MacOS/BitriseStudio"; + }; + name = Debug; + }; + 13C989A71D8319600028BA2C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = L935L4GU3F; + INFOPLIST_FILE = BitriseStudioTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = bitrise.BitriseStudioTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BitriseStudio.app/Contents/MacOS/BitriseStudio"; + }; + name = Release; + }; + 13C989A91D8319600028BA2C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = L935L4GU3F; + INFOPLIST_FILE = BitriseStudioUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = bitrise.BitriseStudioUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_TARGET_NAME = BitriseStudio; + }; + name = Debug; + }; + 13C989AA1D8319600028BA2C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = L935L4GU3F; + INFOPLIST_FILE = BitriseStudioUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = bitrise.BitriseStudioUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_TARGET_NAME = BitriseStudio; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13C989781D83195F0028BA2C /* Build configuration list for PBXProject "BitriseStudio" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C989A01D8319600028BA2C /* Debug */, + 13C989A11D8319600028BA2C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13C989A21D8319600028BA2C /* Build configuration list for PBXNativeTarget "BitriseStudio" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C989A31D8319600028BA2C /* Debug */, + 13C989A41D8319600028BA2C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13C989A51D8319600028BA2C /* Build configuration list for PBXNativeTarget "BitriseStudioTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C989A61D8319600028BA2C /* Debug */, + 13C989A71D8319600028BA2C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13C989A81D8319600028BA2C /* Build configuration list for PBXNativeTarget "BitriseStudioUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C989A91D8319600028BA2C /* Debug */, + 13C989AA1D8319600028BA2C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 13C989751D83195F0028BA2C /* Project object */; +} +` + +const testIOSPbxprojContent = ` +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 13C4D5AB1DDDDED300D5DC29 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C4D5AA1DDDDED300D5DC29 /* AppDelegate.swift */; }; + 13C4D5AD1DDDDED300D5DC29 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C4D5AC1DDDDED300D5DC29 /* ViewController.swift */; }; + 13C4D5B01DDDDED300D5DC29 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13C4D5AE1DDDDED300D5DC29 /* Main.storyboard */; }; + 13C4D5B21DDDDED300D5DC29 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13C4D5B11DDDDED300D5DC29 /* Assets.xcassets */; }; + 13C4D5B51DDDDED300D5DC29 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13C4D5B31DDDDED300D5DC29 /* LaunchScreen.storyboard */; }; + 13C4D5C01DDDDED400D5DC29 /* BitriseFastlaneSampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C4D5BF1DDDDED400D5DC29 /* BitriseFastlaneSampleTests.swift */; }; + 13C4D5CB1DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C4D5CA1DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 13C4D5BC1DDDDED400D5DC29 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 13C4D59F1DDDDED300D5DC29 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13C4D5A61DDDDED300D5DC29; + remoteInfo = BitriseFastlaneSample; + }; + 13C4D5C71DDDDED400D5DC29 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 13C4D59F1DDDDED300D5DC29 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13C4D5A61DDDDED300D5DC29; + remoteInfo = BitriseFastlaneSample; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 13C4D5A71DDDDED300D5DC29 /* BitriseFastlaneSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitriseFastlaneSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13C4D5AA1DDDDED300D5DC29 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 13C4D5AC1DDDDED300D5DC29 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 13C4D5AF1DDDDED300D5DC29 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 13C4D5B11DDDDED300D5DC29 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 13C4D5B41DDDDED300D5DC29 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 13C4D5B61DDDDED300D5DC29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13C4D5BB1DDDDED400D5DC29 /* BitriseFastlaneSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitriseFastlaneSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 13C4D5BF1DDDDED400D5DC29 /* BitriseFastlaneSampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitriseFastlaneSampleTests.swift; sourceTree = ""; }; + 13C4D5C11DDDDED400D5DC29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13C4D5C61DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitriseFastlaneSampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 13C4D5CA1DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitriseFastlaneSampleUITests.swift; sourceTree = ""; }; + 13C4D5CC1DDDDED400D5DC29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13C4D5A41DDDDED300D5DC29 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C4D5B81DDDDED400D5DC29 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C4D5C31DDDDED400D5DC29 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 13C4D59E1DDDDED300D5DC29 = { + isa = PBXGroup; + children = ( + 13C4D5A91DDDDED300D5DC29 /* BitriseFastlaneSample */, + 13C4D5BE1DDDDED400D5DC29 /* BitriseFastlaneSampleTests */, + 13C4D5C91DDDDED400D5DC29 /* BitriseFastlaneSampleUITests */, + 13C4D5A81DDDDED300D5DC29 /* Products */, + ); + sourceTree = ""; + }; + 13C4D5A81DDDDED300D5DC29 /* Products */ = { + isa = PBXGroup; + children = ( + 13C4D5A71DDDDED300D5DC29 /* BitriseFastlaneSample.app */, + 13C4D5BB1DDDDED400D5DC29 /* BitriseFastlaneSampleTests.xctest */, + 13C4D5C61DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 13C4D5A91DDDDED300D5DC29 /* BitriseFastlaneSample */ = { + isa = PBXGroup; + children = ( + 13C4D5AA1DDDDED300D5DC29 /* AppDelegate.swift */, + 13C4D5AC1DDDDED300D5DC29 /* ViewController.swift */, + 13C4D5AE1DDDDED300D5DC29 /* Main.storyboard */, + 13C4D5B11DDDDED300D5DC29 /* Assets.xcassets */, + 13C4D5B31DDDDED300D5DC29 /* LaunchScreen.storyboard */, + 13C4D5B61DDDDED300D5DC29 /* Info.plist */, + ); + path = BitriseFastlaneSample; + sourceTree = ""; + }; + 13C4D5BE1DDDDED400D5DC29 /* BitriseFastlaneSampleTests */ = { + isa = PBXGroup; + children = ( + 13C4D5BF1DDDDED400D5DC29 /* BitriseFastlaneSampleTests.swift */, + 13C4D5C11DDDDED400D5DC29 /* Info.plist */, + ); + path = BitriseFastlaneSampleTests; + sourceTree = ""; + }; + 13C4D5C91DDDDED400D5DC29 /* BitriseFastlaneSampleUITests */ = { + isa = PBXGroup; + children = ( + 13C4D5CA1DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.swift */, + 13C4D5CC1DDDDED400D5DC29 /* Info.plist */, + ); + path = BitriseFastlaneSampleUITests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13C4D5A61DDDDED300D5DC29 /* BitriseFastlaneSample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13C4D5CF1DDDDED400D5DC29 /* Build configuration list for PBXNativeTarget "BitriseFastlaneSample" */; + buildPhases = ( + 13C4D5A31DDDDED300D5DC29 /* Sources */, + 13C4D5A41DDDDED300D5DC29 /* Frameworks */, + 13C4D5A51DDDDED300D5DC29 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BitriseFastlaneSample; + productName = BitriseFastlaneSample; + productReference = 13C4D5A71DDDDED300D5DC29 /* BitriseFastlaneSample.app */; + productType = "com.apple.product-type.application"; + }; + 13C4D5BA1DDDDED400D5DC29 /* BitriseFastlaneSampleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13C4D5D21DDDDED400D5DC29 /* Build configuration list for PBXNativeTarget "BitriseFastlaneSampleTests" */; + buildPhases = ( + 13C4D5B71DDDDED400D5DC29 /* Sources */, + 13C4D5B81DDDDED400D5DC29 /* Frameworks */, + 13C4D5B91DDDDED400D5DC29 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 13C4D5BD1DDDDED400D5DC29 /* PBXTargetDependency */, + ); + name = BitriseFastlaneSampleTests; + productName = BitriseFastlaneSampleTests; + productReference = 13C4D5BB1DDDDED400D5DC29 /* BitriseFastlaneSampleTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13C4D5C51DDDDED400D5DC29 /* BitriseFastlaneSampleUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13C4D5D51DDDDED400D5DC29 /* Build configuration list for PBXNativeTarget "BitriseFastlaneSampleUITests" */; + buildPhases = ( + 13C4D5C21DDDDED400D5DC29 /* Sources */, + 13C4D5C31DDDDED400D5DC29 /* Frameworks */, + 13C4D5C41DDDDED400D5DC29 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 13C4D5C81DDDDED400D5DC29 /* PBXTargetDependency */, + ); + name = BitriseFastlaneSampleUITests; + productName = BitriseFastlaneSampleUITests; + productReference = 13C4D5C61DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 13C4D59F1DDDDED300D5DC29 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0810; + LastUpgradeCheck = 0810; + ORGANIZATIONNAME = "Krisztian Goedrei"; + TargetAttributes = { + 13C4D5A61DDDDED300D5DC29 = { + CreatedOnToolsVersion = 8.1; + DevelopmentTeam = 9NS44DLTN7; + ProvisioningStyle = Manual; + }; + 13C4D5BA1DDDDED400D5DC29 = { + CreatedOnToolsVersion = 8.1; + DevelopmentTeam = 72SA8V3WYL; + ProvisioningStyle = Automatic; + TestTargetID = 13C4D5A61DDDDED300D5DC29; + }; + 13C4D5C51DDDDED400D5DC29 = { + CreatedOnToolsVersion = 8.1; + DevelopmentTeam = 72SA8V3WYL; + ProvisioningStyle = Automatic; + TestTargetID = 13C4D5A61DDDDED300D5DC29; + }; + }; + }; + buildConfigurationList = 13C4D5A21DDDDED300D5DC29 /* Build configuration list for PBXProject "BitriseFastlaneSample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 13C4D59E1DDDDED300D5DC29; + productRefGroup = 13C4D5A81DDDDED300D5DC29 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13C4D5A61DDDDED300D5DC29 /* BitriseFastlaneSample */, + 13C4D5BA1DDDDED400D5DC29 /* BitriseFastlaneSampleTests */, + 13C4D5C51DDDDED400D5DC29 /* BitriseFastlaneSampleUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 13C4D5A51DDDDED300D5DC29 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C4D5B51DDDDED300D5DC29 /* LaunchScreen.storyboard in Resources */, + 13C4D5B21DDDDED300D5DC29 /* Assets.xcassets in Resources */, + 13C4D5B01DDDDED300D5DC29 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C4D5B91DDDDED400D5DC29 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C4D5C41DDDDED400D5DC29 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13C4D5A31DDDDED300D5DC29 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C4D5AD1DDDDED300D5DC29 /* ViewController.swift in Sources */, + 13C4D5AB1DDDDED300D5DC29 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C4D5B71DDDDED400D5DC29 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C4D5C01DDDDED400D5DC29 /* BitriseFastlaneSampleTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C4D5C21DDDDED400D5DC29 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C4D5CB1DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 13C4D5BD1DDDDED400D5DC29 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13C4D5A61DDDDED300D5DC29 /* BitriseFastlaneSample */; + targetProxy = 13C4D5BC1DDDDED400D5DC29 /* PBXContainerItemProxy */; + }; + 13C4D5C81DDDDED400D5DC29 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13C4D5A61DDDDED300D5DC29 /* BitriseFastlaneSample */; + targetProxy = 13C4D5C71DDDDED400D5DC29 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 13C4D5AE1DDDDED300D5DC29 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 13C4D5AF1DDDDED300D5DC29 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 13C4D5B31DDDDED300D5DC29 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 13C4D5B41DDDDED300D5DC29 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 13C4D5CD1DDDDED400D5DC29 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 13C4D5CE1DDDDED400D5DC29 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 13C4D5D01DDDDED400D5DC29 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + DEVELOPMENT_TEAM = 9NS44DLTN7; + INFOPLIST_FILE = BitriseFastlaneSample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseFastlaneSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "8e4701a8-01fb-4467-aad7-5a6c541795f0"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.bitrise.BitriseFastlaneSample"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 13C4D5D11DDDDED400D5DC29 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + DEVELOPMENT_TEAM = 9NS44DLTN7; + INFOPLIST_FILE = BitriseFastlaneSample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseFastlaneSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "8e4701a8-01fb-4467-aad7-5a6c541795f0"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.bitrise.BitriseFastlaneSample"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + 13C4D5D31DDDDED400D5DC29 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = 72SA8V3WYL; + INFOPLIST_FILE = BitriseFastlaneSampleTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseFastlaneSampleTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BitriseFastlaneSample.app/BitriseFastlaneSample"; + }; + name = Debug; + }; + 13C4D5D41DDDDED400D5DC29 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = 72SA8V3WYL; + INFOPLIST_FILE = BitriseFastlaneSampleTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseFastlaneSampleTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BitriseFastlaneSample.app/BitriseFastlaneSample"; + }; + name = Release; + }; + 13C4D5D61DDDDED400D5DC29 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + DEVELOPMENT_TEAM = 72SA8V3WYL; + INFOPLIST_FILE = BitriseFastlaneSampleUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseFastlaneSampleUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_TARGET_NAME = BitriseFastlaneSample; + }; + name = Debug; + }; + 13C4D5D71DDDDED400D5DC29 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + DEVELOPMENT_TEAM = 72SA8V3WYL; + INFOPLIST_FILE = BitriseFastlaneSampleUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseFastlaneSampleUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_TARGET_NAME = BitriseFastlaneSample; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13C4D5A21DDDDED300D5DC29 /* Build configuration list for PBXProject "BitriseFastlaneSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C4D5CD1DDDDED400D5DC29 /* Debug */, + 13C4D5CE1DDDDED400D5DC29 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13C4D5CF1DDDDED400D5DC29 /* Build configuration list for PBXNativeTarget "BitriseFastlaneSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C4D5D01DDDDED400D5DC29 /* Debug */, + 13C4D5D11DDDDED400D5DC29 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13C4D5D21DDDDED400D5DC29 /* Build configuration list for PBXNativeTarget "BitriseFastlaneSampleTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C4D5D31DDDDED400D5DC29 /* Debug */, + 13C4D5D41DDDDED400D5DC29 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13C4D5D51DDDDED400D5DC29 /* Build configuration list for PBXNativeTarget "BitriseFastlaneSampleUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C4D5D61DDDDED400D5DC29 /* Debug */, + 13C4D5D71DDDDED400D5DC29 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 13C4D59F1DDDDED300D5DC29 /* Project object */; +} +` diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/macos/macos.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/macos/macos.go new file mode 100644 index 00000000..c252b677 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/macos/macos.go @@ -0,0 +1,70 @@ +package macos + +import ( + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/scanners/ios" +) + +//------------------ +// ScannerInterface +//------------------ + +// Scanner ... +type Scanner struct { + searchDir string + configDescriptors []ios.ConfigDescriptor +} + +// NewScanner ... +func NewScanner() *Scanner { + return &Scanner{} +} + +// Name ... +func (Scanner) Name() string { + return string(ios.XcodeProjectTypeMacOS) +} + +// DetectPlatform ... +func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { + scanner.searchDir = searchDir + + detected, err := ios.Detect(ios.XcodeProjectTypeMacOS, searchDir) + if err != nil { + return false, err + } + + return detected, nil +} + +// ExcludedScannerNames ... +func (Scanner) ExcludedScannerNames() []string { + return []string{} +} + +// Options ... +func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { + options, configDescriptors, warnings, err := ios.GenerateOptions(ios.XcodeProjectTypeMacOS, scanner.searchDir) + if err != nil { + return models.OptionNode{}, warnings, err + } + + scanner.configDescriptors = configDescriptors + + return options, warnings, nil +} + +// DefaultOptions ... +func (Scanner) DefaultOptions() models.OptionNode { + return ios.GenerateDefaultOptions(ios.XcodeProjectTypeMacOS) +} + +// Configs ... +func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { + return ios.GenerateConfig(ios.XcodeProjectTypeMacOS, scanner.configDescriptors, true) +} + +// DefaultConfigs ... +func (Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { + return ios.GenerateDefaultConfig(ios.XcodeProjectTypeMacOS, true) +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative-expo/reactnative-expo.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative-expo/reactnative-expo.go new file mode 100644 index 00000000..f6ef7576 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative-expo/reactnative-expo.go @@ -0,0 +1,785 @@ +package expo + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/scanners/android" + "github.com/bitrise-core/bitrise-init/scanners/ios" + "github.com/bitrise-core/bitrise-init/scanners/reactnative" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-core/bitrise-init/utility" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-tools/xcode-project/serialized" + yaml "gopkg.in/yaml.v2" +) + +const ( + configName = "react-native-expo-config" + defaultConfigName = "default-" + configName +) + +const deployWorkflowDescription = `## Configure Android part of the deploy workflow + +To generate a signed APK: + +1. Open the **Workflow** tab of your project on Bitrise.io +1. Add **Sign APK step right after Android Build step** +1. Click on **Code Signing** tab +1. Find the **ANDROID KEYSTORE FILE** section +1. Click or drop your file on the upload file field +1. Fill the displayed 3 input fields: +1. **Keystore password** +1. **Keystore alias** +1. **Private key password** +1. Click on **[Save metadata]** button + +That's it! From now on, **Sign APK** step will receive your uploaded files. + +## Configure iOS part of the deploy workflow + +To generate IPA: + +1. Open the **Workflow** tab of your project on Bitrise.io +1. Click on **Code Signing** tab +1. Find the **PROVISIONING PROFILE** section +1. Click or drop your file on the upload file field +1. Find the **CODE SIGNING IDENTITY** section +1. Click or drop your file on the upload file field +1. Click on **Workflows** tab +1. Select deploy workflow +1. Select **Xcode Archive & Export for iOS** step +1. Open **Force Build Settings** input group +1. Specify codesign settings +Set **Force code signing with Development Team**, **Force code signing with Code Signing Identity** +and **Force code signing with Provisioning Profile** inputs regarding to the uploaded codesigning files +1. Specify manual codesign style +If the codesigning files, are generated manually on the Apple Developer Portal, +you need to explicitly specify to use manual coedsign settings +(as ejected rn projects have xcode managed codesigning turned on). +To do so, add 'CODE_SIGN_STYLE="Manual"' to 'Additional options for xcodebuild call' input + +## To run this workflow + +If you want to run this workflow manually: + +1. Open the app's build list page +2. Click on **[Start/Schedule a Build]** button +3. Select **deploy** in **Workflow** dropdown input +4. Click **[Start Build]** button + +Or if you need this workflow to be started by a GIT event: + +1. Click on **Triggers** tab +2. Setup your desired event (push/tag/pull) and select **deploy** workflow +3. Click on **[Done]** and then **[Save]** buttons + +The next change in your repository that matches any of your trigger map event will start **deploy** workflow. +` + +// Name ... +const Name = "react-native-expo" + +// Scanner ... +type Scanner struct { + searchDir string + packageJSONPth string + usesExpoKit bool +} + +// NewScanner ... +func NewScanner() *Scanner { + return &Scanner{} +} + +// Name ... +func (Scanner) Name() string { + return Name +} + +// DetectPlatform ... +func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { + scanner.searchDir = searchDir + + log.TInfof("Collect package.json files") + + packageJSONPths, err := reactnative.CollectPackageJSONFiles(searchDir) + if err != nil { + return false, err + } + + if len(packageJSONPths) == 0 { + return false, nil + } + + log.TPrintf("%d package.json file detected", len(packageJSONPths)) + for _, pth := range packageJSONPths { + log.TPrintf("- %s", pth) + } + log.TPrintf("") + + log.TInfof("Filter package.json files with expo dependency") + + relevantPackageJSONPths := []string{} + for _, packageJSONPth := range packageJSONPths { + packages, err := utility.ParsePackagesJSON(packageJSONPth) + if err != nil { + log.Warnf("Failed to parse package json file: %s, skipping...", packageJSONPth) + continue + } + + _, found := packages.Dependencies["expo"] + if !found { + continue + } + + // app.json file is a required part of react native projects and it exists next to the root package.json file + appJSONPth := filepath.Join(filepath.Dir(packageJSONPth), "app.json") + if exist, err := pathutil.IsPathExists(appJSONPth); err != nil { + log.Warnf("Failed to check if app.json file exist at: %s, skipping package json file: %s, error: %s", appJSONPth, packageJSONPth, err) + continue + } else if !exist { + log.Warnf("No app.json file exist at: %s, skipping package json file: %s", appJSONPth, packageJSONPth) + continue + } + + relevantPackageJSONPths = append(relevantPackageJSONPths, packageJSONPth) + } + + log.TPrintf("%d package.json file detected with expo dependency", len(relevantPackageJSONPths)) + for _, pth := range relevantPackageJSONPths { + log.TPrintf("- %s", pth) + } + log.TPrintf("") + + if len(relevantPackageJSONPths) == 0 { + return false, nil + } else if len(relevantPackageJSONPths) > 1 { + log.TWarnf("Multiple package.json file found, using: %s\n", relevantPackageJSONPths[0]) + } + + scanner.packageJSONPth = relevantPackageJSONPths[0] + return true, nil +} + +func appJSONIssue(appJSONPth, reason, explanation string) string { + return fmt.Sprintf("app.json file (%s) %s\n%s", appJSONPth, reason, explanation) +} + +// Options ... +func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { + warnings := models.Warnings{} + + // we need to know if the project uses the Expo Kit, + // since its usage differentiates the eject process and the config options + usesExpoKit := false + + fileList, err := utility.ListPathInDirSortedByComponents(scanner.searchDir, true) + if err != nil { + return models.OptionNode{}, warnings, err + } + + filters := []utility.FilterFunc{ + utility.ExtensionFilter(".js", true), + utility.ComponentFilter("node_modules", false), + } + sourceFiles, err := utility.FilterPaths(fileList, filters...) + if err != nil { + return models.OptionNode{}, warnings, err + } + + re := regexp.MustCompile(`import .* from 'expo'`) + +SourceFileLoop: + for _, sourceFile := range sourceFiles { + f, err := os.Open(sourceFile) + if err != nil { + return models.OptionNode{}, warnings, err + } + defer func() { + if cerr := f.Close(); cerr != nil { + log.Warnf("Failed to close: %s, error: %s", f.Name(), err) + } + }() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + if match := re.FindString(scanner.Text()); match != "" { + usesExpoKit = true + break SourceFileLoop + } + } + if err := scanner.Err(); err != nil { + return models.OptionNode{}, warnings, err + } + } + + scanner.usesExpoKit = usesExpoKit + log.TPrintf("Uses ExpoKit: %v", usesExpoKit) + + // ensure app.json contains the required information (for non interactive eject) + // and predict the ejected project name + var projectName string + + rootDir := filepath.Dir(scanner.packageJSONPth) + appJSONPth := filepath.Join(rootDir, "app.json") + appJSON, err := fileutil.ReadStringFromFile(appJSONPth) + if err != nil { + return models.OptionNode{}, warnings, err + } + var app serialized.Object + if err := json.Unmarshal([]byte(appJSON), &app); err != nil { + return models.OptionNode{}, warnings, err + } + + if usesExpoKit { + // if the project uses Expo Kit app.json needs to contain expo/ios/bundleIdentifier and expo/android/package entries + // to be able to eject in non interactive mode + errorMessage := `If the project uses Expo Kit the app.json file needs to contain: +- expo/name +- expo/ios/bundleIdentifier +- expo/android/package +entries.` + + expoObj, err := app.Object("expo") + if err != nil { + return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing expo entry", errorMessage)) + } + projectName, err = expoObj.String("name") + if err != nil || projectName == "" { + return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing or empty expo/name entry", errorMessage)) + } + + iosObj, err := expoObj.Object("ios") + if err != nil { + return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing expo/ios entry", errorMessage)) + } + bundleID, err := iosObj.String("bundleIdentifier") + if err != nil || bundleID == "" { + return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing or empty expo/ios/bundleIdentifier entry", errorMessage)) + } + + androidObj, err := expoObj.Object("android") + if err != nil { + return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing expo/android entry", errorMessage)) + } + packageName, err := androidObj.String("package") + if err != nil || packageName == "" { + return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing or empty expo/android/package entry", errorMessage)) + } + } else { + // if the project does not use Expo Kit app.json needs to contain name and displayName entries + // to be able to eject in non interactive mode + errorMessage := `The app.json file needs to contain: +- name +- displayName +entries.` + + projectName, err = app.String("name") + if err != nil || projectName == "" { + return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing or empty name entry", errorMessage)) + } + displayName, err := app.String("displayName") + if err != nil || displayName == "" { + return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing or empty displayName entry", errorMessage)) + } + } + + log.TPrintf("Project name: %v", projectName) + + // ios options + projectPathOption := models.NewOption(ios.ProjectPathInputTitle, ios.ProjectPathInputEnvKey) + schemeOption := models.NewOption(ios.SchemeInputTitle, ios.SchemeInputEnvKey) + + if usesExpoKit { + projectName = strings.ToLower(regexp.MustCompile(`(?i:[^a-z0-9_\-])`).ReplaceAllString(projectName, "-")) + projectPathOption.AddOption(filepath.Join("./", "ios", projectName+".xcworkspace"), schemeOption) + } else { + projectPathOption.AddOption(filepath.Join("./", "ios", projectName+".xcodeproj"), schemeOption) + } + + developmentTeamOption := models.NewOption("iOS Development team", "BITRISE_IOS_DEVELOPMENT_TEAM") + schemeOption.AddOption(projectName, developmentTeamOption) + + exportMethodOption := models.NewOption(ios.IosExportMethodInputTitle, ios.ExportMethodInputEnvKey) + developmentTeamOption.AddOption("_", exportMethodOption) + + // android options + packageJSONDir := filepath.Dir(scanner.packageJSONPth) + relPackageJSONDir, err := utility.RelPath(scanner.searchDir, packageJSONDir) + if err != nil { + return models.OptionNode{}, warnings, fmt.Errorf("Failed to get relative package.json dir path, error: %s", err) + } + if relPackageJSONDir == "." { + // package.json placed in the search dir, no need to change-dir in the workflows + relPackageJSONDir = "" + } + + var moduleOption *models.OptionNode + if relPackageJSONDir == "" { + projectLocationOption := models.NewOption(android.ProjectLocationInputTitle, android.ProjectLocationInputEnvKey) + for _, exportMethod := range ios.IosExportMethods { + exportMethodOption.AddOption(exportMethod, projectLocationOption) + } + + moduleOption = models.NewOption(android.ModuleInputTitle, android.ModuleInputEnvKey) + projectLocationOption.AddOption("./android", moduleOption) + } else { + workDirOption := models.NewOption("Project root directory (the directory of the project app.json/package.json file)", "WORKDIR") + for _, exportMethod := range ios.IosExportMethods { + exportMethodOption.AddOption(exportMethod, workDirOption) + } + + projectLocationOption := models.NewOption(android.ProjectLocationInputTitle, android.ProjectLocationInputEnvKey) + workDirOption.AddOption(relPackageJSONDir, projectLocationOption) + + moduleOption = models.NewOption(android.ModuleInputTitle, android.ModuleInputEnvKey) + projectLocationOption.AddOption(filepath.Join(relPackageJSONDir, "android"), moduleOption) + } + + buildVariantOption := models.NewOption(android.VariantInputTitle, android.VariantInputEnvKey) + moduleOption.AddOption("app", buildVariantOption) + + // expo options + if scanner.usesExpoKit { + userNameOption := models.NewOption("Expo username", "EXPO_USERNAME") + buildVariantOption.AddOption("Release", userNameOption) + + passwordOption := models.NewOption("Expo password", "EXPO_PASSWORD") + userNameOption.AddOption("_", passwordOption) + + configOption := models.NewConfigOption(configName) + passwordOption.AddConfig("_", configOption) + } else { + configOption := models.NewConfigOption(configName) + buildVariantOption.AddConfig("Release", configOption) + } + + return *projectPathOption, warnings, nil +} + +// Configs ... +func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { + configMap := models.BitriseConfigMap{} + + // determine workdir + packageJSONDir := filepath.Dir(scanner.packageJSONPth) + relPackageJSONDir, err := utility.RelPath(scanner.searchDir, packageJSONDir) + if err != nil { + return models.BitriseConfigMap{}, fmt.Errorf("Failed to get relative package.json dir path, error: %s", err) + } + if relPackageJSONDir == "." { + // package.json placed in the search dir, no need to change-dir in the workflows + relPackageJSONDir = "" + } + log.TPrintf("Working directory: %v", relPackageJSONDir) + + workdirEnvList := []envmanModels.EnvironmentItemModel{} + if relPackageJSONDir != "" { + workdirEnvList = append(workdirEnvList, envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: relPackageJSONDir}) + } + + // determine dependency manager step + hasYarnLockFile := false + if exist, err := pathutil.IsPathExists(filepath.Join(relPackageJSONDir, "yarn.lock")); err != nil { + log.Warnf("Failed to check if yarn.lock file exists in the workdir: %s", err) + log.TPrintf("Dependency manager: npm") + } else if exist { + log.TPrintf("Dependency manager: yarn") + hasYarnLockFile = true + } else { + log.TPrintf("Dependency manager: npm") + } + + // find test script in package.json file + b, err := fileutil.ReadBytesFromFile(scanner.packageJSONPth) + if err != nil { + return models.BitriseConfigMap{}, err + } + var packageJSON serialized.Object + if err := json.Unmarshal([]byte(b), &packageJSON); err != nil { + return models.BitriseConfigMap{}, err + } + + hasTest := false + if scripts, err := packageJSON.Object("scripts"); err == nil { + if _, err := scripts.String("test"); err == nil { + hasTest = true + } + } + log.TPrintf("test script found in package.json: %v", hasTest) + + if !hasTest { + // if the project has no test script defined, + // we can only provide deploy like workflow, + // so that is going to be the primary workflow + + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + + if hasYarnLockFile { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.YarnStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + } else { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + } + + projectDir := relPackageJSONDir + if relPackageJSONDir == "" { + projectDir = "./" + } + if scanner.usesExpoKit { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.ExpoDetachStepListItem( + envmanModels.EnvironmentItemModel{"project_path": projectDir}, + envmanModels.EnvironmentItemModel{"user_name": "$EXPO_USERNAME"}, + envmanModels.EnvironmentItemModel{"password": "$EXPO_PASSWORD"}, + envmanModels.EnvironmentItemModel{"run_publish": "yes"}, + )) + } else { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.ExpoDetachStepListItem( + envmanModels.EnvironmentItemModel{"project_path": projectDir}, + )) + } + + // android build + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.InstallMissingAndroidToolsStepListItem( + envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, + )) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.AndroidBuildStepListItem( + envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: "$" + android.ProjectLocationInputEnvKey}, + envmanModels.EnvironmentItemModel{android.ModuleInputKey: "$" + android.ModuleInputEnvKey}, + envmanModels.EnvironmentItemModel{android.VariantInputKey: "$" + android.VariantInputEnvKey}, + )) + + // ios build + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + if scanner.usesExpoKit { + // in case of expo kit rn project expo eject generates an ios project with Podfile + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CocoapodsInstallStepListItem()) + } + + xcodeArchiveInputs := []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, + envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, + envmanModels.EnvironmentItemModel{"force_team_id": "$BITRISE_IOS_DEVELOPMENT_TEAM"}, + } + if !scanner.usesExpoKit { + // in case of plain rn project new xcode build system needs to be turned off + xcodeArchiveInputs = append(xcodeArchiveInputs, envmanModels.EnvironmentItemModel{"xcodebuild_options": "-UseModernBuildSystem=NO"}) + } + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XcodeArchiveStepListItem(xcodeArchiveInputs...)) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + configBuilder.SetWorkflowDescriptionTo(models.PrimaryWorkflowID, deployWorkflowDescription) + + bitriseDataModel, err := configBuilder.Generate(Name) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(bitriseDataModel) + if err != nil { + return models.BitriseConfigMap{}, err + } + + configMap[configName] = string(data) + + return configMap, nil + } + + // primary workflow + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + if hasYarnLockFile { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.YarnStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.YarnStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "test"})...)) + } else { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "test"})...)) + } + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + // deploy workflow + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) + if hasYarnLockFile { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.YarnStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + } else { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + } + + projectDir := relPackageJSONDir + if relPackageJSONDir == "" { + projectDir = "./" + } + if scanner.usesExpoKit { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.ExpoDetachStepListItem( + envmanModels.EnvironmentItemModel{"project_path": projectDir}, + envmanModels.EnvironmentItemModel{"user_name": "$EXPO_USERNAME"}, + envmanModels.EnvironmentItemModel{"password": "$EXPO_PASSWORD"}, + envmanModels.EnvironmentItemModel{"run_publish": "yes"}, + )) + } else { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.ExpoDetachStepListItem( + envmanModels.EnvironmentItemModel{"project_path": projectDir}, + )) + } + + // android build + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.InstallMissingAndroidToolsStepListItem( + envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, + )) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( + envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: "$" + android.ProjectLocationInputEnvKey}, + envmanModels.EnvironmentItemModel{android.ModuleInputKey: "$" + android.ModuleInputEnvKey}, + envmanModels.EnvironmentItemModel{android.VariantInputKey: "$" + android.VariantInputEnvKey}, + )) + + // ios build + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + if scanner.usesExpoKit { + // in case of expo kit rn project expo eject generates an ios project with Podfile + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CocoapodsInstallStepListItem()) + } + + xcodeArchiveInputs := []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, + envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, + envmanModels.EnvironmentItemModel{"force_team_id": "$BITRISE_IOS_DEVELOPMENT_TEAM"}, + } + if !scanner.usesExpoKit { + xcodeArchiveInputs = append(xcodeArchiveInputs, envmanModels.EnvironmentItemModel{"xcodebuild_options": "-UseModernBuildSystem=NO"}) + } + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem(xcodeArchiveInputs...)) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) + configBuilder.SetWorkflowDescriptionTo(models.DeployWorkflowID, deployWorkflowDescription) + + bitriseDataModel, err := configBuilder.Generate(Name) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(bitriseDataModel) + if err != nil { + return models.BitriseConfigMap{}, err + } + + configMap[configName] = string(data) + + return configMap, nil +} + +// DefaultOptions ... +func (Scanner) DefaultOptions() models.OptionNode { + expoKitOption := models.NewOption("Project uses Expo Kit (any js file imports expo dependency)?", "USES_EXPO_KIT") + + // with Expo Kit + { + // ios options + workspacePathOption := models.NewOption("The iOS workspace path generated ny the 'expo eject' process", ios.ProjectPathInputEnvKey) + expoKitOption.AddOption("yes", workspacePathOption) + + schemeOption := models.NewOption("The iOS scheme name generated by the 'expo eject' process", ios.SchemeInputEnvKey) + workspacePathOption.AddOption("_", schemeOption) + + exportMethodOption := models.NewOption(ios.IosExportMethodInputTitle, ios.ExportMethodInputEnvKey) + schemeOption.AddOption("_", exportMethodOption) + + // android options + workDirOption := models.NewOption("Project root directory (the directory of the project app.json/package.json file)", "WORKDIR") + for _, exportMethod := range ios.IosExportMethods { + exportMethodOption.AddOption(exportMethod, workDirOption) + } + + projectLocationOption := models.NewOption(android.ProjectLocationInputTitle, android.ProjectLocationInputEnvKey) + workDirOption.AddOption("_", projectLocationOption) + + moduleOption := models.NewOption(android.ModuleInputTitle, android.ModuleInputEnvKey) + projectLocationOption.AddOption("./android", moduleOption) + + buildVariantOption := models.NewOption(android.VariantInputTitle, android.VariantInputEnvKey) + moduleOption.AddOption("app", buildVariantOption) + + // Expo CLI options + userNameOption := models.NewOption("Expo username", "EXPO_USERNAME") + buildVariantOption.AddOption("Release", userNameOption) + + passwordOption := models.NewOption("Expo password", "EXPO_PASSWORD") + userNameOption.AddOption("_", passwordOption) + + configOption := models.NewConfigOption("react-native-expo-expo-kit-default-config") + passwordOption.AddConfig("_", configOption) + } + + // without Expo Kit + { + // ios options + projectPathOption := models.NewOption("The iOS project path generated ny the 'expo eject' process", ios.ProjectPathInputEnvKey) + expoKitOption.AddOption("no", projectPathOption) + + schemeOption := models.NewOption("The iOS scheme name generated by the 'expo eject' process", ios.SchemeInputEnvKey) + projectPathOption.AddOption("_", schemeOption) + + exportMethodOption := models.NewOption(ios.IosExportMethodInputTitle, ios.ExportMethodInputEnvKey) + schemeOption.AddOption("_", exportMethodOption) + + // android options + workDirOption := models.NewOption("Project root directory (the directory of the project app.json/package.json file)", "WORKDIR") + for _, exportMethod := range ios.IosExportMethods { + exportMethodOption.AddOption(exportMethod, workDirOption) + } + + projectLocationOption := models.NewOption(android.ProjectLocationInputTitle, android.ProjectLocationInputEnvKey) + workDirOption.AddOption("_", projectLocationOption) + + moduleOption := models.NewOption(android.ModuleInputTitle, android.ModuleInputEnvKey) + projectLocationOption.AddOption("./android", moduleOption) + + buildVariantOption := models.NewOption(android.VariantInputTitle, android.VariantInputEnvKey) + moduleOption.AddOption("app", buildVariantOption) + + configOption := models.NewConfigOption("react-native-expo-plain-default-config") + buildVariantOption.AddConfig("Release", configOption) + } + + return *expoKitOption +} + +// DefaultConfigs ... +func (Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { + configMap := models.BitriseConfigMap{} + + // with Expo Kit + { + // primary workflow + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: "$WORKDIR"}, envmanModels.EnvironmentItemModel{"command": "install"})) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: "$WORKDIR"}, envmanModels.EnvironmentItemModel{"command": "test"})) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + // deploy workflow + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: "$WORKDIR"}, envmanModels.EnvironmentItemModel{"command": "install"})) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.ExpoDetachStepListItem( + envmanModels.EnvironmentItemModel{"project_path": "$WORKDIR"}, + envmanModels.EnvironmentItemModel{"user_name": "$EXPO_USERNAME"}, + envmanModels.EnvironmentItemModel{"password": "$EXPO_PASSWORD"}, + envmanModels.EnvironmentItemModel{"run_publish": "yes"}, + )) + + // android build + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.InstallMissingAndroidToolsStepListItem( + envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, + )) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( + envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: "$" + android.ProjectLocationInputEnvKey}, + envmanModels.EnvironmentItemModel{android.ModuleInputKey: "$" + android.ModuleInputEnvKey}, + envmanModels.EnvironmentItemModel{android.VariantInputKey: "$" + android.VariantInputEnvKey}, + )) + + // ios build + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CocoapodsInstallStepListItem()) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem( + envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, + )) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) + configBuilder.SetWorkflowDescriptionTo(models.DeployWorkflowID, deployWorkflowDescription) + + bitriseDataModel, err := configBuilder.Generate(Name) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(bitriseDataModel) + if err != nil { + return models.BitriseConfigMap{}, err + } + + configMap["default-react-native-expo-expo-kit-config"] = string(data) + } + + { + // primary workflow + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: "$WORKDIR"}, envmanModels.EnvironmentItemModel{"command": "install"})) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: "$WORKDIR"}, envmanModels.EnvironmentItemModel{"command": "test"})) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + // deploy workflow + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: "$WORKDIR"}, envmanModels.EnvironmentItemModel{"command": "install"})) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.ExpoDetachStepListItem(envmanModels.EnvironmentItemModel{"project_path": "$WORKDIR"})) + + // android build + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.InstallMissingAndroidToolsStepListItem( + envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, + )) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( + envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: "$" + android.ProjectLocationInputEnvKey}, + envmanModels.EnvironmentItemModel{android.ModuleInputKey: "$" + android.ModuleInputEnvKey}, + envmanModels.EnvironmentItemModel{android.VariantInputKey: "$" + android.VariantInputEnvKey}, + )) + + // ios build + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem( + envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, + )) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) + configBuilder.SetWorkflowDescriptionTo(models.DeployWorkflowID, deployWorkflowDescription) + + bitriseDataModel, err := configBuilder.Generate(Name) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(bitriseDataModel) + if err != nil { + return models.BitriseConfigMap{}, err + } + + configMap["default-react-native-expo-plain-config"] = string(data) + } + + return configMap, nil +} + +// ExcludedScannerNames ... +func (Scanner) ExcludedScannerNames() []string { + return []string{ + reactnative.Name, + string(ios.XcodeProjectTypeIOS), + string(ios.XcodeProjectTypeMacOS), + android.ScannerName, + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/reactnative.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/reactnative.go new file mode 100644 index 00000000..39ccc14a --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/reactnative.go @@ -0,0 +1,501 @@ +package reactnative + +import ( + "errors" + "fmt" + "path/filepath" + + "gopkg.in/yaml.v2" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/scanners/android" + "github.com/bitrise-core/bitrise-init/scanners/ios" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-core/bitrise-init/utility" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" +) + +// Name ... +const Name = "react-native" + +const ( + // WorkDirInputKey ... + WorkDirInputKey = "workdir" +) + +// Scanner ... +type Scanner struct { + searchDir string + iosScanner *ios.Scanner + androidScanner *android.Scanner + hasNPMTest bool + packageJSONPth string +} + +// NewScanner ... +func NewScanner() *Scanner { + return &Scanner{} +} + +// Name ... +func (Scanner) Name() string { + return Name +} + +// DetectPlatform ... +func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { + scanner.searchDir = searchDir + + log.TInfof("Collect package.json files") + + packageJSONPths, err := CollectPackageJSONFiles(searchDir) + if err != nil { + return false, err + } + + log.TPrintf("%d package.json file detected", len(packageJSONPths)) + + log.TInfof("Filter relevant package.json files") + + relevantPackageJSONPths := []string{} + iosScanner := ios.NewScanner() + androidScanner := android.NewScanner() + for _, packageJSONPth := range packageJSONPths { + log.TPrintf("checking: %s", packageJSONPth) + + projectDir := filepath.Dir(packageJSONPth) + + iosProjectDetected := false + iosDir := filepath.Join(projectDir, "ios") + if exist, err := pathutil.IsDirExists(iosDir); err != nil { + return false, err + } else if exist { + if detected, err := iosScanner.DetectPlatform(scanner.searchDir); err != nil { + return false, err + } else if detected { + iosProjectDetected = true + } + } + + androidProjectDetected := false + androidDir := filepath.Join(projectDir, "android") + if exist, err := pathutil.IsDirExists(androidDir); err != nil { + return false, err + } else if exist { + if detected, err := androidScanner.DetectPlatform(scanner.searchDir); err != nil { + return false, err + } else if detected { + androidProjectDetected = true + } + } + + if iosProjectDetected || androidProjectDetected { + relevantPackageJSONPths = append(relevantPackageJSONPths, packageJSONPth) + } else { + log.TWarnf("no ios nor android project found, skipping package.json file") + } + } + + if len(relevantPackageJSONPths) == 0 { + return false, nil + } + + scanner.packageJSONPth = relevantPackageJSONPths[0] + + return true, nil +} + +// Options ... +func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { + warnings := models.Warnings{} + + var rootOption models.OptionNode + + // react options + packages, err := utility.ParsePackagesJSON(scanner.packageJSONPth) + if err != nil { + return models.OptionNode{}, warnings, err + } + + hasNPMTest := false + if _, found := packages.Scripts["test"]; found { + hasNPMTest = true + scanner.hasNPMTest = true + } + + projectDir := filepath.Dir(scanner.packageJSONPth) + + // android options + var androidOptions *models.OptionNode + androidDir := filepath.Join(projectDir, "android") + if exist, err := pathutil.IsDirExists(androidDir); err != nil { + return models.OptionNode{}, warnings, err + } else if exist { + androidScanner := android.NewScanner() + + if detected, err := androidScanner.DetectPlatform(scanner.searchDir); err != nil { + return models.OptionNode{}, warnings, err + } else if detected { + // only the first match we need + androidScanner.ExcludeTest = true + androidScanner.ProjectRoots = []string{androidScanner.ProjectRoots[0]} + + npmCmd := command.New("npm", "install") + npmCmd.SetDir(projectDir) + if out, err := npmCmd.RunAndReturnTrimmedCombinedOutput(); err != nil { + return models.OptionNode{}, warnings, fmt.Errorf("failed to npm install react-native in: %s\noutput: %s\nerror: %s", projectDir, out, err) + } + + options, warns, err := androidScanner.Options() + warnings = append(warnings, warns...) + if err != nil { + return models.OptionNode{}, warnings, err + } + + androidOptions = &options + scanner.androidScanner = androidScanner + } + } + + // ios options + var iosOptions *models.OptionNode + iosDir := filepath.Join(projectDir, "ios") + if exist, err := pathutil.IsDirExists(iosDir); err != nil { + return models.OptionNode{}, warnings, err + } else if exist { + iosScanner := ios.NewScanner() + + if detected, err := iosScanner.DetectPlatform(scanner.searchDir); err != nil { + return models.OptionNode{}, warnings, err + } else if detected { + options, warns, err := iosScanner.Options() + warnings = append(warnings, warns...) + if err != nil { + return models.OptionNode{}, warnings, err + } + + iosOptions = &options + scanner.iosScanner = iosScanner + } + } + + if androidOptions == nil && iosOptions == nil { + return models.OptionNode{}, warnings, errors.New("no ios nor android project detected") + } + // --- + + if androidOptions != nil { + if iosOptions == nil { + // we only found an android project + // we need to update the config names + lastChilds := androidOptions.LastChilds() + for _, child := range lastChilds { + for _, child := range child.ChildOptionMap { + if child.Config == "" { + return models.OptionNode{}, warnings, fmt.Errorf("no config for option: %s", child.String()) + } + + configName := configName(true, false, hasNPMTest) + child.Config = configName + } + } + } else { + // we have both ios and android projects + // we need to remove the android option's config names, + // since ios options will hold them + androidOptions.RemoveConfigs() + } + + rootOption = *androidOptions + } + + if iosOptions != nil { + lastChilds := iosOptions.LastChilds() + for _, child := range lastChilds { + for _, child := range child.ChildOptionMap { + if child.Config == "" { + return models.OptionNode{}, warnings, fmt.Errorf("no config for option: %s", child.String()) + } + + configName := configName(scanner.androidScanner != nil, true, hasNPMTest) + child.Config = configName + } + } + + if androidOptions == nil { + // we only found an ios project + rootOption = *iosOptions + } else { + // we have both ios and android projects + // we attach ios options to the android options + rootOption.AttachToLastChilds(iosOptions) + } + + } + + return rootOption, warnings, nil +} + +// DefaultOptions ... +func (Scanner) DefaultOptions() models.OptionNode { + androidOptions := (&android.Scanner{ExcludeTest: true}).DefaultOptions() + androidOptions.RemoveConfigs() + + iosOptions := (&ios.Scanner{}).DefaultOptions() + for _, child := range iosOptions.LastChilds() { + for _, child := range child.ChildOptionMap { + child.Config = defaultConfigName() + } + } + + androidOptions.AttachToLastChilds(&iosOptions) + + return androidOptions +} + +// Configs ... +func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { + configMap := models.BitriseConfigMap{} + + packageJSONDir := filepath.Dir(scanner.packageJSONPth) + relPackageJSONDir, err := utility.RelPath(scanner.searchDir, packageJSONDir) + if err != nil { + return models.BitriseConfigMap{}, fmt.Errorf("Failed to get relative config.xml dir path, error: %s", err) + } + if relPackageJSONDir == "." { + // config.xml placed in the search dir, no need to change-dir in the workflows + relPackageJSONDir = "" + } + + workdirEnvList := []envmanModels.EnvironmentItemModel{} + if relPackageJSONDir != "" { + workdirEnvList = append(workdirEnvList, envmanModels.EnvironmentItemModel{WorkDirInputKey: relPackageJSONDir}) + } + + if scanner.hasNPMTest { + configBuilder := models.NewDefaultConfigBuilder() + + // ci + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "test"})...)) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + // cd + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + + // android cd + if scanner.androidScanner != nil { + projectLocationEnv := "$" + android.ProjectLocationInputEnvKey + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.InstallMissingAndroidToolsStepListItem( + envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, + )) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( + envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: projectLocationEnv}, + )) + } + + // ios cd + if scanner.iosScanner != nil { + for _, descriptor := range scanner.iosScanner.ConfigDescriptors { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + if descriptor.MissingSharedSchemes { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.RecreateUserSchemesStepListItem( + envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, + )) + } + + if descriptor.HasPodfile { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CocoapodsInstallStepListItem()) + } + + if descriptor.CarthageCommand != "" { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CarthageStepListItem( + envmanModels.EnvironmentItemModel{ios.CarthageCommandInputKey: descriptor.CarthageCommand}, + )) + } + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem( + envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, + )) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) + + bitriseDataModel, err := configBuilder.Generate(Name) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(bitriseDataModel) + if err != nil { + return models.BitriseConfigMap{}, err + } + + configName := configName(scanner.androidScanner != nil, true, true) + configMap[configName] = string(data) + } + } else { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) + + bitriseDataModel, err := configBuilder.Generate(Name) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(bitriseDataModel) + if err != nil { + return models.BitriseConfigMap{}, err + } + + configName := configName(scanner.androidScanner != nil, false, true) + configMap[configName] = string(data) + } + } else { + configBuilder := models.NewDefaultConfigBuilder() + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) + + if scanner.androidScanner != nil { + projectLocationEnv := "$" + android.ProjectLocationInputEnvKey + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.InstallMissingAndroidToolsStepListItem( + envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, + )) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( + envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: projectLocationEnv}, + )) + } + + if scanner.iosScanner != nil { + for _, descriptor := range scanner.iosScanner.ConfigDescriptors { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + if descriptor.MissingSharedSchemes { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.RecreateUserSchemesStepListItem( + envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, + )) + } + + if descriptor.HasPodfile { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CocoapodsInstallStepListItem()) + } + + if descriptor.CarthageCommand != "" { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CarthageStepListItem( + envmanModels.EnvironmentItemModel{ios.CarthageCommandInputKey: descriptor.CarthageCommand}, + )) + } + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XcodeArchiveStepListItem( + envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, + )) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + bitriseDataModel, err := configBuilder.Generate(Name) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(bitriseDataModel) + if err != nil { + return models.BitriseConfigMap{}, err + } + + configName := configName(scanner.androidScanner != nil, true, false) + configMap[configName] = string(data) + } + } else { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) + + bitriseDataModel, err := configBuilder.Generate(Name) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(bitriseDataModel) + if err != nil { + return models.BitriseConfigMap{}, err + } + + configName := configName(scanner.androidScanner != nil, false, false) + configMap[configName] = string(data) + } + } + + return configMap, nil +} + +// DefaultConfigs ... +func (Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { + configBuilder := models.NewDefaultConfigBuilder() + + // ci + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{"command": "install"})) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{"command": "test"})) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + // cd + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{"command": "install"})) + + // android + projectLocationEnv := "$" + android.ProjectLocationInputEnvKey + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.InstallMissingAndroidToolsStepListItem( + envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, + )) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( + envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: projectLocationEnv}, + )) + + // ios + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem( + envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, + envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, + )) + + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) + + bitriseDataModel, err := configBuilder.Generate(Name) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(bitriseDataModel) + if err != nil { + return models.BitriseConfigMap{}, err + } + + configName := defaultConfigName() + configMap := models.BitriseConfigMap{ + configName: string(data), + } + return configMap, nil +} + +// ExcludedScannerNames ... +func (Scanner) ExcludedScannerNames() []string { + return []string{ + string(ios.XcodeProjectTypeIOS), + string(ios.XcodeProjectTypeMacOS), + android.ScannerName, + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/utility.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/utility.go new file mode 100644 index 00000000..df357158 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/utility.go @@ -0,0 +1,56 @@ +package reactnative + +import ( + "github.com/bitrise-core/bitrise-init/utility" +) + +// CollectPackageJSONFiles - Collects package.json files, with react-native dependency +func CollectPackageJSONFiles(searchDir string) ([]string, error) { + fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) + if err != nil { + return nil, err + } + + filters := []utility.FilterFunc{ + utility.BaseFilter("package.json", true), + utility.ComponentFilter("node_modules", false), + } + packageFileList, err := utility.FilterPaths(fileList, filters...) + if err != nil { + return nil, err + } + + relevantPackageFileList := []string{} + for _, packageFile := range packageFileList { + packages, err := utility.ParsePackagesJSON(packageFile) + if err != nil { + return nil, err + } + + _, found := packages.Dependencies["react-native"] + if found { + relevantPackageFileList = append(relevantPackageFileList, packageFile) + } + } + + return relevantPackageFileList, nil +} + +func configName(hasAndroidProject, hasIosProject, hasNPMTest bool) string { + name := "react-native" + if hasAndroidProject { + name += "-android" + } + if hasIosProject { + name += "-ios" + } + if hasNPMTest { + name += "-test" + } + name += "-config" + return name +} + +func defaultConfigName() string { + return "default-react-native-config" +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/scanners.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/scanners.go new file mode 100644 index 00000000..922bb8c8 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/scanners.go @@ -0,0 +1,115 @@ +package scanners + +import ( + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/scanners/android" + "github.com/bitrise-core/bitrise-init/scanners/cordova" + "github.com/bitrise-core/bitrise-init/scanners/fastlane" + "github.com/bitrise-core/bitrise-init/scanners/flutter" + "github.com/bitrise-core/bitrise-init/scanners/ionic" + "github.com/bitrise-core/bitrise-init/scanners/ios" + "github.com/bitrise-core/bitrise-init/scanners/macos" + "github.com/bitrise-core/bitrise-init/scanners/reactnative" + expo "github.com/bitrise-core/bitrise-init/scanners/reactnative-expo" + "github.com/bitrise-core/bitrise-init/scanners/xamarin" + "github.com/bitrise-core/bitrise-init/steps" + "gopkg.in/yaml.v2" +) + +// ScannerInterface ... +type ScannerInterface interface { + // The name of the scanner is used for logging and + // to store the scanner outputs, like warnings, options and configs. + // The outputs are stored in a map[NAME]OUTPUT, like: warningMap[ios]warnings, optionsMap[android]options, configMap[xamarin]configs, ..., + // this means, that the SCANNER NAME HAS TO BE UNIQUE. + // Returns: + // - the name of the scanner + Name() string + + // Should implement as minimal logic as possible to determine if searchDir contains the - in question - platform or not. + // Inouts: + // - searchDir: the directory where the project to scan exists. + // Returns: + // - platform detected + // - error if (if any) + DetectPlatform(string) (bool, error) + + // ExcludedScannerNames is used to mark, which scanners should be excluded, if the current scanner detects platform. + ExcludedScannerNames() []string + + // OptionNode is the model, an n-ary tree, used to store the available configuration combintaions. + // It defines an option decision tree whose every branch maps to a bitrise configuration. + // Each branch should define a complete and valid options to build the final bitrise config model. + // Every leaf node has to be the key of the workflow (in the BitriseConfigMap), which will be fulfilled with the selected options. + // Returns: + // - OptionNode + // - Warnings (if any) + // - error if (if any) + Options() (models.OptionNode, models.Warnings, error) + + // Returns: + // - default options for the platform. + DefaultOptions() models.OptionNode + + // BitriseConfigMap's each element is a bitrise config template which will be fulfilled with the user selected options. + // Every config's key should be the last option one of the OptionNode branches. + // Returns: + // - platform BitriseConfigMap + Configs() (models.BitriseConfigMap, error) + + // Returns: + // - platform default BitriseConfigMap + DefaultConfigs() (models.BitriseConfigMap, error) +} + +// AutomationToolScanner contains additional methods (relative to ScannerInterface) +// implemented by an AutomationToolScanner +type AutomationToolScanner interface { + // Set the project types detected + SetDetectedProjectTypes(projectTypes []string) +} + +// ProjectScanners ... +var ProjectScanners = []ScannerInterface{ + expo.NewScanner(), + reactnative.NewScanner(), + flutter.NewScanner(), + ionic.NewScanner(), + cordova.NewScanner(), + ios.NewScanner(), + macos.NewScanner(), + android.NewScanner(), + xamarin.NewScanner(), +} + +// AutomationToolScanners contains active automation tool scanners +var AutomationToolScanners = []ScannerInterface{ + fastlane.NewScanner(), +} + +// CustomProjectType ... +const CustomProjectType = "other" + +// CustomConfigName ... +const CustomConfigName = "other-config" + +// CustomConfig ... +func CustomConfig() (models.BitriseConfigMap, error) { + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + config, err := configBuilder.Generate(CustomProjectType) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + CustomConfigName: string(data), + }, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/utility.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/utility.go new file mode 100644 index 00000000..13e11f72 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/utility.go @@ -0,0 +1,82 @@ +package xamarin + +import ( + "fmt" + "strings" + + "github.com/bitrise-core/bitrise-init/utility" + "github.com/bitrise-io/go-utils/fileutil" +) + +const ( + solutionExtension = ".sln" + componentsDirName = "Components" + // NodeModulesDirName ... + NodeModulesDirName = "node_modules" + + solutionConfigurationStart = "GlobalSection(SolutionConfigurationPlatforms) = preSolution" + solutionConfigurationEnd = "EndGlobalSection" +) + +var allowSolutionExtensionFilter = utility.ExtensionFilter(solutionExtension, true) +var forbidComponentsSolutionFilter = utility.ComponentFilter(componentsDirName, false) +var forbidNodeModulesDirComponentFilter = utility.ComponentFilter(NodeModulesDirName, false) + +// FilterSolutionFiles ... +func FilterSolutionFiles(fileList []string) ([]string, error) { + files, err := utility.FilterPaths(fileList, + allowSolutionExtensionFilter, + forbidComponentsSolutionFilter, + forbidNodeModulesDirComponentFilter) + if err != nil { + return []string{}, err + } + + return files, nil +} + +// GetSolutionConfigs ... +func GetSolutionConfigs(solutionFile string) (map[string][]string, error) { + content, err := fileutil.ReadStringFromFile(solutionFile) + if err != nil { + return map[string][]string{}, err + } + + configMap := map[string][]string{} + isNextLineScheme := false + + lines := strings.Split(content, "\n") + for _, line := range lines { + if strings.Contains(line, solutionConfigurationStart) { + isNextLineScheme = true + continue + } + + if strings.Contains(line, solutionConfigurationEnd) { + isNextLineScheme = false + continue + } + + if isNextLineScheme { + split := strings.Split(line, "=") + if len(split) == 2 { + configCompositStr := strings.TrimSpace(split[1]) + configSplit := strings.Split(configCompositStr, "|") + + if len(configSplit) == 2 { + config := configSplit[0] + platform := configSplit[1] + + platforms := configMap[config] + platforms = append(platforms, platform) + + configMap[config] = platforms + } + } else { + return map[string][]string{}, fmt.Errorf("failed to parse config line (%s)", line) + } + } + } + + return configMap, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/xamarin.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/xamarin.go new file mode 100644 index 00000000..02f640a8 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/xamarin.go @@ -0,0 +1,322 @@ +package xamarin + +import ( + "errors" + "fmt" + "path/filepath" + "regexp" + + "gopkg.in/yaml.v2" + + "github.com/bitrise-core/bitrise-init/models" + "github.com/bitrise-core/bitrise-init/steps" + "github.com/bitrise-core/bitrise-init/utility" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/log" +) + +const scannerName = "xamarin" + +const ( + defaultConfigName = "default-xamarin-config" +) + +const ( + xamarinSolutionInputKey = "xamarin_solution" + xamarinSolutionInputEnvKey = "BITRISE_PROJECT_PATH" + xamarinSolutionInputTitle = "Path to the Xamarin Solution file" +) + +const ( + xamarinConfigurationInputKey = "xamarin_configuration" + xamarinConfigurationInputEnvKey = "BITRISE_XAMARIN_CONFIGURATION" + xamarinConfigurationInputTitle = "Xamarin solution configuration" +) + +const ( + xamarinPlatformInputKey = "xamarin_platform" + xamarinPlatformInputEnvKey = "BITRISE_XAMARIN_PLATFORM" + xamarinPlatformInputTitle = "Xamarin solution platform" +) + +const ( + xamarinIosLicenceInputKey = "xamarin_ios_license" + xamarinAndroidLicenceInputKey = "xamarin_android_license" + xamarinMacLicenseInputKey = "xamarin_mac_license" +) + +func configName(hasNugetPackages, hasXamarinComponents bool) string { + name := "xamarin-" + if hasNugetPackages { + name = name + "nuget-" + } + if hasXamarinComponents { + name = name + "components-" + } + return name + "config" +} + +//-------------------------------------------------- +// Scanner +//-------------------------------------------------- + +// Scanner ... +type Scanner struct { + FileList []string + SolutionFiles []string + + HasNugetPackages bool + HasXamarinComponents bool + + HasIosProject bool + HasAndroidProject bool + HasMacProject bool +} + +// NewScanner ... +func NewScanner() *Scanner { + return &Scanner{} +} + +// Name ... +func (Scanner) Name() string { + return scannerName +} + +// DetectPlatform ... +func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { + fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) + if err != nil { + return false, fmt.Errorf("failed to search for files in (%s), error: %s", searchDir, err) + } + scanner.FileList = fileList + + // Search for solution file + log.TInfof("Searching for solution files") + + solutionFiles, err := FilterSolutionFiles(fileList) + if err != nil { + return false, fmt.Errorf("failed to search for solution files, error: %s", err) + } + + scanner.SolutionFiles = solutionFiles + + log.TPrintf("%d solution files detected", len(solutionFiles)) + for _, file := range solutionFiles { + log.TPrintf("- %s", file) + } + + if len(solutionFiles) == 0 { + log.TPrintf("platform not detected") + return false, nil + } + + log.TSuccessf("Platform detected") + + return true, nil +} + +// ExcludedScannerNames ... +func (Scanner) ExcludedScannerNames() []string { + return []string{} +} + +// Options ... +func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { + log.TInfof("Searching for NuGet packages & Xamarin Components") + + warnings := models.Warnings{} + + for _, file := range scanner.FileList { + // Search for nuget packages + if !scanner.HasNugetPackages { + baseName := filepath.Base(file) + if baseName == "packages.config" { + scanner.HasNugetPackages = true + } + } + + // If adding a component: + // /Components/[COMPONENT_NAME]/ dir added + // ItemGroup/XamarinComponentReference added to the project + // packages.config added to the project's folder + if !scanner.HasXamarinComponents { + componentsExp := regexp.MustCompile(".*Components/.+") + if result := componentsExp.FindString(file); result != "" { + scanner.HasXamarinComponents = true + } + } + + if scanner.HasNugetPackages && scanner.HasXamarinComponents { + break + } + } + + if scanner.HasNugetPackages { + log.TPrintf("Nuget packages found") + } else { + log.TPrintf("NO Nuget packages found") + } + + if scanner.HasXamarinComponents { + log.TPrintf("Xamarin Components found") + } else { + log.TPrintf("NO Xamarin Components found") + } + + // Check for solution configs + validSolutionMap := map[string]map[string][]string{} + for _, solutionFile := range scanner.SolutionFiles { + log.TInfof("Inspecting solution file: %s", solutionFile) + + configs, err := GetSolutionConfigs(solutionFile) + if err != nil { + log.TWarnf("Failed to get solution configs, error: %s", err) + warnings = append(warnings, fmt.Sprintf("Failed to get solution (%s) configs, error: %s", solutionFile, err)) + continue + } + + if len(configs) > 0 { + log.TPrintf("%d configurations found", len(configs)) + for config, platforms := range configs { + log.TPrintf("- %s with platforms: %v", config, platforms) + } + + validSolutionMap[solutionFile] = configs + } else { + log.TWarnf("No config found for %s", solutionFile) + warnings = append(warnings, fmt.Sprintf("No configs found for solution: %s", solutionFile)) + } + } + + if len(validSolutionMap) == 0 { + log.TErrorf("No valid solution file found") + return models.OptionNode{}, warnings, errors.New("No valid solution file found") + } + + // Check for solution projects + xamarinSolutionOption := models.NewOption(xamarinSolutionInputTitle, xamarinSolutionInputEnvKey) + + for solutionFile, configMap := range validSolutionMap { + xamarinConfigurationOption := models.NewOption(xamarinConfigurationInputTitle, xamarinConfigurationInputEnvKey) + xamarinSolutionOption.AddOption(solutionFile, xamarinConfigurationOption) + + for config, platforms := range configMap { + xamarinPlatformOption := models.NewOption(xamarinPlatformInputTitle, xamarinPlatformInputEnvKey) + xamarinConfigurationOption.AddOption(config, xamarinPlatformOption) + + for _, platform := range platforms { + configOption := models.NewConfigOption(configName(scanner.HasNugetPackages, scanner.HasXamarinComponents)) + xamarinPlatformOption.AddConfig(platform, configOption) + } + } + } + + return *xamarinSolutionOption, warnings, nil +} + +// DefaultOptions ... +func (Scanner) DefaultOptions() models.OptionNode { + xamarinSolutionOption := models.NewOption(xamarinSolutionInputTitle, xamarinSolutionInputEnvKey) + + xamarinConfigurationOption := models.NewOption(xamarinConfigurationInputTitle, xamarinConfigurationInputEnvKey) + xamarinSolutionOption.AddOption("_", xamarinConfigurationOption) + + xamarinPlatformOption := models.NewOption(xamarinPlatformInputTitle, xamarinPlatformInputEnvKey) + xamarinConfigurationOption.AddOption("_", xamarinPlatformOption) + + configOption := models.NewConfigOption(defaultConfigName) + xamarinPlatformOption.AddConfig("_", configOption) + + return *xamarinSolutionOption +} + +// Configs ... +func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + + // XamarinUserManagement + if scanner.HasXamarinComponents { + inputs := []envmanModels.EnvironmentItemModel{} + if scanner.HasIosProject { + inputs = append(inputs, envmanModels.EnvironmentItemModel{xamarinIosLicenceInputKey: "yes"}) + } + if scanner.HasAndroidProject { + inputs = append(inputs, envmanModels.EnvironmentItemModel{xamarinAndroidLicenceInputKey: "yes"}) + } + if scanner.HasMacProject { + inputs = append(inputs, envmanModels.EnvironmentItemModel{xamarinMacLicenseInputKey: "yes"}) + } + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XamarinUserManagementStepListItem(inputs...)) + } + + // NugetRestore + if scanner.HasNugetPackages { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NugetRestoreStepListItem()) + } + + // XamarinComponentsRestore + if scanner.HasXamarinComponents { + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XamarinComponentsRestoreStepListItem()) + } + + // XamarinArchive + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XamarinArchiveStepListItem( + envmanModels.EnvironmentItemModel{xamarinSolutionInputKey: "$" + xamarinSolutionInputEnvKey}, + envmanModels.EnvironmentItemModel{xamarinConfigurationInputKey: "$" + xamarinConfigurationInputEnvKey}, + envmanModels.EnvironmentItemModel{xamarinPlatformInputKey: "$" + xamarinPlatformInputEnvKey}, + )) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + config, err := configBuilder.Generate(scannerName) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + configName(scanner.HasNugetPackages, scanner.HasXamarinComponents): string(data), + }, nil +} + +// DefaultConfigs ... +func (Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { + configBuilder := models.NewDefaultConfigBuilder() + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XamarinUserManagementStepListItem()) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NugetRestoreStepListItem()) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XamarinComponentsRestoreStepListItem()) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XamarinArchiveStepListItem( + envmanModels.EnvironmentItemModel{xamarinSolutionInputKey: "$" + xamarinSolutionInputEnvKey}, + envmanModels.EnvironmentItemModel{xamarinConfigurationInputKey: "$" + xamarinConfigurationInputEnvKey}, + envmanModels.EnvironmentItemModel{xamarinPlatformInputKey: "$" + xamarinPlatformInputEnvKey}, + )) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) + + config, err := configBuilder.Generate(scannerName) + if err != nil { + return models.BitriseConfigMap{}, err + } + + data, err := yaml.Marshal(config) + if err != nil { + return models.BitriseConfigMap{}, err + } + + return models.BitriseConfigMap{ + defaultConfigName: string(data), + }, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/xamarin_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/xamarin_test.go new file mode 100644 index 00000000..c95973d7 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/xamarin_test.go @@ -0,0 +1,38 @@ +package xamarin + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilterSolutionFiles(t *testing.T) { + t.Log(`Contains solution files`) + { + fileList := []string{ + "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-xamarin-ios/CreditCardValidator.iOS.sln", + "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-android/sln", + "path/to/my/gradlew/file", + "path/to/my", + } + + files, err := FilterSolutionFiles(fileList) + require.NoError(t, err) + require.Equal(t, 1, len(files)) + + // Also sorts solution files by path components length + require.Equal(t, "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-xamarin-ios/CreditCardValidator.iOS.sln", files[0]) + } + + t.Log(`Do not contains solution file`) + { + fileList := []string{ + "path/to/my/gradlew/build.", + "path/to/my/gradle", + } + + files, err := FilterSolutionFiles(fileList) + require.NoError(t, err) + require.Equal(t, 0, len(files)) + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/steps/const.go b/vendor/github.com/bitrise-core/bitrise-init/steps/const.go new file mode 100644 index 00000000..034df54a --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/steps/const.go @@ -0,0 +1,262 @@ +package steps + +const ( + // ActivateSSHKeyID ... + ActivateSSHKeyID = "activate-ssh-key" + // ActivateSSHKeyVersion ... + ActivateSSHKeyVersion = "4.0.3" +) + +const ( + // AndroidLintID ... + AndroidLintID = "android-lint" + // AndroidLintVersion ... + AndroidLintVersion = "0.9.5" +) + +const ( + // AndroidUnitTestID ... + AndroidUnitTestID = "android-unit-test" + // AndroidUnitTestVersion ... + AndroidUnitTestVersion = "0.9.5" +) + +const ( + // AndroidBuildID ... + AndroidBuildID = "android-build" + // AndroidBuildVersion ... + AndroidBuildVersion = "0.9.5" +) + +const ( + // GitCloneID ... + GitCloneID = "git-clone" + // GitCloneVersion ... + GitCloneVersion = "4.0.14" +) + +const ( + // CachePullID ... + CachePullID = "cache-pull" + // CachePullVersion ... + CachePullVersion = "2.0.1" +) + +const ( + // CachePushID ... + CachePushID = "cache-push" + // CachePushVersion ... + CachePushVersion = "2.0.5" +) + +const ( + // CertificateAndProfileInstallerID ... + CertificateAndProfileInstallerID = "certificate-and-profile-installer" + // CertificateAndProfileInstallerVersion ... + CertificateAndProfileInstallerVersion = "1.10.1" +) + +const ( + // ChangeAndroidVersionCodeAndVersionNameID ... + ChangeAndroidVersionCodeAndVersionNameID = "change-android-versioncode-and-versionname" + // ChangeAndroidVersionCodeAndVersionNameVersion ... + ChangeAndroidVersionCodeAndVersionNameVersion = "1.1.1" +) + +const ( + // DeployToBitriseIoID ... + DeployToBitriseIoID = "deploy-to-bitrise-io" + // DeployToBitriseIoVersion ... + DeployToBitriseIoVersion = "1.3.19" +) + +const ( + // ScriptID ... + ScriptID = "script" + // ScriptVersion ... + ScriptVersion = "1.1.5" + // ScriptDefaultTitle ... + ScriptDefaultTitle = "Do anything with Script step" +) + +const ( + // SignAPKID ... + SignAPKID = "sign-apk" + // SignAPKVersion ... + SignAPKVersion = "1.2.4" +) + +const ( + // InstallMissingAndroidToolsID ... + InstallMissingAndroidToolsID = "install-missing-android-tools" + // InstallMissingAndroidToolsVersion ... + InstallMissingAndroidToolsVersion = "2.3.5" +) + +const ( + // FastlaneID ... + FastlaneID = "fastlane" + // FastlaneVersion ... + FastlaneVersion = "2.3.12" +) + +const ( + // CocoapodsInstallID ... + CocoapodsInstallID = "cocoapods-install" + // CocoapodsInstallVersion ... + CocoapodsInstallVersion = "1.7.2" +) + +const ( + // CarthageID ... + CarthageID = "carthage" + // CarthageVersion ... + CarthageVersion = "3.1.7" +) + +const ( + // RecreateUserSchemesID ... + RecreateUserSchemesID = "recreate-user-schemes" + // RecreateUserSchemesVersion ... + RecreateUserSchemesVersion = "1.0.2" +) + +const ( + // XcodeArchiveID ... + XcodeArchiveID = "xcode-archive" + // XcodeArchiveVersion ... + XcodeArchiveVersion = "2.4.19" +) + +const ( + // XcodeTestID ... + XcodeTestID = "xcode-test" + // XcodeTestVersion ... + XcodeTestVersion = "2.1.1" +) + +const ( + // XamarinUserManagementID ... + XamarinUserManagementID = "xamarin-user-management" + // XamarinUserManagementVersion ... + XamarinUserManagementVersion = "1.1.0" +) + +const ( + // NugetRestoreID ... + NugetRestoreID = "nuget-restore" + // NugetRestoreVersion ... + NugetRestoreVersion = "1.0.7" +) + +const ( + // XamarinComponentsRestoreID ... + XamarinComponentsRestoreID = "xamarin-components-restore" + // XamarinComponentsRestoreVersion ... + XamarinComponentsRestoreVersion = "0.9.0" +) + +const ( + // XamarinArchiveID ... + XamarinArchiveID = "xamarin-archive" + // XamarinArchiveVersion ... + XamarinArchiveVersion = "1.5.0" +) + +const ( + // XcodeArchiveMacID ... + XcodeArchiveMacID = "xcode-archive-mac" + // XcodeArchiveMacVersion ... + XcodeArchiveMacVersion = "1.6.2" +) + +const ( + // XcodeTestMacID ... + XcodeTestMacID = "xcode-test-mac" + // XcodeTestMacVersion ... + XcodeTestMacVersion = "1.2.1" +) + +const ( + // CordovaArchiveID ... + CordovaArchiveID = "cordova-archive" + // CordovaArchiveVersion ... + CordovaArchiveVersion = "2.0.0" +) + +const ( + // IonicArchiveID ... + IonicArchiveID = "ionic-archive" + // IonicArchiveVersion ... + IonicArchiveVersion = "2.0.0" +) + +const ( + // GenerateCordovaBuildConfigID ... + GenerateCordovaBuildConfigID = "generate-cordova-build-configuration" + // GenerateCordovaBuildConfigVersion ... + GenerateCordovaBuildConfigVersion = "0.9.6" +) + +const ( + // JasmineTestRunnerID ... + JasmineTestRunnerID = "jasmine-runner" + // JasmineTestRunnerVersion ... + JasmineTestRunnerVersion = "0.9.0" +) + +const ( + // KarmaJasmineTestRunnerID ... + KarmaJasmineTestRunnerID = "karma-jasmine-runner" + // KarmaJasmineTestRunnerVersion ... + KarmaJasmineTestRunnerVersion = "0.9.1" +) + +const ( + // NpmID ... + NpmID = "npm" + // NpmVersion ... + NpmVersion = "1.0.1" +) + +const ( + // ExpoDetachID ... + ExpoDetachID = "expo-detach" + // ExpoDetachVersion ... + ExpoDetachVersion = "0.9.3" +) + +const ( + // YarnID ... + YarnID = "yarn" + // YarnVersion ... + YarnVersion = "0.0.8" +) + +const ( + // FlutterInstallID ... + FlutterInstallID = "flutter-installer" + // FlutterInstallVersion ... + FlutterInstallVersion = "0.9.2" +) + +const ( + // FlutterTestID ... + FlutterTestID = "flutter-test" + // FlutterTestVersion ... + FlutterTestVersion = "0.9.1" +) + +const ( + // FlutterAnalyzeID ... + FlutterAnalyzeID = "flutter-analyze" + // FlutterAnalyzeVersion ... + FlutterAnalyzeVersion = "0.1.0" +) + +const ( + // FlutterBuildID ... + FlutterBuildID = "flutter-build" + // FlutterBuildVersion ... + FlutterBuildVersion = "0.9.2" +) diff --git a/vendor/github.com/bitrise-core/bitrise-init/steps/steps.go b/vendor/github.com/bitrise-core/bitrise-init/steps/steps.go new file mode 100644 index 00000000..892bca85 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/steps/steps.go @@ -0,0 +1,283 @@ +package steps + +import ( + bitriseModels "github.com/bitrise-io/bitrise/models" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/pointers" + stepmanModels "github.com/bitrise-io/stepman/models" +) + +func stepIDComposite(ID, version string) string { + if version != "" { + return ID + "@" + version + } + return ID +} + +func stepListItem(stepIDComposite, title, runIf string, inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + step := stepmanModels.StepModel{} + if title != "" { + step.Title = pointers.NewStringPtr(title) + } + if runIf != "" { + step.RunIf = pointers.NewStringPtr(runIf) + } + if len(inputs) > 0 { + step.Inputs = inputs + } + + return bitriseModels.StepListItemModel{ + stepIDComposite: step, + } +} + +// DefaultPrepareStepList ... +func DefaultPrepareStepList(isIncludeCache bool) []bitriseModels.StepListItemModel { + stepList := []bitriseModels.StepListItemModel{ + ActivateSSHKeyStepListItem(), + GitCloneStepListItem(), + } + + if isIncludeCache { + stepList = append(stepList, CachePullStepListItem()) + } + + return append(stepList, ScriptSteplistItem(ScriptDefaultTitle)) +} + +// DefaultDeployStepList ... +func DefaultDeployStepList(isIncludeCache bool) []bitriseModels.StepListItemModel { + stepList := []bitriseModels.StepListItemModel{ + DeployToBitriseIoStepListItem(), + } + + if isIncludeCache { + stepList = append(stepList, CachePushStepListItem()) + } + + return stepList +} + +// ActivateSSHKeyStepListItem ... +func ActivateSSHKeyStepListItem() bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(ActivateSSHKeyID, ActivateSSHKeyVersion) + runIf := `{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}` + return stepListItem(stepIDComposite, "", runIf) +} + +// AndroidLintStepListItem ... +func AndroidLintStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(AndroidLintID, AndroidLintVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// AndroidUnitTestStepListItem ... +func AndroidUnitTestStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(AndroidUnitTestID, AndroidUnitTestVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// AndroidBuildStepListItem ... +func AndroidBuildStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(AndroidBuildID, AndroidBuildVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// GitCloneStepListItem ... +func GitCloneStepListItem() bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(GitCloneID, GitCloneVersion) + return stepListItem(stepIDComposite, "", "") +} + +// CachePullStepListItem ... +func CachePullStepListItem() bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(CachePullID, CachePullVersion) + return stepListItem(stepIDComposite, "", "") +} + +// CachePushStepListItem ... +func CachePushStepListItem() bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(CachePushID, CachePushVersion) + return stepListItem(stepIDComposite, "", "") +} + +// CertificateAndProfileInstallerStepListItem ... +func CertificateAndProfileInstallerStepListItem() bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(CertificateAndProfileInstallerID, CertificateAndProfileInstallerVersion) + return stepListItem(stepIDComposite, "", "") +} + +// ChangeAndroidVersionCodeAndVersionNameStepListItem ... +func ChangeAndroidVersionCodeAndVersionNameStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(ChangeAndroidVersionCodeAndVersionNameID, ChangeAndroidVersionCodeAndVersionNameVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// DeployToBitriseIoStepListItem ... +func DeployToBitriseIoStepListItem() bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(DeployToBitriseIoID, DeployToBitriseIoVersion) + return stepListItem(stepIDComposite, "", "") +} + +// ScriptSteplistItem ... +func ScriptSteplistItem(title string, inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(ScriptID, ScriptVersion) + return stepListItem(stepIDComposite, title, "", inputs...) +} + +// SignAPKStepListItem ... +func SignAPKStepListItem() bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(SignAPKID, SignAPKVersion) + return stepListItem(stepIDComposite, "", `{{getenv "BITRISEIO_ANDROID_KEYSTORE_URL" | ne ""}}`) +} + +// InstallMissingAndroidToolsStepListItem .... +func InstallMissingAndroidToolsStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(InstallMissingAndroidToolsID, InstallMissingAndroidToolsVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// FastlaneStepListItem ... +func FastlaneStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(FastlaneID, FastlaneVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// CocoapodsInstallStepListItem ... +func CocoapodsInstallStepListItem() bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(CocoapodsInstallID, CocoapodsInstallVersion) + return stepListItem(stepIDComposite, "", "") +} + +// CarthageStepListItem ... +func CarthageStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(CarthageID, CarthageVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// RecreateUserSchemesStepListItem ... +func RecreateUserSchemesStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(RecreateUserSchemesID, RecreateUserSchemesVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// XcodeArchiveStepListItem ... +func XcodeArchiveStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(XcodeArchiveID, XcodeArchiveVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// XcodeTestStepListItem ... +func XcodeTestStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(XcodeTestID, XcodeTestVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// XamarinUserManagementStepListItem ... +func XamarinUserManagementStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(XamarinUserManagementID, XamarinUserManagementVersion) + runIf := ".IsCI" + return stepListItem(stepIDComposite, "", runIf, inputs...) +} + +// NugetRestoreStepListItem ... +func NugetRestoreStepListItem() bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(NugetRestoreID, NugetRestoreVersion) + return stepListItem(stepIDComposite, "", "") +} + +// XamarinComponentsRestoreStepListItem ... +func XamarinComponentsRestoreStepListItem() bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(XamarinComponentsRestoreID, XamarinComponentsRestoreVersion) + return stepListItem(stepIDComposite, "", "") +} + +// XamarinArchiveStepListItem ... +func XamarinArchiveStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(XamarinArchiveID, XamarinArchiveVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// XcodeArchiveMacStepListItem ... +func XcodeArchiveMacStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(XcodeArchiveMacID, XcodeArchiveMacVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// XcodeTestMacStepListItem ... +func XcodeTestMacStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(XcodeTestMacID, XcodeTestMacVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// CordovaArchiveStepListItem ... +func CordovaArchiveStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(CordovaArchiveID, CordovaArchiveVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// IonicArchiveStepListItem ... +func IonicArchiveStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(IonicArchiveID, IonicArchiveVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// GenerateCordovaBuildConfigStepListItem ... +func GenerateCordovaBuildConfigStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(GenerateCordovaBuildConfigID, GenerateCordovaBuildConfigVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// JasmineTestRunnerStepListItem ... +func JasmineTestRunnerStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(JasmineTestRunnerID, JasmineTestRunnerVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// KarmaJasmineTestRunnerStepListItem ... +func KarmaJasmineTestRunnerStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(KarmaJasmineTestRunnerID, KarmaJasmineTestRunnerVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// NpmStepListItem ... +func NpmStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(NpmID, NpmVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// ExpoDetachStepListItem ... +func ExpoDetachStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(ExpoDetachID, ExpoDetachVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// YarnStepListItem ... +func YarnStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(YarnID, YarnVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// FlutterInstallStepListItem ... +func FlutterInstallStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(FlutterInstallID, FlutterInstallVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// FlutterTestStepListItem ... +func FlutterTestStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(FlutterTestID, FlutterTestVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// FlutterAnalyzeStepListItem ... +func FlutterAnalyzeStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(FlutterAnalyzeID, FlutterAnalyzeVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} + +// FlutterBuildStepListItem ... +func FlutterBuildStepListItem(inputs ...envmanModels.EnvironmentItemModel) bitriseModels.StepListItemModel { + stepIDComposite := stepIDComposite(FlutterBuildID, FlutterBuildVersion) + return stepListItem(stepIDComposite, "", "", inputs...) +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner.go b/vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner.go new file mode 100644 index 00000000..9ab360b4 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner.go @@ -0,0 +1,56 @@ +package toolscanner + +import ( + "github.com/bitrise-core/bitrise-init/models" + bitriseModels "github.com/bitrise-io/bitrise/models" +) + +// ProjectTypeEnvKey is the name of the enviroment variable used to substitute the project type for +// automation tool scanner's config +const ( + ProjectTypeUserTitle = "Project type" + // The key is used in the options decision tree model. + // If empty, it will not be inserted into the bitrise.yml + ProjectTypeEnvKey = "" +) + +// AddProjectTypeToConfig returns the config filled in with every detected project type, that could be selected +func AddProjectTypeToConfig(configName string, config bitriseModels.BitriseDataModel, detectedProjectTypes []string) map[string]bitriseModels.BitriseDataModel { + configMapWithProjecTypes := map[string]bitriseModels.BitriseDataModel{} + for _, projectType := range detectedProjectTypes { + configWithProjectType := config + configWithProjectType.ProjectType = projectType + configMapWithProjecTypes[appendProjectTypeToConfigName(configName, projectType)] = configWithProjectType + } + return configMapWithProjecTypes +} + +// AddProjectTypeToOptions adds a project type question to automation tool scanners's option tree +func AddProjectTypeToOptions(scannerOptionTree models.OptionNode, detectedProjectTypes []string) models.OptionNode { + optionsTreeWithProjectTypeRoot := models.NewOption(ProjectTypeUserTitle, ProjectTypeEnvKey) + for _, projectType := range detectedProjectTypes { + optionsTreeWithProjectTypeRoot.AddOption(projectType, + appendProjectTypeToConfig(scannerOptionTree, projectType)) + } + return *optionsTreeWithProjectTypeRoot +} + +func appendProjectTypeToConfigName(configName string, projectType string) string { + return configName + "_" + projectType +} + +func appendProjectTypeToConfig(options models.OptionNode, projectType string) *models.OptionNode { + var appendToConfigNames func(*models.OptionNode) + appendToConfigNames = func(node *models.OptionNode) { + if (*node).IsConfigOption() || (*node).ChildOptionMap == nil { + (*node).Config = appendProjectTypeToConfigName((*node).Config, projectType) + return + } + for _, child := range (*node).ChildOptionMap { + appendToConfigNames(child) + } + } + optionsWithProjectType := options.Copy() + appendToConfigNames(optionsWithProjectType) + return optionsWithProjectType +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner_test.go b/vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner_test.go new file mode 100644 index 00000000..6c6b2f7e --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner_test.go @@ -0,0 +1,170 @@ +package toolscanner + +import ( + "reflect" + "testing" + + "github.com/bitrise-core/bitrise-init/models" + bitriseModels "github.com/bitrise-io/bitrise/models" + "github.com/google/go-cmp/cmp" +) + +func TestAddProjectTypeToOptions(t *testing.T) { + const detectedProjectType = "ios" + type args struct { + scannerOptionTree models.OptionNode + detectedProjectTypes []string + } + tests := []struct { + name string + args args + want models.OptionNode + }{ + { + name: "1 project type", + args: args{ + scannerOptionTree: models.OptionNode{ + Title: "Working directory", + EnvKey: "FASTLANE_WORK_DIR", + ChildOptionMap: map[string]*models.OptionNode{ + "BitriseFastlaneSample": &models.OptionNode{ + Title: "Fastlane lane", + EnvKey: "FASTLANE_LANE", + ChildOptionMap: map[string]*models.OptionNode{ + "ios test": &models.OptionNode{ + Config: "fastlane-config", + }, + }, + }, + }, + }, + detectedProjectTypes: []string{detectedProjectType}, + }, + want: models.OptionNode{ + Title: "Project type", + EnvKey: ProjectTypeEnvKey, + ChildOptionMap: map[string]*models.OptionNode{ + detectedProjectType: &models.OptionNode{ + Title: "Working directory", + EnvKey: "FASTLANE_WORK_DIR", + ChildOptionMap: map[string]*models.OptionNode{ + "BitriseFastlaneSample": &models.OptionNode{ + Title: "Fastlane lane", + EnvKey: "FASTLANE_LANE", + ChildOptionMap: map[string]*models.OptionNode{ + "ios test": &models.OptionNode{ + Config: "fastlane-config" + "_" + detectedProjectType, + }}}}}, + }, + }, + }, + { + name: "2 project types", + args: args{ + scannerOptionTree: models.OptionNode{ + Title: "Working directory", + EnvKey: "FASTLANE_WORK_DIR", + ChildOptionMap: map[string]*models.OptionNode{ + "BitriseFastlaneSample": &models.OptionNode{ + Title: "Fastlane lane", + EnvKey: "FASTLANE_LANE", + ChildOptionMap: map[string]*models.OptionNode{ + "ios test": &models.OptionNode{ + Config: "fastlane-config", + }, + }, + }, + }, + }, + detectedProjectTypes: []string{"ios", "android"}, + }, + want: models.OptionNode{ + Title: "Project type", + EnvKey: ProjectTypeEnvKey, + ChildOptionMap: map[string]*models.OptionNode{ + "ios": &models.OptionNode{ + Title: "Working directory", + EnvKey: "FASTLANE_WORK_DIR", + ChildOptionMap: map[string]*models.OptionNode{ + "BitriseFastlaneSample": &models.OptionNode{ + Title: "Fastlane lane", + EnvKey: "FASTLANE_LANE", + ChildOptionMap: map[string]*models.OptionNode{ + "ios test": &models.OptionNode{ + Config: "fastlane-config" + "_" + "ios", + }, + }, + }, + }, + }, + "android": &models.OptionNode{ + Title: "Working directory", + EnvKey: "FASTLANE_WORK_DIR", + ChildOptionMap: map[string]*models.OptionNode{ + "BitriseFastlaneSample": &models.OptionNode{ + Title: "Fastlane lane", + EnvKey: "FASTLANE_LANE", + ChildOptionMap: map[string]*models.OptionNode{ + "ios test": &models.OptionNode{ + Config: "fastlane-config" + "_" + "android", + }, + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := AddProjectTypeToOptions(tt.args.scannerOptionTree, tt.args.detectedProjectTypes); !reflect.DeepEqual(got.String(), tt.want.String()) { + t.Errorf("AddProjectTypeToOptions() = %v, want %v", got, tt.want) + t.Errorf("%s", cmp.Diff(got, tt.want)) + } + }) + } +} + +func TestAddProjectTypeToConfig(t *testing.T) { + const title = "abcd" + type args struct { + configName string + config bitriseModels.BitriseDataModel + detectedProjectTypes []string + } + tests := []struct { + name string + args args + want map[string]bitriseModels.BitriseDataModel + }{ + { + name: "2 project types", + args: args{ + configName: "fastlane-config", + config: bitriseModels.BitriseDataModel{ + Title: title, + ProjectType: "other", + }, + detectedProjectTypes: []string{"ios", "android"}, + }, + want: map[string]bitriseModels.BitriseDataModel{ + "fastlane-config_ios": bitriseModels.BitriseDataModel{ + Title: title, + ProjectType: "ios", + }, + "fastlane-config_android": bitriseModels.BitriseDataModel{ + Title: title, + ProjectType: "android", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := AddProjectTypeToConfig(tt.args.configName, tt.args.config, tt.args.detectedProjectTypes); !reflect.DeepEqual(got, tt.want) { + t.Errorf("AddProjectTypeToConfig() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/utility/sortable_path.go b/vendor/github.com/bitrise-core/bitrise-init/utility/sortable_path.go new file mode 100644 index 00000000..dac6ef04 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/utility/sortable_path.go @@ -0,0 +1,90 @@ +package utility + +import ( + "os" + "path/filepath" + "sort" + "strings" + + "github.com/bitrise-io/go-utils/pathutil" +) + +// SortablePath ... +type SortablePath struct { + Pth string + AbsPth string + Components []string +} + +// NewSortablePath ... +func NewSortablePath(pth string) (SortablePath, error) { + absPth, err := pathutil.AbsPath(pth) + if err != nil { + return SortablePath{}, err + } + + components := strings.Split(absPth, string(os.PathSeparator)) + fixedComponents := []string{} + for _, comp := range components { + if comp != "" { + fixedComponents = append(fixedComponents, comp) + } + } + + return SortablePath{ + Pth: pth, + AbsPth: absPth, + Components: fixedComponents, + }, nil +} + +// BySortablePathComponents .. +type BySortablePathComponents []SortablePath + +func (s BySortablePathComponents) Len() int { + return len(s) +} +func (s BySortablePathComponents) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} +func (s BySortablePathComponents) Less(i, j int) bool { + path1 := s[i] + path2 := s[j] + + d1 := len(path1.Components) + d2 := len(path2.Components) + + if d1 < d2 { + return true + } else if d1 > d2 { + return false + } + + // if same component size, + // do alphabetic sort based on the last component + base1 := filepath.Base(path1.AbsPth) + base2 := filepath.Base(path2.AbsPth) + + return base1 < base2 +} + +// SortPathsByComponents ... +func SortPathsByComponents(paths []string) ([]string, error) { + sortableFiles := []SortablePath{} + for _, pth := range paths { + sortable, err := NewSortablePath(pth) + if err != nil { + return []string{}, err + } + sortableFiles = append(sortableFiles, sortable) + } + + sort.Sort(BySortablePathComponents(sortableFiles)) + + sortedFiles := []string{} + for _, pth := range sortableFiles { + sortedFiles = append(sortedFiles, pth.Pth) + } + + return sortedFiles, nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/utility/sortable_path_test.go b/vendor/github.com/bitrise-core/bitrise-init/utility/sortable_path_test.go new file mode 100644 index 00000000..26311f15 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/utility/sortable_path_test.go @@ -0,0 +1,106 @@ +package utility + +import ( + "os" + "strings" + "testing" + + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestNewSortablePath(t *testing.T) { + t.Log("rel path") + { + expectedAbsPth, err := pathutil.AbsPath("test") + require.NoError(t, err) + + expectedComponents := strings.Split(expectedAbsPth, string(os.PathSeparator)) + fixedExpectedComponents := []string{} + for _, c := range expectedComponents { + if c != "" { + fixedExpectedComponents = append(fixedExpectedComponents, c) + } + } + + sortable, err := NewSortablePath("test") + require.NoError(t, err) + require.Equal(t, "test", sortable.Pth) + require.Equal(t, expectedAbsPth, sortable.AbsPth) + require.Equal(t, fixedExpectedComponents, sortable.Components) + } + + t.Log("rel path") + { + expectedAbsPth, err := pathutil.AbsPath("./test") + require.NoError(t, err) + + expectedComponents := strings.Split(expectedAbsPth, string(os.PathSeparator)) + fixedExpectedComponents := []string{} + for _, c := range expectedComponents { + if c != "" { + fixedExpectedComponents = append(fixedExpectedComponents, c) + } + } + + sortable, err := NewSortablePath("./test") + require.NoError(t, err) + require.Equal(t, "./test", sortable.Pth) + require.Equal(t, expectedAbsPth, sortable.AbsPth) + require.Equal(t, fixedExpectedComponents, sortable.Components) + } + + t.Log("abs path") + { + expectedAbsPth := "/Users/bitrise/test" + expectedComponents := []string{"Users", "bitrise", "test"} + + sortable, err := NewSortablePath("/Users/bitrise/test") + require.NoError(t, err) + require.Equal(t, "/Users/bitrise/test", sortable.Pth) + require.Equal(t, expectedAbsPth, sortable.AbsPth) + require.Equal(t, expectedComponents, sortable.Components) + } +} + +func TestSortPathsByComponents(t *testing.T) { + t.Log("abs paths") + { + paths := []string{ + "/Users/bitrise/test/test/test", + "/Users/bitrise/test/test", + "/Users/vagrant", + "/Users/bitrise", + } + + expectedSorted := []string{ + "/Users/bitrise", + "/Users/vagrant", + "/Users/bitrise/test/test", + "/Users/bitrise/test/test/test", + } + actualSorted, err := SortPathsByComponents(paths) + require.NoError(t, err) + require.Equal(t, expectedSorted, actualSorted) + } + + t.Log("rel paths") + { + paths := []string{ + "bitrise/test/test/test", + "bitrise/test/test", + "vagrant", + "bitrise", + } + + expectedSorted := []string{ + "bitrise", + "vagrant", + "bitrise/test/test", + "bitrise/test/test/test", + } + actualSorted, err := SortPathsByComponents(paths) + require.NoError(t, err) + require.Equal(t, expectedSorted, actualSorted) + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/utility/utility.go b/vendor/github.com/bitrise-core/bitrise-init/utility/utility.go new file mode 100644 index 00000000..7dc7db0b --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/utility/utility.go @@ -0,0 +1,202 @@ +package utility + +import ( + "encoding/json" + "errors" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" +) + +// PackagesModel ... +type PackagesModel struct { + Scripts map[string]string `json:"scripts"` + Dependencies map[string]string `json:"dependencies"` + DevDependencies map[string]string `json:"devDependencies"` +} + +func parsePackagesJSONContent(content string) (PackagesModel, error) { + var packages PackagesModel + if err := json.Unmarshal([]byte(content), &packages); err != nil { + return PackagesModel{}, err + } + return packages, nil +} + +// ParsePackagesJSON ... +func ParsePackagesJSON(packagesJSONPth string) (PackagesModel, error) { + content, err := fileutil.ReadStringFromFile(packagesJSONPth) + if err != nil { + return PackagesModel{}, err + } + return parsePackagesJSONContent(content) +} + +// RelPath ... +func RelPath(basePth, pth string) (string, error) { + absBasePth, err := pathutil.AbsPath(basePth) + if err != nil { + return "", err + } + + if strings.HasPrefix(absBasePth, "/private/var") { + absBasePth = strings.TrimPrefix(absBasePth, "/private") + } + + absPth, err := pathutil.AbsPath(pth) + if err != nil { + return "", err + } + + if strings.HasPrefix(absPth, "/private/var") { + absPth = strings.TrimPrefix(absPth, "/private") + } + + return filepath.Rel(absBasePth, absPth) +} + +// ListPathInDirSortedByComponents ... +func ListPathInDirSortedByComponents(searchDir string, relPath bool) ([]string, error) { + searchDir, err := filepath.Abs(searchDir) + if err != nil { + return []string{}, err + } + + fileList := []string{} + + if err := filepath.Walk(searchDir, func(path string, _ os.FileInfo, walkErr error) error { + if walkErr != nil { + return walkErr + } + + if relPath { + rel, err := filepath.Rel(searchDir, path) + if err != nil { + return err + } + path = rel + } + + fileList = append(fileList, path) + + return nil + }); err != nil { + return []string{}, err + } + return SortPathsByComponents(fileList) +} + +// FilterPaths ... +func FilterPaths(fileList []string, filters ...FilterFunc) ([]string, error) { + filtered := []string{} + + for _, pth := range fileList { + allowed := true + for _, filter := range filters { + if allows, err := filter(pth); err != nil { + return []string{}, err + } else if !allows { + allowed = false + break + } + } + if allowed { + filtered = append(filtered, pth) + } + } + + return filtered, nil +} + +// FilterFunc ... +type FilterFunc func(string) (bool, error) + +// BaseFilter ... +func BaseFilter(base string, allowed bool) FilterFunc { + return func(pth string) (bool, error) { + b := filepath.Base(pth) + return (allowed == strings.EqualFold(base, b)), nil + } +} + +// ExtensionFilter ... +func ExtensionFilter(ext string, allowed bool) FilterFunc { + return func(pth string) (bool, error) { + e := filepath.Ext(pth) + return (allowed == strings.EqualFold(ext, e)), nil + } +} + +// RegexpFilter ... +func RegexpFilter(pattern string, allowed bool) FilterFunc { + return func(pth string) (bool, error) { + re := regexp.MustCompile(pattern) + found := re.FindString(pth) != "" + return (allowed == found), nil + } +} + +// ComponentFilter ... +func ComponentFilter(component string, allowed bool) FilterFunc { + return func(pth string) (bool, error) { + found := false + pathComponents := strings.Split(pth, string(filepath.Separator)) + for _, c := range pathComponents { + if c == component { + found = true + } + } + return (allowed == found), nil + } +} + +// ComponentWithExtensionFilter ... +func ComponentWithExtensionFilter(ext string, allowed bool) FilterFunc { + return func(pth string) (bool, error) { + found := false + pathComponents := strings.Split(pth, string(filepath.Separator)) + for _, c := range pathComponents { + e := filepath.Ext(c) + if e == ext { + found = true + } + } + return (allowed == found), nil + } +} + +// IsDirectoryFilter ... +func IsDirectoryFilter(allowed bool) FilterFunc { + return func(pth string) (bool, error) { + fileInf, err := os.Lstat(pth) + if err != nil { + return false, err + } + if fileInf == nil { + return false, errors.New("no file info available") + } + return (allowed == fileInf.IsDir()), nil + } +} + +// InDirectoryFilter ... +func InDirectoryFilter(dir string, allowed bool) FilterFunc { + return func(pth string) (bool, error) { + in := (filepath.Dir(pth) == dir) + return (allowed == in), nil + } +} + +// FileContains ... +func FileContains(pth, str string) (bool, error) { + content, err := fileutil.ReadStringFromFile(pth) + if err != nil { + return false, err + } + + return strings.Contains(content, str), nil +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/utility/utility_test.go b/vendor/github.com/bitrise-core/bitrise-init/utility/utility_test.go new file mode 100644 index 00000000..196f36d6 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/utility/utility_test.go @@ -0,0 +1,306 @@ +package utility + +import ( + "os" + "strings" + "testing" + + "path/filepath" + + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestListPathInDirSortedByComponents(t *testing.T) { + t.Log() + { + files, err := ListPathInDirSortedByComponents("./", true) + require.NoError(t, err) + require.NotEqual(t, 0, len(files)) + } + + t.Log() + { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__lis_path_test__") + require.NoError(t, err) + defer func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }() + + pths := []string{ + filepath.Join(tmpDir, "testdir/testfile"), + filepath.Join(tmpDir, "testdir/testdir/testfile"), + } + + for _, pth := range pths { + dir := filepath.Dir(pth) + require.NoError(t, os.MkdirAll(dir, 0700)) + + require.NoError(t, fileutil.WriteStringToFile(pth, "test")) + } + + expected := []string{ + ".", + "testdir", + "testdir/testdir", + "testdir/testfile", + "testdir/testdir/testfile", + } + + files, err := ListPathInDirSortedByComponents(tmpDir, true) + require.NoError(t, err) + require.Equal(t, expected, files) + } + + t.Log() + { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__lis_path_test1__") + require.NoError(t, err) + defer func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }() + + pths := []string{ + filepath.Join(tmpDir, "testdir/testfile"), + filepath.Join(tmpDir, "testdir/testdir/testfile"), + } + + for _, pth := range pths { + dir := filepath.Dir(pth) + require.NoError(t, os.MkdirAll(dir, 0700)) + + require.NoError(t, fileutil.WriteStringToFile(pth, "test")) + } + + expected := []string{ + tmpDir, + filepath.Join(tmpDir, "testdir"), + filepath.Join(tmpDir, "testdir/testdir"), + filepath.Join(tmpDir, "testdir/testfile"), + filepath.Join(tmpDir, "testdir/testdir/testfile"), + } + + files, err := ListPathInDirSortedByComponents(tmpDir, false) + require.NoError(t, err) + require.Equal(t, expected, files) + } +} + +func TestFilterPaths(t *testing.T) { + t.Log("without any filter") + { + paths := []string{ + "/Users/bitrise/test", + "/Users/vagrant/test", + } + filtered, err := FilterPaths(paths) + require.NoError(t, err) + require.Equal(t, paths, filtered) + } + + t.Log("with filter") + { + paths := []string{ + "/Users/bitrise/test", + "/Users/vagrant/test", + } + filter := func(pth string) (bool, error) { + return strings.Contains(pth, "vagrant"), nil + } + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"/Users/vagrant/test"}, filtered) + } +} + +func TestBaseFilter(t *testing.T) { + t.Log("allow") + { + paths := []string{ + "path/to/my/gradlew", + "path/to/my/gradlew/file", + } + filter := BaseFilter("gradlew", true) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"path/to/my/gradlew"}, filtered) + } + + t.Log("forbid") + { + paths := []string{ + "path/to/my/gradlew", + "path/to/my/gradlew/file", + } + filter := BaseFilter("gradlew", false) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"path/to/my/gradlew/file"}, filtered) + } +} + +func TestExtensionFilter(t *testing.T) { + t.Log("allow") + { + paths := []string{ + "path/to/my/project.xcodeproj", + "path/to/my/project.xcworkspace", + } + filter := ExtensionFilter(".xcodeproj", true) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"path/to/my/project.xcodeproj"}, filtered) + } + + t.Log("forbid") + { + paths := []string{ + "path/to/my/project.xcodeproj", + "path/to/my/project.xcworkspace", + } + filter := ExtensionFilter(".xcodeproj", false) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"path/to/my/project.xcworkspace"}, filtered) + } +} + +func TestRegexpFilter(t *testing.T) { + t.Log("allow") + { + paths := []string{ + "path/to/my/project.xcodeproj", + "path/to/my/project.xcworkspace", + } + filter := RegexpFilter(".*.xcodeproj", true) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"path/to/my/project.xcodeproj"}, filtered) + } + + t.Log("forbid") + { + paths := []string{ + "path/to/my/project.xcodeproj", + "path/to/my/project.xcworkspace", + } + filter := RegexpFilter(".*.xcodeproj", false) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"path/to/my/project.xcworkspace"}, filtered) + } +} + +func TestComponentFilter(t *testing.T) { + t.Log("allow") + { + paths := []string{ + "/Users/bitrise/test", + "/Users/vagrant/test", + } + filter := ComponentFilter("bitrise", true) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"/Users/bitrise/test"}, filtered) + } + + t.Log("forbid") + { + paths := []string{ + "/Users/bitrise/test", + "/Users/vagrant/test", + } + filter := ComponentFilter("bitrise", false) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"/Users/vagrant/test"}, filtered) + } +} + +func TestComponentWithExtensionFilter(t *testing.T) { + t.Log("allow") + { + paths := []string{ + "/Users/bitrise.framework/test", + "/Users/vagrant/test", + } + filter := ComponentWithExtensionFilter(".framework", true) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"/Users/bitrise.framework/test"}, filtered) + } + + t.Log("forbid") + { + paths := []string{ + "/Users/bitrise.framework/test", + "/Users/vagrant/test", + } + filter := ComponentWithExtensionFilter(".framework", false) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"/Users/vagrant/test"}, filtered) + } +} + +func TestIsDirectoryFilter(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__bitrise-init__") + require.NoError(t, err) + defer func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }() + + tmpFile := filepath.Join(tmpDir, "file.txt") + require.NoError(t, fileutil.WriteStringToFile(tmpFile, "")) + + t.Log("allow") + { + paths := []string{ + tmpDir, + tmpFile, + } + filter := IsDirectoryFilter(true) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{tmpDir}, filtered) + } + + t.Log("forbid") + { + paths := []string{ + tmpDir, + tmpFile, + } + filter := IsDirectoryFilter(false) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{tmpFile}, filtered) + } +} + +func TestInDirectoryFilter(t *testing.T) { + t.Log("allow") + { + paths := []string{ + "/Users/bitrise/test", + "/Users/vagrant/test", + } + filter := InDirectoryFilter("/Users/bitrise", true) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"/Users/bitrise/test"}, filtered) + } + + t.Log("forbid") + { + paths := []string{ + "/Users/bitrise/test", + "/Users/vagrant/test", + } + filter := InDirectoryFilter("/Users/bitrise", false) + filtered, err := FilterPaths(paths, filter) + require.NoError(t, err) + require.Equal(t, []string{"/Users/vagrant/test"}, filtered) + } +} diff --git a/vendor/github.com/bitrise-core/bitrise-init/version/build.go b/vendor/github.com/bitrise-core/bitrise-init/version/build.go new file mode 100644 index 00000000..06c70c10 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/version/build.go @@ -0,0 +1,7 @@ +package version + +// BuildNumber ... +var BuildNumber = "" + +// Commit ... +var Commit = "" diff --git a/vendor/github.com/bitrise-core/bitrise-init/version/version.go b/vendor/github.com/bitrise-core/bitrise-init/version/version.go new file mode 100644 index 00000000..6f4c87e0 --- /dev/null +++ b/vendor/github.com/bitrise-core/bitrise-init/version/version.go @@ -0,0 +1,4 @@ +package version + +// VERSION ... +const VERSION = "1.8.0" diff --git a/vendor/github.com/bitrise-io/bitrise/.codeclimate.yml b/vendor/github.com/bitrise-io/bitrise/.codeclimate.yml new file mode 100644 index 00000000..1e4d2d7f --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/.codeclimate.yml @@ -0,0 +1,41 @@ +# This is a sample .codeclimate.yml configured for Engine analysis on Code +# Climate Platform. For an overview of the Code Climate Platform, see here: +# http://docs.codeclimate.com/article/300-the-codeclimate-platform + +# Under the engines key, you can configure which engines will analyze your repo. +# Each key is an engine name. For each value, you need to specify enabled: true +# to enable the engine as well as any other engines-specific configuration. + +# For more details, see here: +# http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform + +# For a list of all available engines, see here: +# http://docs.codeclimate.com/article/296-engines-available-engines + +engines: +# to turn on an engine, add it here and set enabled to `true` +# to turn off an engine, set enabled to `false` or remove it + golint: + enabled: true + gofmt: + enabled: true + +# Engines can analyze files and report issues on them, but you can separately +# decide which files will receive ratings based on those issues. This is +# specified by path patterns under the ratings key. + +# For more details see here: +# http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform + +# Note: If the ratings key is not specified, this will result in a 0.0 GPA on your dashboard. + +ratings: + paths: + - "**.go" + +# You can globally exclude files from being analyzed by any engine using the +# exclude_paths key. + +#exclude_paths: +#- spec/**/* +#- vendor/**/* diff --git a/vendor/github.com/bitrise-io/bitrise/.gitignore b/vendor/github.com/bitrise-io/bitrise/.gitignore new file mode 100644 index 00000000..bf64a57a --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/.gitignore @@ -0,0 +1,9 @@ +_temp/ +_tmp/ +.bitrise +.bitrise.secrets.yml +_bin +.envstore.yml +_FAKE_HOME/ +.gows.user.yml +.DS_Store diff --git a/vendor/github.com/bitrise-io/bitrise/CHANGELOG.md b/vendor/github.com/bitrise-io/bitrise/CHANGELOG.md new file mode 100644 index 00000000..300eb2de --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/CHANGELOG.md @@ -0,0 +1,3155 @@ +## Changelog (Current version: 1.8.0) + +----------------- + +## 1.8.0 (2017 Aug 07) + +### Release Notes + +__`bitrise plugin update` command's log fix__ + +From now on `bitrise plugin update` command will print which plugin is under update. + +__`bitrise run WORKFLOW` command's log update__ + +`bitrise run WORKFLOW` command prints the workflow stack to better understand which workflows will run in what order. + +``` +Running workflows: BEFORE_WORKFLOW_1 -> BEFORE_WORKFLOW_2 --> WORKFLOW --> AFTER_WORKFLOW_1 --> AFTER_WORKFLOW_2 +``` +__Bitrise Tools update__ + +- min envman version: [1.1.6](https://github.com/bitrise-io/envman/releases/tag/1.1.6) +- min stepman version: [0.9.33](https://github.com/bitrise-io/stepman/releases/tag/0.9.33) + +__Bitrise Plugins update__ + +- default init plugin version: [0.9.7](https://github.com/bitrise-core/bitrise-plugins-init/releases/tag/0.9.7) +- default workflow-editor plugin version: [1.0.13](https://github.com/bitrise-io/bitrise-workflow-editor/releases/tag/1.0.13) +- default analytics plugin version: [0.9.10](https://github.com/bitrise-core/bitrise-plugins-analytics/releases/tag/0.9.10) + +__Bitrise Model's version bumped to 4__ + +Meta field (`meta`) added to `EnvironmentItemOptionsModel`, this property of the environment options is used to define extra options without creating a new [envman](https://github.com/bitrise-io/envman) release. + +The __bitrise-cli__ does not use `meta` field directly, but other tools can use this property to expand the environment options. + +For example the `bitrise.io` website will use the `meta` field to define if secret environment variables should be used in pull request triggered builds or not. + +``` +.bitrise.secrets.yml + +envs: +- MY_SECRET_ENV: secret value + opts: + meta: + is_expose: true +``` + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.8.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.7.0 -> 1.8.0 + +* [83309a7] Krisztian Godrei - prepare for 1.8.0 (2017 Aug 07) +* [a79a5ab] Krisztian Godrei - bitrise plugin analytics updated to 0.9.10 (2017 Aug 07) +* [7cc355d] Krisztian Godrei - bump model version to 4 (2017 Aug 07) +* [97d835c] Krisztian Godrei - prepare for 1.8.0 (2017 Aug 07) +* [9412a8c] Krisztián Gödrei - bitrise tools and plugins version update (#530) (2017 Aug 07) +* [b982443] Krisztián Gödrei - godeps-update (#529) (2017 Aug 07) +* [313384d] Zsolt - CLI workflow prints (#507) (2017 Aug 07) +* [7998a20] Krisztián Gödrei - print plugin name in update command (#528) (2017 Aug 07) + + +## 1.7.0 (2017 Jul 10) + +### Release Notes + +__empty workflow id validation__ + +From now on bitrise-cli will fail if the bitrise configuration (bitrise.yml) contains workflow with empty workflow id. + +``` +format_version: "3" +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + +workflows: + "": + steps: + - script: +``` + +__git step's default branch is master__ + +If you use a step from its git source without specifying the branch to use: + +``` +format_version: "3" +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + +workflows: + primary: + steps: + - git::https://github.com/bitrise-io/steps-script.git: +``` + +bitrise will activate and use the step repo's master branch. + +__support for cross-device file moving__ + +In previous cli versions, if a user home directory was on different device from the os temporary directory, you received the following error message during the bitrise setup process: `invalid cross-device link`. This version uses a cross-device compatible file moving function. + +__progress indicator__ + +bitrise plugin install and update commands take some time to finish as bitrise-cli git clones the plugin source repository and then downloads and installs the plugin's compiled binary. We added a loading indicator to these commands. + +``` +Checking Bitrise Plugins... +Default plugin (analytics) NOT found, installing... +git clone plugin source ⣯ +Downloading plugin binary ⡿ +``` + +__dependency updates:__ + + - min envman version: [1.1.5](https://github.com/bitrise-io/envman/releases/tag/1.1.5) + - min stepman version: [0.9.32](https://github.com/bitrise-io/stepman/releases/tag/0.9.32) + - default init plugin version: [0.9.6](https://github.com/bitrise-core/bitrise-plugins-init/releases/tag/0.9.6) + - default step plugin version: [0.9.4](https://github.com/bitrise-core/bitrise-plugins-step/releases/tag/0.9.4) + - default workflow-editor plugin version: [1.0.11](https://github.com/bitrise-io/bitrise-workflow-editor/releases/tag/1.0.11) + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.7.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.6.2 -> 1.7.0 + +* [ba092dd] Krisztian Godrei - prepare for 1.7.0 (2017 Jul 10) +* [37cad84] Krisztián Gödrei - stepman, envman and default plugins version update (#527) (2017 Jul 10) +* [2cb8861] Krisztián Gödrei - godeps update (#526) (2017 Jul 10) +* [45e9fec] Krisztián Gödrei - Progress (#524) (2017 Jul 05) +* [a9b29d8] Krisztián Gödrei - code style updates (#525) (2017 Jul 05) +* [8f00883] Karol Wrótniak - Added support for cross-device file moving, fixes #518 (#523) (2017 Jul 05) +* [03d1b5d] Krisztián Gödrei - merged (#522) (2017 Jul 04) +* [6a53b4a] Krisztián Gödrei - git steps default branch is master (#520) (2017 Jul 04) +* [afd7aa2] Krisztián Gödrei - fail if workflow id is empty (#519) (2017 Jul 03) + + +## 1.6.2 (2017 Jun 12) + +### Release Notes + +__plugin info command__ + +bitrise-cli got a new command: `bitrise plugin info` + +Prints infos about the specified installed bitrise plugin. You use the command's `--format` flag to specify the output format (valid options: `raw`, `json`). + +The command prints the following infos: + +``` +Name: PLUGIN_NAME +Version: PLUGIN_VERSION +Source: PLUGIN_SOURCE +Definition: PLUGIN_DEFINITION +``` + +__plugin list command__ + +`bitrise plugin list` command prints infos about the installed plugins, the command got a new flag: `--format`, which you can use to specify the output's format (valid options: `raw`, `json`). + +The command prints the same infos about the plugins as the new `bitrise plugin info` command. + +__plugin update command__ + +In previous versions specifying the plugin's name, to update, was required. From now, if you do not specify which plugin to update `bitrise plugin update` command will update every installed bitrise plugin. + +__plugin update command fix__ + +From now `bitrise plugin update` prepares the new plugin version as a sandbox and once everything is downloaded to install the plugin, the cli just copies it to the plugins directory (`$HOME/.bitrise/plugins`). + +__export command fix__ + +From now `bitrise export` command will print the command's help, if required arguments/flags were not provided. + +__Bitrise temporary directory__ + +This bitrise-cli version creates an exports a temporary directory: `BITRISE_TMP_DIR` (if it is not already set). This directory is dedicated to store temporary files, during the bitrise-cli commands. + +__go toolkit__ + +go version bump from 1.8.1 to 1.8.3 + +__Dependency updates:__ + + - min envman version: 1.1.4 + - min stepman version: 0.9.31 + - default init plugin version: 0.9.4 + - default step plugin version: 0.9.3 + - default workflow-editor plugin version: 1.0.9 + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.6.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.6.1 -> 1.6.2 + +* [d6ba2d7] Krisztian Godrei - prepare for 1.6.2 (2017 Jun 12) +* [573f411] Krisztián Gödrei - bitrise deps and tools update (#515) (2017 Jun 12) +* [006e187] Krisztián Gödrei - godeps update (#514) (2017 Jun 12) +* [69be428] Krisztián Gödrei - Plugin update (#513) (2017 Jun 12) +* [0fc8020] Krisztián Gödrei - plugin info cmd, plugin list cmd update, plugin review (#512) (2017 Jun 12) +* [f25a2ee] Krisztián Gödrei - BITRISE_TMP_DIR & tests (#511) (2017 Jun 09) +* [0b36108] Krisztián Gödrei - plugin update fix (#510) (2017 Jun 09) +* [7b9e900] Zsolt - Bitrise export fix (#508) (2017 Jun 08) +* [cdc9d51] Viktor Benei - go toolkit - go version bump from 1.8.1 to 1.8.3 (#506) (2017 Jun 08) + + +## 1.6.1 (2017 May 10) + +### Release Notes + +* FIX regression: previous version (1.6.0) of bitrise-cli thrown and error when bitrise was on setting up the help template: failed to get current version map. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.6.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.6.0 -> 1.6.1 + +* [bf6da15] Krisztian Godrei - prepare for 1.6.1 (2017 May 10) +* [8441798] Krisztián Gödrei - default plugin updates (#503) (2017 May 10) +* [30c383f] Krisztián Gödrei - plugin list fix (#502) (2017 May 10) +* [3ec4e0d] Krisztián Gödrei - StepRunResultsModel’s Error field fix (#501) (2017 May 10) +* [bde77b8] Krisztián Gödrei - define plugin env keys as exported const (#500) (2017 May 10) + + +## 1.6.0 (2017 May 09) + +### Release Notes + +__1. Install local plugins:__ + +From this bitrise-cli version you can test your local plugin directly through the CLI, by installing it: + +`bitrise plugin install PATH/TO/MY/LOCAL/PLUGIN` + +_NOTE: You can specify your plugin's source as a command argument, no need to specify it with --src flag, however using the flag is still supported._ + +__2. Step Output Aliases__ + +You can specify the output's alias, by setting value to the desired alias key and the cli will export the output with the given alias. + +It is as simple as : + +``` +... +workflows: + primary: + steps: + - gradle-runner: + outputs: + - BITRISE_APK_PATH: ALIAS_APK_PATH +... +``` + +_The generated apk path will be available under `ALIAS_APK_PATH` key, instead of the default `BITRISE_APK_PATH` key._ + +_Note: if alias specified the output will be exported only with the alias, so the value will NOT be available with the original environment key._ + +__3. bitrise-cli got a new default plugin: `step`__ + +Bitrise Plugin to interact with steps, list them, retrieve information, or create your own! + +Want to create your own step? Just run `bitrise :step create` and you can create the perfect Step in no time! + +__4. default plugin updates:__ + +- init 0.9.1 +- workflow-editor 0.9.9 +- analytics 0.9.8 + +__5. Step development guidline updates, read more in [docs](https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md).__ + +__6. bitrise.yml format specification updates, read more in [docs](https://github.com/bitrise-io/bitrise/blob/master/_docs/bitrise-yml-format-spec.md).__ + +__7. Go toolkit's mininum go version bumped to: 1.8.1__ + +__8. Format version bumped to: 3__ + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.6.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.5.6 -> 1.6.0 + +* [3064a4b] Krisztian Godrei - prepare for 1.6.0 (2017 May 09) +* [5a2d97a] Krisztián Gödrei - default plugin version updates (#498) (2017 May 09) +* [ab1565d] Viktor Benei - proper plugin available message (#499) (2017 May 09) +* [c4039d0] Krisztián Gödrei - send bitrise format version to plugins (#497) (2017 May 09) +* [87fa22f] Krisztián Gödrei - alias fix (#496) (2017 May 09) +* [6e8307f] Viktor Benei - output alias test (#494) (2017 May 09) +* [fc69c58] Krisztián Gödrei - format version bumped to 3 (#495) (2017 May 09) +* [095c9a8] Krisztián Gödrei - step output alias (#493) (2017 May 09) +* [6fe235d] Viktor Benei - Step and env var spec enhancement (#492) (2017 May 08) +* [bd4d909] Viktor Benei - README: replace Slack with discuss link (#491) (2017 May 08) +* [f48ecec] Krisztián Gödrei - install local plugins (#490) (2017 May 08) +* [a3be4d7] Krisztián Gödrei - Step id naming convention (#489) (2017 May 02) +* [a22c2a9] Krisztian Godrei - type tag names update (2017 May 02) +* [ad15062] Krisztián Gödrei - Step grouping (#488) (2017 May 02) +* [d264edf] Viktor Benei - Go for toolkit version bump, from 1.8 to 1.8.1 (#486) (2017 Apr 28) +* [07a7827] Karol Wrótniak - Added version naming convention advice (#487) (2017 Apr 28) +* [2882891] Krisztián Gödrei - Release a new version description (#485) (2017 Apr 25) + + +## 1.5.6 (2017 Apr 11) + +### Release Notes + +* Switch Bitrise Data Model's (bitrise.yml) `format_version` to one component version number. +* Added `project_type` property to Bitrise Data Model - defines your source project's type. +* `bitrise.yml` format [specification](https://github.com/bitrise-io/bitrise/blob/master/_docs/bitrise-yml-format-spec.md) update. +* Dependency updates: + + - minimum [stepman](https://github.com/bitrise-io/stepman) version: [0.9.30](https://github.com/bitrise-io/stepman/releases/tag/0.9.30) + - default [workflow-editor](https://github.com/bitrise-io/bitrise-workflow-editor) version: [0.9.8](https://github.com/bitrise-io/bitrise-workflow-editor/releases/tag/0.9.8) + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.6/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.5.5 -> 1.5.6 + +* [4920e46] Krisztian Godrei - prepare for 1.5.6 (2017 Apr 11) +* [08589f2] Krisztián Gödrei - bump min stepman to 0.9.30, bump min wf editor to 0.9.8 (#484) (2017 Apr 11) +* [bc97566] Krisztián Gödrei - switch format version to one component version number, docs (#483) (2017 Apr 11) +* [2c0d842] Krisztián Gödrei - godeps update (#482) (2017 Apr 10) +* [2fa6ef4] Krisztián Gödrei - Update CHANGELOG.md (2017 Mar 14) + + +## 1.5.5 (2017 Mar 13) + +### Release Notes + +* __Silent setup__: bitrise will do a setup (_if was not performed for the current version_) before any plugin run. + +* From now the `bitrise --help` command output will include __PLUGINS help section__ as well: + +``` +NAME: bitrise - Bitrise Automations Workflow Runner + +USAGE: bitrise [OPTIONS] COMMAND/PLUGIN [arg...] + +VERSION: 1.5.5 + +GLOBAL OPTIONS: + --loglevel value, -l value Log level (options: debug, info, warn, error, fatal, panic). [$LOGLEVEL] + --debug If true it enabled DEBUG mode. If no separate Log Level is specified this will also set the loglevel to debug. [$DEBUG] + --ci If true it indicates that we're used by another tool so don't require any user input! [$CI] + --pr If true bitrise runs in pull request mode. + --help, -h show help + --version, -v print the version + +COMMANDS: + init Init bitrise config. + setup Setup the current host. Install every required tool to run Workflows. + version Prints the version + validate Validates a specified bitrise config. + run Runs a specified Workflow. + trigger-check Prints out which workflow will triggered by specified pattern. + trigger Triggers a specified Workflow. + export Export the bitrise configuration. + normalize Normalize the bitrise configuration. + step-list List of available steps. + step-info Provides information (step ID, last version, given version) about specified step. + workflows List of available workflows in config. + share Publish your step. + plugin Plugin handling. + stepman Runs a stepman command. + envman Runs an envman command. + help Shows a list of commands or help for one command + +PLUGINS: + :analytics Submitting anonymized usage information. + :init Initialize bitrise __config (bitrise.yml)__ and __secrets (.bitrise.secrets.yml)__ based on your project. + :workflow-editor Bitrise Workflow Editor. + +COMMAND HELP: bitrise COMMAND --help/-h +``` + +* `bitrise validate` command fixes: + + - minimal bitrise.yml should contain a `format_version` property + - `no bitrise.yml found` error message fix + +* Dependency updates: + + - minimum go version updated from 1.7.4 to 1.8 + - minimum stepman version: 0.9.29 + - default workflow-editor version: 0.9.6 + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.5/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.5.4 -> 1.5.5 + +* [cb8e402] Krisztian Godrei - prepare for v1.5.5 (2017 Mar 13) +* [e6dc915] Krisztián Gödrei - min workflow-editor: 0.9.6, min stepman: 0.9.29 (#479) (2017 Mar 13) +* [be45cd1] Krisztián Gödrei - godeps update (#478) (2017 Mar 13) +* [2315cd8] Krisztián Gödrei - Silent setup (#477) (2017 Mar 13) +* [332bea3] Krisztián Gödrei - Validate fix (#476) (2017 Mar 13) +* [8e98109] Krisztián Gödrei - not bitrise.yml found error message fix (#475) (2017 Feb 28) +* [3adda49] Viktor Benei - Go toolkit - go version upgrade from 1.7.5 to 1.8 (#474) (2017 Feb 23) +* [ce11f40] Viktor Benei - Go toolkit - min go version update from 1.7.4 to 1.7.5 (#472) (2017 Feb 20) +* [4939b98] Tamas Papik - Include plugins list on the help pages (#473) (2017 Feb 20) + + +## 1.5.4 (2017 Feb 14) + +### Release Notes + +* To allow bitrise-cli, to use [stepman](https://github.com/bitrise-io/stepman)'s new features we updated the required minimal stepman version to [0.9.28](https://github.com/bitrise-io/stepman/releases/tag/0.9.28). + +The new stepman version adds support for local and git setps in `step-info` command. This update will allow the [offline workflow-editor](https://github.com/bitrise-io/bitrise-workflow-editor) to handle every type of steps, even it is a git or local step. + +* This version of bitrise-cli ships with the [offline workflow-editor](https://github.com/bitrise-io/bitrise-workflow-editor) as a default plugin. + +This means, once you update your bitrise-cli to 1.5.4, it will install the workflow editor for you, during the setup process. To run the editor, just call: + +`bitrise :workflow-editor` + +* bitrise-cli checks, if there is a new version of any installed plugin, from now it will print a command for you, which you can use to update a plugin. Do not miss any updates! + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.4/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.5.3 -> 1.5.4 + +* [dd61696] Krisztian Godrei - prepare for 1.5.4 (2017 Feb 14) +* [be02cbb] Krisztian Godrei - Merge branch 'master' of github.com:bitrise-io/bitrise (2017 Feb 14) +* [2b1c1a4] Krisztián Gödrei - create-release wf update, switch workflow log fix, min stepman version bumped to 0.9.28, workflow-editor default plugin (#471) (2017 Feb 14) +* [8810152] trapacska - New plugin warning extended with instructions (#470) (2017 Feb 14) +* [9b39206] trapacska - New plugin warning extended with instructions (#470) (2017 Feb 14) +* [20af75e] Krisztián Gödrei - Stepman update (#469) (2017 Feb 14) + + +## 1.5.3 (2017 Jan 26) + +### Release Notes + +* use envman & stepman throught bitrise-cli + +bitrise-cli manages his envman and stepman dependencies internally, but you may want to use stepman or envman direct. +From now you can use `bitrise envman` to access envman commands or `bitrise stepman` to stepman's. + +* `bitrise validate` now warns you if your trigger item would trigger utility workflow (_utility workflow's workflow id starts with underscore (`_`) character_) + +* stepman min version bumped to: [0.9.27](https://github.com/bitrise-io/stepman/releases/tag/0.9.27) + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.3/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.5.2 -> 1.5.3 + +* [5e48e55] Krisztian Godrei - prepare for 1.5.3 (2017 Jan 26) +* [f528276] Krisztián Gödrei - prepare for 1.5.3, min stepman version bumped to 0.9.27 (#468) (2017 Jan 26) +* [74cc0f6] Krisztián Gödrei - logrus instead of log package (#467) (2017 Jan 24) +* [576ed57] Krisztián Gödrei - trigger utility workflow (#466) (2017 Jan 24) +* [8f832e7] Krisztián Gödrei - use envman & stepman throught bitrise-cli (#464) (2017 Jan 24) +* [d45c6e6] Krisztián Gödrei - godeps update (#465) (2017 Jan 24) + + +## 1.5.2 (2017 Jan 10) + +### Release Notes + +* envman min version bumped to [1.1.3](https://github.com/bitrise-io/envman/releases/tag/1.1.3) +* expanded trigger map validation: + + - validate whether workflow (defined in trigger map item) exists + - validate whether duplicate patterns with same type exists + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.5.1 -> 1.5.2 + +* [23ecca2] Krisztian Godrei - prepare for 1.5.2 (2017 Jan 10) +* [d9e9898] Krisztián Gödrei - deps update (#463) (2017 Jan 10) +* [70514a8] Krisztián Gödrei - Bitrise yml validation (#462) (2017 Jan 10) + + +## 1.5.1 (2016 Dec 14) + +### Release Notes + +* `stepman` min version bumped to [0.9.26](https://github.com/bitrise-io/stepman/releases/tag/0.9.26) + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.5.0 -> 1.5.1 + +* [dc2fd02] Krisztian Godrei - version fix (2016 Dec 14) +* [27c566f] Krisztian Godrei - prepare for 1.5.1 (2016 Dec 14) +* [ecdf381] Krisztián Gödrei - stepman version bump to 0.9.26 (#461) (2016 Dec 14) + + +## 1.5.0 (2016 Dec 13) + +### Release Notes + +* init command moved to a separate [plugin](https://github.com/bitrise-core/bitrise-plugins-init), this means you can initialize a new bitrise config by running `bitrise :init`, (previous `bitrise init` command also exists, but it calls the plugin). + + The new init plugin uses the [core](https://github.com/bitrise-core/bitrise-init) of the [Project Scanner step](https://github.com/bitrise-steplib/steps-project-scanner), which used by the [btrise.io](https://www.bitrise.io) website to add new app. + + You can create a project type based init by running: `bitrise :init` or create a 'custom' configuration by calling `bitrise :init --minimal`. + +* bitrise now prints available step update, even if step does not fail +* bitrise-cli docs are expanded with __bitrise.yml format specification / reference__ +* improvements on available workflows log +* fixed `validate` command + - the validate command fails if bitrise config or bitrise secrets is empty + - fixed exit status if validate fails + - integration tests + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.4.5 -> 1.5.0 + +* [a20102d] Krisztian Godrei - prepare for 1.5.0 (2016 Dec 13) +* [8315903] Krisztián Gödrei - init (#460) (2016 Dec 13) +* [5b38532] Krisztián Gödrei - Validate fix (#459) (2016 Dec 13) +* [5fbb00c] Krisztián Gödrei - remove timeout (#458) (2016 Dec 12) +* [6a7dc40] Krisztián Gödrei - version bump to 1.5.0, format version bump to 1.4.0 (#457) (2016 Dec 08) +* [50e3241] Viktor Benei - don't print timestamp for workflow list (#455) (2016 Dec 07) +* [d41571a] Viktor Benei - Go 1.7.4 (#452) (2016 Dec 07) +* [ab98213] Viktor Benei - Update bitrise-yml-format-spec.md (2016 Dec 06) +* [4b440cc] Viktor Benei - Feature/docs property ref docs (#456) (2016 Dec 06) +* [21708e5] Krisztián Gödrei - version bump to 1.4.6-pre (#450) (2016 Nov 29) +* [89dc8db] Krisztián Gödrei - Godeps update (#449) (2016 Nov 29) +* [a0e962c] Krisztián Gödrei - Init (#447) (2016 Nov 29) +* [22e93de] Krisztián Gödrei - Step timeout (#445) (2016 Nov 29) +* [d6d19a3] Krisztián Gödrei - go-toolkit step template test (#446) (2016 Nov 29) +* [14d74d0] Krisztián Gödrei - print update available if any (#448) (2016 Nov 29) +* [0a6e522] Krisztián Gödrei - godeps update (#444) (2016 Nov 24) + + +## 1.4.5 (2016 Nov 10) + +### Release Notes + +* __FIX__ regression: previous version (1.4.4) of bitrise-cli thrown and error when `bitrise steup` was called on Linux: `unsupported platform`. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.4.5/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.4.4 -> 1.4.5 + +* [742cd01] Krisztian Godrei - prepare for 1.4.5 (2016 Nov 10) +* [bfc376a] Krisztián Gödrei - linux install fix (#440) (2016 Nov 10) + + +## 1.4.4 (2016 Nov 08) + +### Release Notes + +* apt get package install check fix: previous apt-get package install check (`dpkg -l PACKAGE`) was returning with exist code: `0`, even if the package is not fully installed. This version of `bitrise-cli` uses `dpkg -s PACKAGE` command to check if package is installed or not. +* `bitrise version --full` command now prints the __Go__ and __OS__ version, which was used to build the bitrise-cli binary. +* `bitrise plugin` command group now get a new command: `update`. +This command can be used to update bitrise plugins, like: `bitrise plugin update analytics`. +* retry step dependency install, if it fails, for improved reliability. +* envman minimum version updated to: [1.1.2](https://github.com/bitrise-io/envman/releases/tag/1.1.2) +* used analytics plugin version updated to: [0.9.6](https://github.com/bitrise-core/bitrise-plugins-analytics/releases/tag/0.9.6) + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.4.4/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.4.3 -> 1.4.4 + +* [7ad576b] Krisztian Godrei - workflow refactors (2016 Nov 08) +* [cce35e6] Krisztián Gödrei - godeps update, test update (#439) (2016 Nov 08) +* [a5c0329] Krisztián Gödrei - retry if step dependency install failed (#438) (2016 Nov 08) +* [7a78c50] Krisztián Gödrei - envman min version bumped to: 1.1.2, analytics min version bumped to: 0.9.6, bitrise.yml update (#437) (2016 Nov 08) +* [65ca4b3] Krisztián Gödrei - Plugin update (#436) (2016 Nov 08) +* [607f20d] Krisztián Gödrei - print go and os version in version command (#435) (2016 Nov 04) +* [e11bc96] Viktor Benei - apt get package installed check fix (#434) (2016 Nov 02) + + +## 1.4.3 (2016 Oct 24) + +### Release Notes + +#### __Removed emojis__ from step and build run result logs. + +- Success step run's icon changed from: ✅ to: `✓` +- Failed step run's icon changed from: 🚫 to: `x` +- Skipped by fail step run's icon changed from: ⚠️ to: `!` +- Skipped by run_if expression step run's icon changed from: ➡ to: `-` + +#### Go version bumped for toolkit to 1.7.3 +#### Fixed `panic: runtime error: makeslice: len out of range` issue, when printing long running step's runtime in step and build run result logs. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.4.3/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.4.2 -> 1.4.3 + +* [46c2607] Krisztián Gödrei - prepare for 1.4.3 (#432) (2016 Oct 24) +* [200e397] Viktor Benei - version 1.4.3-pre (#430) (2016 Oct 21) +* [e8510f3] Krisztián Gödrei - long step run time (#429) (2016 Oct 20) +* [c7f900e] Viktor Benei - bumped Go version for toolkit to 1.7.3 (#428) (2016 Oct 20) +* [9978d5c] Viktor Benei - Feature/remove log emojis (#427) (2016 Oct 20) +* [807f3c8] Krisztián Gödrei - Update CHANGELOG.md (2016 Oct 14) + + +## 1.4.2 (2016 Oct 14) + +### Release Notes + +* stepman min version update to: [0.9.25](https://github.com/bitrise-io/stepman/releases/tag/0.9.25): + +`stepman share` command fix: in version 0.9.24 stepman created a branch - for sharing a new step - with name: `STEP_ID` and later tried to push the steplib changes on branch: `STEP_ID-STEP_VERSION`, which branch does not exist. +This release contains a quick fix for stepman sharing, the final share branch layout is: `STEP_ID-STEP_VERSION` + +* `format_version` updated to: `1.3.1` (fix: forgot to bump in 1.4.1) + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.4.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.4.1 -> 1.4.2 + +* [fb62066] Krisztian Godrei - prepare for 1.4.2 (2016 Oct 14) +* [9d46c63] Krisztián Gödrei - stepman min version: 0.9.25 (#425) (2016 Oct 14) +* [b250db7] Krisztián Gödrei - Update CHANGELOG.md (2016 Oct 11) +* [a29e80a] Krisztián Gödrei - Update CHANGELOG.md (2016 Oct 11) + + +## 1.4.1 (2016 Oct 11) + +### Release Notes + +#### Tag trigger event handling + +The new trigger map is completed with tag event support. + +``` +- tag: TAG_PATTERN + workflow: WORKFLOW_NAME +``` + +example: + +``` +- tag: *.*.* + workflow: deploy +``` + +#### bitrise-cli global flag fix + +Fixed _Pull Request Mode_ and _CI Mode_ global flag (`--pr` and `--ci`) handling. +_Pull Request Mode_ and _CI Mode_ global flags are available in `run`, `trigger` and `trigger-check` commands. + +From now `bitrise --pr COMMAND` will run in _Pull Request Mode_, whatever is set in environemnts or in secrets, +`bitrise --pr=false COMMAND` will __NOT__ run in _Pull Request Mode_, whatever is set in environemnts or in secrets. + +similar `bitrise --ci COMMAND` will perform the command in _CI Mode_, whatever is set in environemnts or in secrets and +`bitrise --ci=false COMMAND` will __NOT__ run in _CI Mode_, whatever is set in environemnts or in secrets. + +#### output envstore cleanup + +In previous versions of `bitrise-cli` the output envstore (which is a container for the step output environments) +was not cleared after processing its content. This led bitrise-cli to duplicate every output environment, which was generated by a step, after every next step run. + +#### bash toolkit entry file support + +Before this release bash-toolkit step's entry file was the hardcoded `step.sh`, from now these steps can specify the entry file path in the `step.yml`. + +example: + +``` +... +toolkit: + bash: + entry_file: step_entry.sh +... +``` + +#### dependency updates + +`stepman` min version updated to: [0.9.24](https://github.com/bitrise-io/stepman/releases/tag/0.9.24), `analytics plugin` version updated to [0.9.5](https://github.com/bitrise-core/bitrise-plugins-analytics/releases/tag/0.9.5). + +#### minor fixes + +Updated messages with default values at dependency installation. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.4.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.4.0 -> 1.4.1 + +* [49b3b81] Krisztian Godrei - prepare for 1.4.1 (2016 Oct 11) +* [866b031] Krisztian Godrei - bitrise.yml updates (2016 Oct 11) +* [42fa26a] Krisztián Gödrei - stepman version: 0.9.24, analitics version: 0.9.5 (#423) (2016 Oct 11) +* [03337ca] Krisztián Gödrei - entry file test (#422) (2016 Oct 11) +* [8f38591] Krisztián Gödrei - envstore test (#421) (2016 Oct 07) +* [850e87e] Krisztián Gödrei - global flag handling fix (#420) (2016 Oct 06) +* [b9e6d7a] Krisztián Gödrei - Tag event (#419) (2016 Oct 04) +* [9e077f9] Viktor Benei - just a minor dep install text change/clarification (#418) (2016 Sep 27) +* [143a90e] Viktor Benei - Feature/dep install prompt fix (#415) (2016 Sep 24) +* [ff1ec60] Viktor Benei - .DS_Store gitignore (2016 Sep 24) +* [524fb8f] Viktor Benei - base for integration tests in go (#412) (2016 Sep 19) +* [f4fba50] Viktor Benei - Feature/minor rev (#413) (2016 Sep 19) +* [2dffff4] Viktor Benei - deps update (#411) (2016 Sep 18) +* [6ede212] Viktor Benei - minor scoping revision (#410) (2016 Sep 18) + + +## 1.4.0 (2016 Sep 13) + +### Release Notes + +#### New trigger map + +bitrise contains a new trigger map syntax, to allow specify more specific and felxible trigger events, full proposal is available on [github](https://github.com/bitrise-io/bitrise.io/issues/40). + +_Keep in mind:_ +__Every single trigger event should contain at minimum one condition.__ +__Every single trigger event conditions are evaluated with AND condition.__ + +__code push:__ + +``` +- push_branch: BRANCH_NAME + workflow: WORKFLOW_ID_TO_RUN +``` + +__pull request:__ + +``` +- pull_request_source_branch: SOURCE_BRANCH_NAME + pull_request_target_branch: TARGET_BRANCH_NAME + workflow: WORKFLOW_ID_TO_RUN +``` + +exmple: + +``` +trigger_map: +- push_branch: release* + workflow: deploy +- push_branch: master + workflow: primary +- pull_request_target_branch: develop + workflow: test +``` + +_New trigger map handling is fully compatible with the old syntax, following conversion is applied:_ + +``` +Old syntax: New Syntax: + +trigger_map: trigger_map: +- pattern: * -> - push_branch: * + workflow: primary workflow: primary +``` + +``` +Old syntax: New Syntax: + +trigger_map: trigger_map: +- push_branch: * -> - push_branch: * + is_pull_request_allowed: true workflow: primary + workflow: primary - pull_request_source_branch: * + workflow: primary +``` + +#### Toolkit support (_BETA_) + +_Toolkit support is still in beta and details of it migth change in upcoming cli releases._ + +Currently available toolkits: `bash` and `go`. + +__bash toolkit__ realize the way of current step handling, +e.g.: every step needs to have a `step.sh` in the step's directory as an entry point for the step. + +When bitrise executes the step, it call calls `bash step.sh`. + +In case of __go toolkit__, you need to specify the package name, and the toolkit takes care about: + +* moving the go step into a prepared GOPATH inside of the .bitrise directory +* building the step project +* chaching the binary of given version of step + +When bitrise executes the step, it calls the step's binary. + +_Using the toolkit can provide performance benefits, as it does automatic binary caching - +which means that a given version of the step will only be compiled the first time, +subsequent execution of the same version will use the compiled binary of the step!_ + +_Toolkit also takes care of its own dependencies. +For example go toolkit requires installed go, +so toolkit checks if desired version of go is installed on the system, +if not it installs it for itself (inside the .bitrise directory), +but does not touch the system installed version._ + +Check out `slack` step for living example of go toolkit usage: [slack v2.2.0](https://github.com/bitrise-io/steps-slack-message/releases/tag/2.2.0) + +#### Step dependency handling revision + +* fixed check whether dependency is installed or not +* dependecy models got new property: `bin_name` + +_bin_name is the binary's name, if it doesn't match the package's name. +E.g. in case of "AWS CLI" the package is `awscli` and the binary is `aws`. +If bin_name is empty name will be used as bin_name too._ + +#### Other changes: + +* Every __networking__ function of bitrise cli uses __retry logic__ and prints progress indicator. +* bitrise run now prints _Running workflow: WORKFLOW_ID_, for the workflow started running + and prints _Switching to workflow: WORKFLOW_ID_ when running before and after workflows. +* bitrise configuration (bitrise.yml) __format version__ updated to __1.4.0__ +* __stepman__ version update to [0.9.23](https://github.com/bitrise-io/stepman/releases/tag/0.9.23) +* __envman__ version update to [1.1.1](https://github.com/bitrise-io/envman/releases/tag/1.1.1) + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.4.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.3.7 -> 1.4.0 + +* [e229c64] Krisztián Gödrei - min envman version: 1.1.1, min stepman version: 0.9.23 (#407) (2016 Sep 13) +* [1e3cbb9] Krisztián Gödrei - godeps update (#406) (2016 Sep 13) +* [d7bf595] Krisztián Gödrei - New trigger (#402) (2016 Sep 13) +* [80719e3] Viktor Benei - Step deps handling revision (#405) (2016 Sep 12) +* [51f55dc] Viktor Benei - bitrise run now prints the workflow it was started with (#403) (2016 Sep 12) +* [f07a254] Viktor Benei - model version 1.3.0 (#404) (2016 Sep 10) +* [c4320c6] Viktor Benei - Feature/toolkit bootstrap revision (#401) (2016 Sep 09) +* [751dd74] Viktor Benei - Feature/toolkit enforcement revision (#400) (2016 Sep 09) +* [2b2505a] Viktor Benei - Feature/go toolkit beta revs (#399) (2016 Sep 08) +* [fc43c43] Viktor Benei - v1.4.0 - version number prep (#398) (2016 Sep 08) +* [1dd93e4] Viktor Benei - [WIP] Feature/toolkit go (#385) (2016 Sep 08) +* [35ea8d2] Viktor Benei - setup / dependency install : error passing fix (#397) (2016 Sep 07) +* [d7ced31] Viktor Benei - Feature/deps update (#396) (2016 Sep 06) +* [db7f786] Viktor Benei - tools install & download separation (#395) (2016 Sep 05) +* [40277a4] Viktor Benei - fix in tests, to make `go test ./...` work after a clean checkout (e.g. in `docker`) (#394) (2016 Sep 05) +* [5ecb521] Viktor Benei - dependencies (tools & plugins install) : with progress & retry (#393) (2016 Sep 05) +* [eb57eb6] Viktor Benei - Feature/readme and docker revision (#392) (2016 Sep 05) +* [e130bba] Viktor Benei - typo fixes (#391) (2016 Sep 05) +* [452dced] Viktor Benei - deps update (#390) (2016 Sep 05) +* [40f28c1] Viktor Benei - step URL note if git:: step clone fails (#389) (2016 Sep 01) +* [d01ea23] Viktor Benei - deps update (#386) (2016 Aug 23) + + +## 1.3.7 (2016 Aug 09) + +### Release Notes + +* From now you can specify __workflow id to run__ with `--workflow` flag for `bitrise run` command. + Example: `bitrise run --workflow WORKFLOW_ID`. + _In previous versions you were able to specify workflow id to run as a command argument (`bitrise run WORKFLOW_ID`); this method is still supported._ + +* Similar to run command's new `--workflow` flag, `trigger` and `trigger-check` commands also received new flags for specifying the __trigger pattern__: `--pattern`. + Example: `bitrise trigger --pattern PATTERN`. + _In previous versions you were able to specify the pattern as a command argument (`bitrise trigger PATTERN`); this method is still supported._ + +* __json parameters__: every workflow run related commands (`run`, `trigger`, `trigger-check`) now have new inputs: + + - `--json-params` + - `--json-params-base64`. + + You can use `--json-params` to specify __every available command flag__ in a single json struct. This json struct should be a string-string map, where every key is the command flag's name, and the value should be the flag's value. + + For example: + `bitrise run --config bitrise.yml --workflow primary` + + Equivalent with json-params: + `bitrise run --json-params '{"config":"bitrise.yml", "workflow":"primary"}'` + + To see the command's available flags, call `bitrise COMMAND -h`. + + If you want to avoid character escaping side effects while running the `bitrise` cli, you can base64 encode --json-params value and pass to bitrise cli using the `--json-params-base64` flag. + +* feature/internal tools handling revision: __the `envman` and `stepman` (used by `bitrise`) tools install path moved from `/usl/local/bin` to `$HOME/.bitrise/tools`__ to make sure bitrise cli uses the desired tool version. + +* stepman min version updated to 0.9.22 + +* deprecated action signature fix + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.7/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.3.6 -> 1.3.7 + +* [890307c] Krisztián Gödrei - prepare for 1.3.7 (2016 Aug 09) +* [5be9c1d] Krisztián Gödrei - Json params prepare for new trigger map (#380) (2016 Aug 08) +* [d91f6ac] Krisztián Gödrei - remove unnecessary init path from run (#379) (2016 Aug 03) +* [c2187b3] Krisztián Gödrei - Json params (#378) (2016 Aug 03) +* [187382f] Krisztián Gödrei - deprecated action signature fix (#377) (2016 Aug 01) +* [45ed0d0] Viktor Benei - Feature/internal tools handling revision (#374) (2016 Jul 26) + + +## 1.3.6 (2016 Jul 12) + +### Release Notes + +* stepman dependency update to 0.9.21 +* build run result log now prints "Not provided" if missing source_code_url / support_url +* step-development-guideline.md update +* typo fix + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.6/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.3.5 -> 1.3.6 + +* [65406ce] Krisztián Gödrei - prepare for 1.3.6 (2016 Jul 12) +* [3509ca9] Krisztián Gödrei - stepman dependency update to 0.9.21 (#371) (2016 Jul 12) +* [8c245dd] Krisztián Gödrei - godeps update (#370) (2016 Jul 12) +* [2a5d92d] Viktor Benei - Update README.md (2016 Jul 02) +* [acb42e7] Krisztián Gödrei - Merge pull request #367 from godrei/godep_update (2016 Jun 30) +* [3e8bb97] Krisztián Gödrei - errcheck fix (2016 Jun 29) +* [baf812d] Krisztián Gödrei - colorfunc update, bitrise.yml updates (2016 Jun 29) +* [836d298] Krisztián Gödrei - godep update (2016 Jun 29) +* [e36c9c6] Viktor Benei - Update step-development-guideline.md (2016 Jun 28) +* [f5f639a] Krisztián Gödrei - Merge pull request #365 from godrei/error_footer (2016 Jun 23) +* [d6d847e] Viktor Benei - Merge pull request #363 from viktorbenei/master (2016 Jun 23) +* [c4cd641] Viktor Benei - Merge pull request #364 from bitrise-io/viktorbenei-patch-1 (2016 Jun 23) +* [bdce9bb] Krisztián Gödrei - test updates (2016 Jun 22) +* [97bbbae] Krisztián Gödrei - chardiff = 0 test (2016 Jun 22) +* [8ff6f12] Krisztián Gödrei - print "Not provided" if missing source_code_url / support_url (2016 Jun 22) +* [691049c] Viktor Benei - typo fix (2016 Jun 20) +* [b8d6738] Viktor Benei - gows init & go-utils/pathutil fix (2016 Jun 16) + + +## 1.3.5 (2016 Jun 07) + +### Release Notes + +* From now on `bitrise setup` (without any flag) is the equivalent of the previous `bitrise setup --minimal` call (e.g.: it omits `brew doctor` call, which fails if brew or Xcode is outdated). You can achieve the old *full* setup behaviour (e.g.: which includes `brew doctor`) by calling `bitrise setup --full`. +* Logging improvements. +* New `run_if` template [examples](https://github.com/bitrise-io/bitrise/blob/master/_examples/experimentals/templates/bitrise.yml) +* A fix for installing bitrise plugins from local paths (e.g. during plugin development) + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.5/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.3.4 -> 1.3.5 + +* [6e15ca5] Krisztián Gödrei - Merge pull request #361 from godrei/setup_review (2016 Jun 03) +* [433cd40] Krisztián Gödrei - log full setup (2016 Jun 03) +* [b7ed487] Krisztián Gödrei - setup fix for local plugins (2016 Jun 03) +* [a3e3fdc] Krisztián Gödrei - bitrise ci workflow name refactors (2016 Jun 03) +* [f9a91b8] Viktor Benei - Merge pull request #360 from godrei/template_examples (2016 May 26) +* [8501df7] Krisztián Gödrei - run_if template examples (2016 May 26) +* [9119289] Krisztián Gödrei - Merge pull request #359 from godrei/config_fix (2016 May 25) +* [f0f378c] Krisztián Gödrei - log config error (2016 May 25) +* [fd067e8] Krisztián Gödrei - Merge pull request #358 from godrei/setup (2016 May 11) +* [ba22d81] Krisztián Gödrei - minimal setup by default (2016 May 11) + + +## 1.3.4 (2016 May 10) + +### Release Notes + +* Removed exist status error from failed step's log: + +``` +ERRO[13:14:02] Step (tmp) failed, error: (exit status 1) +``` + +* Now bitrise `trigger map` will be validated before use. The validation makes sure there is no trigger map item with empty pattern or workflow id. +* Minor fixes and improvements + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.4/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.3.3 -> 1.3.4 + +* [62c0033] Krisztián Gödrei - godep update (2016 May 10) +* [d352a3c] Krisztián Gödrei - prepare for release (2016 May 10) +* [6b6b63f] Krisztián Gödrei - Merge pull request #355 from godrei/failed_step_log_fix (2016 May 10) +* [5354284] Krisztián Gödrei - removed exist status error from failed step's log (2016 May 10) +* [45c2106] Krisztián Gödrei - Merge pull request #354 from godrei/exit_review (2016 May 10) +* [1946f40] Krisztián Gödrei - trigger map empty test fix (2016 May 09) +* [7e9ec69] Krisztián Gödrei - empty pattern/wf id integration tests (2016 May 09) +* [cbcee15] Krisztián Gödrei - exit review (2016 May 09) + + +## 1.3.3 (2016 Apr 27) + +### Release Notes + +* __FIX__ regression since `1.2.x`: `bitrise trigger [PATTERN]` did not handled PR mode correctly, if PR mode was set in bitrise secrets. `is_pull_request_allowed: false` was not correctly handled in the `trigger_map` if the PR mode indication was declared in the bitrise secrets. This version fixes the PR mode handling when running `bitrise trigger [PATTERN]` and also includes unit and integration tests for it. +* Now `bitrise trigger-check [PATTERN]` also checks for PR envs in secrets. It uses the same functionality to determine which workflow id to select as `bitrise trigger [PATTERN]` does. +* __FIX__ regression: `bitrise trigger [PATTERN]` once again allows to trigger *utility workflows* as well. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.3/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.3.2 -> 1.3.3 + +* [2c97445] Krisztián Gödrei - Merge pull request #349 from godrei/trigger_fix (2016 Apr 27) +* [ee247e1] Krisztián Gödrei - fixed bitrise trigger (2016 Apr 27) +* [256526a] Krisztián Gödrei - Merge pull request #348 from godrei/trigger_fix (2016 Apr 26) +* [aeb9db5] Krisztián Gödrei - fatal instead of error (2016 Apr 26) +* [a347f6e] Krisztián Gödrei - expand cli context (2016 Apr 26) +* [8522bd3] Krisztián Gödrei - Merge pull request #347 from godrei/master (2016 Apr 20) +* [4967f16] Krisztián Gödrei - PR fix (2016 Apr 20) +* [ca9d760] Krisztián Gödrei - changelog update (2016 Apr 20) +* [9856b9c] Krisztián Gödrei - changelog (2016 Apr 20) + + +## 1.3.2 (2016 Apr 20) + +### Release Notes + +* __FIX__: although the previous version (1.3.1) fixed the exit code issue for `bitrise run`, the exit code was still not the right one in case of `bitrise trigger`. This version fixes the issue for bitrise trigger too, as well as we unified the handling codes of `run` and `trigger` as much as possible. Additionally, we now have integration tests (testing the exit codes) for both `bitrise run` and `bitrise trigger`. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.3.1 -> 1.3.2 + +* [5207fc1] Krisztián Gödrei - Merge pull request #346 from godrei/trigger_fix (2016 Apr 20) +* [f248c47] Krisztián Gödrei - PR fix (2016 Apr 20) +* [be73f82] Krisztián Gödrei - PR fix (2016 Apr 20) +* [19b2861] Krisztián Gödrei - common run (2016 Apr 20) +* [9a83792] Krisztián Gödrei - integration tests moved to bitrise-integration.yml (2016 Apr 20) +* [aa9364e] Krisztián Gödrei - allow pull request at trigger tests (2016 Apr 20) +* [dda344d] Krisztián Gödrei - unit tests (2016 Apr 20) +* [5982017] Viktor Benei - Merge pull request #345 from godrei/tests (2016 Apr 20) +* [b88c9e4] Krisztián Gödrei - test updates (2016 Apr 19) +* [56787eb] Krisztián Gödrei - Merge pull request #344 from godrei/master (2016 Apr 19) +* [82e0746] Krisztián Gödrei - Changelog (2016 Apr 19) + + +## 1.3.1 (2016 Apr 19) + +### Release Notes + +* __FIX__: We discovered a critical issue in the CLI v1.3.0. Version 1.3.0 of the CLI does not return the expected exit code after `bitrise run [WORKFLOW-ID]` if the `run` fails. It always returns exit code 0 if the configuration was correct and the workflow was executed, even if a step failed during `run`. This version fixes the exit code issue. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.3.0 -> 1.3.1 + +* [1255aa7] Krisztián Gödrei - Merge pull request #343 from godrei/master (2016 Apr 19) +* [16a587f] Krisztián Gödrei - code cleaning (2016 Apr 18) +* [ea1349f] Krisztián Gödrei - Merge pull request #342 from godrei/run_exit_code (2016 Apr 18) +* [9be9913] Krisztián Gödrei - cleanup (2016 Apr 18) +* [fd09faf] Krisztián Gödrei - exit code fix (2016 Apr 18) +* [48d609c] Krisztián Gödrei - exit code test (2016 Apr 18) +* [33065b5] Viktor Benei - Merge pull request #341 from godrei/test_updates (2016 Apr 15) +* [d56ec9e] Krisztián Gödrei - typo fix (2016 Apr 15) +* [eadf1bd] Krisztián Gödrei - test updates (2016 Apr 15) + + +## 1.3.0 (2016 Apr 12) + +### Release Notes + +* __BREAKING__ : Now you can delete/reset environment variables by setting the value to empty string (""). + Previously an empty value (e.g. `- an_input: ""`) was just ignored, + now it actually sets the value to an empty value. +* __NEW__ : Plugins ("beta"), to extend the `bitrise` functionality without modifying the "core" + * Install plugin: `bitrise plugin install [PLUGIN_NAME]` + * Delete plugin: `bitrise plugin delete [PLUGIN_NAME]` + * List installed plugins: `bitrise plugin list` + * Run plugin: `bitrise :[PLUGIN_NAME]` + * bitrise cli now installs default plugins at `bitrise setup`. +* __NEW__ docs & tutorials: + * Step Development Guideline: `_docs/step-development-guideline.md` + * React Native Tutorial: `_examples/tutorials/react-native` +* Step Template revision: + * Generic format update + * Using `change-workdir` instead of a custom script + * Added a `share-this-step` workflow for quick & easy step sharing +* New `--format=json` & `--format=yml` output modes (beta, only a few command supports this flag right now) + * Added a new `version` command which now supports the `--format=json` +* README.md updates + * Tooling and `--format=json` + * Share your own Step section +* `bitrise workflows`, `bitrise step-info [STEP_ID]`, `bitrise step-list` cmd output improvements +* `bitrise validate` cmd updates: + * workflow id validation + * check for duplicated inputs +* bitrise output log improvements + * Now build log contains deprecation infos about deprecated steps +* typo fixes +* Requires new `envman` (`1.1.0`) and `stepman` (`0.9.18`) versions - it'll + auto-install these at first run if the required new versions are not found. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.2.4 -> 1.3.0 + +* [5181b50] Krisztián Gödrei - Merge pull request #339 from godrei/cache_dir_env (2016 Apr 11) +* [ce86551] godrei - cache dir env (2016 Apr 11) +* [da1edb6] Krisztián Gödrei - Merge pull request #337 from godrei/plugin_update (2016 Apr 11) +* [3f28676] godrei - plugin update fix & analytics 0.9.4 (2016 Apr 11) +* [590015d] Krisztián Gödrei - Merge pull request #336 from godrei/version_cmd (2016 Apr 07) +* [3c2bfe8] godrei - cleanup (2016 Apr 07) +* [d784f98] godrei - include commit in full version (2016 Apr 07) +* [f33738b] godrei - outputFormat moved to output package (2016 Apr 07) +* [e868e78] Krisztián Gödrei - Merge pull request #334 from bitrise-io/update-react-example (2016 Apr 07) +* [534119c] Krisztián Gödrei - Merge pull request #335 from godrei/build_number (2016 Apr 06) +* [bc2bbe6] godrei - move binaries to deploy dir (2016 Apr 06) +* [da00fb2] godrei - PR fix (2016 Apr 06) +* [b999737] Agnes Vasarhelyi - Update bitrise.yml (2016 Apr 06) +* [6feb388] godrei - build number (2016 Apr 06) +* [0bc68e7] vasarhelyia - Remove local path (2016 Apr 06) +* [19cde67] vasarhelyia - Use dedicated steps (2016 Apr 06) +* [a02d148] Krisztián Gödrei - Merge pull request #333 from godrei/master (2016 Apr 06) +* [e0e98ec] godrei - release notes (2016 Apr 06) +* [8d4e86a] godrei - v1.3.0 (2016 Apr 06) +* [471b2ab] godrei - bitrise.yml typo fix (2016 Apr 06) +* [19e845a] Krisztián Gödrei - Merge pull request #332 from godrei/prepare_for_relelase (2016 Apr 06) +* [e5187ac] godrei - removed old changelogs (2016 Apr 06) +* [77ee955] godrei - prepare for release (2016 Apr 06) +* [f34df79] Krisztián Gödrei - Merge pull request #331 from godrei/feature/default_plugins (2016 Apr 06) +* [68c93fa] godrei - default analytics plugin min version update (2016 Apr 06) +* [e29d733] godrei - log installed plugin (2016 Apr 05) +* [d453d8b] godrei - default plugins (2016 Apr 05) +* [2b398f1] Krisztián Gödrei - Merge pull request #330 from godrei/duplicated_inputs (2016 Apr 05) +* [5d8bc29] godrei - test fixes (2016 Apr 05) +* [5d142cb] godrei - check for duplicated inputs (2016 Apr 05) +* [e0402d0] Krisztián Gödrei - Merge pull request #329 from godrei/godep-update (2016 Apr 05) +* [fb51075] godrei - godep update (2016 Apr 05) +* [4d78370] Krisztián Gödrei - Merge pull request #328 from godrei/separate_packages (2016 Apr 05) +* [8ff9a6e] Krisztián Gödrei - Merge pull request #327 from godrei/ci_updates (2016 Apr 05) +* [1e57cb7] godrei - cleanup (2016 Apr 05) +* [ace4e2b] godrei - separate bitrise packages (2016 Apr 05) +* [2ec5090] godrei - bitrise.yml updates (2016 Apr 04) +* [4c2ca4a] Viktor Benei - Merge pull request #326 from godrei/deprecate_wildcard_workflow_id (2016 Apr 01) +* [5477a8e] godrei - PR fix (2016 Apr 01) +* [722cc71] godrei - [b4475fe] [204cd7c] typo fix [b672304] workflow id validation (2016 Apr 01) +* [dbc2e95] Krisztián Gödrei - Merge pull request #325 from godrei/global_step_info (2016 Mar 18) +* [95448a1] Krisztián Gödrei - deprecate infos (2016 Mar 18) +* [85ac8ce] Krisztián Gödrei - Merge pull request #324 from godrei/skip_if_empty (2016 Mar 18) +* [866d86e] Krisztián Gödrei - instal bitrise tool in _prepare_and_setup workflow (2016 Mar 18) +* [797a42a] Krisztián Gödrei - bitrise.yml updates (2016 Mar 18) +* [463e9ae] Krisztián Gödrei - plugin update for new envman version, release configs, bitrise.yml updates (2016 Mar 18) +* [f74bee9] Krisztián Gödrei - removed local reference in create_changelog workflow & skip_if_empty unit test new environment variable (skip_if_empty) handling (2016 Mar 18) +* [a92c659] Krisztián Gödrei - Merge pull request #323 from godrei/test_fix (2016 Mar 18) +* [384ee68] Krisztián Gödrei - plugin version check fix (2016 Mar 17) +* [ed5b5ca] Krisztián Gödrei - use bitrise-core test repos (2016 Mar 17) +* [8542528] Krisztián Gödrei - removed download test (2016 Mar 17) +* [18a1ac9] Viktor Benei - Merge pull request #321 from godrei/events (2016 Mar 17) +* [c8f88ba] Krisztián Gödrei - envman test fix, typo, error log fix (2016 Mar 16) +* [b86b4c2] Viktor Benei - Merge pull request #322 from anas10/patch-1 (2016 Mar 16) +* [64e9f20] Anas AIT ALI - Update README.md (2016 Mar 16) +* [9c4fe9b] Krisztián Gödrei - fixed TestExpandEnvs (2016 Mar 11) +* [e024a42] Krisztián Gödrei - check for updatest, before using the plugin, but only if not CI mode (2016 Mar 11) +* [922e0e8] Krisztián Gödrei - install binary by platforms (2016 Mar 05) +* [2e397fc] Krisztián Gödrei - create plugin data dir at install, check for plugin new version fix (2016 Mar 05) +* [1e61ab7] Krisztián Gödrei - log fixes, run_test update (2016 Mar 05) +* [8f6a350] Krisztián Gödrei - create plugin data dir (2016 Mar 03) +* [242b493] Krisztián Gödrei - trigger event DidFinishRun dont print any logs after workflow summary (2016 Mar 03) +* [814a2b9] Viktor Benei - Merge pull request #320 from viktorbenei/master (2016 Mar 03) +* [49f4234] Viktor Benei - experimental/upload-download-bitrise-yml : updated for Bitrise CLI 1.3 & made it better for quick fixing (download, fix, upload) (2016 Mar 03) +* [a901456] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2016 Mar 03) +* [bba88bd] Viktor Benei - Dockerfile: use go 1.6 (2016 Mar 03) +* [90d32ed] Viktor Benei - yml format update for new Bitrise CLI compatibility (2016 Mar 03) +* [7dad162] Krisztián Gödrei - Merge pull request #319 from godrei/plugin (2016 Mar 01) +* [844ea97] Krisztián Gödrei - NewEnvJSONList instead of CreateFromJSON (2016 Mar 01) +* [53cef9e] Krisztián Gödrei - test updates (2016 Mar 01) +* [8329195] Krisztián Gödrei - version fix (2016 Mar 01) +* [55e6df5] Krisztián Gödrei - plugin requirement's required min version is required, minor fixes (2016 Mar 01) +* [9cc7d95] Krisztián Gödrei - version package instead of hard coded version (2016 Mar 01) +* [3cc0bf9] Krisztián Gödrei - base plugin handling (2016 Mar 01) +* [3acccd3] Viktor Benei - Merge pull request #318 from tomgilder/patch-1 (2016 Feb 28) +* [dc423d8] Tom Gilder - Fix spelling mistake (2016 Feb 28) +* [6eed6a7] Viktor Benei - script content fix (multiline) (2016 Feb 22) +* [59696c6] Viktor Benei - Merge pull request #317 from dag-io/master (2016 Feb 17) +* [52322cf] Damien Gavard - Fix typo (2016 Feb 17) +* [de931be] Viktor Benei - Merge pull request #314 from bitrise-io/update-install-guide (2016 Feb 09) +* [6220657] vasarhelyia - Update install info (2016 Feb 09) +* [c452548] Viktor Benei - Merge pull request #313 from bitrise-io/update-react-native-example (2016 Feb 07) +* [0ccbac7] vasarhelyia - Update workflow name (2016 Feb 06) +* [ae202b1] vasarhelyia - Add sample app yml (2016 Feb 06) +* [3ecbd13] Viktor Benei - Merge pull request #312 from bitrise-io/slack-channel-badge (2016 Feb 04) +* [7c16467] vasarhelyia - Add Slack channel badge (2016 Feb 04) +* [3b64e94] Viktor Benei - Merge pull request #311 from birmacher/typo (2016 Jan 26) +* [2e589c7] birmacher - typo fix (2016 Jan 26) +* [32a52d5] Viktor Benei - Merge pull request #310 from viktorbenei/master (2015 Dec 22) +* [96037ac] Viktor Benei - godeps-update fix (2015 Dec 22) +* [1703e25] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Dec 22) +* [d5d2a66] Viktor Benei - godeps-update (2015 Dec 22) +* [956bee4] Viktor Benei - bumped required envman (to 1.1.0) & stepman (to 0.9.18) versions (2015 Dec 22) +* [60bd807] Viktor Benei - README: intro one-liner text revision (2015 Dec 17) +* [d94054e] Viktor Benei - Merge pull request #309 from viktorbenei/master (2015 Dec 17) +* [b808e12] Viktor Benei - LOG : if config (bitrise.yml) is not valid include the path of the file (2015 Dec 17) +* [35614c0] Viktor Benei - LOG : if local step info print fails it'll print the path of the YML in the logs (2015 Dec 17) +* [b4ba81c] Viktor Benei - FIX : typo: "cofing" -> "config" & "faild" -> "failed" (2015 Dec 17) +* [f98a3da] Viktor Benei - Merge pull request #306 from godrei/changelog (2015 Dec 16) +* [866127d] Krisztián Gödrei - create_changelog workflow for automatic changelog generation based on commits from last tag on master (2015 Dec 16) +* [631a097] Viktor Benei - point highlights in Development Guideline (2015 Dec 16) +* [a47606f] Viktor Benei - Development Guideline section revision in README (2015 Dec 16) +* [469cc5f] Viktor Benei - Merge pull request #305 from godrei/format_version (2015 Dec 15) +* [625dc38] Krisztián Gödrei - format version (2015 Dec 15) +* [98d74a5] Viktor Benei - Merge pull request #304 from godrei/godeps-update (2015 Dec 15) +* [92be446] Krisztián Gödrei - godeps update (2015 Dec 15) +* [a715ba7] Viktor Benei - Merge pull request #303 from godrei/plugin_compatibility (2015 Dec 15) +* [0a246c2] Krisztián Gödrei - godeps update (2015 Dec 14) +* [c630110] Krisztián Gödrei - plugin fixes (2015 Dec 14) +* [5f14af7] Viktor Benei - Merge pull request #302 from viktorbenei/master (2015 Dec 12) +* [3a31596] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Dec 12) +* [a5243bc] Viktor Benei - Merge pull request #301 from godrei/plugin (2015 Dec 12) +* [86573cb] Krisztián Gödrei - PR fix (2015 Dec 12) +* [79dc0d4] Krisztián Gödrei - PR fix (2015 Dec 12) +* [645b2bf] Krisztián Gödrei - PR fixes (2015 Dec 12) +* [d062952] Krisztián Gödrei - plugin install, delete, list (2015 Dec 12) +* [a4a3511] Viktor Benei - godeps-update (2015 Dec 12) +* [90653cb] Viktor Benei - 1.3.0-pre (2015 Dec 12) +* [e7a6dfa] Krisztián Gödrei - base plugin handling (2015 Dec 12) +* [50c8c83] Viktor Benei - Merge pull request #300 from godrei/workflows (2015 Dec 08) +* [324b08e] Krisztián Gödrei - improvements (2015 Dec 08) +* [03668e9] Viktor Benei - Merge pull request #299 from viktorbenei/master (2015 Dec 07) +* [9db86b4] Viktor Benei - typo in bitrise.yml workflow description (2015 Dec 07) +* [a2c051a] Viktor Benei - Merge pull request #297 from godrei/step_info_fix (2015 Dec 07) +* [17ca336] Viktor Benei - Merge pull request #298 from godrei/workflows_fix (2015 Dec 07) +* [7da0a6b] Krisztián Gödrei - yellow no summary/description (2015 Dec 07) +* [0a5f69f] Krisztián Gödrei - step-info, step-list fixes (2015 Dec 07) +* [b8b385f] Viktor Benei - Merge pull request #296 from godrei/delete_envs (2015 Dec 07) +* [74cdd7a] Krisztián Gödrei - change log fix (2015 Dec 07) +* [1e2978a] Krisztián Gödrei - delete env + test (2015 Dec 07) +* [7ddbd10] Viktor Benei - Merge pull request #295 from viktorbenei/master (2015 Dec 07) +* [a87bf87] Viktor Benei - godeps-update (2015 Dec 07) +* [a34bec8] Viktor Benei - Merge pull request #294 from godrei/workflow_list (2015 Dec 07) +* [780de41] Krisztián Gödrei - workflow list (2015 Dec 07) +* [19547ac] Viktor Benei - Update step-development-guideline.md (2015 Dec 05) +* [e7e18f9] Viktor Benei - Do not use submodules, or require any other resource, downloaded on-demand (2015 Dec 05) +* [1d61661] Viktor Benei - clarification (2015 Dec 05) +* [3b3adb0] Viktor Benei - Update README.md (2015 Dec 05) +* [8873c57] Viktor Benei - Create step-development-guideline.md (2015 Dec 05) +* [01e3f36] Viktor Benei - Share your own Step section (2015 Nov 19) +* [cfb5e5b] Viktor Benei - Merge pull request #293 from viktorbenei/master (2015 Nov 09) +* [a451eb3] Viktor Benei - readme : tooling and `--format=json` (2015 Nov 09) +* [acd8356] Viktor Benei - godeps update (2015 Nov 09) +* [9865ba1] Viktor Benei - Merge pull request #292 from viktorbenei/master (2015 Nov 09) +* [cfb4319] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Nov 09) +* [0c024c9] Viktor Benei - `yml` option added/enabled for Output Format (2015 Nov 09) +* [4b64cb7] Viktor Benei - Merge pull request #291 from viktorbenei/master (2015 Nov 07) +* [9ea7723] Viktor Benei - test fix (2015 Nov 07) +* [69b5b9a] Viktor Benei - new packages : configs and output - to help with the new `--format=json` output mode ; added a new `version` command which now supports the `--format=json` flag (2015 Nov 07) +* [0404f7c] Viktor Benei - Merge pull request #290 from viktorbenei/master (2015 Nov 06) +* [2b2999b] Viktor Benei - step template revision : generic format update, using `change-workdir` instead of a custom script, and added a `share-this-step` workflow for quick & easy step sharing (2015 Nov 06) +* [a7ac606] Viktor Benei - Create .gitignore (2015 Nov 04) +* [00a0ab3] Viktor Benei - bitrise.io/cli (2015 Nov 04) +* [921d903] Viktor Benei - Update README.md (2015 Nov 04) + + +## 1.2.4 (2015 Nov 02) + +### Release Notes + +* __envman__ updated to `1.0.0`, which also includes a new ENV size limit feature. You can read more about the release at: https://github.com/bitrise-io/envman/releases/tag/1.0.0 + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.2.4/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.2.3 -> 1.2.4 + +* [044a87c] Viktor Benei - Merge pull request #288 from viktorbenei/master (2015 Nov 02) +* [9d0ba2d] Viktor Benei - 1.2.4 changelog (2015 Oct 31) +* [7e1a93a] Viktor Benei - Merge pull request #287 from viktorbenei/master (2015 Oct 31) +* [8e642cb] Viktor Benei - CI fix (2015 Oct 31) +* [ce1903e] Viktor Benei - 1.2.4 (2015 Oct 31) +* [57c0f18] Viktor Benei - Merge pull request #286 from viktorbenei/master (2015 Oct 31) +* [69849d1] Viktor Benei - just one more.. (2015 Oct 31) +* [4d05b0f] Viktor Benei - hopefully last CI fix :) (2015 Oct 31) +* [87f05be] Viktor Benei - one more CI workflow fix (2015 Oct 31) +* [f496b5b] Viktor Benei - bitrise.yml fix (2015 Oct 31) +* [3c44e47] Viktor Benei - godeps-update (2015 Oct 31) +* [733ae3d] Viktor Benei - envman : 1.0.0 (2015 Oct 31) +* [1537968] Viktor Benei - bitrise.yml test_and_install fix (2015 Oct 31) + + +## 1.2.3 (2015 Oct 19) + +### Release Notes + +* __FIX__ : `bitrise share create` had a parameter issue, calling `stepman share create` with wrong `--stepid` param. Fixed. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.2.3/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.2.2 -> 1.2.3 + +* [89c8ebf] Viktor Benei - Merge pull request #285 from viktorbenei/master (2015 Oct 19) +* [337cf23] Viktor Benei - golint : removed `set -e` (2015 Oct 19) +* [3e89237] Viktor Benei - changelog & godeps-update (2015 Oct 19) +* [4c09ffe] Viktor Benei - Merge pull request #284 from viktorbenei/master (2015 Oct 19) +* [a9e3f59] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Oct 19) +* [fd68787] Viktor Benei - 1.2.3 (2015 Oct 19) +* [8f65724] Viktor Benei - Merge pull request #283 from viktorbenei/master (2015 Oct 19) +* [3424a7c] Viktor Benei - next version changelog (2015 Oct 13) +* [aa15018] Viktor Benei - Merge pull request #282 from gkiki90/bitrise_share_fix (2015 Oct 12) +* [cd0ff8a] Krisztian Godrei - share fix (2015 Oct 12) + + +## 1.2.2 (2015 Oct 12) + +### Release Notes + +* __Fixed__ step log, at build failed mode (at step log footer section Issue tracker and Source row trimming fixed). +* __Fixed__ `bitrise validate` if called with `--format=json` : in case the validation failed it printed two JSON responses instead of just one. Fixed. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.2.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.2.1 -> 1.2.2 + +* [c0abd8c] Viktor Benei - Merge pull request #281 from viktorbenei/master (2015 Oct 12) +* [8d36ef3] Viktor Benei - 1.2.2 (2015 Oct 12) +* [7e06232] Viktor Benei - Merge pull request #280 from viktorbenei/1.2.2-pre (2015 Oct 09) +* [c65bf52] Viktor Benei - 1.2.2-pre (2015 Oct 09) +* [1ef1098] Viktor Benei - Merge pull request #279 from gkiki90/typo (2015 Oct 09) +* [08e6e24] Krisztian Godrei - log fix (2015 Oct 09) +* [74e697d] Krisztian Godrei - typo (2015 Oct 09) +* [44d2b40] Viktor Benei - Merge pull request #278 from gkiki90/validate_fix (2015 Oct 09) +* [e3b0d1c] Krisztian Godrei - validate fix (2015 Oct 09) +* [fec3772] Viktor Benei - Merge pull request #277 from gkiki90/trimming_issue_fix (2015 Oct 07) +* [5ca49d0] Krisztian Godrei - changelog (2015 Oct 07) +* [fe34675] Krisztian Godrei - fixed step log footer trimming issue (2015 Oct 07) + + +## 1.2.1 (2015 Oct 07) + +### Release Notes + +* __FIX__ : `trigger_map` handling in Pull Request mode: if the pattern does match an item which has `is_pull_request_allowed=false` it won't fail now, it'll just skip the item and the next one will be tried. +* __new command__ : `bitrise share`, to share your step through `bitrise` (this is just a wrapper around `stepman share`, does exactly the same, but hopefully it's a bit more convenient if you never used `stepman` directly before). +* __new flag__ : similar to the `--ci` flag the Pull Request mode can now be allowed by calling any command with `bitrise --pr [command]`. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.2.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.2.0 -> 1.2.1 + +* [7e3aa83] Viktor Benei - Merge pull request #276 from viktorbenei/master (2015 Oct 07) +* [366b459] Viktor Benei - v1.2.1 (2015 Oct 07) +* [dd1037d] Viktor Benei - Merge pull request #275 from gkiki90/changelog (2015 Oct 06) +* [24004c1] Krisztian Godrei - changelog (2015 Oct 06) +* [cdd4662] Viktor Benei - removed Gitter (2015 Oct 06) +* [62026fd] Viktor Benei - Merge pull request #274 from gkiki90/share (2015 Oct 05) +* [2648f37] Krisztian Godrei - share (2015 Oct 05) +* [b074d00] Viktor Benei - Merge pull request #273 from gkiki90/PR_mode (2015 Oct 05) +* [d7e28db] Krisztian Godrei - PR fix (2015 Oct 05) +* [0136c97] Viktor Benei - Merge pull request #272 from gkiki90/PR_mode (2015 Oct 05) +* [f539e6f] Viktor Benei - Merge pull request #271 from viktorbenei/master (2015 Oct 05) +* [f3be793] Krisztian Godrei - test fixes (2015 Oct 05) +* [aeffd47] Krisztian Godrei - ci iml fix, pr mode fix (2015 Oct 05) +* [2d5296f] Krisztian Godrei - removed unnecessary descriptions (2015 Oct 05) +* [3309132] Krisztian Godrei - Merge branch 'ci_fix' into PR_mode (2015 Oct 05) +* [bfb6b27] Krisztian Godrei - typo fix, pr mode trigger fix (2015 Oct 05) +* [20e98bf] Krisztian Goedrei - ci fix (2015 Oct 05) +* [75a188f] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Oct 03) +* [94c9187] Viktor Benei - install envman & stepman with `curl -fL` as it's the new recommended way (2015 Oct 02) + + +## 1.2.0 (2015 Oct 02) + +### Release Notes + +* __BREAKING__ / __FIX__ : If `bitrise trigger` is called with a trigger pattern that doesn't match any expression in `trigger_map` the workflow with the same name as the trigger pattern name **will no longer be selected**. This feature proved to have more issues than pros. +* __DEPRECATION__ : the previous `dependencies` property is now deprecated. From now on, dependencies should be declared in the `deps` property which has a new syntax, grouped by dependency managers. The syntax will be extended in the near future but in a backward compatible way. + Supported dependency managers: `brew`, `apt_get` and `check_only` (for checking dependencies which can't be installed automatically, ex the `Xcode.app`). + Example: + + ``` + - step: + deps: + brew: + - name: cmake + - name: git + - name: node + apt_get: + - name: cmake + check_only: + - name: xcode + ``` +* Improved validate command output. +* __BREAKING__ : if you don't specify the version of a step `bitrise` will now try to update the local Step Lib cache before using the Step. Previously the latest version **available in the local cache** was used, but this caused more confusion. The local cache is still used in this case if the Step Lib can't be updated, it'll still work in case of a network issue. +* __BREAKING__ : From `bitrise step-info` the `--id` flag was removed, the first cli param used as step id, no need to write the `--id` flag anymore. Example: `bitrise step-info script` instead of `bitrise step-info --id script`. +* __IMPORTANT__ : `format_version` bumped to `1.1.0`, which means that the `bitrise.yml` generate with this `bitrise` version won't be compatible with previous `bitrise` versions. Previous `bitrise.yml`s of course still work with this new `bitrise` version. +* Now you can use all your environment variables (secrets, app, workflow envs and step outputs) in `run_if` fields and in step inputs. +* `bitrise step-info` got a new option `--step-yml` flag, which allows printing step info from the specified `step.yml` directly (useful for local Step development). +* Step inputs got a new field: `IsTemplate` / `is_template`. This field indicates whether the value contains template expressions which should be evaluated before using the value, just like in case of `is_expand`. The template expression have to be written in Go's template language, and can use the same properties as `run_if` templates can. Example: + + ``` + - script: + title: Template example + inputs: + - content: |- + {{if .IsCI}} + echo "CI mode" + {{else}} + echo "not CI mode" + {{end}} + opts: + is_template: true + ``` +* Improved environment and input value and options casting: + * Now you can use `"NO"`, `"No"`, `"YES"`, `"Yes"`, `true`, `false`, `"true"`, `"false"` in every place `bitrise` expects a bool value (ex: `is_expand`). + * Every field where `bitrise` expects a string in now casted into a string. This means that you can now use `true` and `false` instead of `"true"` and `"false"` in `value_options`. Same is true for the input and environments value itself, so you can now write `true` instead of `"true"` and it'll still be casted to string. +* Pull Request and CI mode handling extension: related flag environment variables can now be defined in `secrets` / `inventory` as well. +* `bitrise` now prints if it runs in "Pull Request mode", just like it did for "CI" mode before. +* Step info logging got a complete revision, to make it more helpful, especially in case the Step fails. It now included the Step's issue tracker and source code URL infos in the log directly. +* __FIX__ : `--log-level` handling fix, the previous version had issues if the log level was set to `debug`. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.2.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.1.2 -> 1.2.0 + +* [9c785d1] Viktor Benei - Merge pull request #270 from viktorbenei/master (2015 Oct 02) +* [67ffc05] Viktor Benei - changelog for 1.2.0 (2015 Oct 02) +* [81dad75] Viktor Benei - v1.2.0 (2015 Oct 02) +* [b0ae673] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Oct 02) +* [59f7cee] Viktor Benei - updated next-version changelog (2015 Oct 02) +* [260a2f4] Viktor Benei - Merge pull request #269 from viktorbenei/master (2015 Oct 02) +* [56ac8b8] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Oct 02) +* [e33aaae] Viktor Benei - Merge pull request #268 from gkiki90/fake_home_fix (2015 Oct 02) +* [f328843] Krisztian Goedrei - PR fix (2015 Oct 02) +* [e80e2a3] Viktor Benei - Merge pull request #267 from gkiki90/fake_home_fix (2015 Oct 02) +* [5e6c187] Viktor Benei - Merge pull request #266 from gkiki90/dep_logs (2015 Oct 02) +* [e65ee13] Krisztian Goedrei - fake home fix (2015 Oct 02) +* [43b31c2] Viktor Benei - `DefaultIsTemplate` typo fix (2015 Oct 02) +* [be50cfe] Viktor Benei - godeps-update (2015 Oct 02) +* [99e7c63] Viktor Benei - required envman and stepman version bumps (2015 Oct 02) +* [306459d] Krisztian Goedrei - check only log (2015 Oct 02) +* [2b13244] Krisztian Goedrei - dep logs (2015 Oct 02) +* [3997ca2] Viktor Benei - Merge pull request #265 from gkiki90/envman_init_fix (2015 Oct 02) +* [4ad7773] Krisztian Goedrei - no internet connection (2015 Oct 02) +* [42d326c] Krisztian Goedrei - step info version fix (2015 Oct 02) +* [591e145] Krisztian Goedrei - fail test (2015 Oct 02) +* [1a8f411] Krisztian Goedrei - fixes (2015 Oct 02) +* [3c74d22] Krisztian Goedrei - fixes (2015 Oct 02) +* [7185c74] Krisztian Goedrei - fixed envman init (2015 Oct 02) +* [f9766e9] Viktor Benei - Merge pull request #264 from viktorbenei/master (2015 Oct 02) +* [49bc8d2] Viktor Benei - base codeclimate config (2015 Oct 02) +* [fe3f460] Viktor Benei - Merge pull request #263 from gkiki90/changelog (2015 Oct 01) +* [08176a8] Krisztian Goedrei - changelog (2015 Oct 01) +* [b14bf8b] Viktor Benei - Merge pull request #261 from gkiki90/latest (2015 Oct 01) +* [87fd6f8] Krisztian Goedrei - godeps (2015 Oct 01) +* [40782a1] Krisztian Goedrei - godep, fixes (2015 Oct 01) +* [4abf72e] Krisztian Goedrei - godep save (2015 Oct 01) +* [8ea8560] Krisztian Goedrei - merge (2015 Oct 01) +* [72f0f75] Viktor Benei - Merge pull request #262 from gkiki90/PR_mode (2015 Oct 01) +* [59976e4] Krisztian Goedrei - fixes (2015 Oct 01) +* [381835f] Krisztian Goedrei - PR & CI mode fix (2015 Oct 01) +* [49b128f] Viktor Benei - Merge pull request #260 from gkiki90/trigger_fix (2015 Sep 29) +* [d4ed963] Krisztian Goedrei - test fix (2015 Sep 28) +* [4b410a8] Krisztian Goedrei - start (2015 Sep 28) +* [8226ad3] Viktor Benei - Merge pull request #258 from gkiki90/is_template (2015 Sep 28) +* [f8ff04b] Krisztian Goedrei - envlist as template input (2015 Sep 27) +* [3d5a371] Viktor Benei - Merge pull request #257 from gkiki90/is_template (2015 Sep 25) +* [ca6f87e] Krisztian Goedrei - template run test (2015 Sep 25) +* [3fd9382] Krisztian Goedrei - template handling, godep (2015 Sep 25) +* [1a462bd] Krisztian Goedrei - template in tests (2015 Sep 25) +* [e394774] Krisztian Goedrei - IsTemplate in model methods (2015 Sep 25) +* [98b68d6] Krisztian Goedrei - require in test (2015 Sep 25) +* [e11fc8a] Viktor Benei - Merge pull request #256 from viktorbenei/master (2015 Sep 24) +* [1b4337c] Viktor Benei - `_tmp` added to .gitignore (2015 Sep 24) +* [53b798a] Viktor Benei - step template README update (2015 Sep 24) +* [15bf241] Viktor Benei - updated `_step_template` (2015 Sep 24) +* [f787c38] Viktor Benei - Merge pull request #255 from viktorbenei/master (2015 Sep 24) +* [b037300] Viktor Benei - format_version bumped in bitrise.yml (2015 Sep 24) +* [5f83c5c] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 24) +* [4d3fcd9] Viktor Benei - v1.1.3-pre (2015 Sep 24) +* [fda85d8] Viktor Benei - Merge pull request #254 from gkiki90/deps (2015 Sep 24) +* [885bbf0] Krisztian Goedrei - docker installs sudo, dependencies bitrise yml for linux and osx (2015 Sep 24) +* [23d41df] Krisztian Goedrei - godeps (2015 Sep 24) +* [7cb5b1f] Krisztian Goedrei - new deps (2015 Sep 24) +* [ef2dc12] Krisztian Goedrei - new deps in progress (2015 Sep 23) +* [24e45a4] Viktor Benei - Merge pull request #253 from gkiki90/triggered_workflow (2015 Sep 22) +* [3ba8dbc] Krisztian Goedrei - trigger check, output format fixes (2015 Sep 22) +* [6000035] Viktor Benei - create release with docker-compose & trigger patterns for release operations (2015 Sep 22) +* [f8a47fd] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 22) +* [7646937] Viktor Benei - updated _tests/brew_publish.yml with more info/description (2015 Sep 22) +* [5c8f602] Viktor Benei - Merge pull request #251 from gkiki90/template (2015 Sep 22) +* [4d60973] Krisztian Goedrei - fix (2015 Sep 22) +* [ccbdf33] Viktor Benei - Merge pull request #252 from gkiki90/step-info (2015 Sep 22) +* [5e6fb13] Krisztian Goedrei - local step info (2015 Sep 22) +* [993d240] Krisztian Goedrei - fix (2015 Sep 22) +* [6aa0e29] Krisztian Goedrei - template fixes (2015 Sep 22) +* [38732b7] Krisztian Goedrei - new envman version (2015 Sep 22) +* [dbe314e] Krisztian Goedrei - template (2015 Sep 22) +* [3bdecde] Viktor Benei - Merge pull request #250 from viktorbenei/master (2015 Sep 22) +* [1918bbb] Viktor Benei - deps.go comment (2015 Sep 22) +* [0af2247] Viktor Benei - Godeps update, with a new `deps.go` to include other packages required only for running the `go test`s (2015 Sep 22) +* [7002f92] Viktor Benei - Merge pull request #249 from gkiki90/step_list_fix (2015 Sep 21) +* [bb52a08] Viktor Benei - Merge pull request #248 from gkiki90/ci_fix (2015 Sep 21) +* [ef055b8] Krisztian Goedrei - step-list fix (2015 Sep 21) +* [7784c07] Krisztian Goedrei - ci fix (2015 Sep 21) +* [362f972] Viktor Benei - Merge pull request #242 from gkiki90/validation_fix (2015 Sep 21) +* [3aa743f] Viktor Benei - Merge pull request #244 from gkiki90/step_info_fix (2015 Sep 21) +* [27a7dc0] Viktor Benei - Merge pull request #245 from gkiki90/ci_fix (2015 Sep 21) +* [e890448] Viktor Benei - Merge pull request #247 from gkiki90/typo_fix (2015 Sep 21) +* [97cccd9] Krisztian Goedrei - typo (2015 Sep 21) +* [817edc7] Krisztian Goedrei - step_info fix (2015 Sep 21) +* [42af9e3] Krisztian Goedrei - bitrise.yml updates (2015 Sep 21) + + +## 1.1.2 (2015 Sep 21) + +### Release Notes + +* __FIX__ : Step outputs are now exposed (available for subsequent steps) even if the Step fails. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.1.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.1.1 -> 1.1.2 + +* [ca8f796] Viktor Benei - Merge pull request #243 from viktorbenei/master (2015 Sep 21) +* [c4b00ad] Krisztian Goedrei - in progress (2015 Sep 21) +* [b1a6a4c] Viktor Benei - v1.1.2 (2015 Sep 21) +* [829391a] Krisztian Goedrei - ci fix start (2015 Sep 21) +* [f44e4b9] Krisztian Goedrei - validation fix (2015 Sep 21) +* [5e49994] Krisztian Goedrei - validation fix (2015 Sep 21) +* [9478b7b] Viktor Benei - Merge pull request #241 from gkiki90/output_env_list_fix (2015 Sep 21) +* [26d0d1d] Krisztian Goedrei - test fixes (2015 Sep 21) +* [571cdfe] Krisztian Goedrei - step output fix (2015 Sep 21) +* [1795cb1] Krisztian Goedrei - validation exit codes (2015 Sep 21) + + +## 1.1.1 (2015 Sep 18) + +### Release Notes + +* __FIX__ : If `$BITRISE_SOURCE_DIR` is defined in an environment with an empty value `bitrise` now skips the definition. Practically this means that if you have an empty `BITRISE_SOURCE_DIR` item in your Workflow or App Environment but you define a real value in your `.bitrise.secrets.yml` `bitrise` will now use the (real) value defined in `.bitrise.secrets.yml`, instead of going with the empty value defined in the Workflow environments. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.1.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.1.0 -> 1.1.1 + +* [35c9a74] Viktor Benei - Merge pull request #239 from viktorbenei/master (2015 Sep 18) +* [50e09fa] Viktor Benei - v1.1.1 (2015 Sep 18) +* [e364fb3] Viktor Benei - Merge pull request #238 from viktorbenei/master (2015 Sep 18) +* [4db999d] Viktor Benei - `BITRISE_SOURCE_DIR` handling fix: skip empty values (2015 Sep 18) +* [956abad] Viktor Benei - slack ENVs revision (2015 Sep 18) + + +## 1.1.0 (2015 Sep 18) + +### Release Notes + +* BITRISE build result log improvements: + * step run summary contains step version, and update note, if new version available + * build run summary step sections contains step version, and update note, if new version available +* __BREAKING/FIX__ : `bitrise trigger` will **NOT** select any workflow in Pull Request mode if the pattern does not match any of the `trigger_map` definition. +* unified `config` and `inventory` flag handling: you can specify paths with `--config` and `--inventory`, and base64 encoded direct input with `--config-base64` and `--inventory-base64`. Can be used by tools, to skip the need to write into files. +* __FIX/BREAKING__ : environment handling order : App Envs can now overwrite the values defined in inventory/secrets (in the last version the secrets/inventory could overwrite the App Envs). +* `validate` command accepts `--format` flag: `--format=[json/raw]` (default is `raw`) +* new command: `step-list` (lis of available steps in Step Lib) `bitrise step-list` +* new command: `step-info` (infos about defined step) `bitrise step-info --id script --version 0.9.0` +* revision of `normalize`, to generate a better list/shorter output list +* __NEW__ : `$BITRISE_SOURCE_DIR` now updated per step, and can be changed by the steps. `$BITRISE_SOURCE_DIR` can be use for defining a new working directory. Example: if you want to create CI workflow for your Go project you have to switch your working directory to the proper one, located inside the `$GOPATH` (this is a Go requirement). You can find an example below. This feature is still a bit in "experimental" stage, and we might add new capabilities in the future. Right now, if you want to re-define the `$BITRISE_SOURCE_DIR` you have to set an **absolute** path, no expansion will be performed on the specified value! So, you should **NOT** store a reference like `$GOPATH/src/your/project/path` as it's value, but the actual, absolute path! + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.1.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 1.0.0 -> 1.1.0 + +* [3a37cb9] Viktor Benei - Merge pull request #237 from viktorbenei/master (2015 Sep 18) +* [6abe52a] Viktor Benei - v1.1.0 - changelog (2015 Sep 18) +* [8658bb0] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 18) +* [d955103] Viktor Benei - version 1.1.0 (2015 Sep 18) +* [f1493ab] Viktor Benei - run.go : param name revision, for clarity (2015 Sep 18) +* [6f6b358] Viktor Benei - Next version: note about BITRISE_SOURCE_DIR (2015 Sep 18) +* [a13dda7] Viktor Benei - Dockerfile : pre-install required tools (2015 Sep 18) +* [3b75762] Viktor Benei - Merge pull request #235 from gkiki90/breaking_source_dir (2015 Sep 18) +* [1a42c32] Krisztian Goedrei - test fix (2015 Sep 18) +* [c37cf93] Krisztian Goedrei - code cleaning (2015 Sep 18) +* [93db802] Krisztian Goedrei - removed expand (2015 Sep 18) +* [8ddef2d] Krisztian Goedrei - tmp path (2015 Sep 18) +* [3f74b81] Krisztian Goedrei - source dir updated per step (2015 Sep 18) +* [828a59d] Viktor Benei - next version changelog (2015 Sep 18) +* [cec4252] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 18) +* [34e87da] Viktor Benei - Merge pull request #234 from gkiki90/ci_breaking_change (2015 Sep 18) +* [2f118ae] Krisztian Goedrei - comments moved to description (2015 Sep 18) +* [90936c3] Krisztian Goedrei - new yml (2015 Sep 17) +* [0b72e80] Krisztian Goedrei - godeps-update, min stepman version (2015 Sep 17) +* [571d9f0] Krisztian Goedrei - bitrise.yml, stepman update (2015 Sep 17) +* [9689dd6] Krisztian Goedrei - fix (2015 Sep 17) +* [e192671] Krisztian Goedrei - godeps-update (2015 Sep 17) +* [7ee4938] Krisztian Goedrei - no message (2015 Sep 17) +* [736067e] Krisztian Goedrei - godeps-update (2015 Sep 17) +* [36a2307] Krisztian Goedrei - godeps-update (2015 Sep 17) +* [f2a1a9a] Krisztian Goedrei - godeps-update (2015 Sep 17) +* [6ae1c20] Krisztian Goedrei - change log (2015 Sep 17) +* [1e30001] Krisztian Goedrei - validation formats (2015 Sep 17) +* [b5fc3c6] Krisztian Goedrei - step-info, step-list (2015 Sep 17) +* [44aec85] Krisztian Goedrei - test fix (2015 Sep 17) +* [56f271a] Krisztian Goedrei - code style (2015 Sep 17) +* [bddd467] Krisztian Goedrei - env order fix, test (2015 Sep 17) +* [716ad8a] Krisztian Goedrei - in progress (2015 Sep 17) +* [fa1dd91] Viktor Benei - Merge pull request #229 from gkiki90/step_version (2015 Sep 16) +* [46c762f] Viktor Benei - Merge pull request #230 from bazscsa/master (2015 Sep 16) +* [67f7e0b] Krisztian Goedrei - PR fix (2015 Sep 16) +* [d65f7d8] Tamás Bazsonyi - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Sep 16) +* [ffb84f7] Tamás Bazsonyi - Updated lesson links (2015 Sep 16) +* [8fa1397] Tamás Bazsonyi - Lesson 5 and lesson 6 update (2015 Sep 16) +* [8efcf05] Krisztian Goedrei - PR fix (2015 Sep 16) +* [4fbdbc7] Viktor Benei - changelog template: curl call "fix" (2015 Sep 16) +* [1a9c807] Krisztian Goedrei - step version logs (2015 Sep 16) +* [e823193] Krisztian Goedrei - version print (2015 Sep 16) +* [cc37c11] Krisztian Goedrei - print fix (2015 Sep 16) +* [5592174] Krisztian Goedrei - start using stepinfo model for print (2015 Sep 16) +* [831fd6f] Krisztian Goedrei - step info model (2015 Sep 16) +* [1845bd5] Tamás Bazsonyi - Lesson 5 WF (2015 Sep 16) +* [0830d33] Viktor Benei - Merge pull request #226 from gkiki90/validate_config (2015 Sep 15) +* [56e8540] Krisztian Goedrei - test fix (2015 Sep 15) +* [a4d0547] Viktor Benei - Merge pull request #228 from gkiki90/trigger_fix (2015 Sep 15) +* [c8a26eb] Krisztian Goedrei - ci fix (2015 Sep 15) +* [ce5b886] Tamás Bazsonyi - Added trigger lesson (2015 Sep 15) +* [93a8e98] Krisztian Goedrei - trigger fix (2015 Sep 15) +* [50cd432] Viktor Benei - Merge pull request #227 from bazscsa/master (2015 Sep 15) +* [9a1da09] Tamás Bazsonyi - links in new line (2015 Sep 15) +* [e6c3fbb] Tamás Bazsonyi - updated links (2015 Sep 15) +* [5e11086] Tamás Bazsonyi - lesson 1 links (2015 Sep 15) +* [030d233] Tamás Bazsonyi - lessons links (2015 Sep 15) +* [dac981f] Tamás Bazsonyi - updated yml (2015 Sep 15) +* [5a8d115] Tamás Bazsonyi - removed readme (2015 Sep 15) +* [21e0df6] Tamás Bazsonyi - removed yml (2015 Sep 15) +* [32d98d3] Tamás Bazsonyi - Added Lessons (2015 Sep 15) +* [ca37f08] Tamás Bazsonyi - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Sep 15) +* [da08152] Viktor Benei - Merge pull request #225 from gkiki90/abc (2015 Sep 14) +* [1b4795d] Krisztian Goedrei - validate inventory (2015 Sep 14) +* [4388a16] Krisztian Goedrei - ci fix (2015 Sep 14) +* [037a0f6] Krisztian Goedrei - ci fix (2015 Sep 14) +* [4c5ce86] Krisztian Goedrei - bitrise.yml fix, test fix (2015 Sep 14) +* [028dc4d] Krisztian Goedrei - fixes (2015 Sep 12) +* [d14a781] Krisztian Goedrei - PR fix (2015 Sep 12) +* [e8fdff8] Krisztian Goedrei - sort (2015 Sep 12) +* [ecefc8a] Tamás Bazsonyi - paths updated (2015 Sep 12) +* [9840a57] Tamás Bazsonyi - path (2015 Sep 12) +* [d9552fa] Tamás Bazsonyi - Lessons README (2015 Sep 12) +* [772bf3e] Viktor Benei - Merge pull request #223 from gkiki90/custom_step (2015 Sep 11) +* [9a0b9b1] Viktor Benei - Merge pull request #224 from viktorbenei/master (2015 Sep 11) +* [168d496] Krisztian Goedrei - normalize fix (2015 Sep 11) +* [90245bd] Viktor Benei - start of v1.0.1 (2015 Sep 11) + + +## 1.0.0 (2015 Sep 11) + +### Release Notes + +* __Linux support__ : first official Linux release. No dependency manager support is available for Linux yet, but everything else should work the same as on OS X. +* Improved `bitrise init`, with better guides, `trigger_map` and more! +* Total runtime summary at the end of a build. +* Lots of internal code revision, improved `bitrise normalize`. +* __New command__ : `bitrise validate` to quick-check your `bitrise.yml`. +* Configurations (`bitrise.yml` and `.bitrise.secrets.yml`) can now be specified in `base64` format as well - useful for tools. +* __DEPRECATED__ : the old `--path` flag is now deprecated, in favor of `--config`, which has it's `base64` format (`--config-base64`) +* Logs now include the `step`'s version if it's referenced from a Step Collection. Prints the version even if no version constraint is defined (mainly for debug purposes). +* __NEW__ : sets `BITRISE_SOURCE_DIR` (to current dir) and `BITRISE_DEPLOY_DIR` (to a temp dir) environments if the env is not defined +* Only do a `stepman update` once for a collection if can't find a specified step (version). +* __FIX__ : Custom steps (where the collection is `_`) don't crash anymore because of missing required fields. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.0.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 0.9.11 -> 1.0.0 + +* [c2c7c04] Viktor Benei - Merge pull request #222 from viktorbenei/master (2015 Sep 11) +* [7ee03c8] Viktor Benei - updated 1.0.0 changelog (2015 Sep 11) +* [a4a1ad4] Viktor Benei - Merge pull request #221 from gkiki90/custom_step (2015 Sep 11) +* [2381bb4] Krisztian Goedrei - fix (2015 Sep 11) +* [bf3f9e2] Krisztian Goedrei - custom step defaults (2015 Sep 11) +* [53270bb] Tamás Bazsonyi - Workflows (2015 Sep 10) +* [164877f] Tamás Bazsonyi - Added step yml (2015 Sep 10) +* [0152403] Viktor Benei - Merge pull request #220 from gkiki90/pointers (2015 Sep 10) +* [3471df4] Krisztian Goedrei - pointer fixes (2015 Sep 10) +* [ead8159] Tamás Bazsonyi - removed <> (2015 Sep 10) +* [5ac8f5a] Tamás Bazsonyi - corrected format (2015 Sep 10) +* [88c0d5d] Tamás Bazsonyi - Added lesson 1 (2015 Sep 10) +* [5739093] Viktor Benei - Merge pull request #219 from gkiki90/deploy_dir (2015 Sep 10) +* [c52bd9a] Krisztian Goedrei - PR fix (2015 Sep 10) +* [8410daf] Krisztian Goedrei - PR fix (2015 Sep 10) +* [ba2f6b8] Krisztian Goedrei - deploy dir (2015 Sep 10) +* [4e71a04] Viktor Benei - Merge pull request #218 from viktorbenei/master (2015 Sep 09) +* [58b739d] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 09) +* [464f0e7] Viktor Benei - Merge pull request #217 from gkiki90/bitrise_src_dir (2015 Sep 09) +* [3912ffd] Krisztian Goedrei - BITRISE_SOURCE_DIR handling & test (2015 Sep 09) +* [ab20912] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 09) +* [118c543] Viktor Benei - BITRISE_PROJECT_TITLE renamed in `init` to BITRISE_APP_TITLE - to match the bitrise.io one (2015 Sep 09) +* [8869945] Viktor Benei - Merge pull request #216 from gkiki90/print_fix (2015 Sep 09) +* [f449dfa] Krisztian Goedrei - print tests (2015 Sep 09) +* [210ef4f] Tamás Bazsonyi - added initial readmes (2015 Sep 08) +* [7452e53] Tamás Bazsonyi - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 08) +* [97bdeaa] Tamás Bazsonyi - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 08) +* [8920f8a] Viktor Benei - Merge pull request #215 from viktorbenei/master (2015 Sep 08) +* [5b9b36c] Viktor Benei - godeps-update (2015 Sep 08) +* [9f034b9] Viktor Benei - step log version printing fix - trimming version string. Mainly affects the steps which are not used from a steplib (2015 Sep 08) +* [caac385] Viktor Benei - Merge pull request #213 from gkiki90/step_version_log_fix (2015 Sep 08) +* [cb9d4d2] Krisztian Goedrei - fix (2015 Sep 08) +* [cddeda9] Krisztian Goedrei - test (2015 Sep 08) +* [28673aa] Viktor Benei - Merge pull request #212 from viktorbenei/master (2015 Sep 08) +* [19da3cc] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 08) +* [a621c28] Viktor Benei - Merge pull request #211 from gkiki90/trigger_fix (2015 Sep 08) +* [2b81fda] Viktor Benei - godeps-update (2015 Sep 08) +* [2a4e93b] Viktor Benei - required stepman version bump (2015 Sep 08) +* [d3e9f76] Viktor Benei - full godeps-update (2015 Sep 08) +* [9b14e35] Krisztian Goedrei - PR fix (2015 Sep 08) +* [eabab55] Krisztian Goedrei - fix (2015 Sep 08) +* [bfde226] Viktor Benei - Merge pull request #210 from viktorbenei/master (2015 Sep 08) +* [d20d08a] Viktor Benei - godeps-update : CopyDir fix & stepman model property order change (2015 Sep 08) +* [ebcba2b] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 08) +* [d0763cb] Viktor Benei - _test/bitrise.yml step title fix (2015 Sep 08) +* [a378ab9] Viktor Benei - Merge pull request #209 from viktorbenei/master (2015 Sep 07) +* [ec5c61c] Viktor Benei - base trigger_map added to bitrise.yml, for CI (2015 Sep 07) +* [7773df7] Viktor Benei - step version printing note added to changelog (2015 Sep 07) +* [909ef5b] Viktor Benei - changelog - version fix (1.0.0) (2015 Sep 07) +* [ab1cefd] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 07) +* [dddf2e5] Viktor Benei - bit more explanation for setup --minimal in CI (2015 Sep 07) +* [f836cfa] Viktor Benei - Merge pull request #208 from viktorbenei/master (2015 Sep 07) +* [8f62ca5] Viktor Benei - minimal refactoring for CI (2015 Sep 07) +* [6fb55a4] Viktor Benei - tmp build fix for CI (2015 Sep 07) +* [359b8af] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 07) +* [44db09f] Viktor Benei - run a minimal setup at start of CI (2015 Sep 07) +* [8f42f56] Viktor Benei - Merge pull request #206 from gkiki90/step_version (2015 Sep 07) +* [017f05c] Krisztian Goedrei - PR fix (+2 squashed commits) Squashed commits: [9e44a47] fix [6ca52f8] step version (2015 Sep 07) +* [c525a42] Viktor Benei - Merge pull request #207 from viktorbenei/master (2015 Sep 07) +* [978ca1d] Viktor Benei - Linux ready release configuration; v1.0.0 changelog (2015 Sep 07) +* [ea9a59f] Viktor Benei - skip `brew` dependencies if platform is Linux (2015 Sep 07) +* [318119f] Viktor Benei - step-template update (2015 Sep 07) +* [9e55f89] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 07) +* [2017941] Viktor Benei - Merge pull request #205 from gkiki90/step_template (2015 Sep 07) +* [3d83f59] Viktor Benei - `bitrise init` now embeds the models.Version instead of a fixed 1.0.0; init now uses the new config 'title, summary, description' instead of YML comments (2015 Sep 07) +* [35d17af] Krisztian Goedrei - readme (2015 Sep 07) +* [f308373] Viktor Benei - Title, Summary, Description added to AppModel (main config model) & reordered the three, to be in this order in every model. (2015 Sep 07) +* [8a63a15] Viktor Benei - Merge pull request #202 from gkiki90/normalize_fix (2015 Sep 07) +* [8ed3179] Viktor Benei - Merge pull request #201 from gkiki90/step_template (2015 Sep 07) +* [bdd942b] Viktor Benei - Merge pull request #204 from gkiki90/util_test (2015 Sep 07) +* [6ebe2c2] Krisztian Goedrei - PR fix (2015 Sep 07) +* [b7d53d1] Viktor Benei - Merge pull request #203 from gkiki90/total_runtime (2015 Sep 07) +* [76fafc1] Krisztian Goedrei - PR fix (2015 Sep 07) +* [e4a39eb] Krisztian Goedrei - test (2015 Sep 07) +* [2ef20a8] Krisztian Goedrei - slice tests (2015 Sep 07) +* [eb7ffe7] Krisztian Goedrei - total runtime (2015 Sep 06) +* [4f1705b] Krisztian Goedrei - normalize fix (2015 Sep 05) +* [364df10] Krisztian Goedrei - missing fields (2015 Sep 05) +* [e2c2652] Viktor Benei - Merge pull request #200 from viktorbenei/master (2015 Sep 05) +* [31a7be0] Viktor Benei - updated Dockerfile & bitrise.yml for building `bitrise` in Docker, using the `bitrise.yml` (2015 Sep 04) +* [397838a] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 04) +* [40c3996] Viktor Benei - Merge pull request #199 from gkiki90/normalize_fix (2015 Sep 04) +* [432e3da] Viktor Benei - Merge pull request #198 from gkiki90/base64 (2015 Sep 04) +* [f22ff6b] Krisztian Goedrei - fix (2015 Sep 04) +* [9082415] Krisztian Goedrei - PR fix (2015 Sep 04) +* [d6ec4a1] Krisztian Goedrei - fix (2015 Sep 04) +* [2046c36] Viktor Benei - upload&download bitrise.yml : ensure-clean-git & create backup (2015 Sep 04) +* [e7cd7c5] Viktor Benei - experimental : upload & download bitrise.yml to/from bitrise.io (2015 Sep 04) +* [e9fe439] Krisztian Goedrei - PR fix (2015 Sep 04) +* [d4b9359] Krisztian Goedrei - PR fix (2015 Sep 04) +* [f9bd982] Krisztian Goedrei - test (2015 Sep 04) +* [937de7d] Krisztian Goedrei - base64 (2015 Sep 04) +* [3b6b4e6] Viktor Benei - Merge pull request #197 from viktorbenei/master (2015 Sep 04) +* [aba4989] Viktor Benei - bitrise.yml cleanup & format version update (2015 Sep 04) +* [2e42496] Viktor Benei - more thorough template expression simple "true/false" tests (2015 Sep 04) +* [7b472fa] Viktor Benei - Merge pull request #195 from viktorbenei/feature/trigger-map-in-init (2015 Sep 03) +* [c70451f] Viktor Benei - just a little bit more test for the `init` content (2015 Sep 03) +* [ea4c0af] Viktor Benei - godeps-update + a fix for recursive `godep save` (2015 Sep 03) +* [a43074b] Viktor Benei - annotated, and formatted `bitrise.yml` after init, with `trigger_map`, test, and a bit of info about `bitrise trigger` (2015 Sep 03) +* [128ead0] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 03) +* [631bf0f] Viktor Benei - Merge pull request #194 from gkiki90/validate_config (2015 Sep 03) +* [90ade1d] Krisztian Goedrei - removed alias (2015 Sep 03) +* [1da63ba] Krisztian Goedrei - validate (2015 Sep 03) +* [b9f9593] Viktor Benei - base Linux setup/support (2015 Sep 03) +* [cf0a83c] Viktor Benei - Merge pull request #192 from gkiki90/normalize_fix (2015 Sep 03) +* [c06de4e] Krisztian Goedrei - PR fix (2015 Sep 03) +* [990f72d] Krisztian Goedrei - code cleaning (2015 Sep 03) +* [876c89c] Krisztian Goedrei - PR fix (2015 Sep 03) +* [77a3797] Viktor Benei - Merge pull request #193 from viktorbenei/master (2015 Sep 03) +* [c566b29] Krisztian Goedrei - pointer fix (2015 Sep 03) +* [96bfe66] Krisztian Goedrei - normalize fix (2015 Sep 03) +* [30e60d7] Viktor Benei - test workflow extended with a fail test & trigger_map (2015 Sep 02) +* [78b2c2a] Viktor Benei - Merge pull request #191 from viktorbenei/master (2015 Sep 02) +* [db3f4df] Viktor Benei - fail if `golint` finds any issue (2015 Sep 02) +* [d5ddee7] Viktor Benei - extended _tests/bitrise.yml (2015 Sep 02) +* [ddc8666] Viktor Benei - Merge pull request #190 from viktorbenei/master (2015 Aug 31) +* [58da26f] Viktor Benei - updated bitrise-cli install (2015 Aug 31) +* [22b32bc] Viktor Benei - start of v0.9.12 (2015 Aug 31) + + +## 0.9.11 (2015 Aug 31) + +### Release Notes + +* __NEW__ : `bitrise.yml` can now be exported into JSON (with `bitrise export`), and `.json` configuration is also acceptable now for a `bitrise run`. +* __NEW__ / __BREAKING__ : workflow names which start with an underscore (ex: _my_wf) are now treated as "utility" workflow, which can only be triggered by another workflow (as a `before_run` or `after_run` workflow). These "utility" workflows will only be listed by a `bitrise run` call as another section (utility workflows), to provide a better way to organize workflows which are not intended to be called directly. +* __FIX__ : Input environments handling fix: Step inputs are now isolated, one step's input won't affect another's with the same environment key +* __NEW__ : The workflow which was triggered by `bitrise run WORKFLOW-NAME` is now available as an environment variable + * `BITRISE_TRIGGERED_WORKFLOW_ID` : contains the ID of the workflow + * `BITRISE_TRIGGERED_WORKFLOW_TITLE` : contains the `title` of the workflow, if specified +* __NEW__ : `BITRISE_STEP_FORMATTED_OUTPUT_FILE_PATH` is now also defined, as a temporary file path. +* __NEW__ : `bitrise normalize` command, to help you "normalize" your `bitrise.yml`. +* __NEW__ : `trigger_map` definition and `bitrise trigger` action : with this you can map expressions to workflows. A common use case for this is to map branch names (ex: `feature/xyz`) to workflows, simply by defining the mapping in the `bitrise.yml`. +* Log format revision, to make it more obvious where a Step starts and ends, and at the end of the build it provides a much improved summary. +* A new "StepLib" source type (`_`), to provide compatibility with Steps which don't have an up-to-date `step.yml` in the Step's repository. Effectively the same as `git::http://step/url.git@version`, but it won't check for a `step.yml` at all - which means that every information have to be included in the `bitrise.yml`. +* Every configuration level (environments, step, step inputs, ...) which had at least a `title` or a `description` or `summary` now has all three: `title`, `summary` and `description`. +* Other internal revisions and minor fixes, and __lots__ of test added. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.11/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 0.9.10 -> 0.9.11 + +* [217e649] Viktor Benei - Merge pull request #189 from viktorbenei/master (2015 Aug 31) +* [53a2eb6] Viktor Benei - bitrise.yml revision : updated `test` handling (2015 Aug 31) +* [46490e7] Viktor Benei - changelog addition (2015 Aug 31) +* [c5ea28f] Viktor Benei - godeps-update (2015 Aug 31) +* [f5bd2ed] Viktor Benei - Merge pull request #188 from gkiki90/model_version (2015 Aug 31) +* [cb62ebb] Krisztian Goedrei - models version (2015 Aug 31) +* [badbdf4] Krisztian Goedrei - model version (2015 Aug 31) +* [870309c] Viktor Benei - Merge pull request #187 from gkiki90/published_at_fix (2015 Aug 31) +* [6244d9f] Krisztian Goedrei - PR fix (2015 Aug 31) +* [8493427] Krisztian Goedrei - PR fix (2015 Aug 31) +* [2bab801] Krisztian Goedrei - godeps-update (2015 Aug 31) +* [3b13c39] Krisztian Goedrei - published_at type fix (2015 Aug 31) +* [742321a] Viktor Benei - Merge pull request #186 from gkiki90/1_0_0_models (2015 Aug 31) +* [f10a1bb] Krisztian Goedrei - merge (2015 Aug 31) +* [dd63058] Krisztian Goedrei - # This is a combination of 2 commits. # The first commit's message is: (2015 Aug 31) +* [fae8b05] Viktor Benei - Merge pull request #185 from gkiki90/last_step_fix (2015 Aug 31) +* [8555f5d] Krisztian Goedrei - PR fix (2015 Aug 31) +* [64f78a9] Krisztian Goedrei - trigger workflow & last step fix (2015 Aug 31) +* [c2c8d94] Viktor Benei - Merge pull request #183 from gkiki90/run_summary (2015 Aug 28) +* [e2c65bf] Krisztian Goedrei - removed log (2015 Aug 28) +* [6197e11] Krisztian Goedrei - print run summary (2015 Aug 28) +* [5766950] Tamás Bazsonyi - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Aug 27) +* [1195f37] Viktor Benei - Merge pull request #182 from gkiki90/ci (2015 Aug 27) +* [0506d05] Krisztian Goedrei - ci fix (2015 Aug 27) +* [38dbd44] Krisztian Goedrei - ci (2015 Aug 27) +* [f54b01c] Viktor Benei - Merge pull request #181 from gkiki90/master (2015 Aug 27) +* [bf0cf88] Krisztian Goedrei - bypass checking for a TTY before outputting colors (2015 Aug 27) +* [9bac0a9] Viktor Benei - Merge pull request #180 from viktorbenei/master (2015 Aug 26) +* [d526963] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 26) +* [b85fbc5] Viktor Benei - changelog update (2015 Aug 26) +* [a129cbb] Viktor Benei - Merge pull request #179 from viktorbenei/master (2015 Aug 26) +* [6ad9602] Viktor Benei - step run summary log box revision & a bit longer fail-test in bitrise.yml (2015 Aug 26) +* [25b0e67] Viktor Benei - Merge pull request #178 from gkiki90/remove_defaults (2015 Aug 26) +* [a9a811e] Krisztian Goedrei - code style, tests (2015 Aug 26) +* [a1839c5] Krisztian Goedrei - remove defaults, fill step outputs (2015 Aug 26) +* [f10ada6] Viktor Benei - Merge pull request #177 from gkiki90/init_fix (2015 Aug 26) +* [93db1a8] Tamás Bazsonyi - Added local app install sample (2015 Aug 25) +* [44571e7] Krisztian Goedrei - init fix (2015 Aug 25) +* [90a9d83] Viktor Benei - Merge pull request #176 from viktorbenei/master (2015 Aug 24) +* [dc7f135] Viktor Benei - Merge pull request #175 from bazscsa/master (2015 Aug 24) +* [a8cad55] Viktor Benei - doRun_test major revision: most of the tests now use `runWorkflowWithConfiguration` to run the test, which is much closer to how a full `bitrise run` happens (2015 Aug 24) +* [69562d7] Tamás Bazsonyi - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Aug 24) +* [9eb00ae] Tamás Bazsonyi - Some rephrasing (2015 Aug 24) +* [589c1b3] Tamás Bazsonyi - Listified the Documentation overview (2015 Aug 24) +* [3cacdfd] Tamás Bazsonyi - Added _docs to README.md (2015 Aug 24) +* [84ce3ef] Tamás Bazsonyi - Added README.md to the docs folder (2015 Aug 24) +* [9b83f6e] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 24) +* [7c0af85] Viktor Benei - output env test & some logging text fix (workflow 'ID' instead of 'title') (2015 Aug 24) +* [39b1b12] Viktor Benei - Merge pull request #174 from gkiki90/separate_run (2015 Aug 24) +* [a5798c2] Krisztian Goedrei - separated run (2015 Aug 24) +* [e6536c5] Viktor Benei - removed unnecessary 'unload' from react-native example (2015 Aug 24) +* [c802db5] Viktor Benei - changelog v0.9.11 (2015 Aug 24) +* [f995a49] Viktor Benei - Merge pull request #172 from gkiki90/run_old_steps (2015 Aug 24) +* [eea413e] Viktor Benei - Merge pull request #173 from viktorbenei/master (2015 Aug 24) +* [090005b] Krisztian Goedrei - typo (2015 Aug 24) +* [250b197] Viktor Benei - AppendEnvironmentSlice replaced with the built in "append" method (2015 Aug 24) +* [433a48a] Viktor Benei - godeps-update & envman and stepman min version bump (2015 Aug 24) +* [4b6aeb5] Krisztian Goedrei - code style (2015 Aug 24) +* [231a151] Krisztian Goedrei - code style (2015 Aug 24) +* [9349b1d] Krisztian Goedrei - code style (2015 Aug 24) +* [5a5be3d] Krisztian Goedrei - run old steps (2015 Aug 24) +* [757f4f1] Viktor Benei - Merge pull request #171 from gkiki90/title_summary_desc (2015 Aug 19) +* [989ff21] Krisztian Goedrei - title, summary, description (2015 Aug 19) +* [c4da2c3] Krisztian Goedrei - Merge branch 'master' of github.com:bitrise-io/bitrise-cli (2015 Aug 19) +* [237eea0] Viktor Benei - Merge pull request #170 from gkiki90/step_working_dir (2015 Aug 19) +* [378a1c6] Krisztian Goedrei - Merge branch 'step_working_dir' (2015 Aug 19) +* [41c3069] Krisztian Goedrei - step working dir (2015 Aug 19) +* [a799972] Viktor Benei - Merge pull request #169 from viktorbenei/master (2015 Aug 18) +* [6670815] Viktor Benei - Slack examples version update: from 2.0.0 to 2.1.0 (2015 Aug 18) +* [d5faf3d] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 18) +* [91c9185] Viktor Benei - added RunIf ENV template tests (2015 Aug 18) +* [46446f2] Viktor Benei - Merge pull request #168 from gkiki90/go-utils (2015 Aug 18) +* [6cc2f51] Viktor Benei - Merge pull request #167 from gkiki90/workflow_title (2015 Aug 18) +* [8ef0281] Krisztian Goedrei - missing go-utils methods (2015 Aug 18) +* [3c8f325] Krisztian Goedrei - workflow title (2015 Aug 18) +* [77f14e4] Viktor Benei - Merge pull request #166 from gkiki90/step_input_fix (2015 Aug 18) +* [fc62a83] Krisztian Goedrei - environment handling (2015 Aug 18) +* [4272084] Viktor Benei - Merge pull request #165 from viktorbenei/master (2015 Aug 17) +* [17ef411] Viktor Benei - minor text change (2015 Aug 17) +* [df5ba72] Viktor Benei - dependency manager "OK" message - unified (2015 Aug 17) +* [f947c2e] Viktor Benei - Merge pull request #164 from viktorbenei/master (2015 Aug 17) +* [c4da8b7] Viktor Benei - slack step update to the new v2.0.0 version (2015 Aug 17) +* [5e1a2c2] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 17) +* [4845209] Viktor Benei - added "dependencies" to step-template (2015 Aug 17) +* [bf9533e] Viktor Benei - Merge pull request #163 from bazscsa/master (2015 Aug 17) +* [229c201] Tamás Bazsonyi - Added brew update (2015 Aug 17) +* [1674a3e] Tamás Bazsonyi - Some grammar corrections (2015 Aug 17) +* [278d29f] Tamás Bazsonyi - revisions (2015 Aug 17) +* [c037187] Tamás Bazsonyi - Added Share Guide (2015 Aug 17) +* [22ae699] Tamás Bazsonyi - Added React Native (2015 Aug 17) +* [17a7b1c] Tamás Bazsonyi - Added CLI share guide (2015 Aug 17) +* [796e8af] Tamás Bazsonyi - Added CLI introduction (2015 Aug 17) +* [02a0de9] Tamás Bazsonyi - How to guide (2015 Aug 17) +* [f73a40b] Viktor Benei - Merge pull request #162 from viktorbenei/feature/export_command (2015 Aug 17) +* [03eba50] Viktor Benei - godep-update for a required envman model fix (2015 Aug 17) +* [a00fd3e] Viktor Benei - export command : export a bitrise config file in either YAML or JSON format, with optional pretty printed JSON (2015 Aug 17) +* [9e52a55] Viktor Benei - Merge pull request #161 from viktorbenei/feature/predefined_envs (2015 Aug 17) +* [f633487] Viktor Benei - set predefined ENVs, so far only one: BITRISE_STEP_FORMATTED_OUTPUT_FILE_PATH (2015 Aug 17) +* [686b5a5] Viktor Benei - Update README.md (2015 Aug 14) +* [c02a212] Viktor Benei - Merge pull request #160 from viktorbenei/master (2015 Aug 14) +* [43d7c05] Viktor Benei - README revision (2015 Aug 14) +* [2fffdb7] Viktor Benei - Merge pull request #159 from viktorbenei/master (2015 Aug 14) +* [8039c00] Viktor Benei - removed the now obsolete reference to `brew update` in setup's `--minimal` flag (2015 Aug 14) +* [775a146] Viktor Benei - Merge pull request #158 from viktorbenei/master (2015 Aug 14) +* [6d396da] Viktor Benei - switch to bitrise 0.9.10 for CI (2015 Aug 14) +* [77da03c] Viktor Benei - start of v0.9.11 (2015 Aug 14) +* [0d2e718] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 14) + + +## 0.9.10 (2015 Aug 14) + +### Release Notes + +* Improved `setup` : it has a new `--minimal` flag to skip more advanced setup checks, like the `brew doctor` call. +* Removed `brew update` completely from the `setup`. +* Step dependencies: before installing a dependency with `brew` bitrise now specifically asks for permission to do so, except in `--ci` mode. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.10/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 0.9.9 -> 0.9.10 + +* [59bd349] Viktor Benei - Merge pull request #157 from viktorbenei/master (2015 Aug 14) +* [37f36fb] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 14) +* [08f1d6a] Viktor Benei - v0.9.10 changelog (2015 Aug 14) +* [59ffe92] Viktor Benei - Merge pull request #156 from viktorbenei/master (2015 Aug 14) +* [ba886cb] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 14) +* [18f2bfd] Viktor Benei - Merge pull request #155 from viktorbenei/master (2015 Aug 14) +* [29abf82] Viktor Benei - godeps-update (2015 Aug 14) +* [6f2fa64] Viktor Benei - stepman min version bump (2015 Aug 14) +* [4cdde7f] Viktor Benei - Merge pull request #154 from viktorbenei/master (2015 Aug 14) +* [3745167] Viktor Benei - _step_template and it's CI test moved from the stepman project to this repo (2015 Aug 14) +* [64f6f4a] Viktor Benei - Merge pull request #153 from viktorbenei/master (2015 Aug 14) +* [a8ba919] Viktor Benei - prepare for bitrise setup --minimal (2015 Aug 14) +* [a940056] Viktor Benei - Merge pull request #152 from viktorbenei/master (2015 Aug 14) +* [7f0c2e4] Viktor Benei - install bitrise cli for ci script (2015 Aug 14) +* [99b365b] Viktor Benei - every `brew doctor` issue counts - use the `--minimal` flag to skip `brew doctor` (2015 Aug 14) +* [2663282] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 14) +* [9dc16b3] Viktor Benei - don't do `brew update` in setup (2015 Aug 14) +* [c1113d4] Viktor Benei - Merge pull request #151 from gkiki90/dependency_fix (2015 Aug 14) +* [7932300] Krisztian Goedrei - dependency fixes (2015 Aug 14) +* [a6f7827] Viktor Benei - Merge pull request #150 from viktorbenei/master (2015 Aug 14) +* [f3a91dd] Viktor Benei - minimum envman version bump (2015 Aug 14) +* [f2e4caf] Viktor Benei - Merge pull request #149 from viktorbenei/master (2015 Aug 14) +* [0bb4e1d] Viktor Benei - minimal setup mode : skips brew update and brew doctor (2015 Aug 14) +* [6ee580f] Viktor Benei - Merge pull request #148 from viktorbenei/master (2015 Aug 14) +* [da000b2] Viktor Benei - godeps-update (2015 Aug 14) +* [b840881] Viktor Benei - Merge pull request #146 from gkiki90/master (2015 Aug 13) +* [f75b685] Viktor Benei - Merge pull request #147 from viktorbenei/master (2015 Aug 13) +* [6fb4c64] Viktor Benei - stepman dependency bump (2015 Aug 13) +* [63c6059] Krisztian Goedrei - cli fixes (2015 Aug 13) +* [fed4916] Krisztian Goedrei - godep-update (2015 Aug 13) +* [148f29a] Krisztian Goedrei - go-util update (2015 Aug 13) +* [b8de012] Viktor Benei - Merge pull request #145 from gkiki90/workflow_fixes (2015 Aug 13) +* [cb7e3c5] Viktor Benei - Merge pull request #144 from viktorbenei/master (2015 Aug 13) +* [6689c24] Viktor Benei - start of v0.9.10 (2015 Aug 13) + + +## 0.9.9 (2015 Aug 13) + +### Release Notes + +* `bitrise setup` revision : better `brew` checking (calls `brew update` and `brew doctor` too) but no direct Command Line Tools checking. + * The previous solution was incompatible with OS X Mountain Lion and earlier versions, this version solves this incompatibility. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.9/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 0.9.8 -> 0.9.9 + +* [a832bd5] Viktor Benei - Merge pull request #143 from viktorbenei/master (2015 Aug 13) +* [22b4e71] Viktor Benei - 0.9.9 changelog (2015 Aug 13) +* [2ae0825] Viktor Benei - Merge pull request #142 from viktorbenei/master (2015 Aug 13) +* [fc44dc0] Viktor Benei - Xcode CLT is not an explicit dependency anymore, only brew; but brew check extended with brew update and brew doctor (2015 Aug 13) +* [8bd8098] Viktor Benei - Merge pull request #141 from mistydemeo/xcode-select (2015 Aug 13) +* [6dd0fe9] Misty De Meo - Dependencies: fix xcode-select argument (2015 Aug 12) +* [2f72f83] Viktor Benei - Merge pull request #140 from viktorbenei/master (2015 Aug 12) +* [3cefbbb] Viktor Benei - start of v0.9.9 (2015 Aug 12) +* [662012a] Viktor Benei - changelog (2015 Aug 12) + + +## 0.9.8 (2015 Aug 12) + +### Release Notes + +* __BREAKING__ : `step.yml` shared in Step Libraries / Step Collections now have to include a `commit` (hash) property inside the `source` property, for better version validation (version tag have to match this commit hash)! + * You should switch to the new, final default StepLib, hosted on GitHub, which contains these commit hashes and works with stepman 0.9.8! URL: https://github.com/bitrise-io/bitrise-steplib + * We'll soon (in about 1 day) start to accept Step contributions to this new StepLib! + * You should replace the previous `https://bitbucket.org/bitrise-team/bitrise-new-steps-spec` `default_step_lib_source` and every other reference to this old (now deprecated) StepLib, and **replace it** with `https://github.com/bitrise-io/bitrise-steplib.git`! +* __BUGFIX__ : the `$STEPLIB_BUILD_STATUS` and `$BITRISE_BUILD_STATUS` environments were not set correctly in the previous version for a couple of multi-workflow setups. +* __NEW__ : `bitrise init` now automatically adds `.bitrise*` to the `.gitignore` file in the current folder, to prevent accidentally sharing your `.bitrise.secrets.yml` or other bitrise generated temporary files/folders. +* __NEW__ : built in commands to `share` a new step into a StepLib - through `stepman`. +* __NEW__ : `run_if` expressions can now use the new `.IsPR` check, to declare whether a given step should run in case of a Pull Request build. +* __NEW__ : Step dependencies : `Xcode` can now be specified as a dependency for steps. Unfortunately it can't be installed automatically, but you'll get proper error message about the missing full Xcode in this case, rather than a generic error message during running the step. +* __NEW__ : bitrise now checks the `format_version` of the `bitrise.yml` file and doesn't run it if it was created for a newer version. +* You no longer have to call `setup` after the installation or upgrade of `bitrise`, it'll automatically check whether `setup` was called (and succeeded) when you call `run`. +* Bitrise now creates it's temporary working cache dir in a System temp folder, instead of spamming the current directory with a `.bitrise` folder at every `bitrise run`. +* Improved `bitrise run` logs. +* LOTS of code revision + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.8/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 0.9.7 -> 0.9.8 + +* [e03719c] Viktor Benei - Merge pull request #139 from viktorbenei/master (2015 Aug 12) +* [f1e0a95] Viktor Benei - minimum envman and stepman version bump (2015 Aug 12) +* [f88e7da] Krisztian Goedrei - workflow fixes (2015 Aug 12) +* [e819d32] Viktor Benei - Merge pull request #138 from viktorbenei/master (2015 Aug 12) +* [84d15d2] Viktor Benei - godeps-update (2015 Aug 12) +* [9e72f25] Viktor Benei - Merge pull request #137 from viktorbenei/feature/debug-env (2015 Aug 12) +* [015089d] Viktor Benei - bit of ENV revision in general, and a new "--debug" flag (or DEBUG=true ENV) to run in Debug Mode (2015 Aug 12) +* [91c1b5c] Viktor Benei - Merge pull request #136 from viktorbenei/feature/pr-env (2015 Aug 12) +* [4fe4dba] Viktor Benei - PULL_REQUEST_ID env related template-expressions: .IsPR can now be used as Run-If expression (2015 Aug 12) +* [73e69cf] Viktor Benei - Merge pull request #135 from viktorbenei/feature/xcode-dependency (2015 Aug 11) +* [a91e67f] Viktor Benei - special "try check" dependency type, which can't be installed - a special one is 'xcode', which has it's own error msg (2015 Aug 11) +* [b33450e] Viktor Benei - Merge pull request #134 from gkiki90/master (2015 Aug 11) +* [d1742dd] Krisztian Goedrei - step title check (2015 Aug 11) +* [76a7d7a] Viktor Benei - typo fix (2015 Aug 11) +* [f8be2a4] Viktor Benei - Merge pull request #133 from gkiki90/go-utils (2015 Aug 11) +* [f98128d] Krisztian Goedrei - updated to go-utils (2015 Aug 11) +* [1e14821] Viktor Benei - Merge pull request #132 from viktorbenei/feature/check-workflow-version (2015 Aug 11) +* [faafea3] Viktor Benei - bitrise.yml format version check (2015 Aug 11) +* [55848a3] Krisztian Goedrei - RemoveFile instead of RemoveDir (2015 Aug 11) +* [946033e] Viktor Benei - Merge pull request #130 from gkiki90/working_directory (2015 Aug 11) +* [83aabab] Krisztian Goedrei - os temp workdir (2015 Aug 11) +* [8a5c48b] Viktor Benei - Merge pull request #127 from gkiki90/define_configs_as_string (2015 Aug 11) +* [e51c416] Krisztian Goedrei - removed test.yml (2015 Aug 11) +* [a36c4ab] Krisztian Goedrei - tests (2015 Aug 11) +* [7ab56cd] Viktor Benei - Merge pull request #128 from viktorbenei/master (2015 Aug 11) +* [fee03a8] Viktor Benei - godep-update (2015 Aug 11) +* [babb0c7] Viktor Benei - go-utils migration (2015 Aug 11) +* [6c3778d] Viktor Benei - Merge pull request #126 from gkiki90/refactor (2015 Aug 11) +* [d9d329a] Krisztian Goedrei - failed not important -> skippable (2015 Aug 11) +* [ec81f42] Viktor Benei - Merge pull request #125 from gkiki90/build_failed_fix (2015 Aug 10) +* [1f4b840] Krisztian Goedrei - build status env test fix (+3 squashed commits) Squashed commits: [3d3392c] build status [e77352e] build status env tests [9d9f504] var to const (+3 squashed commits) Squashed commits: [a60406a] godep-update [0f48b5f] run tests [8223985] do run step status fix (2015 Aug 10) +* [7df8b74] Viktor Benei - Merge pull request #123 from viktorbenei/feature/print-version-under-ascii-header (2015 Aug 10) +* [f21571f] Viktor Benei - print the version number under the ASCII header (2015 Aug 10) +* [d9ceb9e] Viktor Benei - Merge pull request #124 from gkiki90/build_failed_fix (2015 Aug 10) +* [9d9f504] Krisztian Goedrei - var to const (+3 squashed commits) Squashed commits: [a60406a] godep-update [0f48b5f] run tests [8223985] do run step status fix (2015 Aug 10) +* [ce2c9f1] Viktor Benei - Merge pull request #122 from viktorbenei/master (2015 Aug 09) +* [6cc9dbd] Viktor Benei - separate slack from-name for CI OK and error (2015 Aug 09) +* [c2cc3e7] Viktor Benei - Merge pull request #120 from viktorbenei/feature/init_add_items_to_gitignore (2015 Aug 09) +* [fed202a] Viktor Benei - Merge pull request #121 from viktorbenei/feature/build_status_env_fix (2015 Aug 09) +* [44fa69f] Viktor Benei - PR fix (2015 Aug 09) +* [e691054] Viktor Benei - fixed (2015 Aug 09) +* [cd81303] Viktor Benei - doInit : append the '.bitrise*' pattern to the .gitignore file in the current dir + example workflow renamed to 'test' (2015 Aug 09) +* [bde1c0a] Viktor Benei - Merge pull request #119 from viktorbenei/feature/include_build_url_in_ci_test_slack_msg (2015 Aug 09) +* [b6048b8] Viktor Benei - Merge pull request #118 from viktorbenei/master (2015 Aug 09) +* [7dfd358] Viktor Benei - Build URL added to CI slack msgs (2015 Aug 09) +* [ea67acf] Viktor Benei - include branch name in CI msg (2015 Aug 09) +* [1806749] Viktor Benei - Merge pull request #117 from viktorbenei/master (2015 Aug 09) +* [8663044] Viktor Benei - test added back + error text typo (2015 Aug 09) +* [8184360] Viktor Benei - bitrise.yml updated - 'ci' workflow added (2015 Aug 09) +* [4e9ff9c] Viktor Benei - color strings updated - white color was removed because it was invisible on white background (2015 Aug 09) +* [bee0e0f] Viktor Benei - Merge pull request #116 from viktorbenei/master (2015 Aug 09) +* [5a21b33] Viktor Benei - moved a couple of things into the new go-utils repo (2015 Aug 08) +* [2f52692] Viktor Benei - colorstring package moved to go-utils repo (2015 Aug 08) +* [4cc0d05] Viktor Benei - Merge pull request #115 from viktorbenei/master (2015 Aug 08) +* [f2bb4a6] Viktor Benei - godeps update (2015 Aug 08) +* [f2dd7ed] Viktor Benei - godeps update (2015 Aug 08) +* [bfa49f2] Viktor Benei - timestamp ten step revision / ref fix (2015 Aug 08) +* [d9985ad] Viktor Benei - added '.git' to the end of the default step lib source (https://github.com/bitrise-io/bitrise-steplib.git), for clarity (2015 Aug 08) +* [74dedb1] Viktor Benei - Merge pull request #114 from gkiki90/master (2015 Aug 08) +* [d4c4449] Krisztian Goedrei - Merge branch 'master' of github.com:bitrise-io/bitrise-cli (2015 Aug 08) +* [576d095] Viktor Benei - Merge pull request #113 from viktorbenei/master (2015 Aug 08) +* [0cd5690] Krisztian Goedrei - godep-update (2015 Aug 08) +* [f3ba246] Viktor Benei - godeps update (+1 squashed commit) Squashed commits: [e9ed7e8] util fix (2015 Aug 08) +* [7f3dfee] Krisztian Goedrei - run print fix (2015 Aug 08) +* [151f879] Viktor Benei - Merge pull request #112 from viktorbenei/master (2015 Aug 08) +* [e4f2052] Viktor Benei - print fix (2015 Aug 08) +* [cb5ecf8] Viktor Benei - Merge pull request #111 from gkiki90/master (2015 Aug 08) +* [0c048a4] Krisztian Goedrei - math fix (2015 Aug 08) +* [9e9fefb] Viktor Benei - Merge pull request #110 from viktorbenei/master (2015 Aug 08) +* [c877ca8] Viktor Benei - godep update (2015 Aug 08) +* [95cf4e5] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 08) +* [b2b3c48] Viktor Benei - Merge pull request #109 from gkiki90/master (2015 Aug 08) +* [a4d1e07] Krisztian Goedrei - godep update (2015 Aug 08) +* [1bf5e85] Krisztian Goedrei - stepman migration fix (2015 Aug 08) +* [94f4de3] Krisztian Goedrei - Merge branch 'master' of github.com:bitrise-io/bitrise-cli (2015 Aug 08) +* [12be6ca] Viktor Benei - godep update (2015 Aug 08) +* [51389eb] Viktor Benei - brew_test mL removed but a new brew_publish one was added (2015 Aug 08) +* [748ed98] Viktor Benei - updated main / default step lib spec repo url (2015 Aug 08) +* [8f327d9] Krisztian Goedrei - stepman migration (2015 Aug 08) +* [a0f4c43] Viktor Benei - Merge pull request #108 from gkiki90/master (2015 Aug 06) +* [a5182d1] Krisztian Goedrei - reference cycle test config moved to _tests (2015 Aug 06) +* [605fc51] Viktor Benei - Merge pull request #107 from viktorbenei/master (2015 Aug 06) +* [455d9c9] Viktor Benei - Merge pull request #106 from gkiki90/run_tests (2015 Aug 06) +* [7b86e33] Viktor Benei - _tests for test bitrise.ymls (2015 Aug 06) +* [5844849] Krisztian Goedrei - reference cycle test (2015 Aug 06) +* [ec2e74b] Viktor Benei - Merge pull request #105 from viktorbenei/master (2015 Aug 06) +* [fd98b01] Viktor Benei - Godeps update - pathutil (2015 Aug 06) +* [5bb93ed] Viktor Benei - 'setup' is now called automatically if it was not called for the current version of bitrise when 'run' is called + code revisions (2015 Aug 06) +* [4226622] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 06) +* [a561b0d] Viktor Benei - store `bitrise setup` for the given bitrise version, so that it can be checked whether a setup was done for the current version (2015 Aug 06) +* [ef60060] Viktor Benei - Merge pull request #104 from gkiki90/build_failed_test (2015 Aug 06) +* [407df1e] Viktor Benei - Merge pull request #103 from viktorbenei/master (2015 Aug 06) +* [4de3fe1] Krisztian Goedrei - ci.sh fix (2015 Aug 06) +* [710f90f] Krisztian Goedrei - fixed ci.sh (2015 Aug 06) +* [884006c] Krisztian Goedrei - fixed build failed (2015 Aug 06) +* [5f9e786] Krisztian Goedrei - doRun fixes, doRun_test (2015 Aug 06) +* [4942a3a] Krisztian Goedrei - reorganized code (2015 Aug 06) +* [f4e1ed3] Viktor Benei - switching to the new StepLib, hosted on GitHub (2015 Aug 06) +* [a683856] Viktor Benei - Merge pull request #102 from gkiki90/master (2015 Aug 05) +* [3aee65e] Krisztian Goedrei - removed bitrise from gitignore (2015 Aug 05) +* [b33ff0c] Viktor Benei - Merge pull request #101 from viktorbenei/master (2015 Aug 05) +* [6bd0268] Viktor Benei - start of v0.9.8 (2015 Aug 05) +* [17e1c54] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 05) + + +## 0.9.7 (2015 Aug 05) + +### Release Notes + +* __IMPORTANT__ : The project was renamed from `bitrise-cli` to just `bitrise`, which means that from now on you have to call your commands with `bitrise [command]`, instead of the previous, longer `bitrise-cli [command]`. +* Improved step dependency management with `brew`. +* Log improvements. + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.7/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 0.9.6 -> 0.9.7 + +* [c1759d3] Viktor Benei - Merge pull request #100 from gkiki90/master (2015 Aug 05) +* [a35b632] Viktor Benei - slack step titles (2015 Aug 05) +* [23a5966] Krisztian Goedrei - tool dependecies (2015 Aug 05) +* [c77a3c8] Krisztian Goedrei - Merge branch 'master' of github.com:bitrise-io/bitrise-cli (2015 Aug 05) +* [0f6629f] Krisztian Goedrei - changelog (2015 Aug 05) +* [c484396] Viktor Benei - Merge pull request #99 from viktorbenei/master (2015 Aug 05) +* [4bfdfc8] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 05) +* [4b9d9ca] Viktor Benei - added another Slack msg to announce (2015 Aug 05) +* [4e47b96] Viktor Benei - Merge pull request #98 from gkiki90/master (2015 Aug 05) +* [6d0a9ef] Viktor Benei - Godep update (2015 Aug 05) +* [98a0a76] Krisztian Goedrei - flag fixes (2015 Aug 05) +* [abf0257] Krisztian Goedrei - init highligth (2015 Aug 05) +* [82daa8f] Viktor Benei - Merge pull request #97 from viktorbenei/master (2015 Aug 05) +* [88ab022] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 05) +* [a06ac0d] Viktor Benei - Merge pull request #96 from gkiki90/skipped_handling (2015 Aug 05) +* [a9de033] Viktor Benei - Merge pull request #95 from gkiki90/runtime (2015 Aug 05) +* [2df0d40] Viktor Benei - renames, from the old `bitrise-cli` to the new, official `bitrise` tool name (2015 Aug 05) +* [4b16a00] Krisztian Goedrei - run if (2015 Aug 05) +* [2d93845] Krisztian Goedrei - runtime, dependency fixes (2015 Aug 05) +* [c5cae54] Viktor Benei - Merge pull request #94 from gkiki90/master (2015 Aug 05) +* [bc46501] Krisztian Goedrei - CI flag fixes (2015 Aug 05) +* [1a9a1ed] Krisztian Goedrei - chek with brew if installed (2015 Aug 05) +* [0fc0102] Viktor Benei - Merge pull request #93 from viktorbenei/master (2015 Aug 04) +* [93eb603] Viktor Benei - start of v0.9.7 (2015 Aug 04) +* [a205dec] Viktor Benei - changeling template + changeling generator added to create-release workflow, similar to the one in stepman&envman (2015 Aug 04) + + +## 0.9.6 (2015 Aug 04) + +### Release Notes + +* __BREAKING__ : `.bitrise.secrets.yml` 's syntax changed, to match the environments syntax used everywhere else. This means that instead of directly specifying `is_expand` at the same level as the key and value you should now move this into an `opts:` section, just like in every other `envs` list in `bitrise.yml`. +* __NEW__ : dependency management built into `bitrise.yml` syntax. Right now only `brew` is supported, on OS X, but this will be expanded. +* if a step or a version can't be found in the local cache from a step library `bitrise` will now update the local cache before failing with "step not found" +* greatly improved logs, colored step sections and step run summaries. It starts to look decent and is much more helpful than the previous log outputs. +* updated `setup` - only the Xcode Command Line tools are required now, if no full Xcode found it'll print a warning message about it but you can still use `bitrise`. +* quite a lot of minor bug fixes + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.6/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 0.9.5 -> 0.9.6 + +* [1dd2a00] Viktor Benei - Merge pull request #92 from gkiki90/master (2015 Aug 04) +* [96cab0c] Krisztian Goedrei - PR fixes (2015 Aug 04) +* [58c4154] Viktor Benei - Merge pull request #91 from gkiki90/envman_run_fix (2015 Aug 04) +* [9964b04] Krisztian Goedrei - PR fix (2015 Aug 04) +* [dea2cdf] Krisztian Goedrei - PR fixes (2015 Aug 04) +* [6c1e275] Krisztian Goedrei - running step log fix (2015 Aug 04) +* [9acbefb] Krisztian Goedrei - log fixes (2015 Aug 04) +* [e136db0] Krisztian Goedrei - run results fix (2015 Aug 04) +* [26d0296] Krisztian Goedrei - envman run with exit code, log fixes in progress (2015 Aug 04) +* [d2f25ff] Viktor Benei - Merge pull request #90 from viktorbenei/master (2015 Aug 04) +* [4b37119] Viktor Benei - Godep update : goinp bool parse improvement (2015 Aug 04) +* [4806c04] Viktor Benei - Merge pull request #89 from viktorbenei/master (2015 Aug 04) +* [48ec5b2] Viktor Benei - at setup the "[OK]" strings are now highlighted with green color; Xcode CLT setup/check revisions: warning if only CLT is available but not a full Xcode, with highlight, and as version it prints the info text (2015 Aug 04) +* [f643144] Viktor Benei - dependencies: no full Xcode required, only Command line tools (2015 Aug 03) +* [4795201] Viktor Benei - setup: envman v0.9.2 and stepman v0.9.6 required (2015 Aug 03) +* [71ce059] Viktor Benei - init: generate new style .secrets (2015 Aug 03) +* [1b5112e] Viktor Benei - Merge pull request #88 from gkiki90/dependencies (2015 Aug 03) +* [3635cfe] Krisztian Goedrei - print header fix (2015 Aug 03) +* [16946a4] Krisztian Goedrei - godep update (2015 Aug 03) +* [95d7529] Krisztian Goedrei - refactor fixes (2015 Aug 03) +* [c60cd93] Krisztian Goedrei - godep update (2015 Aug 03) +* [28943df] Krisztian Goedrei - depman update (2015 Aug 03) +* [9fd991f] Krisztian Goedrei - log CI mode, printASCIIHeader moved to ci.go (2015 Jul 31) +* [3c118a8] Krisztian Goedrei - dependencies (2015 Jul 31) +* [5ad6512] Krisztian Goedrei - dependency handling (2015 Jul 31) +* [575be1e] Krisztian Goedrei - dependencies in progress (2015 Jul 31) +* [1e707d7] Krisztian Goedrei - fixed env merge, models_methods_tests (2015 Jul 31) +* [3662056] Krisztian Goedrei - test start (2015 Jul 30) +* [b08a92d] Krisztian Goedrei - godep update (2015 Jul 30) +* [3e86ee6] Krisztian Goedrei - fixed stepman update flag, typo fix (2015 Jul 30) +* [630cc2a] Krisztian Goedrei - refactor, code style (2015 Jul 30) +* [9af552b] Viktor Benei - Merge pull request #86 from gkiki90/new_envman_models (2015 Jul 29) +* [1272839] Krisztian Goedrei - test fix (2015 Jul 29) +* [1fe5d13] Krisztian Goedrei - PR fix (2015 Jul 29) +* [d442821] Krisztian Goedrei - godep update (2015 Jul 29) +* [91324a2] Krisztian Goedrei - godep (2015 Jul 29) +* [57c2476] Krisztian Goedrei - godep (2015 Jul 29) +* [0301e85] Krisztian Goedrei - use envman models (2015 Jul 29) +* [9d52cfc] Viktor Benei - Merge pull request #85 from viktorbenei/master (2015 Jul 28) +* [654aa47] Viktor Benei - start of v0.9.6 (2015 Jul 28) + + +## 0.9.5 (2015 Jul 28) + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.5/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 0.9.4 -> 0.9.5 + +* [017e896] Viktor Benei - Merge pull request #84 from viktorbenei/master (2015 Jul 28) +* [87dc5a9] Viktor Benei - require Stepman 0.9.5 (2015 Jul 28) +* [4a20cb1] Viktor Benei - Merge pull request #83 from viktorbenei/master (2015 Jul 28) +* [04910f2] Viktor Benei - Godeps update (2015 Jul 28) +* [1ae7e36] Viktor Benei - Merge pull request #82 from gkiki90/log_improvements (2015 Jul 28) +* [a2975fa] Krisztian Goedrei - test (2015 Jul 28) +* [63ca923] Krisztian Goedrei - Merge branch 'master' into log_improvements (2015 Jul 28) +* [eefa57e] Krisztian Goedrei - build failed fix (2015 Jul 28) +* [dabdb97] Viktor Benei - Merge pull request #81 from viktorbenei/master (2015 Jul 28) +* [4ffbfa6] Viktor Benei - BITRISE_BUILD_STATUS and STEPLIB_BUILD_STATUS printing in before-after test in bitrise.yml (2015 Jul 28) +* [c698340] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 28) +* [cc9030a] Viktor Benei - Merge pull request #80 from gkiki90/log_improvements (2015 Jul 28) +* [9ca35a0] Krisztian Goedrei - template_utils_test BuildRunResultsModel fix (2015 Jul 28) +* [3e7f3ae] Krisztian Goedrei - fixed build failed mode (2015 Jul 28) +* [c5c94eb] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 28) +* [3cc6fad] Viktor Benei - Merge pull request #79 from gkiki90/log_improvements (2015 Jul 28) +* [c8a6ed0] Viktor Benei - before-after ENV accessibility test (2015 Jul 28) +* [ec7c9d2] Viktor Benei - Merge pull request #78 from viktorbenei/master (2015 Jul 28) +* [0929268] Krisztian Goedrei - log fixes, env handling fixes (2015 Jul 28) +* [68ff822] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 28) +* [6fbce1b] Viktor Benei - Merge pull request #77 from gkiki90/master (2015 Jul 28) +* [595f6fd] Krisztian Goedrei - validate fix (2015 Jul 28) +* [061bd82] Krisztian Goedrei - log fix in progress (2015 Jul 28) +* [6e8ce72] Krisztian Goedrei - comments, env imports (2015 Jul 28) +* [2eef2e8] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 28) +* [d2e8ca2] Viktor Benei - before_run for install (2015 Jul 28) +* [5bd2547] Viktor Benei - init : doesn't print the content anymore (2015 Jul 28) +* [59e2961] Viktor Benei - using rsync instead of cp to copy local path:: step source - it can handle the case if you want to run it from the step's dir directly, for example while developing the step (2015 Jul 28) +* [179ed5f] Viktor Benei - Merge pull request #76 from gkiki90/after_before (2015 Jul 28) +* [811f7e2] Krisztian Goedrei - godep (2015 Jul 28) +* [b4e3c9d] Krisztian Goedrei - PR fix (2015 Jul 28) +* [5d5fdd1] Krisztian Goedrei - Merge branch 'master' into after_before (2015 Jul 28) +* [39870f5] Krisztian Goedrei - log fixes (2015 Jul 28) +* [1a34984] Krisztian Goedrei - validating bitrisedata, workflow logs, (2015 Jul 28) +* [1b8747d] Viktor Benei - Merge pull request #75 from gkiki90/log_fix (2015 Jul 28) +* [4961f6c] Krisztian Goedrei - test (2015 Jul 27) +* [47e65d6] Krisztian Goedrei - run in progress (2015 Jul 27) +* [94d9574] Krisztian Goedrei - colorstring package and usage (2015 Jul 27) +* [821a6e6] Krisztian Goedrei - Merge branch 'master' into log_fix (2015 Jul 27) +* [388b536] Krisztian Goedrei - color log in progress (2015 Jul 27) +* [f4482de] Viktor Benei - Merge pull request #74 from gkiki90/step_source (2015 Jul 27) +* [4b59182] Krisztian Goedrei - bitrise.yml fix (2015 Jul 27) +* [189453c] Krisztian Goedrei - help messages fix, code style (2015 Jul 27) +* [70c1719] Krisztian Goedrei - timestamp-gen workflow fixes, step source log (2015 Jul 27) +* [60c5475] Viktor Benei - Merge pull request #73 from viktorbenei/master (2015 Jul 25) +* [b394e22] Viktor Benei - just a bit of template expression doc note (2015 Jul 25) +* [0af570e] Viktor Benei - Merge pull request #72 from viktorbenei/master (2015 Jul 25) +* [e178a1d] Viktor Benei - test for "$.Prop" style referencing, and annotated template examples (2015 Jul 25) +* [e99c312] Viktor Benei - enveq function, for easier ENV testing (2015 Jul 25) +* [1b482b8] Viktor Benei - A TemplateDataModel is now available for step property expressions, for easier "IsCI" detection. You can also just write ".IsCI", instead of the longer "{{.IsCI}}" the "CI=true" env is set at the start to force every tool to work in CI mode (even if the CI mode was just a command line param) (2015 Jul 25) +* [6dfd682] Viktor Benei - Merge pull request #71 from viktorbenei/run_if_and_templates (2015 Jul 24) +* [d232bad] Viktor Benei - first version of Run-If template handling & a couple of revisions (2015 Jul 24) +* [8ca8b9f] Viktor Benei - MergeStepWith #fix (2015 Jul 24) +* [026a752] Viktor Benei - Examples & tutorials section (2015 Jul 24) +* [d81a27b] Viktor Benei - Merge pull request #70 from viktorbenei/master (2015 Jul 24) +* [0a5a60b] Viktor Benei - the failing steps examples are also moved into examples/tutorials (2015 Jul 24) +* [e3bd023] Viktor Benei - a couple of experimentals (2015 Jul 24) +* [5cfd72d] Viktor Benei - examples/tutorials (2015 Jul 24) +* [afaf8e4] Viktor Benei - _examples folder to include a couple of example bitrise cli configs and workflows (2015 Jul 24) +* [c3d9605] Viktor Benei - Merge pull request #69 from viktorbenei/master (2015 Jul 24) +* [ccf3c89] Viktor Benei - Install instructions now points to /releases (2015 Jul 24) +* [052fb87] Viktor Benei - start of v0.9.5 (2015 Jul 24) + + +## 0.9.4 (2015 Jul 24) + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.4/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 0.9.3 -> 0.9.4 + +* [017b840] Viktor Benei - Merge pull request #68 from viktorbenei/master (2015 Jul 24) +* [5f5be0f] Viktor Benei - Godeps update (2015 Jul 24) +* [8db50a3] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 24) +* [bcb3ec3] Viktor Benei - Stepman update - related: IsNotImportant is now IsSkippable (2015 Jul 24) +* [80a1348] Viktor Benei - Merge pull request #67 from viktorbenei/master (2015 Jul 24) +* [1268b06] Viktor Benei - Godep-update workflow (2015 Jul 24) +* [6e1e1bf] Viktor Benei - ssh style remote git step in test workflows (2015 Jul 24) +* [0238347] Viktor Benei - fix: in case of direct git uri which contains @ as part of the url it should still work correctly (ex: if git url is: git@github.com:bitrise-io/steps-timestamp.git); no path to absolute-path conversion should happen in CreateStepIDDataFromString; unit tests for the new "path::" and "git::" style step IDs (2015 Jul 24) +* [760c42d] Viktor Benei - StepIDData : now that it supports local and direct-git-url options the previous ID was renamed to IDorURI and some documentation is provided for relevant places (2015 Jul 24) +* [3fc9807] Viktor Benei - in case the step source is defined as a direct git uri a version (branch or tag) is also required (2015 Jul 24) +* [ce65526] Viktor Benei - a bit more, and more thorough test workflows (2015 Jul 24) +* [ed71074] Viktor Benei - Merge pull request #66 from gkiki90/master (2015 Jul 24) +* [7e8c95f] Krisztian Goedrei - git src (2015 Jul 24) +* [ee8ce3a] Krisztian Goedrei - err check fixes (2015 Jul 24) +* [23a365a] Krisztian Goedrei - move local step to .bitrise work dir (2015 Jul 24) +* [4517333] Krisztian Goedrei - Merge branch 'master' of github.com:bitrise-io/bitrise-cli (2015 Jul 24) +* [c20bc6a] Krisztian Goedrei - PR fixes (2015 Jul 24) +* [0dd36ff] Krisztian Goedrei - support for ~/your/path (2015 Jul 24) +* [973b4fc] Krisztian Goedrei - local steps (2015 Jul 24) +* [b60f4a3] Krisztian Goedrei - local path in models and model_methods (2015 Jul 24) +* [f620295] Viktor Benei - Merge pull request #65 from viktorbenei/master (2015 Jul 24) +* [b5cc18b] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 24) +* [ac1564b] Viktor Benei - minor run command log formatting (2015 Jul 23) +* [da057fc] Viktor Benei - start of 0.9.4 (2015 Jul 23) +* [b700273] Viktor Benei - install - 0.9.3 (2015 Jul 23) + + +## 0.9.3 (2015 Jul 23) + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.3/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 0.9.2 -> 0.9.3 + +* [469750c] Viktor Benei - Merge pull request #64 from viktorbenei/master (2015 Jul 23) +* [8628a5a] Viktor Benei - requires stepman 0.9.3 (2015 Jul 23) +* [121afa1] Viktor Benei - Godeps update (2015 Jul 23) +* [ee301cd] Viktor Benei - Merge pull request #62 from viktorbenei/setup_improvements (2015 Jul 23) +* [269aec9] Viktor Benei - temp switch back to previous stepman min ver (2015 Jul 23) +* [1874e0a] Viktor Benei - Xcode CLT version check and better Brew warn (2015 Jul 23) +* [61d42cc] Viktor Benei - Merge branch 'master' into setup_improvements (2015 Jul 23) +* [41bdc3a] Viktor Benei - Setup can now update the required Bitrise Tools if an older version found (2015 Jul 23) +* [9fb88c3] Viktor Benei - Merge pull request #63 from gkiki90/build_status_env (2015 Jul 23) +* [bb503ca] Krisztian Goedrei - set build status env fix (2015 Jul 23) +* [db8e469] Krisztian Goedrei - build failed envs (2015 Jul 23) +* [b7b34c0] Viktor Benei - Godeps update: stepman (2015 Jul 23) +* [7c7878c] Viktor Benei - Merge pull request #61 from gkiki90/build_time (2015 Jul 23) +* [6b7de38] Krisztian Goedrei - total count fix (2015 Jul 23) +* [341b560] Krisztian Goedrei - log success count (2015 Jul 23) +* [13f4df0] Krisztian Goedrei - log fixes (2015 Jul 23) +* [8901ab9] Krisztian Goedrei - skipped steps (2015 Jul 23) +* [6066019] Krisztian Goedrei - log fixes (2015 Jul 23) +* [59b895b] Krisztian Goedrei - typo (2015 Jul 23) +* [075835b] Krisztian Goedrei - summary log (2015 Jul 23) +* [a0f3e5d] Krisztian Goedrei - build finish fixes (2015 Jul 23) +* [35d2390] Krisztian Goedrei - code style (2015 Jul 23) +* [afe217f] Krisztian Goedrei - failed step fix (2015 Jul 23) +* [6fcf9e4] Krisztian Goedrei - revision (2015 Jul 23) +* [b18d334] Krisztian Goedrei - revision (2015 Jul 23) +* [a241271] Krisztian Goedrei - fixed merge (2015 Jul 23) +* [ea56eef] Krisztian Goedrei - merge fix (2015 Jul 23) +* [f6136c5] Krisztian Goedrei - Merge branch 'master' into build_time (2015 Jul 23) +* [5787288] Krisztian Goedrei - register build status methods (2015 Jul 23) +* [b9d861a] Viktor Benei - Merge pull request #60 from viktorbenei/master (2015 Jul 22) +* [da46aba] Viktor Benei - start of v0.9.3 (2015 Jul 22) +* [0650b85] Viktor Benei - install - v0.9.2 (2015 Jul 22) + + +## 0.9.2 (2015 Jul 22) + +### Install or upgrade + +To install this version, run the following commands (in a bash shell): + +``` +curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +``` + +Then: + +``` +chmod +x /usr/local/bin/bitrise +``` + +That's all, you're ready to go! + +Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run +is installed and available, but if you forget to do this it'll be performed the first +time you call bitrise run. + +### Release Commits - 0.9.1 -> 0.9.2 + +* [45e51d5] Viktor Benei - Merge pull request #59 from viktorbenei/master (2015 Jul 22) +* [7300a42] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 22) +* [8c75a3b] Viktor Benei - Goddess update (2015 Jul 22) +* [8f0da08] Viktor Benei - doSetup: install stepman v0.9.2 (2015 Jul 22) +* [a060d39] Viktor Benei - Merge pull request #58 from viktorbenei/master (2015 Jul 22) +* [86790aa] Viktor Benei - Merge pull request #57 from bazscsa/master (2015 Jul 22) +* [aa3c01d] Viktor Benei - ci: now does a build & calls setup on it (2015 Jul 22) +* [b641a4f] Viktor Benei - fixed possible infinite recursion in Setup (2015 Jul 22) +* [a1f6f45] Viktor Benei - create-release : now sends a Slack msg as well (2015 Jul 22) +* [2c31fc3] Viktor Benei - envman call fix: do adds with --append (2015 Jul 22) +* [f1d612f] Tamás Bazsonyi - Setup (2015 Jul 22) +* [4829d33] Viktor Benei - Merge pull request #56 from gkiki90/isNotImportant_handling (2015 Jul 22) +* [34a9326] Krisztian Goedrei - refactor, typo (2015 Jul 22) +* [6d66525] Krisztian Goedrei - build time in progress (2015 Jul 22) +* [15f0d63] Tamás Bazsonyi - setup description revision (2015 Jul 22) +* [f0ce128] Krisztian Goedrei - isNotImportent handling, buildFailedMode fixes (2015 Jul 22) +* [84a9d8d] Viktor Benei - Merge pull request #55 from viktorbenei/master (2015 Jul 22) +* [5b11e83] Viktor Benei - "environments" is now simply "envs" (2015 Jul 22) +* [26a3eae] Viktor Benei - doSetup : don't ask for permission to install required dependencies (envman & stepman) (2015 Jul 22) +* [0a9955d] Viktor Benei - Install command syntax change, for clarity (2015 Jul 22) +* [1b59895] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 22) +* [ba9a6e3] Viktor Benei - start of v0.9.2 - bitrise.yml now contains a 'create-release' workflow (2015 Jul 22) +* [b4cf5e8] Viktor Benei - just a minor format change for setup (2015 Jul 22) +* [3ae6a0e] Viktor Benei - Install and setup instructions (2015 Jul 22) +* [e38911e] Viktor Benei - setup: now can install stepman as well as envman (2015 Jul 22) +* [40cfd3a] Viktor Benei - better 'init' command : it now adds a default step lib source & a simple 'script' step with hello (2015 Jul 22) + + +----------------- + +Updated: 2017 Aug 07 \ No newline at end of file diff --git a/vendor/github.com/bitrise-io/bitrise/Dockerfile b/vendor/github.com/bitrise-io/bitrise/Dockerfile new file mode 100644 index 00000000..35993eec --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/Dockerfile @@ -0,0 +1,34 @@ +FROM golang:1.7 + +ENV PROJ_NAME bitrise + +RUN apt-get update + +RUN DEBIAN_FRONTEND=noninteractive apt-get -y install curl git mercurial rsync ruby sudo + +# From the official Golang Dockerfile +# https://github.com/docker-library/golang/blob/master/1.4/Dockerfile +RUN mkdir -p /go/src /go/bin && chmod -R 777 /go +ENV GOPATH /go +ENV PATH /go/bin:$PATH + +# Install required (testing) tools +# Install dependencies +RUN go get -u github.com/tools/godep +# Check for unhandled errors +RUN go get -u github.com/kisielk/errcheck +# Go lint +RUN go get -u github.com/golang/lint/golint + +RUN mkdir -p /go/src/github.com/bitrise-io/$PROJ_NAME +COPY . /go/src/github.com/bitrise-io/$PROJ_NAME + +WORKDIR /go/src/github.com/bitrise-io/$PROJ_NAME +# install +RUN go install + +# setup (downloads envman & stepman) +RUN bitrise setup +RUN $HOME/.bitrise/tools/stepman setup -c https://github.com/bitrise-io/bitrise-steplib.git + +CMD bitrise version diff --git a/vendor/github.com/bitrise-io/bitrise/Godeps/Godeps.json b/vendor/github.com/bitrise-io/bitrise/Godeps/Godeps.json new file mode 100644 index 00000000..81e4dcbd --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/Godeps/Godeps.json @@ -0,0 +1,135 @@ +{ + "ImportPath": "github.com/bitrise-io/bitrise", + "GoVersion": "go1.8", + "GodepVersion": "v79", + "Packages": [ + "./..." + ], + "Deps": [ + { + "ImportPath": "github.com/Sirupsen/logrus", + "Comment": "1.0.2-34-g181d419", + "Rev": "181d419aa9e2223811b824e8f0b4af96f9ba9302" + }, + { + "ImportPath": "github.com/bitrise-io/envman/models", + "Comment": "1.1.6", + "Rev": "e914cc7038450a801f3c5713af755f66bf09ca37" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/colorstring", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/command", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/command/git", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/errorutil", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/fileutil", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/log", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/parseutil", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/pathutil", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/pointers", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/progress", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/retry", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/stringutil", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/go-utils/versions", + "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" + }, + { + "ImportPath": "github.com/bitrise-io/goinp/goinp", + "Rev": "a602d207503f909271ecd91a17abd9ac919e273c" + }, + { + "ImportPath": "github.com/bitrise-io/stepman/models", + "Comment": "0.9.33", + "Rev": "ecc703a00038ed94fb22ae86b4daa0d4b8ba49d7" + }, + { + "ImportPath": "github.com/bitrise-tools/gows/gows", + "Rev": "e4fb47946c852c6f46d28b27a4d0d1a02f5eb0ac" + }, + { + "ImportPath": "github.com/davecgh/go-spew/spew", + "Comment": "v1.1.0-6-gadab964", + "Rev": "adab96458c51a58dc1783b3335dcce5461522e75" + }, + { + "ImportPath": "github.com/hashicorp/go-version", + "Rev": "03c5bf6be031b6dd45afec16b1cf94fc8938bc77" + }, + { + "ImportPath": "github.com/pmezard/go-difflib/difflib", + "Comment": "v1.0.0", + "Rev": "792786c7400a136282c1664665ae0a8db921c6c2" + }, + { + "ImportPath": "github.com/ryanuber/go-glob", + "Comment": "v0.1-4-g256dc44", + "Rev": "256dc444b735e061061cf46c809487313d5b0065" + }, + { + "ImportPath": "github.com/stretchr/testify/assert", + "Comment": "v1.1.4-70-g05e8a0e", + "Rev": "05e8a0eda380579888eb53c394909df027f06991" + }, + { + "ImportPath": "github.com/stretchr/testify/require", + "Comment": "v1.1.4-70-g05e8a0e", + "Rev": "05e8a0eda380579888eb53c394909df027f06991" + }, + { + "ImportPath": "github.com/urfave/cli", + "Comment": "v1.19.1-67-gb99aa81", + "Rev": "b99aa811b4c1dd84cc6bccb8499c82c72098085a" + }, + { + "ImportPath": "golang.org/x/crypto/ssh/terminal", + "Rev": "42ff06aea7c329876e5a0fe94acc96902accf0ad" + }, + { + "ImportPath": "golang.org/x/sys/unix", + "Rev": "d8f5ea21b9295e315e612b4bcf4bedea93454d4d" + }, + { + "ImportPath": "golang.org/x/sys/windows", + "Rev": "d8f5ea21b9295e315e612b4bcf4bedea93454d4d" + }, + { + "ImportPath": "gopkg.in/yaml.v2", + "Rev": "25c4ec802a7d637f88d584ab26798e94ad14c13b" + } + ] +} diff --git a/vendor/github.com/bitrise-io/bitrise/Godeps/Readme b/vendor/github.com/bitrise-io/bitrise/Godeps/Readme new file mode 100644 index 00000000..4cdaa53d --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/Godeps/Readme @@ -0,0 +1,5 @@ +This directory tree is generated automatically by godep. + +Please do not edit. + +See https://github.com/tools/godep for more information. diff --git a/vendor/github.com/bitrise-io/bitrise/LICENSE b/vendor/github.com/bitrise-io/bitrise/LICENSE new file mode 100644 index 00000000..a6a5c39a --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Bitrise + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/bitrise-io/bitrise/README.md b/vendor/github.com/bitrise-io/bitrise/README.md new file mode 100644 index 00000000..1d64eecf --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/README.md @@ -0,0 +1,120 @@ +# Bitrise (offline) CLI + +_Discussion forum: [https://discuss.bitrise.io/](https://discuss.bitrise.io/)_ + +Run your Bitrise automations with this CLI tool on any Mac or Linux machine, and use the same configuration on +[bitrise.io](https://www.bitrise.io) (automation service, with a mobile app focus). + +*Part of the Bitrise Continuous Integration, Delivery and Automations Stack, +with [stepman](https://github.com/bitrise-io/stepman) and [envman](https://github.com/bitrise-io/envman).* + +For a nice & quick intro you should check: [https://www.bitrise.io/cli](https://www.bitrise.io/cli) + + +## Install and Setup + +The installation is quick and easy, check the latest release for instructions at: [https://github.com/bitrise-io/bitrise/releases](https://github.com/bitrise-io/bitrise/releases) + +Installing with Homebrew: + +`brew update && brew install bitrise` + +Optionally, you can call `bitrise setup` to verify that everything what's required for `bitrise` to run +is installed and available, but if you forget to do this it'll be performed the first +time you call `bitrise run`. + +## Tutorials and Examples + +You can find examples in the [_examples](https://github.com/bitrise-io/bitrise/tree/master/_examples) folder. + +If you're getting started you should start with [_examples/tutorials](https://github.com/bitrise-io/bitrise/tree/master/_examples/tutorials), +this should guide you through the basics, while you'll already use `bitrise` (requires installed `bitrise`). + +You can find a complete iOS sample project at: https://github.com/bitrise-io/sample-apps-ios-with-bitrise-yml + + +## Tooling support & JSON output format + +`bitrise` CLI commands support a `--format=[format]` parameter. +This is intended mainly for tooling support, by adding `--format=json` you'll +get a JSON formatted output on Standard Output. + +**This is still work-in-progress, we're working on providing +the `--format` param to every command except `run`**. + +Every error, warning etc. message will go to StdErr; and on the StdOut +you should only get the valid JSON output. + +An example calling the `version` command: + +`$ bitrise version --format=json` + +Will print `{"version":"1.2.4"}` to the Standard Output (StdOut). + + +## Share your Step + +You can use your own Step as you can see in the `_examples`, even if it's +not yet committed into a repository, or from a repository directly. + +If you would like to share your awesome Step with others +you can do so by calling `stepman share` and then following the +guide it prints. + +## Documentation + +We added some documents to make it a bit easier to get started with Bitrise CLI. The documentation includes a quick and a little longer guides for CLI, a [React Native](http://facebook.github.io/react-native/) project workflow guide and an overview of the Step share process. You can find them in the [_docs](/_docs/) folder. + +## Development + +### Guidelines + +* __Easy to use__: the UX for the end-user, always keep it in mind, make it a pleasant experience to work with this tool (and all of the Bitrise tools)! +* __Code should be kept simple__: easy to understand, easy to collaborate/contribute (as much as possible of course). +* __Compatibility__: never do an incompatible change, unless you can't avoid it. Release new features as additional options, to not to break existing configurations. +* __Stability__: related to compatibility, but in general stability is really important, especially so in a CI/automation environment, where you expect fully reproducible outcomes. +* __Flexibility__: should also be kept in mind, but only if it does not affect the previous points. + +### Updating dependencies + +To do a full dependency update use [bitrise-tools/gows](https://github.com/bitrise-tools/gows), +for a clean workspace: + +``` +gows clear && gows bitrise run godeps-update +``` + +to test that all dependency is included: + +``` +gows clear && gows go test ./... && gows go install && gows bitrise run test +``` + +and/or with `docker-compose`: + +``` +docker-compose build && docker-compose run --rm app go test ./... +``` + +### Release a new version + +1. Update go dependencies (`bitrise run godeps-update`) +1. PR & merge these changes to the `master` branch +1. Release a new versions of bitrise-tools (stepman, envman) if there are changes, you can find the dependent tools in `./bitrise/setup.go -> minEnvmanVersion, minStepmanVersion` +1. Release a new versions of default plugins if there are changes, you can find the default plugins in `./bitrise/setup.go -> PluginDependencyMap` +1. Bump bitrise-tools and default plugins versions in `./bitrise/setup.go` +1. PR & merge these changes to the `master` branch +1. Bump `RELEASE_VERSION` in bitrise.yml +1. Update the version test at: `./_tests/integration/version_test.go` +1. Commit (do not push) these changes to the `master` branch +1. Run `bitrise-run create-release` +1. Fill the current version's `Release Notes` section in `CHANGELOG.md` +1. Update version integration test in `./_tests/integration/version_test.go` +1. Push the changes to the master +1. Open the project's bitrise app on bitrise.io, find the triggered `create-release` workflow run's build +1. Download and test the generated bitrise binaries (`version --full` and plugins) +1. Create the new version's release on [github](https://github.com/bitrise-io/bitrise/releases/new): + - Fill Tag and Version inputs + - Copy paste the Changelog's `Release Notes` and `Install or upgrade` sections to the release description on github + - Attach the generated (on bitrise.io) linux and darwin binaries to the release + - Push the `Publish release` button on github diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/README.md b/vendor/github.com/bitrise-io/bitrise/_docs/README.md new file mode 100644 index 00000000..949d6eec --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_docs/README.md @@ -0,0 +1,15 @@ +--- +title: Bitrise Command Line Interface +--- + +# Welcome to the Bitrise (offline) CLI Documentation section + +## Documentation overview + +* We recommend starting with our [How to Guide](cli-how-to-guide.md), we know it's a bit long but it's worth the time. +* If you are in a hurry and would just like to get a quick overview check out the [Introduction](cli-introduction.md) that was created from the longer version. +* We also put together a special little documentation for those [React Native](http://facebook.github.io/react-native/) fans to make it a bit more easier to get the Workflow for your project up and running on your own machine. You can check it out [here](cli-react-native.md). +* And of course we added a [Step Share Guide](cli-share-guide.md) so you can share your first Steps with us and the whole world! We are looking forward to your awesome StepLib Pull Requests! + * Before you would share your Step make sure you read through the [Step Development Guideline](step-development-guideline.md)! + +Happy Building! diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/bitrise-yml-format-spec.md b/vendor/github.com/bitrise-io/bitrise/_docs/bitrise-yml-format-spec.md new file mode 100644 index 00000000..31cf0c40 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_docs/bitrise-yml-format-spec.md @@ -0,0 +1,152 @@ +# bitrise.yml format specification / reference + +## Minimal bitrise.yml + +The bare minimum `bitrise.yml` is: + +``` +format_version: 2 +``` + +Minimum `bitrise.yml` for a single (no-op) workflow: + +``` +format_version: 2 +workflows: + test: +``` + +## Top level bitrise.yml properties + +- `format_version` : this property declares the minimum Bitrise CLI format version. + You can get your Bitrise CLI's supported highest format version with: `bitrise version --full`. + If you set the `format_version` to `2` that means that Bitrise CLI versions which + don't support the format version `2` or higher won't be able to run the configuration. + This is important if you use features which are not available in older Bitrise CLI versions. +- `default_step_lib_source` : specifies the source to use when no other source is defined for a step. +- `project_type` : defines your source project's type. +- `title`, `summary` and `description` : metadata, for comments, tools and GUI. + _Note: these meta properties can be used for permanent comments. Standard YML comments + are not preserved when the YML is normalized, converted to JSON or otherwise + generated or transformed. These meta properties are._ +- `app` : global, "app" specific configurations. +- `trigger_map` : Trigger Map definitions. +- `workflows` : workflow definitions. + +## App properties + +- `envs` : configuration global environment variables list +- `title`, `summary` and `description` : metadata, for comments, tools and GUI. + _Note: these meta properties can be used for permanent comments. Standard YML comments + are not preserved when the YML is normalized, converted to JSON or otherwise + generated or transformed. These meta properties are._ + +## Trigger Map + +Trigger Map is a list of Trigger Map Items. The elements of the list are processed ordered. If one item matches to the current git event, the item defined workflow will be run. + +## Trigger Map Item + +Trigger Map Item defines what kind of git event should trigger which workflow. + +The Trigger Map Item layout: + +``` +git_event_property: pattern +workflow: workflow_id +``` + +Available trigger events ( with properties ): + +- Code Push (`push_branch`) +- Pull Request (`pull_request_source_branch`, `pull_request_target_branch`) +- Creating Tag (`tag`) + +## Workflow properties + +- `title`, `summary` and `description` : metadata, for comments, tools and GUI. + _Note: these meta properties can be used for permanent comments. Standard YML comments + are not preserved when the YML is normalized, converted to JSON or otherwise + generated or transformed. These meta properties are._ +- `before_run` : list of workflows to execute before this workflow +- `after_run` : list of workflows to execute after this workflow +- `envs` : workflow defined environment variables list +- `steps` : workflow defined step list + +## Step properties + +- `title`, `summary` and `description` : metadata, for comments, tools and GUI. + _Note: these meta properties can be used for permanent comments. Standard YML comments + are not preserved when the YML is normalized, converted to JSON or otherwise + generated or transformed. These meta properties are._ +- `website` : official website of the step / service. +- `source_code_url` : url where the step's source code can be viewed. +- `support_url` : url to the step's support / issue tracker. +- `published_at` : _auto-generated at share_ - step version's StepLib publish date +- `source` : _auto-generated at share_ git clone information. +- `asset_urls` : _auto-generated at share_ step assets (StepLib specific), like icon image. +- `host_os_tags` : supported operating systems. _Currently unused, reserved for future use._ +- `project_type_tags` : project type tags if the step is project type specific. + Example: `ios` or `android`. Completely optional, and only used for search + and filtering in step lists. +- `type_tags` : generic type tags related to the step. + Example: `utility`, `test` or `notification`. + Similar to `project_type_tags`, this property is completely optional, and only used for search + and filtering in step lists. +- `dependencies` : __DEPRECATED__ step dependency declarations. +- `deps` : the new, recommended step dependency declarations property. +- `toolkit` : step toolkit declaration, if the step is meant to utilize + a Bitrise CLI provided toolkit (e.g. `Go`). If not defined the `Bash` + toolkit is used by default. +- `is_requires_admin_user` : indication whether the step (might) + require administrator rights for proper execution. + _Currently unused, reserved for future use or will be deprecated (undecided)._ +- `is_always_run` : if `true` the step will be executed even if a previous step failed during the build. + Default is `false`. +- `is_skippable` : if `true`, even if the step fails that won't mark the build as failed, + the error will be ignored. Default is `false`. +- `run_if` : a template based expression to declare when the step should run. + If the expression evaluates to `true` the step will run, otherwise it will not. + The default is a constant `true`. + - `run_if: false` disables the step, and the step will always be skipped. + - `run_if: .IsCI` will only run the step if the CLI runs in `CI` mode. + - `run_if: '{{enveq "TEST_KEY" "test value"}}'` will skip the step unless + the `TEST_KEY` environment variable is defined, and its value is `test value`. +- `inputs` : inputs (Environments) of the step. Syntax described in the **Environment properties** section. +- `outputs` : outputs (Environments) of the step. Syntax described in the **Environment properties** section. + +## Environment properties + +Environment items (including App Env Vars, Workflow env vars, step inputs, step outputs, ...) +consist of two main parts, a **KEY: value** and an **opts** part. + +When specifying an env item the **KEY: value** part is **required**, **opts** is *optional*. + +An example environment item, specifying a key-value and some opts: + +``` +- MY_KEY_FOR_THE_ENV: my value for the env + opts: + title: An example env var item + is_dont_change_value: false + category: example +``` + +Or in case when you only want to specify the key and the value: + +``` +- MY_KEY_FOR_THE_ENV: my value for the env +``` + +- `title`, `summary` and `description` : metadata, for comments, tools and GUI. + _Note: these meta properties can be used for permanent comments. Standard YML comments + are not preserved when the YML is normalized, converted to JSON or otherwise + generated or transformed. These meta properties are._ +- `is_expand` : if `true` the shell environment variables, in the Environment value, are expanded/resolved. +- `skip_if_empty` : if `true` and if the Environment's value is empty, these Environment will not be used. +- `category` : used to categorise the Environment variable. +- `value_options` : list of the available values. +- `is_required` : used when the Environment is used as a Step input Environment. If `true` the step requires to define not empty value for this Environment. +- `is_dont_change_value` : means, that this value should not be changed / should be hidden on UIs. Mainly used for debug inputs and for "connection" inputs (set to outputs of other steps, to connect this step with another one). +- `is_template` : if `true` the Environment's value will be evaulated as a go template and the evaulated value will be used. + diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/cli-how-to-guide.md b/vendor/github.com/bitrise-io/bitrise/_docs/cli-how-to-guide.md new file mode 100644 index 00000000..3c575efc --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_docs/cli-how-to-guide.md @@ -0,0 +1,141 @@ +--- +title: Bitrise Command Line Interface How to Guide +--- + +# Installing Bitrise Command Line Interface + +Yes!!! Our Command Line Interface is now available via [Homebrew](https://github.com/Homebrew/homebrew/tree/master/share/doc/homebrew#readme)! First call `brew update` and when it's done you simply have to call the `brew install bitrise` command in your terminal. And BOOM! you can start using it right away! + +If you choose to go the old fashioned way just check the [releases site](https://github.com/bitrise-io/bitrise/releases) for instructions. + +## Setting up Bitrise + +When you are done with the installation you have to run the `bitrise setup` command in the terminal and you are ready to use bitrise-cli! This command checks and installs every needed dependency to run your awesome Workflows. + +## Your first project + +Now everything is ready for you to take it for a spin! You can see the available commands if you simply type `bitrise help` in the terminal. + +Let's start with `bitrise init` + +The command first prints our awesome looking logo - don't feel ashamed to stop and stare for a few seconds, we also stared in awe for quite some time when we first saw it - and now let's get down to business! As first step enter the project title, next is the primary development branch. + +![Success](images/success.gif "Success") + +Great success! Now all that's left is give it a test run! The previous command created a bitrise.yml file in the current directory with a simple workflow. Just type `bitrise run primary` and watch CLI do the magic. +The file contains a simple workflow that only has the Step called [script](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/script) from our [StepLib](https://github.com/bitrise-io/steps-script) (this Step can be used to run a simple script). In this case it writes `Welcome to Bitrise!` to the terminal. + +## Creating your own Workflow + +Feel free to check the example Workflows that we created to show you a small proportion of the endless possibilities in Bitrise CLI. You can view them in the [Bitrise Repository](https://github.com/bitrise-io/bitrise). + +Let's try and create one of the tutorial Workflows together, shall we? + +We'll start off with the `bitrise.yml` that the init command created. Open it and take a look. + +## The bitrise.yml + +As you can see `bitrise.yml` contains a header that consists of two information, the `format version` that indicates the version and the `default_step_lib_source` that shows which Step Library is used. The default is [https://github.com/bitrise-io/bitrise-steplib](https://github.com/bitrise-io/bitrise-steplib) feel free to look around and experiment with the Steps. + +There are two main parts of the `bitrise.yml` the `app` that is filled from the information that you provided during the `init` command and the `workflows`. + +You guessed it right, the `workflows` contains your Workflows for the given Application. Currently there's one Workflow with the `primary` id. You can use this or you can create another using the ancient art of copy-paste, but don't forget to rename the pasted one to something else eg.: `myflippinawesomewf`. + +As you can see there are plenty of extra fields like `summary`, `title`, `before_run` etc. For now we don't need these, feel free to delete them or just leave them empty. Your `myflippinawesomewf` should look something like this: + + myflippinawesomewf: + steps: + - script: + title: Hello Bitrise! + inputs: + - content: |- + #!/bin/bash + echo "Welcome to Bitrise!" + +You can try running it again with `bitrise run myflippinawesomewf`. Let's carry on and add the [Timestamp Step](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/timestamp/0.9.0) that is used in the `steps-and-workflows` tutorial. +Let's stick to a minimalist approach when adding the [Timestamp Step](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/timestamp/0.9.0) and only add `timestamp` as a new step in your Workflow. This will tell bitrise to search the default StepLib for a Step called `timestamp`, download the bash script of the Step and run it. You should always pay attention to the indentation! It is crucial to keep your .yml well formatted to get the correct functionality. +Now the Workflow should look like this: + + myflippinawesomewf: + steps: + - script: + title: Hello Bitrise! + inputs: + - content: |- + #!/bin/bash + echo "Welcome to Bitrise!" + - timestamp: + +Great job! Now let's see what happens when we run the workflow! We can see the `Generate time` step title and that it finished with Success. But what time is it? How could we see that? Let's do something about this and take a look at the Timestamp Step's yml. + +> When you are adding a new Step to your Workflow always check the .yml first to see the inputs that it requires and the output it generates. + +If you take a look at the yml you can see that it creates two outputs with the given timestamp: + + outputs: + - UNIX_TIMESTAMP: + opts: + title: unix style + - ISO_DATETIME: + opts: + title: iso 8601 (RFC3339Nano) + +The outputs section tells us that we can access the generated timestamp using the ISO_DATETIME or the UNIX_TIMESTAMP environment variables. So let's give it a try and print these values simply by adding another script and echo-ing both of the variables. The Workflow should look similar to this: + + myflippinawesomewf: + steps: + - script: + title: Hello Bitrise! + inputs: + - content: |- + #!/bin/bash + echo "Welcome to Bitrise!" + - timestamp: + - script: + title: "print time" + inputs: + - content: | + #/bin/bash + set -e + echo "ISO_DATETIME: ${ISO_DATETIME}" + echo "UNIX_TIMESTAMP: ${UNIX_TIMESTAMP}" + +Now even the geeky ones will know the time thanks to your UNIX_TIMESTAMP output! Great job! Your first Workflow is done! + +But before you close this tab let's put some twist in it, shall we? + +As the saying goes: "It's good to know the time, but flooding a conversation with a teammate on [Slack](https://slack.com/) with the current time is the best!" + +So our next step will be adding a [slack](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/slack/2.1.0) Step to notify the chosen teammate about the current time! **Keeping your private informations private is very important for us so we'll use the `.bitrise.secrets.yml` file to store the informations needed to send a message via [Slack](https://slack.com/). Make sure to include the `.bitrise.secrets.yml` file in your `.gitignore` file!** Let's open the file (or create it if there's no `.bitrise.secrets.yml`). The following inputs are needed for running the [slack](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/slack/2.1.0) Step: + +- webhook_url: +- channel: +- from_username: +- from_username_on_error: + +Now that you added the required inputs add the slack Step to the Workflow. When you're done the Workflow should look something like this: + + myflippinawesomewf: + steps: + - script: + title: Hello Bitrise! + inputs: + - content: |- + #!/bin/bash + echo "Welcome to Bitrise!" + - timestamp: + - script: + title: "print time" + inputs: + - content: | + #/bin/bash + set -e + echo "ISO_DATETIME: ${ISO_DATETIME}" + echo "UNIX_TIMESTAMP: ${UNIX_TIMESTAMP}" + - slack: + inputs: + - message: "ISO_DATETIME: ${ISO_DATETIME}" + - message_on_error: "Whoooooops, There's no time!!!" + +You can notice that the [slack](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/slack/2.1.0) Step contains two message texts. One to send when everything went well and we can send the time and another to send when there was a problem getting the time. +Well that's it! Run the workflow and wait for that awesome Slack message! Happy building! diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/cli-introduction.md b/vendor/github.com/bitrise-io/bitrise/_docs/cli-introduction.md new file mode 100644 index 00000000..2e884401 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_docs/cli-introduction.md @@ -0,0 +1,22 @@ +--- +title: Bitrise Command Line Interface introduction +--- + +# Installing Bitrise Command Line Interface + +For a more detailed overview see the [CLI how to guide](cli-how-to-guide.md) +Let's cut to the chase! Our Command Line Interface is now available via [Homebrew](https://github.com/Homebrew/homebrew/tree/master/share/doc/homebrew#readme) so first call that good old `brew update` just to be sure and when it's done simply call the `brew install bitrise` command in your terminal. And BOOM! you can start using it right away! + +If you choose to go the old fashioned way just check the [releases site](https://github.com/bitrise-io/bitrise/releases) for instructions. + +## Setting up Bitrise + +The installation is done so let's run the `bitrise setup` command to finish up and install the missing dependencies! + +## Create your first project + +Let's run the `bitrise init` command in the terminal. Your first Workflow is ready to be run! + +Make sure you are in the current project's directory and run the `bitrise run` command. It will show you the workflows listed in the bitrise.yml. Now you simply have to choose one (after the init there's only one called `primary`) from the list and call `bitrise run ` and watch CLI execute your Workflow Step-by-Step! You can add Steps to the Workflow from our [StepLib](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps) or even from your own StepLib fork if you have one. + +Happy Building! diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/cli-react-native.md b/vendor/github.com/bitrise-io/bitrise/_docs/cli-react-native.md new file mode 100644 index 00000000..4f341da5 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_docs/cli-react-native.md @@ -0,0 +1,27 @@ +--- +title: Running React Native projects with Bitrise CommandLine Interface +--- + +# Running React Native projects with Bitrise CommandLine Interface + +Check out our [sample workflow](../_examples/tutorials/react-native/bitrise.yml) that uses React Native. Some of the used variables were added to the `.bitrise.secrets.yml` before starting to run the workflow add the variables specific to your application. The list of variables: + +- REPO_URL: +- webhook_url: +- channel: +- from_username: +- message: +- from_username_on_error: +- message_on_error: + +Now that you configured your .yml let's see what is in the bitrise.yml file. + +We presume you are familiar with the bitrise.yml structure from the introduction so jump to the `run-react-native` workflow. + +There are four steps: +- The first script is a simple git clone or pull if the source code has already been downloaded. There's no need to make your funky music a bit laggy or just simply take the precious time from seeing a successful build log with deleting and cloning it again. +- Next we have another script that configures React Native. You can also check our [guide on our DevCenter](http://devcenter.bitrise.io/tutorials/building-react-native-projects-on-bitrise.html), this script was created from that guide. After this step you can run every Xcode related step. +- Now we have the project and installed React Native, all we have to do is run the [new-xcode-test](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/new-xcode-test/0.9.1). +- Well to be honest that's all. The final step is a Slack message. It sends a message to a given channel. The message depends on the success of the build. + +So as a quick overview let's go through it again. We have a clone, a React Native setup, an Xcode Test and a notification step. And we're done, it's that simple! Feel free to try it with your own project and if you get stuck contact us! diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/cli-share-guide.md b/vendor/github.com/bitrise-io/bitrise/_docs/cli-share-guide.md new file mode 100644 index 00000000..4270ca5a --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_docs/cli-share-guide.md @@ -0,0 +1,18 @@ +--- +title: Bitrise CommandLine Interface Sharing +--- + +# Share your step with Bitrise CommandLine Interface + +Now that you got CLI up and running and already created a step of your own there's not much left just publishing this awesome step and to let the world see how great job you've done! + +[Stepman](https://github.com/bitrise-io/stepman) can be used to share your steps. To be honest it's as simple as it gets. First you can check the help by simply running the `stepman share` command in the terminal. You'll see a colorful text and a step-by-step guide on how to share your Step. + +## The process + +Just a few words about sharing: +- Fork the StepLib repo you'd like to publish in +- Call `stepman share start` the param is your fork's Git URL +- Add your Step with `stepman share create`. Don't forget the Step version tag, Step Git URL and Step ID params! +- Call `stepman share finish` to upload the new Step yml to your StepLib fork +- Create a Pull Request and enjoy hundreds of thankful emails from developers around the world for making their life easier! diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/images/success.gif b/vendor/github.com/bitrise-io/bitrise/_docs/images/success.gif new file mode 100644 index 0000000000000000000000000000000000000000..a5862366d692640dbc57c7d286fcf1750349ceec GIT binary patch literal 960828 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%0DGM6-yPCzgt65L;F$ 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 z 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 yazry2Tc1a6#&XU}^c>jUrnh_i2nJGMTS>K9V$ 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∓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Ⓢ&^9RcM9B1L6Fj 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

fOJh%aAXUgWDAf9Cg1@(c- z9i%fHijBA`BE4F0`OVId>k~dW`*V4LNbRod3YQ=dvXWj79(bIdyS;JzyF{Ua+I5WL zAyuIYUK{jVk=m^rR6^Q+p}P;arh+N)j-letJP%+Y_Ke_-lNSK;xo#s{=Y_ZW?X2>m z42?S>3o5xx;`FA4bI!#W>;r`2cOwZ$4Cw0ihs2lh(;ChUQz=+(c$^T=DhBFU!_`cI8NMx>wlXH{vr z79Q%EXzaWt)hgjR~F(`p6%Qb^4y|5@^*Mze?qrh>cB2c zaD$lnYFrf1v2tnc3cl?(Tv3rnyIO>g^9SQBDCGL8cBs|zXl4;h6zB2&xeNkl`%V(^ zJnl!FqaWq2zdzB4X3`2aDGiFQ)V;r@)l@c@CN&|ueP%vOZXxZvn&JiWK=$KFZ~uZ^ z_0GKd%f#ngagS`Zl`Af+Ke2H0?T!yu3@JA_8LgHTPhO;ZzP$##<^7OJa8HFSz6_3T zlCJge4WdKo>1*eWh6Hh8yrtMceWQVt9#3AS#fejtxL`f;&VHiD-Nl%i_SH8J)+P6a zOhuK1vvAEVKUAcGe(k{_1rK{pzJJF#2;KaX1HuE8zOVsI(EL8045Gx18l`}7sHTPj zaAc~3;tYEz6;hAfa*f;#`W%$HAe7ibe2kDKGGxsK;=!V*hG^rlS6LCw9Qc}%gXL84 zvy_&)%Ws9$xADQ|JH3nvB z_5_#lVI@}Fcx~G6oXX0?T{ZX>%_F1?>IKeKu)iwODYwCK_qyibLk5BoRZEQ#G_$cc zkGTX5>W+QXD8+`8ln7Fel2DvQBln17Nx8DJvErUAPP)ace@o1a^v$`idJ%Hx;o7u< z53h0qJ0s4U-K@^QS@O=j1Nkz>NljY2; z9jCZ5{8sguU3BCh0>5oV+K9OQXU4$dWb6;*1=j)S?Ds-Y0v4pY8l-5It&*R*3bH0M@!dcNLT5p= zouU_Ohn076el^C1m8|dj?Js%Wpq_lcrF4>Xb2I1SQf}FPMTW$=K2Wu}iz{<$t$4qaqUs(= zCI&x$jP3-gzN^c==e(f;8@Yar-yw5t8i1D!neiclbo;ZPoa%g_6x3JGQlRw740I1@ z)D!RB>uoYv%%!1Bd~(!emd-Flz=H3UaNHyPTNeiX4|cEzYaY1jH4a+6Ka@y&c#zm2 zDTi2yt6e0H%{iI1(8f0`J7DIkRQulFImh2`6z{F|BYshgr6~#}J_cEc|Bk}gp-xf% z3)8ZiT^mp7mGaIJ*TUt|foE%hK1W;=EQueO=XPtdEfEc3YL+mIu+7QZp0;fDJ9P}z z`ns}Cf1>q(cgDW^jhEMC9A?S~JufkUumrlZ83?XzsLCeHwkUR8Sct*lpfCP2NH~gx z5w66$X(Cy!R~#Ugx%O7faZ$Ot`3S2G4Je*tGqhNIum`ll^DP+NefI$j+CKDqXam#rFA?+~uJ zy!L&qDKbPer$%m-$WPE_Q+m=NnIMpHBQ+mepgv1)lVpA@1qZuUov1)CLu*5l1k;t> zA2p>^^Y%d6U26yoW$5H}j3gxAin2iT!HtOLdd>C)YD{2zPAFrz*83AG2}h-j_wV$= ztl53HYZF2BXEk0QgEm*io#uyYJ|v9I{!C3c9r)K%&ESQ+a{1>UyO+Mp5`wH3RHOBgz83Y8|s0P`s z{?4)NydhrPloP3icU7dbhRcVaF-md6U8`(Sy4@+N zIo|hPb;J?PH+I4t?1LwYbtvEL21p%So7cNrdfpB`L^hNlivn?k4#)k1q2cU5JSLkOoSt;>n%?@%)~jC3YKdbwh!1TU*emnxt3bYsJ& z4_$hfXPLsi-{v8kAx`Q}ute{8TIcBy(2tw*@`}##6CZkORdFPglnp|(J5=If3ianB z8dg-ta-0Vu7~<1(sMX5c#F6X}&ENEFF2Zw@y|*c@g+}y=l}U&WZ03^zU_R~7?t(z` zFOt`c2Ew3(!*(1STgOYPy-Tp}^ zPdXbQR#^gS=g8%7r8&eU>{WF%`BMRoG(tpg(xrRHyoJu%&CbLGG-;iFYMX|aREHeN zQF0RM_0@xXHI#D0uRf=JiD>ICLxYt_)SwagVRWp7o6Yl)W%U%S+#Y5S|Et>5K8Bp z6n(OfFHz6Iax;o0f5%E0B)#p_GGTn9y5=ib0`W`^#-1TafGC>l!M0Uf;VC-UWzd6J;4mC%CM(k9rewA&tI+k?J=U5WgjR z$Qb^wX1&k2^R~&*_x-!qWxMCmhFK@yQRN_{HOZ*-6}X0|PQFqAdPIYunCmwWiyz@U zsQ+M94Q6`LzjMH}iiXQ+U|Yc#+v)4C-#OUMMMl{pNGK|Hl>g=t6B_7>JADfm9+yqM z78|GeoZc+ylg|sII9)7GrN>7TeQ4eDD%28S8r5)%o}Wg)dhVLyoU=qdHwAwbof>Y$ z+hL9DP){2RFHgBo-y(}kkuKNEa+kEu)w@0Us4_rDT-GPu#4UmN((N46K_VeT_2Fv@ zZaUf2)>OX9=R`qA+tTulQmSYeEjYT(^dSun$%k8x`Iw7n$SIt~X(@dndOQ{W#RPdf zCaW@NEhP|8#o(Kyd7M#h%+80#oYRSE9IUzdDK(pH>!J3wNL5BRT=oLgA?8b3<6_$G z1~sin69&;TC>GVs*bVIivFMfw;}(EiXy`Fb(L5yk!}{i^RdpuAT>ug4&TIxewAlOCUc?lp>@^MJqIs+T-6U)KqKZ^s%<@Nv@J$XKBgU*c`3I3RAKCYP0z=FQ5yZE)VX&}BugaTkqoM9z3GwhU)zv1 z1Knw4;V8@7pq^}b4o^i1%=99RY7IR+@pA0#K^v~C)!Yn z7_*h?z|W{JN^9t63qJTIoWZKkmv&VbnLxu{qr)-`U@ah+A*^9FRSo^j-u_8^2@ekK z&W@^^!hpM%Psv88Dw_xr9*q8PqVgy%^FL28r6?R6*Pod4LHr#cNEKv3Tu@xT8uVXe zCJ}}P%pX5OztSsawch$2L+O1^J^8{R;++~^)7jQ>AMy3!&|i^JM0lUL}E= zFD1GroG1GJDg#>s6O0awEzr>w|MiPq8<1$L8`=)HJdog_a}%{2BepD|-i#|<+J`RX zkc9V8y4lq=Tb=lSX$`QjGnPlfuT8A^EqP8P zD)M|0-+<-nk{(>PgJ2d1%kHl&U2_ruIliL;BO^mQ1Feo z)Bof%d?*451gX7w>4^HGP9>-CHR9ORFBN@|*z#0VPkz;Jy3G1MkJW7DQw77A*=h+! zI?uno$>&q9K+Q@ZgEwVnlB8M?7ezc7&Vn~GV8YR<)(M(vY^glVAk@bJ3%*L8T0t4k zqizZ=U2MS|x4wMrbC=+5s$pjr-;2hIrs#f1c(7PizxXX-Q)F|{!}w1?PjR@^?s31W zo!dCv{dka)NDKA;Ap3_3iizymQAoPN8=!p+wyBmEcngLMXpHZHJIq4A*Sj8VHfg9o zsFZG2C9zOc+Zf({2iMp7+B6dchXdrL|4~8BD|jxUO#p0_q*jfZx6apElXAI^Px{^m zUYE}7c^?=XkFOy$?g%`BY>NB@36_m_@!d+{*GXqF%Lkp^4%^TuR89SxIDW7_b=tk# z|5kQ?bs1eu)82D_U=If%Hb9KY?5UQ3s>)a|&n>Gc|5Yhv80)>ML3S4S*InQJT=u&viaW>+C zR?mMQ_II_aSw!9~{d+clvFD{$+V*e49}fB)hb$AB^XG1LbJGLs^)wSYfXb=;N$uGk zF1`(fr8UTDa$ZX91*aHbP=cAFmllv@OLrpuloCC|fLU456ik)+3I_SpWt1&&$K`Ds z3tu^%=6=GqJUcH9$&j8B;O+w;<_ukpUR`nZ&7-h`2VuFCv{L52v(&lG3leAKAYk$? zsC`2IxvPrLX)Wf5f$>~$FZJw0*Rvx30&N+R{?UqJI8-G%@X4IFGT$uIi&#p(c|ZP! zrj!_B{+P_D))Uw5g<@r4b5y2kY-EwB^V7202z76as_{!0=s$ea_)=j>;b(UhD=R)Y(Vos1(pp=tARdrWF3IN#}#A`WGRZ4HW0d z2kA&z-wy|kj#vLM4^;R_Xyl+xxFOX3v;x9~_IF=bzos<`%+azhMdA3J&FPgUc?#!# zj4Wx0Mm$$I{X2|)*+EQwUL4nA1z4h9k7PDF5C>?pZ$AJP88HD|x%=T8^@W8eQa99R zYyj>&RanG!xVWb?C<-74dR4q<$}!zl?@M9h$lEIi{5OA?S4YI{M?~${l&dkBRNM(E zDyl**{|acU3t;&lJ&wYy&_&|%G(y+Dt-HU<-&jRL?c=*9hUx_pm!BYfw->I5CbEeD zr-{WPA3$172{uAz26n`7`}iN0M`=yxNiWiXIx+?rDVg7c)PwwOqu$H2tf02EX6SofbR&Qne z{&H~QBIwZ4z~Dy}@8H3x@c}cxO8u~1HOdX6ES;P6!$ljdKl=ASt+A~k>jbW9`!$C@ z-Yy+v?bpC+;*0)NeS7gHJs+>*uURlxnl6h+=~KKf-US_T-11QK7DGyk2v;Z9B}^ii z1^-O4+o0=ogh;tXRK4*H8OvPQu29zYy3qGxhP$b>e1H6&<$?RIY~G&FhE1KFIvWOG z-WxJsh5Z#jYSSZ}8G+v4%Go;O5ehnUr@*lNw{dX5rL+C~Z|UqAS+ToIBe2^xi|W&!Ig}+*}NkKZR9& zqUIP%hbF&qyLV}qN==D9`sA`gKd45ZfytO;__?6O9M^JXt@XED7@{Af0)>ZhLWy)y z9EYhPVwkX*aNVebrC_C=1-pQ1ZK{;j1UZxH-JRh`POBYR$k(~1Je?L8zhswjo`VSBmj z7*=F!oCzt}0^7@ez99Ha?bH6g7YM4;1rew18=l@4xvGs@meT@Xg%`fEVz=FK`B|)9 zNc$-vroXwtGCe}gZ0l^*&=V)n$zuDzciw(Jq?R89=#wi^@8AFHt%QH*{T0o7myW-# zIsY};F!-L5(cD8(Xu#aAebPJSF1G!4E{pAk*thAEV^t1TG^P&luyq|SM3M*-*0LjH zXkyj8J>iip4ri?HHl{N&X-`yZaB=togM>I2Tq0C1-@9I_Zr%yUy1+_ou78MajZ$g! zk%Y8E8^d{6SF8gm##|Mw_}Na!K2t%x(fBcWlnCxAmE4&-%3~Btrg8>A=s+;BT3uZ; z3G}v>-)uhqQ8tm$Up+=I*xeqbXDIjK(h|$!(%cbEr_uf1`Nu15nYWv@yb|*D|&(Fs4+junt1^W2|koCMY)-|`J&0&kMPgYC-D1d?ES)ypr zVAYt@>M3JUDJ~!bZvLs>Ote_q28(Y)lM-oHQq#=XliKGg*F;I*O0fkvZ=c83dhHU!L)9I3#Y{TG(4oS}dV!_#Z|PI%pBhyNF^DTClb8kNn!@&N zs#x)p2BA~o=-d{jyp1r|c$FJ2*-))O;Zwykz?q(u2}v}yt~NYZ!etgDlg*ekKu}{e z^Cu^Aqw4T(h*}RpFL`1^%;~Wg3 z^}5vMg^FkO8HNxOjK4w5zVl}a>`%da-yQi`1zi?9n6e8SyXmgTg)~#jf4%q(kj3r`7t$~q(FPWJmxzH$g3ya#@E4MsO{h!v_oo;Cyo**+zj`8U?(>8(c?WXwqd{HeHv-Gx7vrV?S9IE0 zJc@75*=rUYux;GnM?d9aowGzAY_5W~qc4IvWgh@&cZZJU)J%@mxpneOwtQbS|8C2^ zj&;rXt?;~gSBtEgOs)oYtyt8yQSL^C>T0jU^XvP%Z5NUt*nzH>)&7IXU5AifLn9SI zu0*-SAgPP5Aw3uHzcr?f-MwXk135vlrPX@p_YDL~t=QyS&8-ap&T$LP&-~td99167 z$mlUn%~J5CKc$P9K}^XGQNco*Qls zHTNVSHn?8!;|H@=Mak%I|6QQwtFv?ERr}??3P%Giu`+#ecBH$nSo@QoU;fw7uY9w{ z^=w_eh_I#tOU#s&pIn{6s}C&-GZ~0v|LYZMFdZT9@xx{5jHfjAdi45b8~d8- z30}kOkCopQBp-2A{*kZ20#^>p1zq`wwwQGjaXm#GgB?ELE3R$Tw8CR!!}pZMY7zk^!i44qF`jpAN(xc!lF#5(^#3>knHu)LRTx$_DKMoynAK@X^Ay=yf5 z?iU}5V62q*KvEcb+uN*zUs$$Wm`yDl8MTtkl8i3X13TuO7; z;H0!0e?DrUB`Qk8)qP(2;s>27bu5@uboqmh6d%oNfT1QYs%V3r39O!BF+8`7yWg3h zI_vwUdfMHDKS6TrN!dEGm@EKTTNl!(ik+dyK|h0T`N;bVYoTl=h$m+wL?q_0L}X|} zdx@Y=LGW8)zJAY>4lxrUc!V4OuB>#pq_hWdAjd@MDby%D-tgNC$p9X52erq>I`b9- zjKU*Nc!Lj6Q3xyL#geeUtelS}5grSb`^wDr=H)um0q1a)9anRET>OGtv054?ms;v~ zeb1vT%8qMci#gapFyI-|_o z;=|oQNp34GCu}T$=hnm-Nx_Udq^p)VA8YW;JN8+IUQ32vN2m3_6uI-pA)*!}@K7sH z?NXDf_!L9GV^FufU%&0S_-nrSd%D$YI{R_yrAt$8vSjFkMe+8P{ck>=UTihI?9E`p z}x77Y7V;Os=f;A?4_3J3vytZ`NDd;S+2LF*#)~g2+@hUl^9=@KY2diNm?5Tk8>XAtS7jes5<%jh1H~as~H~^ z03xyQ$zKv1S(xk94Zlr^pbnZ20|PH1qK+f?kwMWU&Gd%I0YdettJE=2k1y4t{7^jU zuuB47$49kO!{fH;WJf%-**QrjY!D)cl>mDq^@Ngdcovz*3s&E_7Rv=Bu0SHEFNg#e zI)6*tJ_H2V1OCJtT!|t)L=vv~c-E_9J#GW$OJ2!6~T;<`k8Fvc^dnp9(vp$Vb(z{x=i07pw}*$VJz~U}6}K@D|XT zIO0KusU0(w4_joLtNJ_|y@ri`DXHJFJBp%}NCdC3fyw=WM!cqVI5)#y%3vPfhjKaV zr*u>@TKZGKj3ppYvx7!cqN+OI>s2b&aV`BQ{mR=7l^~l>23-CiTeKyg?Sq&E0dI5U zW!tcxPnoE2LVF*!H(cxe+ z=(Oncy|24cE}3Kh^G4b6;X^NNnGY0SPwf^;rY|H9?wi}wznC?ex^rpJCEWM`pCeF%v?pP}SY9Y~Esvc;tepbTa7H&gcqC#cf zT-Etwy}8=0`yQ&2{iKINID~;HW>>Ch=K65|13ob3urbajv7DLwWPLK1Q+4#kxxBpY zyn*K6pLp?K;OKU-N-j*XRfn09 zQCISL88mlSavitq-|enZFxcnRb8p1x$2bucI!4k{TxE)=EzYN{Nn6%E_8<+>T#I>a z>to8L8Fp)ARo~UgyOu53+@V_Vq?=d+>y;^D2Y>uL`4IEhb=2AbsswG~jXX{E@@Y}{ zp!RmUYq#8yLEQg#tNbhdK*5FpA+maQ<#IPp`0hKzE1SBom)p(}VWDDV*qK=)<4X)K9q~MR>UEpZePF5mM!Y$2n7(1!;I+zqBo~S0rQ$Xgu@<=)=DXrj3G1Kve+L zyL|u4)?`cf`>+h+qW4}kp@gcjPS6nYL+n$K&!eP>sOV(0oe-Sd`RGn)RB(J$_+r&J zvBwCwR>B}&=9fgK5d7!MBiq1tvU$kb8F_v*;V4(><4lerqml$^nR!`T4E2^m^EHR-XMUN0T}XKZ@z*!#5x!B z0X`%^?59KxY$~4ckJ?=lZ3=`x@J60^p``pkO6954tvMHxNUKx$*VTEcW6|NUQ3<*O zJGQSf#Xd=^(d~&{cv51%Oq?-7lCykrO#&>C&9At1gb!nxV;ys!wMHoZXyG*;vF_{1 zC^we#xju31-Kis=WGB2oE%3$l=_pbA_vZVx62eas>`Q$wC1+`sE{Jml;weExv+P4f=5-5i2Y zLM_p0ny-vne0S+Ow!F3h6#m~D%s+4sa(sx0VWjQdw(&00?rCj~~vfXF(Dg?)Spana1<4~Sh4Hdm}mKK#$cdEZ*U zgSkuV-KR=VAMJJ4K4BtrR;m*hD}0!a9%)BHaus4&GQCu#61N)Ra8#>Y%nePb@69#e znuZR~sQ-S{tPhOtduqb`aFngWRTwpu--ihpW*Evn8yaR9@+M7-pF z#ONu&+Fp(+QzR)}t|@(Z&h+80uDNFG2RkeD52E~gBxAmoMRtb;^+?FRPwz8&L=lc+_&v|;c$qI9 zajx9)(!qxX+|UTXcxk%4rr+0%;!f&Nm8opnYIhYg{phKJGd~c=3hy2JJ8{E$^GZVX zB=w(1cvWK8apRlZJ^*~eSkou{U*z)&H@}ad`K=HB-)A~LJ!ZYvc>&kI_4$QJNi?W; zZESJ)iFrk|;+E0R)yLmUq3M78f>+B1<%zT*)aQY>_e5EF5h0~b)dyh|vTbQ;W+dB` z>izMG;kF!yXoMosTg)MUmaZHN(xzU-h$c00M^G8gtU9_pT%NjpX6*`PTXjcPkDmfH zOSuwhOnK2{#&9}nGlz%>R9qQJr<_?3smSC<&5vZW{aZL%ISb1wM*09+zO<{mbLCF9 zMq`IGW-{8C?rdVJP~+u}s&QZFaUe$d>gKttk4EHcZHJM}VR-oK`718ii#K4e+JcE?>DW9~RN@b|0Xx0RK$|>w^o;>F zxnK>5@T#pY$^+=x7U|5l{W;nq+{SrjyZe*8dWEx*MLH*|LW~~m5VQzA&*ro}3{_XIs^nW8^E!)OH=qEH)Uram7=nJv-pF|9kU4ac#lMO3ZrRc0S(O zvP$shk#&*pa z=8h5-yeyxjL9)a`+j1h6uJ1LLuU1~bYRSd6bL~9&&@v{KD7EQgYVt#(n%73O*2^`2 zO*hrjsK=7nLlcg~{&c=j<(W4FR9&gY6p{~_Uch-C_h)GJP!&PE;F9vqMtD>!(JR4M z@veEfr49Ws&TZN*g$xx#Q&DrS&aYifB<~Cw>^XA$Vohm>*s+m%i3&FLlIwG+O7zOH z_{_YIs6MTx)g;~jDCH`7er0X00Lh58eCmL&&UMA;2tsa=guS=!zK2l%bRgWk9ap%o z$${AVXIP{GslV}n{w+l$1dtHIoG|^Rz>7NLl8H_=gJYO|9ck#V+a+z$tw2RH?GbvL z)(!mJQo!5iGWS4I)gJiU#96R2Dr~;+M9tOo-z&x2i0P4(ylAV_497js)s%MvzS>_7 z&79pFP--SD+C1L8oHCGEo-?Q>`CRpi=j;pWFYg_p_Ky?zWdQFcdsRhdgfq%EgUTed z5Y69s=c5)1q{jI%n!34$$w3Zec6;`R&0%DVBS=L?UEIvw0egD0QE%5M9z%=pDEa7E zJrLe3nU@JRWRx6`WWaH+d^H$sapU#>X~pUz;t+_!R1`ByX^rO@92m#oRpXJ%l8cY z#)l+y*w+)DA&}79?AyK@^IpEM z)l?cO(F{p#2X)IjWBWVm;lO`I_qXyC88BDc;3zjUPFQ@R%~ei$LC!Pdu6^JXJ4^W= zoYL%%r7qh$5pRT_6Lt7Bs}{POKJh7Zb@wIF{ukq#c7YjQk1V3-kfi5F$`V%CA{=QH z7{bbf-X}uqFF^LB=}CDn+$}T93OZInV4zPOA8uX1C7!`Q@gcZKS8?Fp=>4NtP+HTG z)gRzR_UcTCug;x?AYv_-y31Yw=dlf8RrK|v?Q6w4DpDajoBBUp#j5U+#c({Z_)W&i z>vf!P<8tXCJ9(Zyy>rFjTyCJIy)^W7`^5exG$8x}^5)yNCNw)4#4B}ZX&#xj`Ta#| zr^ljgagBA~VNJ#6%5l!3H_A5a#n-cp!*-~jOvcahAP{s&$^D)Obx_AGsrLa<6CDB8 zxCrvp1lUJAIx1xGUwLyT!|MJOut@wtel|=B2RRaFobOXy9oSXJ77=r`3=nR25;a*`zs9j1KClz@b5cKjE&^*tRE3q<>pD^6XaaXEuSOK(J%H20$&~&Q3qq6G9LV& zqI3Ue!hQevPMDd~40DK$Im{{MEQguXoX}D#xrt=VAkco$PZ%kryKrP5}LE zJ))#>nX)T>agOGOc6_-?;3bV)W%#1|!8cFudHQN?Ss2!SQ877+MK|N1A$--U|J`M1 zapKsri{#?P`>^BhqUs~Fa38`DX{X4%mKBZ{d|mmiFH8oh|F2rRA}~(Q|HT>D#o_jQ z*N!!)Zdqn{H?mIE2R-&54Of!yYi=X^7Kl?}E_nPF4cJcu292DwWz)*Cn^YCEu(p?( zESa;h)nS~=%EQRdnv{Sr*Ql?e(6LIb*u0-7AxDRh;pA0X)g3lSX z#B+M%V%7nF=vi(Jmm4gunuor~J6j=yxtr2Tf468Bu<%^?9fQB>xj^kPt9z~O+@m$Z z33Y<01($`5)>?;Df|B-xLuY`&Yh0mw>47-*TvIlckfXe2=!MH(b*%^?!eb@so_Z^? z8EF8L(OX=)y*D`#1+P7obrJwl(!54h#xo6kWCbn#0tVA2@iJsCCbPj_MD`ur7Q{k=@ z9F<{!29zS8bH5%bO*AP{x^m}P7ic06GoD|hWh_}d4^=MkpHI`D%F|8Yy;fj}62T8- z+^1;c&HiRc(ZzqwbDd*wNoev(U&$~dn#DgUY`>I>oM?YaO1IkO$@_>6f_Cj`i}o<6 z<1FoNXv-`L9`uuJv)iuoFTKB7ZM0pK3tsPzfX4w+Q9@6+8o|!nU3}dYoN%KjU%TjT zwu}`--n{B7aIV?rG%L5>WgxXgoU*0En5CO; z!II5;{*oJj+pVkK*jj)DDVVxNEMZNe`3%w{NH_T7l_|;jIa8@3LXaPld$kx7>4hqz<&o>jRJCAWqpihJEAvLPX7MEx#ylmnxQ%ZK%ENv( z*T*n$>DI`k3%HvuSQO~GirkUZazNT+3dzaCezzyyM>O}dS>aqt{q+gzcS7LC2g zMk><~%aD~u9b&XVGB%GWFMEw5d-uWZM2yl)S1Zq0bi0fA!9uRo&fVVGR5&BGy^tbb zR+yy1*V)}8C5_Lqy=me4a3_c=O3*4WFS+hr;=jlkq49DNFkApgp@*=@&(}FH$;is? zQZ+`I8j!M^KPh5j2ydIVm{3Y!7?1S{xV9+(h1J*fireJOA&=`F#BxcaS;_Rcj|h<& zvS_2eE%Nyd1700F>8i%Z-CDQ&lZ$01as@*(IA2ASKzqDLvG)6{$8z6lc9Sz&mc~sr z`Q%db+VIkueK7c1`pMm_scOCCVcDzF?dGG3S-|3A=3QL> z?L|4%Uy9N$2}QkQbFgi`$%n3|*4Q!VHF32+3{dw&db68knN5OOe$1WWG)E!E?vd+2 zD9!t2YOrt}2D4n*YbH|O4jLBd5GIo;c1QS*>0!x*Y zbE~#2Mx<3jq3VpZU7@pen$Q6gPQ$dE%$DRG2#|M}tanLYTD|7+p1!+r#`UeVP}B?j z3?xibq^Vi{*S8FI(@WV4!{)AK)H~Du+QWO!4+9K2bNT%wtyUU6n;y6(0KFXLD9Uq2?n& zg->mEhk}fQkW_LvRGw}!c;$ZOcKlM$YAPg3)zX@rPJggz)a0{LFP4Qfudjifm1)UL zkUUbN&yZxEpZB9m=lO%-?2-oMj^17A%wNq_{J1I+1KR`Skv}y5z!Fh3-IyT>+c&li z<@bwB?L#mj{Y4hFUu`**HXw$=JL}%299?}q8364BK)=09)3ki~(2rPaQbEP3#L&*Q z*`1ha)AV({5jA-)`+dr-TX@?v(={htWyaOt8Le|Q?)3Y0$=;tu2QKaNkUGJ#;R1FC z@^^h-L8joyps{hVx)_~jKm9fda&ePj7s3=Hu{*A;A?xGt0$Y6e5Mp2~Y8%gDg z(>TVPm{%x(`ph(K+$VoX&HCs-&u3S#C%~a3in-Oz>N5iWv(2r3CVLovg4X3*jT&E_ zQc5XxTF#kH0k!_q`uH7NDLYISjT*kMoVDwcNqlME804JnCO-N#6Ej^2SA`y@bDt4P z54Nx!esf&9cZ4H3g%NQ(E=(8+dAkty>0aH};}IgPqdRHZ{@K`>|Bs$wpO9V9CyN2D zpk&ZzRR#k(dPJZ8a00<|q@$emy5*LGL*3#xm2aZnx(S8dVappt7nm}54vHU3i<+Xe z2U2r*&3R|cqupM9mB0Lqc1im)TC}VhTGBcXImh%?JYkgeX<(>3fPb#@fB({r7{KSu z2gqVR7xb*?SHmE=Knj`t97W^u2J|QM<+yi+&%KzkbA%%qALssy+M)?;D1OrM>Tpx|bMT>69A zlSb<&TO`Y_*1#})g)gM<(Cu{s)@X!lC(<5zSbw~4#%l%-f~=*kuvHX#$zkeJ<@ry| zW{iuJ=d-$)+O8j7dA4HhCcsUHNDAYDVYtlHtg1>6o8RHdROzc}zoCaviaEd2Qt|C9 z3H-U{jx9zWyJhu8|IBWP~z{o6u#ZK2zDYOwP}J zjb}#_8-QvzfzI7Q|FqLqGu5n`4CW%y3ijWMbdD6juN$37_M zt5BqUC1r#c4ILz97OFC%s^7jeDZ>C%#{DSoCg#o~OcSAWfmB|3a$T7wt{DnKNpYi$ z@ge^2iyq6I$*4p(*R#ki9wNqH-#d>zck4I5i5@Ucaw|V9@LvNzmo8SQW6>^egw8`! zP!!dz&JP{zNmnB`l^7N6q6Ad-JA*-^dA=2i%nYtnBLG*Nu#aKg8)Xtte4(RLwc9`K zA=v)cPs4$1X06$+C+%4`k9FVfP>H{$letm}yBK4xw;H|^r<4=@0YH`IJjkacZ%u1T znuHh*p3`cy^uTx6a3mm4Iz zn^nV?-72;o27~i=y!uvxcS5M|nG9z5lOt51hQHK8O5FvsDMoE#f64<*wr4d5UQMSs zn5?{UJI?c6>)TGWU_g@WzH7(12#sZ|5LFE-oOD|JKb89`$X`JpIZN|tCyGUI1}{xZ z#M=-slU1eb(V39o*B9?)hI&)HcX@=?s4zq2Vl;~{jx66}LUqX(nLmY^tP1+n$Rgsd za?{=&rswu9u|wDL zNmM#&i(;&vRP&M$Zug2hJXOVX)c@vfDgqS@a}?a?acRt&F;+Cu60PeStA45N`JUjR zboZ+kw~aT2@K5li&$q*Eqr0k|+loqL(cnX%#*et#&B`)P#A_QXit1>JZ z)+kajO$+Xn@?ct=K%r*5dmv3AN|XbDu>)%5PH5nY`l`ffw zkmr~AXNmxmp#3bdwf~5z6cAT3N_~Myn`t}f69d##B(Ci2^Z5w)4PVD{TveKWVo6Ml zcr#q$%%WupH2S#%N{eZc#v2)+HOS^IyZ+)DM{A)d5qB?eDEIJG%ns_fk7xJ+>-P_o zMzh29Gb;n%{_|G!Pgz*}UD)z2)VW03WN(WuqPAYsm#>Q*I_@@X{8t0gy6vaT>u6ref*qFUO0*}I>R>w>;o?6n4G2nh z6)ptOg>~jL``#NKi;qS>HCqIb4wrk(R;xJv$wErl^D@SV!q5@wfZ1ymPvZIOHFSoqk;*hz-w$7x5;2zf1 zc1Dfvs3U$*%p|Q8CQg}M65@5zLFYr940XCXT#h~r!(56ajuk2pky_J2R+GEI+43l3 zE}pcr3!t0s)&pFgBwHVaVy^+6N63?o`Bq+TFBlKK+)#^D4-ZGzl|r=(wr#fDxxvFq zCsjf99+h#a^g>2v{%~<W10h{bL1_RZD`}7_XxebN)BUO@4N$W7vR!151Yiu z2O}&+uY;z>LOaKWu7=qd&>wMc4p!d6czd{w7zS}#eAgzDe_PJb^)aj$#v();a@W1BP#-fQ+ z2@|ibX&pMGfUEa{kQB%pd9RBYcDL?b-3woO z1;djx*@F34Z4>&*Xq5YmSeo)e2FM7m%m{lB_4Y4+Bdy_a7yu zI>~{Ay_Tzsx^xC`r+hLl}Uw+qQPg^2DJFy@&jAb3ImaS=Uj2t9#cAU zgA*CNs`FL6l1dUn$#y*5Ib(R8=ds#|_Zt zEzXJ_j`zI(vD|KulMECKv(M_5XLD8l2CTY#)5f?06J_E~5af>gV|?kx9ukFS!@d$QM6VCU$c00ePi)la2D#f0JmJLhqANUlZ74)>qjE{Tf@fiixl9+`+>DnfiG}oD6Te~9- zpPUjFj4)#ojO7+`?xxfcOF<4^OgwP_3C3lcXAYuupxPTi2)Hvu)W&mU-4G0VjdA)C zlPSq5-5fmGkPkV=*@n*^l~8(&a@)?<7zN?T^>^%IVu*cZJOpEr6!?cFy?U9Q@$-e#V>@BeJVSL8*UCgb8GUy21 zLqN6(f)cAmL-;xw_O<9o_xWw%9(P3EvfVLAxNq?o8R+DYN|&ugR!Kjs@2a)Bb|3-w zN}5df6NpjAE{piIL=^RPDV+Uur@y-6k^VzuVMuHYpSO`@(SRlCTB;*ASWa7FsD#4? z1+X1RdhRMkaSZ_f!ZdB79B8q&N?&RC)j5~IsB_-=WZDXZ#;lr_loxk(8u`He`;P&>of=2vzLWYA;sYi1= zUz!El6LrcbenlVbelHnLaA=Qi?sSZ1`cBB*=zzdfkK`x#zZRe9Ui|JH#t5uznbo;D zKm&eO$5cqlyZC5o)rzPL1|{Z*N*OfD$smU!;QfZRGmz zH*+7?z0eTspBj2|kPF<2z2H|KQvrN3Bk|?qYl9p&N<-f@0Qi5_NTGnF~)XvsX`t+&|M5STT;(MGef8wP)QX^{B$C!hgr#wQ(!M`F%^# z5c#>5kz70|>e9I3Yc14Yv$V~0X6?M}*RAt9ozLrT$8N#ow>W93b_U|1cFH)Z(2a&6 zyxe{IcT@#qAb(HiW`j_(bGgizH3yNgS%d*Q6iYJ3k}$&MkIfS8-k23P7P(jqtJ9cA zN#2_Wn-(!b8+I7)TUDsof`=7d*1^) z^nQ(*EcHb>wS~98u(ZLESN=!P%v!%70}fPV-TvR4eLA{^9>;|r2JA!9=#~DwJ|7s% z|Cx1?%&@+%A+KJWe=zrOhgSx@(#a?&r0HJq;vo6+3?xB&uk)frG?z! zL19D~{f2JiyVq;|H&B*_^bzR3S3^9qf;sWYm}@D8f3C_!@MnfnzXdmgix@pzGxO3_ z)755V09M!oAOXPpV4?4hzEFYCZ0O0(oRAq8twtdYU6SueFD#6pP)u+tC0{NJww<|Y zmuxML3$#rhT05{XSR<0Kz39)#uXOMicu4y)cUt_68GvZu*B7H^Pvy6_B@6IEQ1Qxa z1GO0gxSUR>t3$2%wqJ;+Ip=M1SXLwip%`PMyfH4K>Gp71>ZdE~LR|G$zj=_{oLMK2 zR59TS+f!*I>)Kovd*-Evo!@mei8O3qqbKMeKecbRdO*~xM;U(eH)7!7YmH&HB2<3Z z3RYb5N%MklS%- z4}V4KXPb_C!PTf|?$0|2(dCCL1K_atNW0S&bHXt6P;DybfPjP6q;-C(I|KKtz4fL0S-EQGG)ULn2RG3N1Jmd(0^tti9%=^Rx*<5a z5%R_RqZt{94dE1fDi(VpY_3Zbx9wQVeg1rQZtzJL>`P~6bd-XbvMgj@s*~{c`N1g# z%3ybDT4dB>G60b*%>qlyq8|pQpp|6@%ss4ylrpn z({&-AswlnDX9`Ga#PYY67O@%O+m>erONbLbkptNiSlhP)WZ8KiTbBlox2<6}b!MSp zKYK!pGSJ^W@)iQK{9{YpCUWnykI}8tW|;f|vq7ZMz&IkdWm>RnVBVGMQcRWor5Is7 znd_yTR5fVHRm)y(5<&ECVc1NMSsAV8SbEBEFqIlWHL}1EXa?{QIRY69MY$6jwf(@l zS3XLl_OF=(uVxhXaG_uRKvEq$vbeEO453o#n;_|HJqj1XiXH?B{OnX-b=gc8EfZ_9Ct1cSXcClcan}gYp0VoR z(h_sZCHYp)*TQ~ig@J{J*(&^iPhsSd54W#n`vD$_{$-YF{xKiAn-Q1-{nySo)i``` z3Cz7?0sSkX$#j74gO$B4j66O~Opcuh;niE~u?zzNKQh8i;&L15dR)YWK@NKyk`tqK zjQhCe?ZM$uh@*+W`843>TyxG_pJvLb3Yys!Au*9CQLQ8GsJ^|@9Gx}s8Oku_C{zXRLcLUnPF0w%) zjW5vxSbQY9T`^1S>l_?JODe?I=E^0^oQ?Iit)0}?EOtDgwQN{R#Sk&6gUOad$@`nw zm|nj_n&R$Sw>cq&tq@qvgWgu%TK|}_?V6!p#v98dgb6BY? zmVyPAz98XJh}01bkbsAl`qhc%9`B097xoBly+YJSILw{P#B#Dv71My$H?kqGbZ2Mt zrEOoa$0ql)_3|xYWUx*TBp2O$hWiz$p-<0AsO*4VM)B9F422Wg%y-_MlW=cr-Vkm+ zR-#q--eF+2z6mmC?uBx=K6WOA{mGOwfF_Z(vH|*<*4OuK8w@iuJnVwEjv-7)N>x*i z*X&4%=HWWy4d9PcWzs3q6HQtev z;W@g}Q}^CSciMS^Z0NsRWpV#u&hS94Ov;5;Z%f?X+TU@#gSzNv!+ca16Ze@bF5t47 z^c6TrUwQSHWqoMCkBIdw+z+{{TSqFrywXE^ES$CH)-f?MGvB-?2sWLH>Nmidtafmk$Wv=h-7bpGNh%aCEAv4+w)Za zIGA0hteDMN&}IiS>Rmfmw!ijU%9;@{cV@B_*jeT2&+$WQYUw?QhcTT|9lIB|r|QJQ zhmb-D&lfhPQ9oIR!%8E3%6%+ya?8p0i`T+9$LE6l@Ih?b3(nP*x;ILH77u3pBgv}= zGLVJlvQc&ndG3JM(VDUWB=|2gwKt->H6{rU#=OsfV<0!kU_in736h9KW6cyW!{GLO z#mX?yitapY85!6cX+tZ`(KL0`X-CoYyrpb!$4kLO zr0Dt5Ni= z+*0Td6sBD%>F=IX?b`6#_s8z2-}EW9uGlE^8h)D$=hg&a``N>c2jy?+Q|hl?J6cBI zws@&TBa+M6l@ntO?c0OzxPFvPfnG)E?>Ved3^pZ9>d0EVM+T|s{ShYP%D{~EKqa&w zv;Prf$K0zegQ!=?8CiDQXp;zPx}`LY74-Q&zYkt^({^*}RA?~O^VXM_1I6B@C# zMprrD$Ho-&(8ZRa{_j!LA8i~!fN7805XA4pElID%OVhVf`px@5ub-%uLHdUjU#Az2 zhta%G(4}VEuIqY-p8MY+m{N) z$u8&1e;et5&H1pd82x3Q9^n7{4o7kZ(syN>K5S@VY5s{PX-IAhQP_~n!lw*p1khSf zd0h0sCjuqi`?dQq;J>bJ4`y0_N0B9_k=yR}cE<&9jXjt^=2cn@EEVA(4%{0FK;Bf2 zG~RgErJpSVtH6p-@s5QVI9N6D_ z_f_lc5G(!^re=x^tdmo4A1+dz4TW(Imma=vA)X)lTi8_i-8;~yJM7rf@Bev+j*>Z^ zH}>0pnw>*wLhOXW{@JLNw&1LKO<(f6$u_RcKbMl8T-S07v)g{{t8@BmHSmGrobUGM z`J>e7{j$Bfg$Ln0j7=l*x7p|Jewkhx)=*hOS=`^3bn$|r)Nrl6|Ec0z16c}7h}b_@ z55|VB9TcFeUe6D4U`k6X;={SY)$ASkv>Jj}WnnO5c-Zb7p=US?-rf_Jzap-P*LiL1 z!<|nAi8ufjZfkJw4$-fx#l@uSL!;Jj^C2^7JPkyZb?XK%w>7B>nV1@zR4#>d1Rqzs7Vf@q*oU1r>FBRqCKorT{4z#>!~}Pc4_zQ`z}S@tQ)ht z(?(dx%(`W6oF4?>#TzKzad`EY@8(O;za81LD)~XoQ8~Vix1j+mW9`mLd@XpVn9~XQR(z(ovI}RM^m=k04&aH;5eSh-a z&;6CrX|XtgGF0~7xVM1}{)g;u#g}T@9PY5=FMw}Pdt!d&Zm*^TYu92;_I3PrZ=aO4 z-|Y)EmI#6vNEWzM@xK&dw)1gt)>R=lx(X?dhd>}*lp{e9PK~-Y46KjpaGgx2yA3-V zrXGHgN@eXw)=9lEyo3{j#ra~eA2t70$#e44s?tqelyeYhw&8HW>jH#DeVVZ!eOf&PmEL5#T^3~dDWnhkijS^u^`(or4r?E7!-WapjL3NCtj>ZW-W;u@Uh;7|^PLEXwciI6 zAc+8Fwi@{QYn}91BdBnMg@Pjm$3e5DlIE59;6Y%x<}v-}?V_WOxoX80uPwsfij4sj zB_tYO3hq#i9&|LA@L~DTc^EL`z%GaeWCr5FRc{Shgy-5rW}2E*E_#sRJLU9_!HeYp zfQ4>WXTO;k0CpOkp8V2>0N>vdJRkj9vb}}YCFb%niBfYDmeI5A(x>)cVU2X9x6|Jn z?zz`Gf8asHAc{&9roA{@{GO3_MAC1WBSSj3Q*xeHfIb%Jf>( zf2rfYWcJ#=Sd180W5)r#8jLNB`rjeQ;Urw&G@OUSu#kolloYl(K{gn}54l&SYnVua zl$9$&2kFKj8Yo2jeyeg9NT4Ffj)+qUFww^QmZ>flwpeC=5sA>CR2!|VX84kc3=sh} z0{AaJ7(sob*EjH5xu!OMeEqqQm^uT_JyA>*N1e zw_O}m0Ed%{iO8?0@vxuy2%qDNEf#5sXniAG3JVx;Xm};9>B>iWkF`Wmz71SGGbO z_e~V9?culs+ZS@|iq|lgQB`B!I>6LY7hAlfhqQ~PPA%a#CW-iAlI)9Y@}G=36J=|c zFrAa$=pmoX?$^DPEPT{zl=xW8f=2xnBmjsQq!;u??BBdwD*f2#C$7jMufXb@;w*@! z?eW+}a(6jB)x^AvMJLuU>OC>%wt69IK|CU5M{u3wIgRcoMR&bpREO7K!GDRN<8c-q zd@KSM@80$HQ*Kw*@)!&o1m~v5GU=cA!o_AQpyu36nE3{@wQYv$3Lmno`;}bpdtZNq z$bDxT8Y-f`ykDeR-CYwPY8w(Zg6 z!(V*oN(Tshcxx=E*lZY;%`4TLN>PiJU<)j&ucqV;0775w6QCod!DOu%y$MP^BZB)N z9iK!Xr)d-dV)rHB5!1Y46VRH(%ftE|lREo)cWqR=2*?7+RFsIVi@b1K>dp2XCDK>f zAn-?Fj;k<}D1Sz&GVTMKs8?o+tyWYVA)(t4&<@BEMJC=w_9vu?d-OTs#LL^`y@L6+bSPoW7{Zo*ohmq(rGUHIOen7vUmJyDXQVV!2!Z|(vGAEOZ*T>GYvU7wz& zZR?gO3*MrtsBio8MI87(+#7)7B=+51DZHbvl?;7mXK4R=oLl-1kf$mq{YYJ|WUwN? z8s!eioC-@%Zr2g0x=XpmRa|wotn>8 zlXpo|%KiKgC(~+0D*>O#gI0vZLM@3mktfZFM27ymKEUay339?fKp!dA9G;?j*7 zHNOyciPN2jir+elc~PaCvg~oeeYQd7QsHQ%Ub6hdq#A+P5y4&f>xrIrF*c8N5LdR< z-BtPPhjoO2R0|kH^N>3AC->x*63a;eQ8s^EfMn4rSGJ$mHqARWRuiVYRHm{tEmRw# z*k{Z#hq=+aZ&3M>)MwYu%*>G_N_%G*#}z>~n9{q-YB4yk>X-^gDOk@7nQAGAA8FZX zHJd^idPg3OMaf-2k5~xX5`BhAf@C+&^+w=YYjX_79H%uIpz%GA?5wimjT-aM3N5qB zdYfAAJzGEW-i2AB$c_uxvx~#W!JT$)#o2Dz8waMjoGIAB&f+pY$V@I+b&J$?)+WTa z4o_x#;O8x%KL9V`1uD^_Mm0d2N`dSi0S(Mm@6(SGMgfQ&JXe!`^{swV9sJnnc)^vPt57B^b##^T$t zK7cw7dH@UE-7FoJH%qF!=B7y8z~L)nBRuGIWC-YpQP@n$qUJ0h7ym`jf5wD~*ix&F zVQJ5Uj~b#;%UhUAbAg)1H0EFoQ7xkklz}8m>R>Qp+qH-0;x`s!WvH?`EyCwUF%Aya z9DztD<~eJE!#NdK%miBG)}l4^%fRRUxX!66hlHv!q6CsCK@Xlmrn=U%A+^-R+Mqkd z4#T^>ZYm9V(iY1s;bmhBbuY41_Gzs+s{$F@0>4d^W~GK4cbgcL*JpF(LkRwl3chpU zq9z$A9bA|e`OsL^Fen;`UqDRtB$o|?KR1FVBfag<-l(bR{|B4 zFBqtsxG9e126O3Ax)UOBbaU{fVE8dIoI~N`9=(8W*lcQI4wzPNQ^al83PQP_}0hw-o-?~Of= zAl{^i$OMpM{W*rH(Q|??zmEz+Gu241~vX>84XXy|G7!QKTEqDdRYYb42 z;Ih5Q9VW8!et zzlcd+LNs#pZ>koc3w|idi<{kD83%9vrSgc1V!n>xcX2`cHO8D;dsBwa$y|)MBi)if zw(e^)<^Zj2w&P?%TuSeLtWeBO2vey1uJD_gb{H%Talll!i--iJfB25+HP5KPV7BIC zOG%hs%%=*VW#wkx5M4@-dIgM_4cB)Vb4yP!j5*7CZFx`&O2Wta#`eq?8D2@>I+ zw0WERh;K`QgkWvNIdZr#Ba ztgE>(Sf}ev1AmOyzp*5`o9%M`*Hr@=b<-9q>S@8*Jram#8Hi5YLIJfb_z5m&c%_!! zW)^(c%+Z{c_xKTAkaR~AEsswM-cCrgy5jh3+#OSTZifEdrJn3^qt{xqdG>6rGof0c z*;=x>UWnB%LDj8wSmfQXv|(7Yp*xJ~-?DxLj8l;CZD2;_kqg(g*9k3O0mUudJwI7{ zYEl`?I*0AP(0~jzw(b$0N*4594&!oNr_HSXLI)19;xo!|t6gS~| z^XfjY4ztA%HR@V3ck=YY#Xm24Bm3;6EE2dy?0`^cHV~pxb)H`M4tBnEYBOm)15Xdf zo7c$x?SZ?!?3hXfmX0SkYaA=XaZfEIb1Ie$Kj=sr*!y|We7%Ab zUjwIT$M*;BF@v{5>Mgy)hV$7k>Iw6)978=h)nzTOt3mc2@eSzUM}C{nPJP{`!<7+_82& zZc(>-^<7mbUSOljWn64GQ}2IB}3l zDiGFi5_LOVC@{`_*Eq(*DWHg)Jp&Np)mZ$Y7N&sz_v0kGU=`2^Jkce~Xb=bz)AInI zI3nY|E4)IkGV1+npn(y|&ed&QDzKQBmC2Dpf!jW4xZRVWn9|3#zPwFCmLZh!te(5l zH`H2wVf1S_qc_AALkV#crTV&?A8<^KR9+2yB;2klaaDHWXpAN!ZSSkvU&?|hq((G4 z5*Do=eOK^JAo=ztq~mdkwaS^Ef?6P-oS*1Lh@J9=MD`4h5QAZ_La@Q|=u0~~7{v{` zgJlSAwcl|LdO>S@4?8+Ph02+nx$yWNRyY!sG{2WbRvltjB>}3Db=}TkuJwRGb+pZQ z>b@LB^l3~2XZK!;?ra0c+~XP`pKvx$=IimKV67L0Qnh5H%ZgoBtQvQ8^XqDR(JjD3 z$>evgS9~pW%SI4sOX58c0$VhWM({Ao122jd-CoQD51dfcVzHBZ}M#eg*M_v=h3AMSV!=u z+A1kFx;OT?eQYLVBdhCVH9*q?dsIS6gIV59@b5yTPPy4mE!UiSoJBZs@sn~bCm0@L z>B{LQyU2{1i1u8JCPYyG2Ul58?~3sRbd4=HAD_t6_{+}6^PZoNffn@eq6i=YT5Lc1 z0?g~UIKya9<Qy@ z?qU{y%bNNk#h;(`G|CYj@RwCd?fubw?fo=QC0s8kFTYAK8{VzCGQ9ixHXbb&@hlj2 z8u(%nkMfzVBiqRXPj<#Guh-9fLp14+cy#qhq1f*mi_xZn$YC(J74ZB&qW0JUk4dt=Pxp^YsT89?a|bKfwX3TL#_ejK%~7vw)ekB* zU7>Z~ET)$XfzhhSAF|c^m{N1p_yVSL{=wJp@6p0h){+F(%rxD5{FeWV`73eThx)qc zoOK16$Mi>9B?Z5Ijzr#mXOLF~RYpAG&@8KDuroqWF@zG1Gc|2%)Ik;M$~icBD*`sX1?h z_Zdq56I-fNHGYrK7g6-^Td_}zLnVGUe#NsKsnufDC#Ft7Klw_?>FStwgLP`{_oMdp z-7_6HY)Y2i&3K(SoSHxG@j}skJpOO;iI?AwLaXKR2U85y;ON((c?Y90V!zizugVo^ zJ!*Qbb=II{KeYO<_t)#)%xh;eJ#ZSYz4K~x`E`3{BQqFj`esaVc=+k zzz%ZP*cTDdSvqoyrGhiI@<9BElc01gQ*+Wx&~J-;b<> z{f}7T0!EelT<<}Aa4~Ql-@fi3=p7k67LsKWk#mewqP8Vylv@@*ASz`^E%GV9@AV;a z9GE^GKLGr@?lZD4GNOne5x(3e1wVWf^)TW0N3AE{^@NT)7z}N+JeK?4>vP~wiSGE* zaNK9JIopg1upbfw3kaU0*2kC%*^ux}>dk5b+;>?JG(>avr2BmkoA&D(PCa zpv?nj4W=o%XixAe!LcC>Y!qG?V)%jQcsJMtBb34!o5?2{#4Ilt$j(0aY~F9KBLuYe zr85cA+r#bF@iX#F(sAeQ?}Pw{_&R$Hel%Erc4qmFS#qYtG$G>HK!lr9dXf7{PTcu1 zm=qjMfq%F(^2XJ!&PVa~y&DQ^XP6#8?Ibv+VRkX_D!ld0kay&e7#L%}04K+a7{wVR z0Dl!XGQvV^VSJUD>k^1UjFoE=K3FXw!X)c$WJ}5Kmyt2&j%6Ut2mu*z)vZTT51OCZ zePcd6jP5$$vyG69o3y?q9UmAnKoE~5f6UgLuXy??$xW_<1_OW23b4kS?R~xkQSz92 zD$Q96B>oz2U+Vs_d~1R|L6UcL&k*7nHhT@|T}YF*?tVp+I%{yBupzL=zARLG&-FNO zJQ)l{*WP@Xh2aWjjSE%RVpbZ(`?V!5c|X$ry-(vgVZlm{`ID!gOaaVoQ+ZDQ%;Wb{ zka`Mz*X+U(2K%pS4sqzQ1sCv?Hs$SV7`5DGd=1 zg*-k}DZmHq0U;uJPD9vdv%ew@ssWj({iWmePx{3jxiXz*?@G@T%tUt%jkRiMaflBLQjC8x6oZq4r1tCla!#jD*cd|SpX zK9S%Qo%@SVQqkUMc7}XJy_ETZtt})i65R=D*!7Yxm#`lv4-r!pN9#6I4|J|hC9slb zY*BXa!%cNT%jbTFeG*VfhE^328r5+Q{o|iazfLb2q0BDbq7bemBv$Wsd)O}z&qa-)G6!oy^sgeDCFzmO_4LvhRB zOV=d_8w}PiX+>GuAlCtzM*Fme3xuJX(FN4d@N`2KZ83PBnwwxyEr948gZ^!!wlC+O zzhj;yn^)Cu3J*y%>s%8M9L;Gtz!|ofe|tyz1qg?uIC&DyvBQXnT>q;n} z!@S@LMy0iM8%^Hmfd96WpQW#hW!?MsBdP!r~J203>(!=4NL0Um#y%JmoxAkDa zoD}}B%kR;gakf$ahe+elQYB?`cgubZmooXfIH{j#bVBGeE4z$WV#4S`VFh0RfOfw- z-x5)cPfGhGJ1zyaKD*;B7awK|b}g|Mcxs-r3#Tvs<&h$^QH;LJrJCcaFVT`8)x+ae zolbBIl(Tb4{a{7JOh1}T&aMY)IY#xtJi=VkKsOi8gm+T4TA593Oh`P3!RLCv%35a? zc?>leCz&D1Umb8?8XsPxura|<){*1N2$>5)N>hbUpV8>Ik#<2-ptZCOa5&rO@yk zl=7z}ToOSrUc!{uOKx%F_ybOqGi4EeJWOxp3fGLFXl)hdDD*8ozLmGf0Ho@+ZwDOwGmqmm)3mT#nf%3+Av2~q_KHHPH=!GWx~b>c?ZwHEWfwR5 zg6&SW>Akmk-fC!=k3B89Cukp$d$(JH|Nf1KBIvSUg}IX7N$QAiWi-C`%Nb{1B14=# zZYl{>pB>fbRLLc+NR)6v8_wwxKY%5o%>Ehv_s1pdcVsQa;$vJQv2md->c%`|=e)DQ ze{Uij{n+2JmwOS4h#Ja_@(n}^fcxWqH^%UXjS#K@c>CYFQi+nu7tDFk-m|>(zkqVJr_MjGis+ZMQO%DikJ1gcSiC0VmbXA&U>9$hm>+io zb@%hiL|aJW|50>b@l^kR9Kg?VoO2u;`&c=SJv;V{gM(vlA!Hn7mYt#;`{2+qvy#0l zq>}XO*fS0-tE>i6Ar(^R?|=Wj`rLdUpZDkWe!hgjgtZvb;73H5M;0l zGBU2?UAzOBJWBuw{1dGE7Uhngud$T#HJ{l-4?4b2yQA@hwrtICQ%sSlcs zCdlYpG_*$@n%|W}W5qca&li9LIpTF!AH&tfS@LMvc>u_l$Dqsk{QpLzpLFVm zYM`#CS_HDhhrL!T;}KCGQ5@P!9GVqG;sE@#kmdfkk(YKpFlEnn{m&q;rHIo5JE>5~ zBwau5*YzlN0`_}el5UA%ZJKrKYvsdP_R(m}=O_cP??wK6)R!Lyfg=VjJDjgp3^;25 zxtcco4-8l9Y-ID3P5@8&cE}4g2~tBU?V;y(o!z8_7YoqybPAWugA1a0aqwLzj4Ry} z&CI2ZZGd)W12tM~>kp*_x*JILR78)zvCfwP(6br&s=P^2sCl}Lb+nb1mpRoyrs zPtcVaJ}&uo2{=J=N)6LOl4v>P95gwVm7%7GmHd+$@_sI^~43JT1_ z!2wQr1;GVfJg2#F3Lat0caS2+Qwd%J)L+1B8pr+Su8a}#JSN!UoLX8T?$SX<;69_% z%|Q&XcaIt1=a`!w4tOHwO01km@bNG1@eJl6e{^`PH44Y}%fUlI z8~3U9!FZiP9wKP_2XcMI!zexl|Md2%B+5zP49AKJh?HAk=?U7+o7eF)F-JAHx0rDo ztwyRI?zc;`}|H@bg-c+g16G07T|2tO{mYqn&vY^OF4)pH<+zRtBm9 zaOAQf53=r4BkzmFURL4*l>i|Z^IStzBPAXS->PjlA0~}-SUjxNsp??6j$v!z;r%Zu za?n`AD)PXr>~NCVhCcn@FGN=Nd4^9*z&^3AcKzy~|@rsN!^ae7(v$tUL5SL>qjy z)xwu2p`5qoim-7|w*lSv9Rhh)9i=mwKo7s%xJS9%<-ElvX&;6NAb!4_26878#i*0>1wjhO{_ z{8kBM#*IOc>_5?lWB}~mN6heGpbU!k^@I*(xSxsR;_wmUQ^6urFI;c5otA2hm67@s z^bk-ek=dw>h&c7S=)wexTF5mC?g}oh?WiR#qy8!|zYpl+L$*;eQa4W>5t9yiz$3T< z*DdDxQXlH&Ve+1)Ub=2#1?Icl4+ubkZrF1~8wo55v1=oznW$;^AnmFh&YHmM8vrNV zL!bm1_!j^Hz5r|hv08P9Qywcn4?F0`#<~IE+=+S~+KF(=*RRubW=r#f`bQTo|{^i#6%&xgP;qlJhLnA$^F zx=9a{%&w<9E%xg&vx?Q2D)&H%C5jB}`3i9#V1N7!Bte!fF*dEOlbw9rotxbgJ%bMX zz-k@CyljAS2|&Gbb0FNA;o11B7^wR4=@-`_H|m?)z}t!i#?5+b~8Q>uuXZ64b@o62&>KF)%0`Wwy z!t+jSjdS1p1Jk8Jf{z}w91&W~B~_u+C>`zUzVJ6Y4W##a8sj=_kVwUwo%Ny)D}1@9TFMP?Z^L1 z3LM{Q{}!}=H6Jz6&hhb?#&8vw1B1MCX)fibn!#gBhXc+5K$3Z2rAdvd8S70@f@hq( z*pWDBNRC4m#O}P)xGe#4XJ6%3MP(y`cK&d(<-h*&a^-xOt!sHu=AMRDMnS96SGP6JzMC3Wd` z%+6EB54!>Hunjh7nZo%!nfLbmG@6eAnKEn~4f^%6qejC2{>iW^Zu5U$N9-O*T`@{#lK$#xcFYl|Tq@(^vD75dw7jDt(>&DljX=?Ox&7o@5rA0L&B9{o}lCB_wLEduIg%fhkt-Q8uVm>dA~M>-h#W+eztkB#S5tslA#m{6%){BN}pZHJt|u zBGHk5_pQX!5^D}+43M=mIH3^b(wzY-emT|>t_hVHE-`8z9Z5V|`GiS<)NDw83Gd(# zPYEwuo+45$*ldFZPnjnReGp0We&)n#k@^&{D^+(D9)p;)4ZTx;~0rLqGyr{hj+ zi-k;a9C=0TBtK~hX~cAI-ML)1X;z+C)${32M~^O$7S62VT1{=ST??*Ag#Bf`d#~r( zCo%bcogkHBw>Y0hI^Yjm{WGkiqRIp}TNb&*&oWfXQ6hfK60|ov);MUU7c^Ps=496J zR^2p{Jm21nRChQxkfTMS!E<}44T~SEH*Z)?p#@16(xscWYk|7ClAzowf6lF@$D#Q% z9QmsE;#hjQ#{RI1u=fGY&+NP(F#GSyL}p(;T%c*aNMW+1)(FUB9d7pJ_zX0KYir}A z&)%`!%mfJ@#g(B|4{_<}HXv(x)Kre#v{99JWk15qJ#IeYgkUY$7)gd1 zhZB9(bn~@0ss_G|@=cZauoh@L4Fe0>+ElScOoFkCbGa+~kBO=pju#Ww1z9lpMs=>hY3<0ow zNUb54%7({pvsQv(YWHY!H?OEs?M!^I_t3oAeS*8csn=zuxe}!2FyPAp9bDqYVF{%p ztGpC#rGlAJQb)3CLWVY{j^mhTdcK5;IGE{m;BpIVES2vwtgbLqL_)k(ANx9Ziixha z9UbK1NwzpGPeK@N5E`H9tiv6OK=nv$Wv0ca*>oabNckhB3p{l2eFlbh6&#PB4IXB#Xpv8pV5v2tfkcwPkP5?kF~ zT#%&bv7=?k!q9Dy_PcSjM;FX{Rv_FSdAO6VvezSXE+H46<-s;X-`>4AhVjf@c^9|+ zLzyj)Rphhdw3K3TF4cWNXlMemztv7wqtbrua%xetaXQ^94fRQ-?POoAxYVU!rRPJBrc9NT}}nTaMyKvqpPuM`5|gT`dx z(obMJUgBl}p8dT$?5`=OqB}-o$2LI#~E%AY%xu)7h zG9M&rvueNZ9srD!kp|uh*jGl98cZYCFwsF_N7(_vIM!3Yd)b8NVED9A7cow+WWTDa zIYUPpTyNtuA_i?lWBS z!G~4wIYK9E!!$LES`=3`PZ`W{K$m3;GCk*{ExRR~oC;RTy*8}PZ|Wdbc2}8l1h;3_ zfD~G~WU}4+NesYp^Pv)!(hRo=@-xHL2eW#@Bm(0=jK=C6*Y^1u2lIOF(cU~MH5W62 z{4u9odv1)+!_tT;T{^M}mRLO@A3c6eY4LWHz!D%?^2)^;f6)C0{4I>(sfQF6`_cQS zmZ$b_g6cNKP#eRBU3s@IA4_nd7{gksUNS)+b_7>GZdf#R$bL722nEjb zm`c=;O`Dw?GCQzFdr&#nc3yV#iC1<#7wUUg#v8o5;P-kVX7L_4DcdtHDUaubYMy+E zyz47(RT=ZfKj4p>RIIC6FAD^Ymf)Trg}S+GJ>_J}Xybn3{+KkgrrNu9*G=;RVBtqV zJX6N5bObyE(pRsxrivRI_|R!N6(nW&V29Zy2JGkui5qBAD9SC}vjD!YbXlycbC+FhD}1bL40q z-=~7X6%;H<0~Z|#y(%~i6>$E(Xd7r6juzkgWv-KNieD)FtofK9uOpd?rjC4y^I+zF$PU zZ?pN_{A%VBC5rR_Xbdw^v;&>UUtb&(jQ?;48Nmnv9Sr5AS`zT^#y@-F+u* z??;-!wcNJ7banu6#Ho&2l^d9Z37cn$i|^Q_)8bndkbRwp^tx3bYc$iI^RIRa%N%)Y z_00QnE`=vtn#ZhB7+BN_B#I6o;<^5HCqD-%rU_PNFQPUkwApm-7)o-AZRD!bfU7u6 z^bg!?#|IRe&PBo0IPH{oXv2lWjJ*P#_xN05RaK&Wx@Z%wN=Hb^SMyf4p);wh%wFz# z7Hq;^F0ntG4OZbXSR`|lY3-Y(NrUS8BAReb<`OWAlmb91qLYpg_shOGTJQ*_*Y+j- z2};9wo+p`}GD+r5XwgWX({NkjWiaj+koyK5iYn2%BN#qaIU_+4*)a>#I7fEjUQFW} zhJB&tDiyY*ox+EK1s-OMnxuQt9|TZ(*LQkDchb3FKsXZs@^{C4KIml(6t8Qi^T)Hm zcYr(*XvGH|JuN8Ziks}>oYD9U5tCGfsxts_l>3^P`|PtJp~g$P&?s%_=vD?74$M0y z1$PG+wnIPpm$2Ud!^TO9kFF8B>*SLSCtRNthZBAM&uLk6NAM@rnDZ!Uh% zXip&*`};sX(zI|7Xz^@!6NJY<@r)gfcJRir{KbI*ySKK;W8d2z6g#j&N=>5Nq%H$M zKT}h6iAlQlH+h>%jH#5Hy(q?!V*DbS$B5Ou8+4N0HVChq-!#ag z343X|_lMQOSqrlPVVg_pM4Gaqg_aknvv{Fe6-Q{?q_qgnL za@Ut+hcNWy7B_1UhPNF<32~#gh6}g(^Wv&NKWacOarmb*g&u!|J^Y`JcU3uC6vZbS z#V42jx|Vr_Z5~EF;^l66QzFOCG%l<6u#uO`RCUp0(>~dF9W@!Zad%_IR#4sqaqvF{ zy|C(^9c$r~&}!W(et3=LYHQslFF_bQb{NOWG1dVS1bpj#mQwp5Y~f7oqGns2h4y~B z4iaVaHG`dT^Oq-Q2qrHty2J=FraVz+NRl>(JwD+n7m_HKk_n=vcT)suMF(iZAj&-6&qlir%-D?++-%;JpR z{Mv*6U3}iG!dFtocY*muq^=KHdG5uV3gl@3yD1B-K2^8#u7qhnu@_m%~6#$A@;UIj}?EgMXzN^PE@5*>hGuo_u25Oab?=k~wQLdc4 z0S&^RbQ%@5tED}wMg&%YM))!;Yt!4}&OSMSA7I^P+p(qi6sNv(EZJ|TtdXF0)&x~P zyFABIGVev^3z?@6ktmG z#1zh&Dtk6QJ?Nv}P`BxjY;9r*_UK3c&?s;Ics5^*`7*n_?3)?^>z32k%KQ{W44-lb z>Gc*dca$DD=hR+pQAPtV1#;xPaKkFP-ZRIOW`1?A-RG$g&V3O%k5oE?E;GLb(eV@ z^N1CN^It{vr#O343i}hAR@4AVNIgocu7b|>VuNmPF^2ck&W>+?{SRdCe9BhkXcqX3 zOE>#^OH2KUa1sf&_G~`rfrzgXxg%F#HQfbauvuLq(u&??QOdA5BlL3mrG!8|L@*`6 zWjkCEDw_g4NOF!fAwx!6NpS7!9FhJ2wjrIBpC2js6y(1Ep8+KQ;37`|_DZ4GGdEq> zhK9v_IYpMdrD}G*5E>{88M%(Yf*??&Mp!t$JsW1s1vG)bvzr;FcaxQhsnQ&2u|fEo?( zxl3WAO@UFS5(-FvqPr=ZVh6VX61?jno9#s^7^$`#C@ESS zyfdZnq1hI|ad!i`*~5MJ?u+jvKr3Jw_SFprr+g3A%@u1(9Syl%9<b-d8zTT|0Dxf`+Tt3lv7_BW%=&NN&LA_W*81%RRrYuHJhE5vYtI zbg0=TRB9_L#i49(S-Bbq2D{Z=Frj2 zd?kwwshcru{oJD3;Jj7bY8+8=@X@`8#agGxC|dHRzS0j&>-2Y;V1JmiVy)KGqjl1j zzu+Im5b_UnJGdB%LeeAMf&7h|5MkJPPN5ylpf zP1RiyP^!H`Vfc-L@#wWE2y!ZX!h1c_d!nX-GSs($t^IP^P3G9DkFTfAiqp@ie8DST zo!#v;J4YFNAr7vICUZ6IYbeK@M~!B{Mh(_B6`9Lq6uLMQ(7GANe}bL*&xziOIdDqj z{NwqfTWEQ|SwJ=Po9nf_CJ~v~mr;3-zis0VXCo}E%mlwyZd4A}_g!PL7ac2j^w{M3 zzJmc5RXxREr9EupUAkKsh;;fUjlvzyOlB((hYDO?}ZWNesMU-jgXU2~*F-HwCz znoU|D&Yo4{Gp%N=7S-I6-4d&VqS9&NOy?3t^)gj2noP?_&KnNS(*nx7zbvmOb>>Tz z_x7Kw0}f*&;(J^7J{#mx1%Ehv+kYNN|5s!s`c+KRXU5N*4JGXMB=C)29g(ZnHjCxL zr#P!$_jgss#sWnBl_w5=Bu+mtO60h{((&O%-q-%0y@$UOUkq_3RQwf#A}@F=HSw%F zB13>RGn^>Q&*P*Y-@)s5{zU8K_C}@)3fjv|D8t6NE%dpGDip<87wJ_MRU&3U1%66v zCYtH|OGFEa#2FdG{tlcqVgK1rGg07>oH0_=+`nlg@WSP7j_sMBb|R;)lJd_)2kqO@ zWCh`~mWsSM8UjW9F>QsGq8o$6m`XY*zBFrIXDuFamSKW9Zt^OTG38jjRU&B{ofqb9ngF>uy{3=+wE!Tq4z)@cY&=Dz7L>+TTgk$$E>wB2@>L z69yz%tIlsHPm(_R?hE=CO0Bb_ijekWh=K@9cc?HrDwDJMVR2dLlSjp0fh3~?t|U;bQtNlbzpS<`ol3zMhLKN_&a_Dut9-v5BjZ;3f?(nLdubmA~6)BPn(?F z`_(?06XEDp-4TaJ{ZMHptag$ErqWd-EX~a;R}ZS!o-|DCukbg!?%~XB28k|opy_=g z%vkCLKwcW^OVU$j(7XF~!j>^9udE51d(+x3%%~UI1Yp`L!leKWlp8<0$!jxCJZ{6V z%(Kd3LB7kqp(?JrNal{c(#(XDoyEr)GvA1zx*C;bPb~|MrL+fLMcxE*{Z+wA1|+ls zNbuuHJSz2vsXnsuZ-@kEUdW4E7Z1Rfa2W?}92`wQf7YPMxZn2YNa4~OMOweqx3qAx zPPEASiwEQF6GB{0(OYA{$xwC1 z8V!X^KA3%oc8^N|W?>vA(V%9YnGFoD#Y@9tngGX}ddz?uaWb%&*XkXW=s7cw-xkk* zWG_i%QeG0)a0Pex*5GAv`I^OrWcll<3K1Upnk6;75hpVsJM6e&Ri8!;wN^N7rHptC z)yxfrusc7YgWJ~f9srdTe$fyX$DfKHN@j)2Nfw-w#+nHoeZ^)7jjhQ>a+xWOhu@6i z#YCtBMUepR+5(m<-6mX_S2yB$tcMYoz9F#!jc;mjV(H?J;=r*G8`uL(- z>F7HJrg3=MdoKmA%}(1WKRG3{B_l*V#aycJai-TLzxa;P0R1U@oM*a%;Fo;UgRIY@ zBuA3K{1}H!?`qDC&=og>?m>{Q`<=+8jPw8Y-V(ee+LE{?(S|sCC|VjkL;@$D6cVM? zVZ_}@)~YwJN=j<^0NJ+jf(cjsjeK~U4L3UorxN^MpvEQDn~?I-9a%`e z(dmVqy2ON9_OnOxh6p_2)C$QG9JGM%c~ww_BtR^WSus_kvjRdY%AqeQFwvSJR>@@R z(0IV&n^?2|(pc=^V=vu!(?$}}RRY2hdCoh7#iFID@+9D*3*7ki@Ykjg#Z*Miw@Bu# z6WR|^+gj1+aVW>>&gon^4h=ANP6v)+${HkID7g8vhqPb9(zCom!_$P5=9PKAW*Wg# zQ-HS7W+vdIs$`!aQCrc0YpS#)UcZh^x58xKU0D--<3A~1EyTg=FKIF3E}}o0zMwzZ zyRwKzq%99tJ$mB!;)eIFJNCa2=r^*gx+3D|{CSDcBAQVE|I3nS!Ov&~*_5F-A6>&t zDJoM%#sZ3XY+9Xc$~DitZ0yHQc2J>N-j=+X&xb++yCjG4+6+O3TN20rHCF1vXsXIiDl$6>kpO$>nyc@9S(u^NeEX+sv~y0P?d|GermOkMEZy?Ps8zs9P1X z)~c=x_qlomkL6#B3~9{Y)qz_p3p&!j1pQh#`+SXGm|4R4HMPEc@$eI37|)MNcwBKY z^ZxC2>&YnX-<+4XYt%Bv@RGPMnKqu9+I9{L& zc716|w3Refz}~;Al8?=RnN(S9^3020lYDNpB#p89o! zW_pwQVz8NYf1`!_AVxUU(=YR`&oWOE=6WCuURRIH(%zqgr0-6`L$5I2sNEF*xJEeifBEH= zVR!Ytivbw^&+2wDxK8EW<4Y_^S{QPPRZMJ@bF$=ySE6f*A)xsl@`5H9r3-F>4e2Ct zs+mCxb1`;*{RR8Y#{ipZlX_^x{{?{(%3Ab%27bS8sNMFS4W8FC`v#o^xXJh1osw{H>p? zvPrXFx`u-e0700yEuyf`bUrQXS!uLW%vitf@e*?|9HB+U$Jzkuny8&zgVbnyct9!+ zX<}kjX$nLXAR$7Nlg7A3B4zLd=3w7E$JI=*J_Zxn#TKJYER5hYfpAj@mswL`@(XAC zK$BbceRhK{mba-cREUu5DfhTk>A+;unX@kF^XaSb=dY3PAh~_37CCZ!79ZGs?6Y!T zv9HT6n|6<$e?_f_%o=R~ghTO?LD?XKHy24{9XZh}inp|AOPi^9(ZGBSa!TkRG>pzc z_eq(DPw=7nr_$&O`%?qA z#UZtj#`oOGxbRyh$41Z>#Ew$fL^l=TKf1 z7j0rb(AwsR;lSssc{TP!Yf>z0`@Z#nijOL`Y`(8^VJWcNM5#66@4rJ-OZoSrilFFb7Ff8}3FF>-y{djCSIIeW!YW|J3Djzu8z}n>Qemal!}a z*KMmCgA!q#X5`*vl?0JSd-F(k$pNC#=VK94#MC8^Qn)pK>>y1n0+mf9aLX6Ai}IRk zFPk}k<{st}3M5U1ry_@Lu{orQdoSiN>eLf>O$$$1Wu3EhNG)zJR1Y-y%EDs*O49s* zXkL;^Xd4pG53M{>J1x=(V+0*c91+KCT{DKI; zNJo(#`ff_>nk}YBLi+*7c%TM17Pt!Gx~PdGt5}m+(5Fl) zDBKOS*jz>qY!FAPCb$k(BYAV$O8RnAky0Bu#}{W3cp;u+gh>K0d=W1)N1zDX`a!tm z=4=+cPmlo9xFJGJYPq6q5-5lAj!{AJ?W?|Jw!e5b|An|8#56g_STf6d1#S9`5_>fB zOLWobA9(0C5Ii~LaeEw!B=fdo8>|q6MHMp5Bt2h-3J&nkt{?=juk-AFo}0OsBs}JK+=lJDz{MOot>dt{Ka}VOB3HZ zFnw~)`7_^zL9rY4W;7P3OEb$1stc{!dg*OGqp`RoM=-J_uH|AbfXn`PfKNP4_`L=H zUt;-w=oBc+bk*Lz+TpEp^{KloE{Z@z46QXehj{$r1xjHLKJ~Nu?OsI9+Mv<|iJ*3D z;!Awz@5R=LByc&-A`%ddB(k7qA79C^;ZBj#w~r0a0xvhGhZcTOc$FldgJ-H_nd=E= z93+^T6^=iq&Bcx-eB zN9s2cfzME~;VCsondbr>a&1zLUXA5;!;!D;)1JVI@H_Th1d-dmP5-SHH5E4WU$l@n z6{ik75g)PwT2?c*43De9ll>pyfll3-t|%$D9mN-(AH+c8Rg;YQtZG-K!AiG54o}H@ z>8RhpVX>i6Pgv@m%02`peP43;A+CQkJ+~PER7BmAXyQ2UZsOC&!R)?K=EMHR=il&$ zdjRNxe`><$qj1~U{Un-e^G>fzv1JB0U`ctqe+W`n;3pEpPG}SXYi+Do7>`DyS^sdA zsp-6|Rl&m_nO{aVT>i_q)6*myBlK^SUrGhvhP%(p%YZnjKrBX%RPZu8FJ+QHdUYc^ zlBAXzXth&|_(0#c`X6BzePcV@JgWx(3^Vv*U2K;KAo>`dw5T``X+O#iJiEc({)BK6j@4lXx!UIFj>*>S`W^quKM`e)S1`tr;lH;?oCN#Nud$i}h-BI@)Hzw@cp!ydI(mk!$-YC8OuhH@q+UU0 zy*iY~JM}kTp~PEaZs^Bo-NCcpDD0+UXA8fYjc?7AiJRf+f;Et&*N~!wfoa(=yTC01 zdodw#OdZ2$Ioh7e4`Uni-M@i4~g<^N`_qg0Z4S0mQ;ZKLkoKk7=8wjD3F zufDvgvI-iXE(^Fo+@XQXWr{^bVF|AYJU2KO&$8hqkb6J&_1rA+e0LIb2zjuT zhexJ=Ab^l(r~LPUlD@+LG95}xVnTTDA^L3t*4HMl3kCcsZV%VQrQ;EFFUfkLb2a>) z@M*+xLpNX=7|@&^=vgBet^eV6#k^()1reg;qlx=81Om36Vg01ZeYHDZrJi*xR=rf| zH)#7 zuJ{c8*tO{MigbC$AhvJ)QO*_4JAkp6-8WL@QI2=S6A*F(hU7cGO+|xf)|R>r{l=x^ zL%x6qxwk%!WVft)N5nGq3(VMeM6{wfMlw15ajc(piDGR4Pxtjl1ll9#++>lIFQ@jm zFLfwvhLmKFDEu-S3hhVeuROm9sSaTcZuwLyIwALt?d!hO`3nRspn&cHU19RUMs(Kc zF074~qw0zfti7)@7JKF0D0k)dQYF&pMVM*(ThBRNm!`qexm$U(xhoBXdh7eAie#%O z>Y&thUR|Z^gWfTl;d1>Fmm`RmzhYs0=Oapf#NSfT{}gXl)2#g87az15a`c^#8J-3n znuV#-p_2TiULe`+*QD=OoC8zO+nSG+$SQ?EaP;`WGd{*um9a8_Fs5+~^5v#Q&jaD$ zHQM9Jx1nC1lCiEV2#f_W{FF24y5PM?D^Gga=nTi^;Ee)#)Am>36<^zbDKLGoLl-lP zo%i(s^0i?&)s!#J7WWnul?#L`~|Ot5ny^M=^ZoMmv-@uL^%K536hW1VC98b6km?|a{rD!{IeWckl`N)^1;SX6QM{rsPe^shUL zy`8!5@@T7WHrX0OxdCyZRj!vta!vE?rBf6idkxsWI(Mu6vA+VkN*EVo)L`|djY~r` z-&Z~4Bcu99#4!3p=;#vmWx3WTgdBJ-?p2ejuW_!h1Jg>7d(ALlNDe_Vm`obX6xjY7 zkK555VAY4V(7OKuA?a&OKJ-D(YJXyxa@J`Yd58`{O$tz$cEZ7d;AW(V%vxy@#_}E( zB-l@^k*SlQW0HloIV7l%{!t*GeDsQOGCGn>mMW%GFepw^E&(j)VN7SY6Z+K6E65Cv z$bkO?`lcB98AC@B!?z_$iCeJMtfb^48FFp1JZrgRt-}^EgxmowCbKYn0V!?W zvop$%dKUt2`K!i-(!9D?gIm3pgaVa&|25~_OF|c{-hb%F%{JW|*kU!^`$4s!gYO2~ zhv#x{MJv`I=x9XvGS0_`-T$Q2zH5eT!CeAquMQl|Y&&e!5WCl&H8Q_fW25?jznz{8 z(%B<+C;IVOnBMfX?zpw$EE0iLw|Dw}XKVW^z9&gJj_kHgeRv7`ZA;>24QoQqW&ZRP zt1A7_FKYMokELhpP(i=Ge7_U%k82gbq1=M23qIYogH68>VQZ3y!(whWePN8tKt zraClyY}Hwc3fNHX?G$XftN3Rglg7Ixs|S_2<$-9*%t8u&dvj7xR$>!6Y4-VUzPosS zk|M1`z@4QrkiHCgHR{TvhQ)~S8ci2x53q4C2zC;`vppsx z=TsF9h`w-X-zG4b?JUfxxPE9&EI@9;^&eMPFAQoI7yt4N$v4;WRkKpGj>nxj68gR* zO2xB}E=?6@c?7Y+h0gNKzVC11)89ghhtEs>x|kz%mtDimVmvs(k=36FkbY$0qKaWj zhL6JHW!!V`O8d30h7JEO%Gw|+^`}k@>!DkuLQ`k}g$2b=q~AaDz~)+MCge|axtHQyPC05>!0_w~ zR_ukO3P3VsO$GKOlwBi1ceqsK^-bNmKL0?8oCWDwxpeI@q0Zxx5UN9_L05q07@!{t z-NE)*IJnx?iA0oiEA;Kulz@OPIW57bXMTp3pDYfk?MEs z5K{s7_5X;2XIKL}GX+G6>ybY#Si4}@ewe3m?hh9Vg%fgdl#YPTWF+Bhkm3jCI^!Lk z(U{EQpD$POm$au7tuy7tyB&*#hVb_xUzNQr(si&7>6W-;m1`kYB>RKn`!UVRSv?Jq z+|A;Ckr%kDyH$Z7lgl5`aNHrjRtTOUkFoF+;V5(;--;kuZm3EmSlAITXLd4X8fG=1 zkyS`!>Z3qwgqP1K$I_0A`-|Wwa+VcFY_3%l*E|BU2LSx0f%+DnD?V~Gb>&(2Y?5Sh z{i>iR@ZP|w=??rkb)_KDr?1&}DI?6YWSjs7+J8QnVdc8Dm5WdBKyCF+?j6vXD^eJ8j((9 zG??E$Sj40GEwg%qS>;ME#XOW(;FbE^!qfM`Mc7Lb;BVghen?$FkX!7)ctsc0eVP-q zQ}t(u{9kdwCc*KU`ONNbK?IXwO152b@=!?yycQbf6PtVr(=dbP{r9j+a;+DVq+qJ4n=)GAH|K(gQ)))twMPb&ZODnB1}vQ#(c z3;xS+1U&XzDqDG>3DnO%>e`boYibUOD}NEt94ah6(J$=o{W5^7)?E|mO!nJt^M)*_ z(P1{h$Os;R=sdNk@D{xi|v&h)}Y28UR%J=A-p zfSbFkg<7?Uxd!`BlIbKWe?R@go+RoW%9Y0~&*BDO>r1l|i(c08T-3JM+5@r&Qw7vB zHe0#*-{%tz{Stj)6^Sfy52&=boRCs)&fRqKFRqrBS^hIt_*TgCl&rd;oV%Ke_yp@y z=%drZ!_M5xmZk4H8#q60F8gy$ynVFBK5sVkPUn}2jx7iE&eD$?wpry8X7!fPV-r?g zRI8j$DEBgo(l+(_s7**X5GXiSmzyM+76`LLC zigd_I)$rZoZFFz7DplI9$4u3AL#YRTSzeZZ9An+}siLKAJ!>;?r{Ct0A6OC(t%3*f z*Q+Y8D@9)r5MlwJ0DR;BW9Y2onqcBEzOext3>c$(jM3rf6t+>LyCkGb8bm~tjZR^7 zJ5oSUN<#5`8Cdji~!5#9SLSD+<7tZ;5Wr8Tptc#-6EgO8C5+C9ZuxdGKZ4%DfNihmUBH zWcJcM@>;U()pdp{5cSl!n{T;lxX>GE;@R&RO;H9v4}&LeH0NQt+r2iY7&Ykc#Acvm zm&VV|yWH4$ZclK6c{oHr=|7r5&#*U-5>j}Y+J4$@QG;O)_rZ+A!#8cVrnkFR_PUj1 z-~KR=OPPE->-Cm9aIOR{LR*44eSzIHXL*dB2;SW(z!UIkp@erP)3tJM=U62uVrD%a?g;^<(~S-|1qofj!1k5t#l>3-uL-;R(q-I-PyO1!4FdC z`$Nx1Q<;K4F16m3UcXOwH4F*cRHJYDi$Oa{7#pIpzU90FJ;^&6&AM0)@;W22oNf8& zikHsIpHr<_+lP7qvh?5Jps?+r|C%sA>J+ZkvfZM31SNf;W? z6(NDSXqNONR4q#m&k|~Baqf>2^Vu4Xc*qY4sgjo9fB!C+%DWwum_V5~C^U+0K`m~< z_J}MoR5AH{*}{>C|ANsXt=QgfuEIbR1R%!gt99S5!M6*wmM7X@|F0$nIr}A=GYQpC zAEU(*n-qn5G<3J8HnsuBPe%t3{R5`w1JD3+fb($(j0FN9GyqNhk5euM$U-}tQlv&e z@gSOt;5iDVjwH^@vhbHNatf2hBbC)7+0`&rDDgG&Pk>eGbSc!Kt;mGzk5^Em%{BD@ z5E)1u(Y^2XiE`H_ixnZ^{+ZZ9@H>arg1wSt9GIb!=nx6>L6HYGy< zRVxVt(RhDoJBHaC0Z^VV)kMc|bbir$F7f2_)q$15^F;TDmdAx1ThtT*fxlnH_wHe` zp?^-z5E7XM1t!JU54=BKK2qR3-&dOh+J+ECjwIUmy}diqKRMk`?JjTb4E-i*hM@zU zwnsA9*@Jl@Xh1a#+)y9Ih+2f_1eu(p6n>q*bD9z2avH09za0}dYLNP?I3 z4(zbd63M$k~Xl=xO>2R`h z)Hwv0>YZgh-&_ciIX$vECyi|RC_`^vgIyI$ZqmAJHjJB~CB5K84IRQ{92kQ@gg# zucT{lu8ZRqYT_p5>xMZd&wlRdO=tCl;G24!ujQU%`Ci+#^fWh97sE(7JW8N**dIUE?yM-rNz9O2f}ER83XiPVXez(+$k8VR&;pv~L+7{}gqa16 zAdKiD2-=KS7^YYNG(VW!uxqdw$%Es2<8u#b*0IXOBNNaUWX zjVAng>2qoVJF^QluR20dJPFF%oduvCG>zb#psGJ=HNQ}ZF6)ipiXmUPbRms zBDgNfKseWN$y2o5BeBa~e5}zkN=~(l&L%@HV=*NtKdEG9=}VbPXFrs`q?q%1M_F81 z^DE2fFw%2Zqzj?@ts*uXWJzx2Fl_6UG!{>>x}n7qtil1t-P}@YU|$Jv&QR4UU$BhP zqPE6ZBL=GFe0%uTLTTS(yP(c^-o*TK)lXN$n4CY-aAXd*uqp8YET&0=WD69ailwRF zO`(b;Xcr1b=cyn%VG8Kfnb0Dq+|H#3JnQa{?l=fEL&M=hhW{PON4~cN(Qi#Yj(@J4 zJ$e`2Vp@#nENCUcFqb(a7YO{L1*BrIB&=eJ>bvwH$tK84u+`dX(ioNPmuJ8vnC7;L z5aS&{+`M3$CL<!iwhNqkovTUkWw~9t2R0!fuv^YUS33YmY zG+1V%mOzKJZu24C&Hdm~Pxu{Z9DEVS5vY^L&)m`etAGB|=5Q0kXJvw6Np}A4&S$s_ zOYGPF8S4tSSpUb6##CeYOn4?cUpzeNK`nNdMMD%jERj-c`=&uswhrJ}$Di-<3OdXBcYv&nbw$IdSZax%>DnxS))^eA4gD@_hg>P;|Jm%*49$!VQ&T zaoSQT>6c#-*yiCqm7#oF0i*x+MB${sP+Qj;+se8Qzw*$~FUA3yUIUX&GRR(P^JmEo zQh%nL0|7$zWgvTDTKpxv8jJdor762%bRs3W_C|iC%pZPXQrTgbtB$vw#zGv7w)gzn z7@9e>TtbvoGAT_V1u*RxE)}YioxRnGxZMig>87DmAKK^- z3j&)hNn?a>2uL%}2th2RDt^c?d#VrO@ic8C2j6!_|FgU?nEmFv_mw6n@zZdB;cE?B zgC@#3Ip)=}c6}E5+Hw`)5&Xw}9BTnefGGo60E)`y$`mw#Z|Xx;4_C(?*jK}510q~Dvr>kaA2(_T1f{iVlLj^vp|Pz&RHfh zOEw0a%5rSla>47?j1j6)_ZO9~InrVy&tCtWpMNCE zQu@&HvynN^y=y0t=kGh!xdz6o&7>W+R@p8>Jft#?bMb<8>IF2A!@<~uPw5JpjFG6d z_r7Y%$}Mckg$>{#Mc2m4*M;zcGeDX(irMK z*qN_6q@NVEBcb`R7ZjJc1XFaj9jDH#cIJ1@i-t5p)6c zsa^3YN|!hzQ?Dh(i%06mBc#MDx ze%2vfOk5F$;)~2Cia>6-(%K@us1Kb7(FWdq6XeDAsZ_3&CU$Eo$pd{PM^V;};>;9V zjh^?mt1UG803=X&2?jhO>And3k%B>ABG*u`%)*7!t2(yn7g)VbfW0~_bqEcT11t`M zszI+SJ`MsZ++uGFLooQ{Pu)2fe?|I8){hZsOJF_TtB!N~xa-YXbR-6aCgs;n zx+4oP&^vlNN-`9v;z4E5c1-l>Ag@s@p3MgjCqxgSOo-&125YJ0Y(pl=^nWrf}Ju_8l7Dd%lE zuBDy;XOg%FR^vuV%zlY0B$XZdu)Nd|{cBgj2;d??0_><*!Cd$y4gA{rDm>oBOOY<2 z4AO?N($XBc1Tv>%9hvEFM4usnxAU60QuqU)b^4;25k;^i!Z6|7a!-WUBoUG9s^KG{Y(f7HQ{O_XdX%!X7pT*B`Q+>sXH9ceZ$ zEZ#50ZAGsg4MzpW$X9U~zo8Wv6$ZMOM|-@#>n=%_3y+caVQQuWDzXlFNb_VR9txQ4 z)cho0cl;z`ssxh2o*R<~PhTYH6l;5E_-|@3h8`&U@285k{-v$9o8#I~y~K2Wuzkb|pn}~?(u`KPGN0Mn<~`T^ zpja~&C9G!1UJ%>$g(itu#WWEcVupQ0i~(6+i{Aw^mBL41V1kW@*3Y&K9mt$cI28oNICwf zoO>kq$wKb6E3ttS>~0G-d`H$^XwB6R^v+&9A6|w%==2Znx@Lec%O-n&v6XSK2BKD6 z%(d{HzvHi=5MM_ttR(a+WaCkz5+`1fSC?C-7pxX5;9LPsk{k&K#rJ*4E_6fu-3}Jf zNp4OJV09`w{;tXFiIx>==9K~DC`L8iMlT;`Zm71z9m-Wuqe}_V z&rJr^##^*$SA1$U{`)FkOh?XwEh-3h24g#kUB~v!O&8qMAA0|}it^%wKIf>j=Y;#0 zP?_5N${pWCdvSjb{S@tW9)LdJ4O9+3*G>j1>hlHI~|XXX4Fdi_)XOU&y+r0fhm8FXBSJizYz@-)PGs1>s3kKgCJ?wgNH!v-pw-`OZng>_AlIeZ zFgrLSc%P#J@MPhy`(CU!gkAUP)M)QqI8kK?b znt|Y!K}dmj_|kQE+>NI=zNEXZPE98`m?1+m!FQ@!y zEl-WSya-#lJpHk|KPe#dWWVfbjDOs%Esp63)@P0gygvIMu_5S|-WiZjXoGp9zmC;< zRer7RK@S+D?A;TY3{|AB?{Yrz62I5!U^?5;)0r8cLR-JS=Tr6YGZC| zHoO6+-!_3SB^NZ{WiB(6pB0V9N4uiZnv7Je1I7j5?A$3(%fL-%4`RqU%oMX~kB@Lp=J* zDoHXM(#Dq$gNB|BPG%XHb3~&&p$@PvBz>7E#$8m=8^sgFIsY|Rg`swi)|?Y`)fC~6 zof{1u%i`Q^g+*WE5jA4}h*7zMkn|@TqJHbqkyqum)6+F!Hx;=B+RFd=n(Vsid2lMt zEx`Wyii}+sG|>p(Gi{*q#o?G-AQO3N8&a0>l5gGO?N8!u%I2g*x%LXwm&sxr*0-hg zWj@2wLnxpUc!Jg5c6SiK;Ma z{a-a3yy(rRLbXz(9(TFSeE7Y8{wi4lY2rG_cI_*?A8MO#N(cq~R2;*_|9ASEoxFG9 zQCxO++C}GL&oHl&+LfuhoVAj`unq&~clBPg_H8kmZKq#3WJQfG(^lM$>Ul;|8Gb&| zP$#0zV}@Qg&p5Qdy;VEI?DP^Y$<-&SQcq{CEzXP2VZU!X^GizkcG$B5mIs;lA}Esa zvDG7S1zQj2!$nx*CkNCw07g?wowC@)t(x^wT+UGhD$@t z&2u9-I?DW_0OwYL3KG2CB~q`oVHmyHILGbwhfNN!Si_;f_0>qluqC|5n3ivcchSS? z0p$8*o40fOP6|ZZ&Y8#0xp3e3{V0`LI@N~Ym-J)gfK%EBC{T{D zzHgWm-UQ-rQP%=ID?thb(AX}_3jiBudrnvWh!xxf@tQ>d5-_sUL;QV)zEx%N=`(2cH1#H;FE%^ZeXP@uxS!dDx$&{(v$Zqof*HqT?( zH@sVKHtJ0djKF6?^NhOm?wG^~<#1Teh!${Og!E3m`C?y|a*>-mM;M=DU3;LyEi{`^ zGNNWS6ZpnY+flxi%2&LL#DZ>=+`JAkk4*$kZ|Ow=_+n|U;c=Db3Tu;oexBS{dT z6nf2*Qb|gTIsMr+g6~e|I5YRGk3^y_gn++!Di1AT#TXWN*_~4ggR> z+9I!pUlu%{PWAB8lAZH>;&6q@*G8|;1JN9immHJliIa5hG#p|70??(K9+6@Z5i6gP z>_=a5@}eEvM~V;)T#6x4QlXb<4mTbhUVh-OiA!qxWu^Zv_zCK#;94~FV$wa<4Gk*e z+|#CuH_k?M_U+hCftws{9;=6tnnM|BABovYuO{;( zukBlvfBHBr2)m&x_Yk|hQW-AN`$S`ywAx=)>AiiYnuG*1UC}9QqRy+IJ>l5}%ym@# zUes_Ne*0gt#~z49lm!;Ihhgba8Ui!dEwv6+*DYbo8gmv{SSoWc+ug8x3lS9Y1q_vi zF1dc=wiC6o0tJ0L?tW0FNZ25yutGzkAZNmV;} zCt@~;HOS=$$B#0%o7$RC79GpOB}fa{`qI`KL?Be`_*gnKQCA?CsV=Y#PYG@-kh{D1 z+|EeGOYok5(nZNNuPo*?8&M$l&I!#F2^m)Ua_8Lra0U4g>25|byOOdZXK}W;13XvH z%Sn1kG*5Sqwkho?j(l!okYp)YGf@Za98X9bLlDYVHM=guDqMTl=Y-e1zqI!)$5(z6 ziMl0zoAR|t^PASYlEQD=jQ6X*sl7Y<#H#s8Fz~*1)YC7lm!j@+yyzKW;G7&x+;smW zxDcp??IsPjIjDzE%}JkQ&p4#0e<^g&0C&M%fE3Nj#b8G0wI$+cBPKq)$>EJ_V^+?^ zkZ9ecv;E;w5bLoqfg74MImRn$>av^~w-V{cFL`MlnFT@Fenbl$5|9R3f9{H_ohSL0 z)YCK@7_`BD`stGNC?Y^*BZ5|_o3pXoO%-?C0&%E0{YXkvcmyAAj{b~Z^(1j;Gd1Ye zp9CaD$Ht93_%Ql|Nx@LPF{bU|%T|>(f>7#u`4i!V23*pbGBPZq&!dfhT(CPDGsQYG z1$e!yNEFt@{QPkTBs017Zm}f4-f`Aaj9Wv&x=?6+u-x!|@9Q67w=a?I|NHjqRqdB= zy*r;r!q%^5D_gvNw|wFd{N|Q26Td zs>BNWvX-;=4zt~NuJ~ESlQ13!1~dn>AM{t92xkf8at$cgB?&L>79g0Q?Qcu8iG9&|XI4Bd< zd{|)k)TC1`BGaCSkG{s=pxMyT#k@^$q&N1xgk$x)Qea5Zy~G`0v3gwI=a8`nZ`~Nx zx_r2w9@9_0;VNK$%v=*a`&;mC(v0i)_4%ld)#^wgwELXhivb2YuBZr-6nFj$oUY0o zQ`xLUl|JZKzu12s8+SG6#W*?x_UpT}?C!c2=xVWdwY#WwAWxNlOhYE+^xWCtG5 zLFM20lc%}i$fDi<@_q@CTz%+Uu_9JkHNTkCu)9FW&7Nj0@&aa9h)ZfquWIVH2;6tJ z$r2TU)N!jWbvO7m1S<;SzoFoRRPh`dAjbjjO=`1lZbV*(d7nMxEFC{EubX;`NyGfe zB$nu2Dgcadg|%&pz;;u>tc&x@^IPz0h)o%jeK~0Zz@eF+cRO4O=@q1jHl??ayvk_Td`UjULZEfY|{g813X4e@DZ1*9bOQo;6dhg!rjpb{TZm)bM z&6osLbT?9YWX=+C`)U)3cN#ccE7TJk;;_fd7PpwzYL}N>6|#C)bJD9+_Q_`**2El1 z6R!)96^;w=qdYdC3Z5(E58tWOJKcZmYY)o0z+Ss?mMRh7TK;S06w2qiIK41U)8yaz zF8T;}^A2!1fz@-Z<`#iBM_h8!@&gJu44d6tInKS5;sQYUTJ}WTt@(KldXvHes7JYpD^ixe|H zd4p}$P8Gt+-P%z@{lXmOSr(>-``K`|JT}YkFgJa&8KfG|=uKki+z&Y5D55`6AyqdV z+J7@Nzv*%`z8JwOD&x(;^6Il@O~kk6tTx4NbGyAp-I=)Ysmh)z<#r)gjY}dmGBH2W zR=)>6G}-`K+fCNOUOHookb%~BQoe{6N|V?mMsmW&S69_CzdQK^mlg!lC}{DqP|2&o zrFrt0m(q}NI*+x&_ZYnBNkm|xYRbQYw{~@U&fbiPo9)8d^vFuaRMIvJz_iAVZ!VlAO%TBFH;KhMf+p{{2+^8$+i_Z6Oip`1#?kEnMMS zEQb6UYn{ViFj@#PK+)_TR}U`dWpnazvv~`<;|T5oF<*~#f8Y;e_H-wdx88pZ;y}Lex2~{}3Qf|G zxonqohax&l!6y+|mYq9Jcx6J~(bfAjY&*BUM^4aQuVF$KxfN%t>HZGB%5Egcl=|A$ zeC=1a;%7oI*T0XxxO7l}E^88qP@i366QqOFj=7dMd3G>BZH(fFrSa`-O%jqxG+Dup zoF5qls-Wh(4z)Ps=Shw7ORWRVb_+jPv(wp*rCS?bkUd`u0BbXmi2}{pq!f;f`R=Fr z>@$$c^t8>C50BF?HOTv5l3`R$-(yCXV@A4JqG?7tK_ktV_*6e5xm6z?`IpiB*pMdL z*HXqLHb8BO!`yDx|M0HBUqiobA?pRECm+?#{6BA#Zt5;KQxv1`bMbLD`M35XQ0_27>+^4iTp!GW17P2D>pT|GN z!67dD4B>Hl$cXlY$#m7`0*+}$IF>513vvmxc`7&VQ$!RYq`6DYZfMOmmML63O}7p- zNB*FzZ=Q%>p-ibD#>$vnt}u+_1SU3325cDUtkXK;i@+Im+|qOX$B#OZf6CUytXTLx>uK-3kzKk~{n2@a5btm)HTKXOhvk8bn5cO6J zIM(9rOxKr!#Y%aa2ls@Cm6_|%oWMevg_;ClhszpV9b3j6eCJ-n9H_$j%C;%=@Va084Yq#S!@peEYF-GB7`hJ)dZ?M*rWAnagBah%-qkpU5IKPHHA; z9G3zWlUxA3Z5$cTS14urD53m;{uas&d&J=yP+4?;&<^fTtv9qGhwyscKd?fVzV#cXW7@ERR>h{~&dXaorYgXC zcI#<~R3j$;8cy+Yut;{@eZQ+Pzu-RqZT%#;BztYv_M3wXj0*aAo1}N>=lw2K-Bt*Sww2b=WRo3T3eO63EHFks_y%)lr=c1te2@ zM!t?HSM^#ZwAtv`wtyi>m)g3z7l}{WG!WBJB4t#RHs=KmE9G>v|5#Q^f-66oYl3>P zdBq`W53H4s8Igb_3xJVyKCmQtF>!wZu*1NR4*>5lNb>^tkme*_qc8v+I0sBW%itTQ z_b7&H2P}NH(3yQC!YJzTPd|r{UCB^Bb2KUFYKJmcv2`L_d-=i zsF}>y{M(JT_GI(RQIW#Ovud$k!plVQ2lrOgolRO5lD~RlD0Blvbmwa7;JquTKq(W=e!Q9WcU3QrO*9jd?#T}KFMlMo z#eMx1i;DrHMu8=O4z{1h^WnKt20dH69(~A6q6R}un3*(o;CSX7Wtgfh_9FT=7&`gX z7n|ml)2=z0e!}Id4$rR%;zeJQ+Ezt~u{ZUz=g_v)l{>c+wbR8j+=U4^nxdMslA1*? zri!nvh!nA=DPi|Rr<4Brg8|X5r^9PnwW_C&=Kdy8^>jW|@LHB-WUjjCeDPyZAeK$K z%IG!f1mRe|z81oZUpASZzxAi%EA@$mFhkp(nx+5SXSb3s1Fc>YC${f^pm{=gpbil=PbRN~I`pY)#eq z3!9YthZR;YD6%*c;a4@G$LUJPKqMR+-tX2MV>WE?k>D8;7E6b0PO-?}n35k4+1*m= z9{@JEg;9z89nI`61{r+GPosF}-PvUP&q+<*L%T^F*~W}951KXfX`<71QgqrKvX2W3 zvQvb7dd${-pqPGY^!-rzB#ee+JbE~^sio(VHAUfC6@@elDwspE{saoW?*H_rd@?o1 zrEF|)H(}>f2-;4Z^jx2M0a;&HM6q{ZV=%0KGAtf zeP8y-tMNhWyXrE7qvIzGBwh*KS3-4+ZibC_tjUca1@iG~Gc{xBfIY{Ik4YXGB;nPe z^R%>+zcwOAS!083_4&91l}jTnx+K*d!w3{if$;*tMM;$-L|rM+)7Pm})f-&LBzSWQ z(Ef$D8%eoL%j>hB(5n|D-FhbPI@?dxihhEe%eY=3r(aw>Ef|94%&3#GK<=MtsmybB zJ$`K=`t1CMxTBL7ZO5y`^?U?--~T*P&jhBGiM`lL7e6^#CLy#1?s#Xw`sMYT$PFQ` zj7jCe-k)i&l@rir@v>xJLWHu3`L|Rve%`7>wF57n?5x6QWsZ+uM-25KO}wL3&DHAG zU`>~O>fb&m9SGrhmTKVO2^O{_DVXJSjr(RTMvrJ^2*Pda^>i{pk8pE zB$)!OvgF}W0FoB~jhLx?m&W>a!F@LwmQP}F-c#}<>h=%Jlq6LZe)I(-37-MOoui%# zdKb&0;^!y>q{@MVdp8a94LKQhh!5%p^_hEQS$>4lM7Nm~?;mg7M)BBRI4WRvs*g_D z4>WBcb;&4_)$EiEGq%rZK6YFWhc3JqXL*J@bmbfL4l`%wV~KKBd$;b9k}+TXq&LWB zMY%|Zf9AIH38u}oM!l-xBo1@>i)VQOV2zrlZ+Tm@kI>b4-`1-k7<}*GAlR`ao;Sbn zxsFUKibHqZTSu=iL8U^e<(zOIQ+}4-VMBJzZ!U6x+WNpgRL_bnHGb zo9NMSmS26H>`>H!A^m`7{FuGdU+nJt{^gF0Uz%0|3@#s9H#;o+yvL;pH`eOr*qk39RbZoXzy zJ0};XxcJ`3_yR3fE+Ht|L0>oFOnN;YP*<6%b!PtW-3O~@0yKU(0{{Rw(uaPn7GqZ0 zpbv=q{q5>UJi9y<1v9ppBNzU-;OI}l_}Z`%%`wh3E+jbxww}*| zFZtS8l&I8S?NJswmUde!-lD9-XJSBTPVkr9=NmhdHLLN^FYRsb^!O)f7VGPO-+mLC zj^)@SZ4c>jLVGULChE6-KY zb1;Wo;wr2erz*pT%>y#?c@YJTCOh*5CCU%2giSqvrx57sc~E76Haj-<$WLMe`zdz zqxW^lNrd|DL;BkUw0r>2AuM>o{6g?aPbJaYkz)nHtb1(5b;yfj8z*@XY?E1v31U-X zTEuM&nPCXZLFc5IHp|Ki-y|}LrhUrCL4YO;x$1w+7v>~?EBWFs{(|&W%*h8N+1s4y zzt7Z6VlB|PL2G3gt$qls60=prBQ)@@fgw(7mWOiV7or4{ zXufYe9LwKnEnjNN^tq|n-?YW!@7LmEJyLWW*15@B>W}C|TYj#(AA#zkKeeEB#V;S` zIRSZcHZtesXFtr}IeGeSj;R>tX~d8WqjOObI9;CNT(Vqv6-~=2!t}l@__|#2m^_eg zc~tJn$nta~X63t-?3)MI)-FFQeIl3RR1?2D^dc30W74TZAGFc;pFX#s&gyerheBUv zu#!$EgRt5pZBviiY7I`|uLFxyNWz!Q*r(vm^+oLvL&XzDAUB<|K<1=>-vNuJb(2_{ z3OEopMb3|l5zA5mqeM}Y8ya|#7Ko3%b$5ZF^EN{2;f|eAfQ1_CGPX_>)&MGJuDLJ-#+HY<6#3e@QClc4X+H*|sp3(ZE_`L{SoF zGyiJ7fZHzTLya*L_z=J$(g!|{E%7N3%?|NRcAOxbbD1L2M%z5$Y3FQMwTPVYsx%kb zg&eSLH-q9W%t~dj)QHwwXkP3YN$BfT zHtoErC9WWV_z&EZ&Tzx?Hhf-P^*1J&q9zKAAe5?G-4K8wIe%TbF38 zbmozL^;tAM=L`&Rcg7%o7ffw5XI!|vq5;`FUpe+-FK=OjyN*D9F@!bx(Y{=9sX zm<+rG^B_>q17cG`w@WFi$}}^82AUI5i;4O5iMl}7vUm`CLk3emg;H& zGrhClB`NtC%Ue>$w7+02)!UPr?Uz%;7g@pch-jLl_BtD{Sj4EIW(rlug5g#9EMW}{ zh>851E*(kci}7OlWqd<=2GNgj1W%5;b|qi>MJH1^e~KWEC)m`6h?=MM=bz;rSmm>5 z0b4pruZPmC)wl3PaZyT|5#E;$4btSl$d4AIx5U~0>w9utk^AyVaNg4t41INs7n{4( zJ!qDRdF0nIx{$S*wF0oku3)nlc6}`g<4=@14ZqOz(sc5~r3YAWsEzA}{89Ztyj4XldvaB}Q0`Ujxoreazf37avT5Vni3e4NO$yMFe zI5NDW)c|Daw-%7UJ_}rLHOy@HgAliWW!th~|rI(>kQ|uE&TEJUii)?|N_6&$jnH8xPBnNTtrZOpDiMhX0 z08rxj36NiXYrsr9716+Ro)9*&YOHWeP^WU3q3UYK1ZStd( z?_j}L?9?68v+0E&|I0D!~a;!*OzMHV_Xj~ zK)d(s2=5@D5EP!prPS|ULQ__EO`;FQBn$14FxuL*OwK}WYFwa%YWdWh`J{MM#4LbU z{Psbb#^`prCzCs1h;whh>GeXNdf0xxNwa}?Vb15Qh@87wzybbT!jLfr=ceR&H`KEm@Krj7QP$?N~%zVRcsKI*| zb4U?=)mXb4%)5}j5yBgYBp9ZmiX@Bo-|3^LZ38!pXo-XyqVEw|6^D=WnYlcot|`CK zi?J@k^Lg2@Al;Jm9PBV9WV__$7M|a>*$mVJ5y44&VauzJMg&h|3T#%hd6vH8%Deyw zjS&WKQF`gqO-4q&V>a9Fd;%=$T5BH+bep8L(zyzyX@I}4VRSazL^Q6;pPLw!cGAiy z*EMm@%7PIj#+||rcmGna>wMP^ndi2au5D&N-4*;*wvMM#^-kT&A-LRXwjsFP&>HDh zgy8T7mo41zPPIk!U&1Wa?^e7b5@Q*|_E&xoZ;1cafA?j%bnVw>KB z?g}OG4-Oe@*fIViFnWzH9OsaaIo=+;w2-5j7!yZX`+@EBAg6?7Qb^VeS=x%&Ll!@ONY?{PzXHv4GHIEz z0o$wrAjC3e4|58=`4H!!6R!fU_B69M$gpf-OC;fPc+Z<#^bQs*Yqp5BThuphRDt@v zF}fPG*8L|)(KjC0V(02rJ%1hL-s?kKc^!B{m( z2w-fy@2!7q>>xGr9U^v_9tO4?0v`@%ubHblm#g@*mB@dPy>6_M##b)?JicljUzB%2 z(Is~)^%jz2Nq&?yh6c8!`tn*X50^1hj@Tw9fzT*(F3)OPi6O@j7zYaO5rdc1+2oWn zez1##%kA8#8se*Z27pnYjp54BFFf|_?wO2QtQqLqfb%$~YSpGua#5%*ngns zIH-BdD}?oEem-0eNWa!>!Z+Qp7Uvn#`QjyK4#6-lNHc>AXvzkemfNl9Jq4cEeWsby z%?tK+!}Qx|Z`23j%MI*wqpr#4UOprPD5kj8?RJ-2vx{2V1K(Y@DE%J?i&u~!xG#~@ z9xo0TbRB*#p0t+zZ(47q(Kuuyz-EjvJHEkbjIpKfSpYzqEk;z0Rhy~Tnu);Kylc)A zQXOWqezd%+zI-Nvp8A{FW$sqTRH{^j&mT%Te1Qo5M)Ir9y9KH0&sYiYo;h8;OC%zb z8ZygxJ;v>EeUZzxi(>H%wIJ&n%xP7Vya8ST3{TnCJN@Kbj+IhVhO-MyIpA zc7q1X(ytr&$c+kndmP~bO9fZ0pjPGY*!fs%D= zCKFr8CKy@u5bC`uhAR=`Zod{$I0j#xu%~`fzK5 zNfyEwhDxo!<+G}}$e5>QRh|OnfdwVYO%(^N7klOT__L|_TV0>b_K~*qlW3bis(N;D z^MXZNIrz0O4BN(?lR$V?Nd`y2*C#X$g1Ukxu^GU{4{HBpJlgOxsE>N1xbHcfj^6JD zF>VZ}$r;}7k$`h*&0Bm)CLzaQJ@-53B-BG%@^=2mT&#>4Yc3!i@*=o_q{G-#{ZY>s zXrcwN9vp2J+%h%oFgk%lBwY9`?bhud7^qGT@9Ze98|w#B6I@b@|&;keUV1uDQS-WQk@#3C?Qbxbk@g5v^1A*?3t1JB5|v1 zz5otPrxSl?Z^irLuk+Oh+lQuR`zk`c=PAY#6aF$IR{hybDNv?Qf2FB7ZTT1H@&doFXFpnS98f!>R;=XX$Nb6Qb`Soy)=Ly!`??Xy^)WTF#Z{)pyk!Zivq! zZ0z)XOLrz$=x+#Yc)U0|47F2w@6CQ2c_M7_4<|HBQb*FvL?UFxLS+%j3Oo4wAH%-% zChanvb3IR~wl}|_rjpo2Vs^(F$e|m2i1JDT{IL?>vnLZa2KjUW69URD*V+_IZXOng zPFy4A`F%5<-%|Y5{|&!4^t5Tc#8R%xjr+tcM1wnLvRXmwj8W{bQYGEXSRnm`XXgF( zVdzYYCT&Da4t)0?NInFcTYmeilpA_fUmP>6XFvW`g8@WjaID5j3MR4sK$=AGzdV?a zXkNlry#y;e8S-x_Wxr5-AI|K61CV!s38jn-so)!-w6v+!jaeH(7pPwm-?Z<3RW8G> zLG%Jqb9&;p^fcap{hGH|O@lDECWezGi`|VN52vx(pMCXKoeppkMdc9HG1yOQ-99Qy z*+yO<&raCd9y7DAl;Hx zz1sqWVP=g!WiY-m=hLdebfBB+eEIRIZ+DYF3(soIMjcm0<8%MSI$L#Nq( zyi$B+NY4`>76r~xtZZAmEDSpv@K&@nlyKDchD_YwnZ4JOLJ1j=iZv>?eZl=^#>3VEZ zm8WQZDg)SYnv7%L;YzTvH^33RPrdky!!XD*ogl5Z?;C6;*~-7Tyo50`srn)|%{QW6 z^4Ur|0i!e3KyJ9B%=0EdY3r3E_HkF-cOMC}!e~2(N25UI5?eI_=fHIHolq z!cr>OCWr~fw0v>DM)rE@8#9|bP#_EQ?GP%ZQV-`fZ<=HdZxAjuNf>lrUH3Ur2{awt zN@;SoWW)4gdi;~hd>-JT59GiBrI(?9@m`&`sZzSX6RCR_dLJt;-v2%NX^~4dtk>j5 zbf?;DGgHU{$TDs0$<1gW=RkVO>*WiqN8{jk)70rZcuMFhi+`R|+e0%QxM>z){mQ)Q zz6+tfZeH@@z?ll=;_^$wLy>!ZC;a^{a>F3E>lcV(=>U{FSF0iPQ`y;Jjng03_C_KM zjYkWW9dRyxo0EROJc|eUtm107T>y-TSO|~l*o*D_-Y|;FfV}@3k<8>f z8{4GzZOxTL?dBn=kD>@oZccxXYtMImxc;*OLo#AzkFH4WUuH5$(zbFQbFJ)oDhqBu4~$aTtShuF6rpqBa{tM} zaIRSxNpz|D^y$H_KHPKU&mT!+xvWM1eGNgK%~<8n+Jr2T=+NcF8=!@bOyE8iB^MrXgDJpX(1C2OSH>*-&$AE*)5&pqybAu$0tcufxL^F|GVM)7V*L6?@HkMVf*z$IiXvwfM2>!f;)xj8vHe zCr--u361+y2=hK|)nu{YUF_|-j`Ox_4Pe~m9LqKRU(HhI46Zy4axMstQpkMT)i$=& z^Yx2QW{+2AFC+Ir`8ZDdYH>K2)qE6U3T_okO&m6(HwdkPAiRZ$K8R#PD3MdLc&itt zGmoj@*6~2;37q{~MNNyS1g-(kFnV8LU`Z5o5UgbK9oHKm!e4 z<$)ZYugb0Z?@}|^GrTfa4|lM9JGhTmu{L-z-H(^ZvbCytJ~k*c^}(+u{@Xki_%Vq zSV)QRV_Qk|;Bskz{1y?Tm}ldKCr{IkwvFHl>)xp2z><+eIf8$Qv`DfI!0Di9^K?@o zQ98=Mqw+3|;m3max^Dt_()j{T?FkkCAoVM9dRw_U=#MBi4~Q&Au1I9l&87Qtk)_Pa zv%|v#nUyeDwX%$m(tfYe_blXNH8!~4zB$y1N$vh;4DxeXam2=@-$mjKlXWvH;IHK$ zH&r_%MWlUB%C2_|Sd#49iTjMrVe3V$IaD2<`(p93D^s~#Xu>8*uuxNQVtUO9{s(D6 zsNh)yr1{CR^iv<-hmUf_Xg*l{RYwcA8G36V|)33RrCiFID=#nbZE0z~AcF`xdTB7(0ON_gDb9It3WjQOZ> zrg|;Gtbsc>!IqQLp&xMn>#@bcF2~a{uG&`Wx1)Xs-|_)? zIFsLGnw-!*>4VpCVY4s!o%vz^O8)RNVf#pNVP&HF!RP@@#7Y;yKJKIcqnk_ThHKdt z74~rPo%oZ<*JZnPQz9bK!+I{2;Dh`8I)zzIvD3_!KO5^3si=7O zT=)hPsR<;9+{B)D&LPT{_Xtl)m;KWBu&(m0WEKs_4WREi}hdm`qO z4R6a>1p;!5A0Bh^9U_8sSA9a z>z~xf$OcPY`Xn@Sfmf%mm=yKnGgK3Q#n~TE6-{RV|4lYCYH!pgLVbt+1GhZWa@Fm6|I*K(>#C&=yAt9TtR$#gF07i;iK-z+Dql;E z39^Zhs`lSV*C-^L|DzX&FrhMj1cM%KZ`CqBgRqlm!|5MM@%P(b5Q@kh46w4c91lhW z`T8e}vwe2`saKc2!=9}gJc=MyD48Vv&IHfDbfp`5x4ZPgWRO1q|BlQ%dK_IKx~{sh ziP5?Ff~A*<-Yv=h%vE+m?;rC4xgM6J31MuWMz$7BEL;=(Eq&fqGxo#;`TKj0DnOjA zeZpk$r@*Z!DVc`7ASKCCro<*5NDvP|>JTR)^bdzJW}vg>G3DHn6F7CAil)BI$|!Z(M- zEdzM~DvY)qFokTVc$JP)pWb@G`eLtvwO&nG5x}~&%_BQ@oyJ0wRw;@E= z!MFPcq+=}(yBYA++q+kpQB9|O+0@oldmO$sy2xp`)ozAvY9jlv zPLR;Q69@au=0mWwA<3vyF-iXOL4%B}0N1K;^GbN7F2hT0{t|R_ra#WmDErV zwKd1)W>$S$A6U1|{QBFewr8s`ed+q805BI3VsFhlBKkKhxg~hPqLwuM2|Ni)qR4)6 zI6w!!-oaHhgI+HoK|yi@B{sggid6t>X)w#^+ZSAbs)-H6^&|KqgfAD7V1XZN3x2WF z&oJzB)CTcn1oj-2bJ*ZcuY@=+FpahSc34)Nt9TftMBDJyZ~+*n zP;ExvPan{L3|}*TfVUyFA@itm(@DzhR(g`z3xJvDa7?DyT(boPqbNUb0XG2Qo?<}DxYW&SA(#*wAv@T zQ$ryCCCSb7|8DtdTalbemD%cj8sbo00^zoD11SScet)7lQT}Wn~xX`fQke7ktkiv97*_$&?N*YEDdJp?Ok^^ zfAKR#(Z4%kHBS8@I83UmVa)4WKLcg_-W_kW<5gbvvDtHd=nIFMHDf2RZ`+%x*Qp8F zkxT_aKCY2qnq>cgdfsUYPJlo8m}3ZKpua1LMD?YWPUv4v)e<#zI9+X!tSCKM$Em^A z@1QKxAmf~7b@C-W!p#_Aw0%};6mPb|MXSU)SO_&+@|fV~lBOVb@wdI>eOX4hrL3ld z94vb{45uDIe8P{ze3t+$5mi)Ir1Q=z%<~2ektHj5YnYg8&DUCxF`I$$ z@_~%U>q?arxR7@_+G^QO4+uw5Kz7FFf@xXUm4m7u&_gdHRh(9ox>VMDsdyD7UsDGO zzpD$~h-&miSP+rlJl%a`GJ{M~6JDHBA#hblD%Od}otZW9zNiA|OI=gv2#|7;QWtJ` z37EX4Nj-Kb%Hhh9dDC2cj^FY^Iduy(Gl;gaTFL@U3tR=SXfN`JN9P#G@ode;w_4Q( z?I1X0-}=qSMJ8(p_hFj+AnEuSe;}XY?fIZOCg$d#fegOMauyaBxLYaq2|w03!~Knb zomG^Ae$NVtK6oFITlNS>@EO6{j)sEAKxvBg!Dz|vfRI&Z!};|V86A^I)z0*^yo#rN z#vVtsLKoF5KE1q!>ljaBp(0FA-N4GLI z=l=+>s^OGFW+fj#E^kqTFgf>LKq%w(1f7r|g{0NFJAKGb(9dh%ra*=QbPW6dgco-DOW z2a3jk59k|)9~INM8~u#NrV3ELcrb^b^SbkOHoWqB1lT}Hs`1tXA*(ZIf38ZMBHh&* zy46j9-?Xt#@?kfuKtOgR-pU!t64-^7Y+)E2>hjMU)dl9%=Zbc;+DfHY!1S_=_8Wc; z3mTkZ{i-JX_9k+KTfqW#2_61z;XPRctUC6F$<%%IKKq}+*-K%nQCga}eUwsOIa7ID zp4}-JtaEZ7yGpFk4v%+5Hz%*^mBGM&G*wC2{klyjT6)yE)-CirEa~hqsv-R#3yP|n z^MwiZG#dMOc!0<1viq$iSXTLjE(NlQePYw7suHK{A;o?pQb`NkXo7-HmJ8YR#*S`O zWxU37^L_z*tGXI=t&&R^))jMyN9XO5NHi7TYl`cR)OhY-VrXxV@pLgm)Y0sS8Rxy;(tyiJNK6&x`LFqrufJAHab&JP3Tw!fDjkj#?VT}{Sff4?!*hn?K+jWnif5bo&tj7(TRvxIwKP7f>JLy zORWy40Zmxk5HaqzlTEAK5_ia|3%L{Vy&mD+xxeJ;Xl-t!8t;@&c;Jp>zY(pv=|2jK z$(Id5tEipt{RCYE#H;|^1g@SBTXo~AX&m$FxN0d-K)cXCuFvrUcANOriU+JJj9X7H z_<}ADe*o)aKGaGdvUXIHdf06Q#bpUTu88!!KTj0>k4RXnK3(IjYSwdaS?CBTY_cTr zmMNr)T_v8Jx41dGex^Iz>$>@cc}ZNcJT8k~q~+2t#WnK6_ihpCu`C4nRu&rx>)qKY zZ!qX>rJq?~v54$6GOsKex5l)&TFxQahx=50Hn6>-o9y&=XW-48n$2It*!@UDl0S)E z)uB4>Nx>skbJbyxzw*Y=8i+T<-7WG3Abc?^cvT`2Kt6w0laJ_|>ON!@{^1yaqh(As z5XPOAL=a@kXr1i}GxU_?@WgmPH(=G?@vwb%IigZPm?>lS0%1li7Zm1yE;V6wEGJyS z7*fvOTE^h9oyh}oG%Xpl&Xgfcs+$^*^Z6{p>&f|#a}6|;Sq76oNz1wY1PFq1VA!M6 z50h8!^(C^JycbOvz$z`g~yYhj~AAuPM;-tz* zP*#oN;-?_NHt(P}xUmjxk;g=l9{B1$cisEMr=q(1I>?qj=F>gUzsFT~pkmmo%9@N8 z!5JBo8Nj3T=oH(e3!BOxy=Lj9vEf*R%6*%7Xp#yG9(3?y(a2QO=BUKeGxe^*4J~f1 zrJtE4u||U8_z}Egf%tHV0V^5MZOJuVuK@vw8T3P0T#KQ9r<(f=7lR~qez>n2iA9Zv z=H_!gYl?0AGWYz%*M+}cL+cVxc+W{&@7ktPhsb~dkibIkpiz_yfy08sG0Tdji1=@7 zhEG#|XV7Sr*6(<#s`r}D*eJ-AK_VMV|3`upqRU>722P#SK zuW>es-_oARrn|8<1wkKa*jp*8CFESvy6Xkxls?&&Lt46GB6@7W&2#oJV}z}d`Z<|Y zyLh~og)@RP4rySd!?H6z0XNKWTt3>LkfwfsT;9J*n9LZ zIr>H)w)B71DB)riqN^{9@G4zUo$ZP{h9W9w&Oa=KFD8ZH52hsat(TMv z1$t%@QK|5m#7mmdKt=OsU9E2=zN&c-gmL@|9aZ4)>9t|wDF0o`vYc@g{I7FLlJQ@J z2dV`ni+JgI*K+B0Z5B@m>cyb&;=mni?cXyx_eclqO&^Tj;IqeNh34h@Z!Z4zGE3l& z^CreODo8VpDABIIr?OP`!!Rfly z(|0d7Q^$?Y_HH;FOMLCkph_m5gWSj-z4h`Ze;=kj58_my$Y)-R5F?vux`JyW8(fAvyw??~vy_A~2{Ht_hJ$SBt?G9JtQ?*1CD>CeS zTiLal+0bs`C8&8H;ZNodOw5?5vD06#Wzu%>S5eRus~@<_0VJO^enqK6)g!`cpI+Mv z$3J*nee|$j!Vi!=p>{M0d(Ql|JbSTs%TL4NM(DR8cDdxpRz_-TFDPpJKZR%H@(eww zgKW!egqiF^VHFcoFV1QDG_}Vtz7b+YIi+mb3 zNwL9kz>+#o?S&nkV^*Hv8yDyj`cXmb{D$JOkSr??ZG5J9K*dlYl2|QPP~fs(i>_G^J5-Nzr|lBNys6%Z9;zmC$5fu+(mgU&avB zdfYNasOqJTE?*_y9>N}bys`SCa(t{_KH1rJ^{KnQpT}(?_%|Q=K`cYAaJY;1)OSW{ z@u{EWHp1oBPXE0_PUI<;B1Y~gEJ?eq#$!T~P#r2)@|od}l>pn0@U7rfl_BwXG!I9J zbVU-zq)cDf6tuY^MaN6~$!emt7(BQ61^R@z$*)OmPR2sB1A3O{Ex5Det6wuif)Lvy zfk>*XeaaVZfh-W}?Vx~uucgf;`%2+#maQDoa!EpcpT;-6R`6WGTkM(35aT##tGTy< z)Cw3mBmWX{+g%ZV+xRSHKWC^i&QjZ8zY@#8A~JvhsM}w7=}%r|l#9x-TN3^wi3W5# zW}9gmx~7wkrWvm-@^T+8PmsC+iZ&em@VtnA2~s_6=>W~ys5!FJ{Of*+3IsUYUrid7 z5&Ilk`yl-W*OlR`W#2`aa5H9>e4iBK_wrED@01$l@onp)zs5#Yce!jVWgSOutGquaydm<1SH|zIOAr;A;fSpi>`k(W zj01d1-Yz?|#YXr8nZ~hWm%+M=4*<0YITiR%6Hp969i$qK8#X;Xs$zl>A4xekT17J| zvpm^H?mr&C3*@$4Qk1{KVF5Fj!v=c-L|o_n~)6%(lX`@w^S*@b{W}nT;U$ML9g~aza!#JX`wDuleGli5Ku3kvB)X$ z_{yL42GGRW?+fa&9pEk>XcBX&xZRXn5R{_+?0zKH+V)$ssQu1JJSe{Nd`xI*P40wL7Tg$@9{(}$U4Pkz&5 zLrc+mhQ(P0r#>5z)+f0F#e9E@mK%#x0>7?o!evVbVa<(MK>$8sG8N!y$Ll~@vXRuR zG04l(;FSP9c|A2E%#$ehBTd{9 znsD_bB%Vt~(tzM$&| zHy}*lz0h*nWLrDu_xP5aOcdJkH)?zyQI^#g#7!enDhe);MqM;wN++#;6z zni7K)=D;p{++IxK_B|?+7~m|Z4j%?v*HR_XfyfymJp25Vi@Lt22EYG>Ii^NJi^hDS z2A|pmQ5T-6t-ug-itGg zgllq%mlkRSp5e`40d_ZS*w02&Sb&g#a)DS4iO_QS>`=BdZ9In4pszF7=ou2e%o&nupuSFpqquN_x@;td0 zD}?k@PR)9rsKDK1)cFi&pM0yPN`_i5JU+d%V%V1#J|8CfzFZC2%Q6vaLH+B!)kZ}C ze%{hIPy0T^$^8(B4(GC83WYvGiD*DQmf{}y81yfkm%+K?a?@Ybq119v{fx7#nz+tS zsDyeVTbTRFKG5MVcLJXCtqW)_6fzZBq}NaTZ#ZMhhKG9#qDY0x4;#lMi>6{!AMEh_ zk~cg*hinYHc<5-@M~1Pk*hBQp3WlQJ*O+m}+l^2K5 zy5cyDiA+-APmGeUzW;)S>;Xnm;5rMS9f(1f5mmG;g*$MtFkNB$8gNe^)c-gz^a^QX zkCV?4e+vg98&;cT`08;ilE!j*?45t^fZL(y=9HRso92nP z$>KMEabF7sge*a^Oh6CSZ%@i-+`-7Nt3VBA^g0qQJB@DRfz=TJ)+2^oY|a^GRItlg z_Aqz(okF%k)E<*9xgNFOp3X_beb%In?(K!1{8yom6-&YR6ul%x|5Pm_OVaF57D{ZVS|*LOb+8)-8yu+6B`Ih^uC9yHc^C z0&d(IFu!SZ2phPL+;Ebd^1So5gfyhM%90zBk)lA1PeBnm0*3R?JDaP^giBcoOBuDH z^7S}nLLr8^fv+7eL{niB$CQ<)<6RB!tw zj3`jF`H;IHEBk57#l}3yHEQ^s^)Sw9fRy>kq+cnY?M`NX%A4;GU-mq8v%371Cs1k$ z5+M+=OKGnBWR2r!-kvKs*penaMipQ3=g&I)_BRM8#vhy= z+yOn_T2#3;zDg=7)^dHauPykXb*5x}D8Cf%pC= zF?!#_K71cl`uV(_JU>Ox=b2ohd{CHL3yoxam`W(mREmW}uj%5KVww&>M zyhb|_$oJEJZQE*LOy>(PL<{lIGK$D&>VtuG(V@z;SiSn} z$H=e~C6rUe@|~lpqEj6C>8J0{TJ<}Qpd7JXO0B?Bl$%+4!5*b~cU7X?(d|z!1cTxi z2H|@AC2QYVVo_)F_qf4@FSI~|u_;uvw8ko=r4A72Zb|J5jQ9 z-f|GDfj9jT2e8+}=W=>^2S$|c$9a$0miy?Z+UYeJsd4FoEtrU*sF@8Lqc?1sG@-nB zlj-21D0kYKePIbase@IrWI*0aDjq$W3=|q@hR3!s5mN(QLrlo@M~NXOCvuvtJ>o&# zmY^jwmP76O+dTFC(e6JtUX1Fhcl+RY+Htp?svx~oVa-A9pQ7hdGk)1{3qH-jT!%Ei zDzZQ1p3agUCD7$2dYt2nPdT8Ut%*Jui6_t=KYj!I7*%?y|5-|SGyy?Dgq6ls%E#f@ zPDOHExvntEK+?+J&t6lIrM>h_tbgz~3JLUTHFtF5{E($)L7I7q_{3HaCQLr#RF%t0 z@{~b_JqqnxlqL>#JR8j%C4dJ#MM8d2u05hpjq`-qB%i5QQ8bjCiJwS2AD)3o9kcRT z8#%*8?b!H@H*|lqSu63y-6;R&o6L+io&G=)l|vKomTLOI^$J&hh2?L3>U9NT7ypw} z#p#_s>e6=Vbxbx|Sb%25`F&~Jos02b0_jS;@1`Wep+TR3{UIZr#%bB>v zIr^m3X)fsI_|==rZF7w^wtX^2^-jBNd`7JpY_=O^S7$@%61ZrjTj+gnk=dJ%l6`Y( zl1YL|*`5b?KiDO11k&&R`uFnk@0W?1A7lc6Y6q|cY1q9}q2Ua1R+Qy3k#Mt2Sl=Gs`=Dq$(|LGIP*n#=uMdcPnod}T^v4FuC7gTqX{P))?!7FbcqdHbpMEWhN z-7NjK@L;KdpTG*)L+bhE5J_W=Tbgzw)Wet2=YR_O%?<$T?R7v@~*L%-CzUsV1b z1v}IRjA^yVd7>a;?{Mv(E_$pbkT2w=kc`_ny&s=D`ICD-lrzW!PTY}k-?170$31!f zs?Qg!24z^dt7jtA;hId1VIr~(UmD9{r*(dv@HGqCe<{F3puIMvw)DN_8B7YrLM5Pn zN2Ls~x#5ub=(h^2EcImq#EUVhxKa0Vmto@lWK4gF4KiTxmBRjG1if{@$`#$<3Aem) zuMNzFa%}m|c2y?jrjP&29^F)`4%3z6lzaZ=QX-poJjx&A*G^KUs=Sp)P}zlPk5*p#-*$@DqEl%;qbxrpTqK0z1@?II6g0i)fg1X zwE4z!z+P6IdenHfmv=mqvtr4gpUxHX>esM7pnUmWON3@ZWYG@m#PV~G(??x1Tc1Ce zoZw~Yh5nox9^haJyNr8oU*yh~Q3;n%S*o(iRdy{-IN+`xk5%+LYOJU-FN}p9{YTcF z>3gn$x%JOLHmJfb+bUPQ|H}A^y{mP!Wd0RvC40HWSJMojS4B41iuCViPUw19Y#o`N zC@KAYz?Ni^`tP>@8&dq5?WQSZ`|?FTLl8{1cIDpkb6x~2DVw0231L4xQc_ckSmT|8 z|0~hv+~t|6$PE7TyL|WgrFV6<57##@iU@Ee^lGXx6NZ6cSuOz3ecy!FSk9$1)Zm&1 z=i?FtFWl2O$@@vv0V!$oV$}B$<-n;jWYK=g+g#3^oy+}2EZ|TsH}dW2BD~MjU~b-5 zN_7_C)5yHd2hU@X`#+uo5t05l7Sw&(NKZCLiWtMuPGRqH zF)C7yLVrkTTg}ok-W`QD3vmXlR}7{^g)SL!Vvnh|DYD_<46F(+0OzZGxN^^4w&7FF z9aMBmO-e<2{iwnN6M&5T=^ab_~nIit75|7YH|Ob1Y(kmEjpF8{i6F@hscR1u|2Dki(8`;W>9u|3H?-dg3wxp zM?&lUDw%=ZDI*RQ%`J%-W#P^b+}z?WODwc^^l~g(libI~3K%p2aHcN+^l%07;(8xZ z!#Qe>?cShl%cIZS+72BS*nQ1tFODbOovn9R(=F@VyjSVJxl5gk>XI|9(AcUJU2;H; zvP#U3e|89Z!?Mb`FDJezkf**fc4vJE!nLOq^6% zSxZkD_`;Uqs$9{_m9|2q+4igzN7}KvzE6I&d%^1F!`q2f?uK;=h_F3ip3TpDIf0k* zE=C)cz+cT+@m?z74teowH`cunF3fXuum+;sF4@!ZJkD=DUW{Ko>1N>H!hHG$rq89n zu35;)zck%W(|omr9CTH}cie@#3KiRmv{S)?O^ zwF_lW=Jx(_;(y7pIGSSjx22;3i-q{(STu~SDT*~slUZm1XXlTj*-M!cQ4Fy7hB;QC z5Hum+siD?Igq=K$hft)ErLGA(@Le496qhPGlLE=e;mNNP$fc&BJRJpiCK|U2g{BkN!1sIko9om#4O=GJUUw#a z<**eI+F(SD=sQ{7c=*KfeTbU^+Ds#aSE@aj%=K=g|7V|;>i4X)m|@Jo<58{buRdV7 zpYH9BL)adS%P>mec*2`mW>T*vZLg3M7_qv>tij2DzU+{?X{+? zGZAw;abP)5_K-DeX-_n}OJH7>*siDf^fm)e{+KB74PYko*BosK%H=&95O|`uJin>R z7}FOTVAnQmhL_A?iNHs2QR{K+3*@+Ckf1_HuD#uQdu?6&x8TlUD+kR6K3Cn|b5uo# z6N^h!HJK`*(e;&OWzRePFW8_(QtlooLCiUoB!~DCxS+mI=CG!jU93j_DdJf{vd_pY;;+?Fcp zt+#%q5en|Q`~g&rjR?pWAMyCcM~jU;+Ei-E|i+v?Ve|v zWh)wEtRdX2=p+jcCdl{<8D!jb_hA!Xsjn#_+M~|Nrm&J0=pV!x;XY{CrL3 zpw~zpix5)waUUTAK!0k}b5|N$A4~fd3Pu`TJj@~cLBHW8oW>_^b&z=7RA}DEW@47yK_$fVre2y<&aZ_&a(zvpeo~OHf<^v9XdQ%`YLiD#gMJ?e7*gx zRYbndhQ0*5BtavlHJ-p-Eu@|rkAl=rS$1iLqiZ%+Rpspa$%Ue~TZiS_E*06erlOj| zU(!`@jgE=F11+PDU!blpqo`1A0h$>QzIF2LCzq?w)s$pXyp&I%u42_^34I^lzTdH) z667r}Q;_7^_I3cR7}3q4ZMJ3n7QL4rOMdWLmU2zoLU`_#xKIb1-o%Qfli@3@>A-Jt z&&R^)6E9nq{(vAjqjM2BUO=Sj0^vMKB)LTP&ypT*$k|i{X&e8IdlsgOSystgX0h1% zbINu!d*uzp%m3W0>Tb%2G(&m-k0f7pYGz~KCq4E42U9qrafd)kDs9s}O+H9J)+gFl z@~V}X>f0J#at)rGEeOC2eP24QCXQdYHMVL!TW|3qyN>r%zQw7Z>9tAUBR`i7^5|c6 zm}RU%SkYnY)(tG zmEXhI9V)onQ0KIr$d5gCNoKfYZe59*fBC<62+DpJ2MJFie#Jc6N|KtYWbF8vImp+c2QcW6)yCqA{l!;>=C-6pUvA#&si##X$5Vr$k{fc zg$`?Na#L)S3_AX>r!j>nEdYx#F~T31To&(bl1O@SYHTR4F1z;gKQLWdubv(>q>i>! zr12(I2Kg{;#I=V_NmL`QG=;>FaxHM0VXx*3(tvt?A@8Hs$9)~ot*#REg~Bdmb`SanYOj? zS~2-L5rV0V0{UwlJNs_CPmij@T+aPDdPZR)m?;!*W!hPyg}|~Ygn~4$aWu8$N+}Zm z?L4aPOg$o{dWFUjt+_<(o7ji5pkImVaR~N^o)=wweJ;ozGT(JVB9mGGWid3D@OpIMmN@1u`>Y!pas@Y5|iUG8!C`NJwQt9zdhN4$j-9x_Ri`!Fd+(=skwR%*GIt9t(<-@oNl@$p^}o~i1Lf5Z*6%fH7VeE$ zopIOu)!U?QfcjB+kESM@O_w+jO3Qs`!de{QqBUE!)60$<^8>6wAqZ6mf1_XY$*m|a zo1VU8x{ra=;38=N0yq*(&l<~HE)pqh&t7kLSO>O<6kjtG7Pb;bG)anUr4Whn*qiis z7KrB?Ab-6){^h|8uaYr$4mw&}po&*_WZck|_dbRQmB)*SaBLcjiatfW*iIX?FMUk! zvFb|i!2%oz_lL^Qgn{!R*bJ$Cz!jyGDH|~*NJawkE;>4Cd>`F=eP(6qZoq*1Q$mwK(}KA!T+vkyzMit}TWXv#s#iZ2IErPST)e#-tiBsr zmih-4aa=6e%um-G{vBlE4BWKiM>#CJ0AkY}pJcrGv%vT70WC2)b5rLa44v~2;|?OoB#Fr}Btf-Y>Hxuy+H&c{5kE*yY&A zk)m2Ra?mquXiaRMWPqv#*yRI5>(_pA#>4TRlH9KB-eYizOezs$2&Fp?#TLdZ)SXo? zdN|vjGw)u1;%Qz%qUdw72YeN1K?BBq|URTN8!>vFo3&jr|s89t)V8#F9C+zp~; z__3lnO-N6J$ajM}hfNCUApF~WN8mqZ*Q5CY9dd^ly=YFjInkxxV)o>kWfEwTYkiDM z_P+7fLgJ}?pwL31qJd!VA#4{eUQ(pI3w%-3=*VI&*+8D(kE}VQSov|hLhqde*F6Nj z8PK{u4W(oWc*H4UvThg2@-}1o?#6Le-)|Mo<>pUv{$xr=B@Ny;0?cz;@;dWqWf@sK z088`TZ={MZv`?986;9jM&g49N;`uX9H9FPU1lXJKmQ&_aLWZ$OTHSv=4<9tr>-fjV z<*aySbrO9^YXJ0#KEflDCK;kvh2U(xzg4kgAh+ti|bHo)P%USmcr5V#$DwPJnVbZV_d56 zB~|1&gKHJzRd$+TamI|I2*Ul$jFb_}i91nv_0*OpG(JtY_v?>f^MRFPEZzK&pB^=K zcVF&PWA-Zv_bmcZZ!Z+l@BMpY@@P0#o|?AlEpvkkqhA#01Td`IW3kO~8oKdb1@C#- zyKqgQ+s}?cSy%Sw8?TJmkB)42f`!(>KNfBisnr-xOl$!BLYt78CiR&Ih7)_=Um&ZA zPs5`@4@DW}wp<|vHj?q!Fr0}iVD!sf{IZ9M!L~D(Zo*;HfEbUN z`5a9OK2EWdM%$?&o5WiiP-#&6+E(V%@p3zj6iId*d-tFx{vWlDTtT9l%tx? z!Eaa+mX|o;{XKl~QEXvFtV?_>96y`DZb8X=LYO%9eL*6)M}wtU`F#y|_waikhIHOm z?WN1ddH-Yquaj#gCmpw3?bnn1Fv%h0iytE=V;LaDeOE!7$=GPXnd6mDOETh5GQtL^ z`VfxNWvm=#u*0~3?*Z7XPZ^b;+K(JIu`(TJ1;6bVgw-z%%r@06KD$XV;#SRiCb0P& zYMTG^WyZcQtoq$=+QaP#8?()UV6Hl^FPT`@+VM3g{D51JU`i>B1Ag4BcyboYD-;>d zCFQgtnMluYnHjw4E-d??Y#7d9>H{Ff;OVi}RaNGV!CHk2g~M>kYwLMJxQ8UZZ2xS| zr3AVEPVgp_cq(Z(=$2&3?qUY)6e@7Mg;WK4eSw*1DJu?03s{EP``fGV<1N7T=>+I&i1ttRhF!HnPohpWcn1k&uZJTHiVT4RZ3> z(GJ0nQf%U+r^m|QtUOOIe-=5IKFO$xDQqnJyZ3I45---?H#d63T>Xdw?X!p-} z;?EB!aBgN2SRm+^eDFiCdPeWIQ9z$~w$L>WR^jZMxp7ihm0W1Ec67tXqk62I(=i|8UP%=LoTGj{5t)9jDote3)&5GD&muFA-m?_xoJe1r*h-Sll6&}4 z-0!+(1e8*xZ*z7mneUwZKAz2KP2wcq+IE9+D*CxAAqd0+}fR zESnKFxFeYe(=`Z3f5_1(J&-V9@?`^sM~)NibWO+G_!?+R<#f?Jx$J$ovtp%v4Yw5szEUr&qeD&fv!`!ApJr>|*`+bCoZ@gvJj`bEC`(WxUb|gV=E-vr~P>pRZe^ zH2$(Sd*IElF)q(9w-oqQY)lrvbIz@ zBxLJp%+BPOznsGGip%Mg)(TO21oGt|l?a(nw~X#G(nET68D%O;>EvcWRdsTukrl*veWS291(xJi>kk?N#3wrW z3aX2FktT+vELc?GEZWFfwbZiM?y?}?N{{~*i@DFG_Ir(RYh5Ng`~BKH?3{?UHbHk_;erInE+Lm>5n2H9665@2x;qr_Q@v>z^QC@&i+ z@QSw~a@2`HP#GK^bxGC#xfGm@Fvj3=-eaHd6qUCj`QquM`*%*orQaOtNpwsg(&^fV zcg7jOeg9>JJ{LVtYNzttkf zcyRqf?n_RKM11DuAkepp>y)J~f4Myk+rmqltw#mTM>Wi9{rcrj*qtCZ3q2%e(*8c3kNIJ|kF+BnChBs_<;LSYkubN>w}$Mw%q2PoNKwnl`fq}{ zO9?{gSj)nMQ~A1}CShHcG?%z=L2{Kurl`Lqi(#!$H>*2T49A^1t2ZFjU~eLgY#LL= z#TW4%?nu;g87ysS6>HXwOI7fUom{|5D$rRW$gtUZco&})LMSXgx7t(vr^9J^6)XE21XKA(>YrdIdwZn!>a#B^t+hX35)F~{*ap>NlQDdwL(`WyyPY}3s2 zAz7kR2?Y7eX$Fp2OYSeBSIlaMj1M}Fit20_1ZPjaskukN)LtS}cBGeEo}&0Du3k@1 zOFwBeMNjFIcP5TKx9|~p0`Bc=_ip?HtguQ)=tIDm;iq4@4BVN9LK6@gA4}N7ei;*! z{5%WXOg<~EuBd9u)$H(acdN0V0Sku8zy3vXcCQv|A_@w5XH2=T^;*~!}Kkyk+JkBeYsQgKyk_v1RzVW?@ zo#XIlU4d~3&vBZa7?UXw+vgszqu!F~}> zwe7!5Rj^aZd}~O8Y`j_m0aq2Zrqv>i|9ETl88CbH8S5`N^>fb`HKH~@l#BkcpK=e+ zXSfXXy|$h=w`M(9{>$n=3Q6uFOB(ZXDzA9GlJT;P0iia^ryRi@D<+$RAG%t-s*Bp= z1-%6udq`BlgAO2#+G#!qj}#lYo?|C~X%gpOA$mQ@AD zs5D_Ory%qxrgqcOyJ4OkYF@uA&=$8UZom=8l z!>{?gnHMS?VGUmxLZ|;BJvg^|d=UFXU}n0}N0|Xab%VSQ+K! zvgDQ6<`>OU8b7ORu$u7H`Y!_V)Q?-F2DhLPqh;bp=y5PSDPn8s9lJ6}rMuS)Yj*b6 z2QpVQn%pGec_wYe|0{c7qd!egWkO^7Z??$kySlSXV~_d< zj;A!!j8>Q$(+_eM&qNSRiGK#U6^x%>FPl_;6We}eO{i#48tH%gEL1@Gf=N#e*wV}# zcW`gyTCRxu5dI(IQ%k45uIKvsZ(G>!EF}I{S8FA@^mpVXg;3sZ>INu^&j3;xwsH3# zh@PfXkdvIWbQpt8gGF8F0MatWQCr_fZ8+(V5ydVr#^J3-U52$~`xgIa{9`kQ^n z6v)fK$ee=(U^O3MUD2=CJR070qs_$chx+O5G+3XC`aGl&wDuU!W8T1+DeCGzC+ws* zJj{KqQTK-NaWvcQBMOegw5Kmnu|}@!@{yO3({SZ%ohTg7ReSZF2<|3F=FlZ@4k_z~g z%(Sj;>wcU%IIM|=vF%|v)krwgo4dj^!`mCrcj_}Qr%j4DredW7QnY!Id2A*1;l9~n zp0x^&HFKx`dig$4M~hDp*Ka0<3=n5XI+FiYO{qUJH&Cgts7hEgdGG6sQAE?4{R90M zTIScMx2&4`*_YHx8jm~~aKC3V9ms6dZQVcsd(CRO>7>a_eOlWq`^Md{*S7QOwE9Vvft&e~1OlYpn2-4~`s3 zt8{$)qvHQSY?win2oNQv2#YPcmy+-^Q)g2FWYs>3C5`GV(J24XQZimsBF+oC=m;s9 zXJ2SD;Iu@ltnG|Vw89JwmKO5}Lk7KHLxFt%nx=|WoK4kDENDK?6&RFKu9TwfLKZXT zCWhPy^nL;^5s`cWdSP8-B0|MoPT2p$M&>5~ufmvk2GYgG z5C{PK{y%L<#0G@5eE;ZS-(Dql!V}K@Rfg;9kTv`8KLdH^FB*jTv6U1Zdh<+)DYUJ- zPVKyQitC@k3B8R90~y+Gr&LSa)MTI4S?)nT54T9V!Mc2<_&CDg6rA z()pT6wu~`T3+HFhjjDMAR^72T4VkD^<5_~i$2@q|#zv;9Ly}Uko-%A$qxXv&0NhqM z;tc?NDHZ&ZqC=UM05P7unip&g1yGmZ`7YwOMW~^blU;m9VJm6ts>B|?^ln-Ho4jZi znb?GXgYec^f9%^$dziiE6^Cuqn`6T{0C)~1(3%%d!^g(3R>%1wwzZZj+J4=Xeljd= z+!H^{@hekVEA8_a?nf5sM$cI9%zeaWB>pJLCt?=Xa8#TcO1De5oB0`a%`MqxD zfUp00`-aA>3-7B{L%BB!8+0|5fCDaGx5b%1tXk2R@k;baZ<`D(zE*B_$bnu*39X&E zr`1%pn+f+)-W+{<=p(c%?BB7dK|l)C&RQo47CEIFSFIUIh=|WyO(Gh3t8{V~C~qJ> z&a4a(nP<(H=8FK<&58jm2uKYsN2Y+LC8O*=-!D*m3bVR2@N6S?o4|;#`h4~VCNf-l zLW#9WLZ`B-+!>Cns`*H;^1yV;xBp-0*VO-!4rim*4Qzt;FfhP}4UOP6GASBvZu<8l1+`jb0J= zU+!rXY`hX|p4eSr4*ga`QN+hsl%S%mT+2i!Gn!N)Jao8$u(WU9#@J)~X+7gVm?mTt zxGI&nDzW8I6#DD7@4k%&fo45c;Hk0x$?cO+Q{(LSM@zQBUXuxw9g&H^WBbli7E2vH zd$|^==jPMpl;*rX6qX;ut~dx|>v@9}r-5zexfVtoL6)&Z(dAt&l#`>M^?@P*Vhd=I zHGL3kNVnzZm8J8_UEe`xQyttCxrf)BpAORD>G?JMcpEH53F|myj=;29sP4(~<_&;H zg3z3i+gtphd)?&=k%Y?fW*+kGi@^{%LkNT_dteniOZOY?A30x1+%4e=);CC_Jor+1 zJBu>jY0%_>w(`9}k^SxFauHy5xQc3-38Zf!fWg3yw!7XmMlu@)|MQ6}%U+<;3Rc+N?2b^Mcxp-uNEv7Pg zubT00jgi@rCRo8zD7C%DWZJwr6U@a-RSPG0S{h*!X#oj_&N{;t5&ir+Mh?6ltcn=r z$ZJ)ttCQD8i_5zoO37RX4D5gEh0LAH6PKH1ta zj1Zi%sVN+qg8cb!YU1l7+sslCJCQP-$%rLaLk_31At&6_g6DS~gV_L_R^XEJMjnk7 z$?JKg-89_vi-Ay%UJ_$~6x{(;3~_s5<2%lnB&!Ejem?6~H5eokR?N<3MNf=3NQUkj z4mrr)r11oVd3<6wNOb7`v6c_gF#z1lfrZfsA2QePx6%*ry4e0UgLi*eU2H!$fTKUc+Z|?Fd z$Rv{cFXE9bYbc5EWGE?R4`VABuR4kyn|=J7XnQL@UuTa-&~o3bnBJtzc9S#9jbtA! zGd^2oWmi6=<_wt~3X+KT%10;KQNrykWu(qa?e1m1s}RTgV!AKAs(tlMGE#8X^&IVu zl+D|Y#mCMIR64OAt0W?{9$PCZPhx$$&^r!EKNaf^8*eX-;f$^QO5p2f-!-h;=(75E z*lVwSTkr0W3ZX=VA5LGrZ{cY}VYpoQzS=Eyry` zf|UXe5+5cr6d$2+K$hA(Vm3B!wW{SzF4_NO7?fh{^Z+^#w1FKHS8JAaHfOT zaKNyB<8@1|*uO{tpFcIbC80HL1J~mfQsPxO!_UHas7%n+VqGE56!X3-a5p{c*3n zF*1BC5ma6vU!>E>llH7w>Q!5pqiZv%NXx3WYZ(jp;;iQxdEIre zLU5V@;!a{i{CyNM?l#-c8%nV$+p)W>w?3z`GjTR}nzZL28)1EFZGw0j&6C`17z} z7IcmqMRYd283O*51E9tRjIDkPriqz!IhEe({7;{f;Ek*z58g_-@_z|8{9O%yaYi3P ztgd1UP|Y>CE3I3uu2^G#=S7rA8HUObYrZ_E>)e|d8+k{RXZ-93062x=og&G)lwxr& zTUB}+AH8ed3-~=^r`lYMRRBEpiAr)+!2r&L55Hn4sp!I22vZE)%H+rk)FD$6QkPuM zdJzv@9Y~~RVh=q0afMDRs)U^j2W-mPcRD?H`jaPU;v(s`R&8%wG#S$yaa?Y1KL5B( zyi_K2Z2xPTJlQwm*;&YY4FtI!nb4nGZJQ-yj-YphGAza!(Vl8`)qZ^K;(J$-wz<(u!M9s??7lCKdhR=XEBmhD+7s|Yt0?g7Fec^l-FfmBDYMI$ z%#|XQ?D8YVXo`DV(D|=@0+@8OnE!ri?%wpvVpn(keNEK%2>I@Ni|5gMzO^33F<C@gGS?0vxo?y@ zKBfeh+UOB6u*q3kl2d0S-aQKws*Jj`^Pj8j%vfU#d3-NbxUNq+j zA_8+4*Lb-*uJ+>$Mc3c+X$B+JMz2jHtwr-8{V|L-ZdR6bi3n`(H=H{84<%P<-06qz zGM4kNj7_5Ty_~Lx)4(y?aiT(H?(wso9i8xboVIs&VE8|3WzPgh2$yT8W{;AWBTrVZ zXBAsG>PE0{*t0nLMpMRZup&Tn`%_x*iz|`0^o=LW9v0V6X`SDH`8^!Z_~m$9?meM< zV*PRPpXKyzCO6jdx3OAm7b6ThcP$PvUhrC*0SFxhaFCgK22&Cvi5ZQ{psy7Fy4m`O zf+%=%BWxoVBF9C{UHa=&Lr+fC7UT9^aI4`Vm-ATbQED}>BqirOv@|Vw5I5%Q;(Ha& zUFW6wyuRT>K^>>k>!Hnt!YGUJI;leUwAdI4Xmxr^TeZJ=3%*xu|H&_%#PqIlvBu|J zcbm2Zp6yn*zzu)5u_9idO%_NG(tO>TL9uIJ86#;mG&`9ZMQLnoMpo%5rfJs@ThyxO ziaiV7e+!>}!hgTTVV}g1g>w(NTh#lWVR!?q&^#Lb=ydAZHJ7I=aZ-IhR@NCMqu(Yz z?fZVB?fW8NGd|1p*I~Zhs{`Y;(Bu@_;O_`9^^K^NQ0C3Ps*L1|5yleo;*>sLF_JD$Rxc}3Cq+N@cqFEZtZEq>r%py=twjE-$cTtnFMp&bcdkEq8JeUTT5W18 z_emow_DNjCn4n&IY7x{@sN$qn*yj#JDy^T=$=WZ`@fw)g-ZDq=3WUOmVizgi^u)CW ziN{d9t?ZS__&)SVnQ3RrsYp1(n-v|Bw8M&r*A>T{)WHSLlGz%LbS#Y&=RLWwCWR=jnT3dKP&U92hlN=F}vmA`xq{3wTE7t4YTtP8j0k z?kopYxkXN-XCF+(!S_Rboc7!}o=wH`tvb(0o<28yKJ`;}XVK?+K!1_wZ?=jffN=R}dUm~}*K3dzYM!jt%1Fnx%&Gb34BxJ+<^wqsrMdNdvu&f> z>w3_$+9-+WzJImfqmD|+*@C~di_UZOopgUa zM;eh&kVx%2IUI~pujm=9IC;-ODYTr%`3h*=W5n8BF;XXwY`f>B(Wn-sZ+o)W1|j0v z0*EaOWt|4FILBt@;^ojVZp(6{U8EX=c;vWj4v>w5N!cnZ zFy4j?O%^piV)BX5?hmA7@Dswvn%;ZIv*hcqNq94Roqed;%4>HwQm}Q!sSx)Lh7RkE zElXDMdGPkgS)aTUSrKw{;GE$8{$5gX<+nZY=y|udw+1#BI;%86UZTgaG>OkoFtzQx z;xv9UGjuv!8InJCwqec0jAW$`_gIhPAhhAcnD$u2`cMk+C7_FOOfI;Jl}pJWsphgN z&|F!h{+68g|3zX?8FUDso8qJxz6SU}H{2p+E7e4%TadFg75r~Zq4F$2#PfKDHXx66 zFA^eTypqJC)ZTGjHYcc#y#P8sPlwnlNeLD;%7J{hZ5s`fk*b(w;j;=5KbU=G@5#G* zgYAQ4n!nsC&4?mth2Ez`Yq4V{$vo2#Ko_wJT{=usrYy}|S~|Mf?f33!7)qjUtGQVb zqHL7*i~PJEPpQe<;kvXzBK4jN-(H)Mjah4M`_?Nwx*)5S4AqIyvt&w zAe7u3iaq`7{b#BDx*%)qdnbEgC6B+!JllAnxH$xDlagOtCCW|{Z9pCE*8SjEUKpFp zZPI#Ii|v)@ck9l)az?kf9|KiltKC`AuPvc3N@qqMo0_+_5F6ucn9~!Z*9@up0unqs z;2Gcl)>p==l(D1bXdu*_LkLEyYam-H8Zo-11)iJu&nk!mHmzF!ez(+X5pUMoLn9Hq zYvfiuW#kkP_1%k-dWeDHriY|fJBt*9XTzhaak3>Q@GcsJhDznj*Ae>&J;w1+owCV>!RAa84+7GW#x9qJ_U_PB3)7?o$26 z#5JCR%o$WgNw$9z7|T_2ZTT#_M`8%Xfs}g^D&4n5vJfD#RYvzXKt}>3xLSxZG+{y~ zp;k}$Nq}HGooEgPBWD%Ms{r%St)yVz+153&1ez5UEy)M;^O*5qdQvbH8Pc9&r9zSJ zG_FPnk_Btm(`w6}o62P{aJOmpCmaId@M9c-I%8LW69DS$=hc!v2^?$+^x(piJ-AGL zaN(#H3cp)V(r^hNt|X9RiO*ed%D&T&Uy}omdUZ8M>uQccp63kFNe%B7#GIEYuG`kC zTkd=%OpMQKYcAulycks|1w#&Y94+PgDFe~$MqNHZA)({0^OkXCL`SWbYl8-n0y!Oz zbJ@}+03L0+2OCUL^>prvA_)N^8I}T+CV4npUj3p)>Rw6ALj|@ms^Q* zr{M^un2{3^mU{F&a=^&NeMTZDpgBQMWmME@CrOAlc^sU)uvHz`s60hTDR~Sp@}-E= zNp0?TEJ{HUGDB~W(wxS_4E-CXEaX(&sYht6dsaak9dOutD*UF60(n*774RxDw45;( za(k7tQU+lMx^+*x>2O=AY&?5t|EKY{Y^@L#Ta1=E+6S91z7_;o3o`HZ%3BhAJhOJX z0CHxWT$yi)j$|6W(e(TD5mR*F%LAw*4I1f7#R46m;ntEY(jKSKY0JQ~RqtF^qXT<_ zwfzI`k%sB?hTMZY=%$jj7n*t8_AVR(wn``o3y`OwO>i|LJPysO1!Q*>M|d86)2jaF z1oqAX0&XSxW^7Tr*Tg>o$3LO;i;CSnhka=K{-0h7#mfzj_5iM?1i=z5+T#pVTSs4n zZ%~`oy-{~ImkwnixID{{##&q9Y5lz6ZrTk3wKf&v_(;l_e6zluc*rX(cQQ$e+7)ou zU0f2ey(9wo5sX6kt8wJdul6JSJ+gz>l^bZ4pw-Q3H(Q3@(WMWz)Z@#ak36~K5!`va zvOpV+PGvcI)ONbgFvfIk{_yMQd&Tt zZ9mPWq?>O#N@7S<5+IgpP<~N!pB`J<>&{e{x)nj`^?XMB+JKszy95M|Z5@ndrI?gC z5n!kBxdi4yiOQRuyY3Y6kyo*kZbAj=|6RsP|MBy^Lt#`7PUsXecG>1^r%NaZ6B!8R z{yQYgrb+oWfIArxT)qyr1!@!rYLB4WGL4 zNW@zsQQf+bZ`^>`{U_cg7)&tb<4$M(29=b^FNYzk=#$N_!|@~5shUCnHYaA5 zycL0{V+42H4Zf-_Z$A;;VO_FCh&1X1n1mA`u=m;`~LYDR038llcuYaV)+pF)ot zkBHKF$RB~=#&kp7+1`F*n#sRZbN4y$(WXN(iQ!`>kD1imsLJ60)gXO0Bxp^OwnxWq zqgMCfmh)eji$}H5d3bW*(G`foff9a>Yp)9CbOg zy(y|~m6FSqilb*)j{vv$X56?*Dj&6=ud&`ZGHL<}JVo$Uts2*9at{#&DiFu+Z!(c< zPFiATemtFu2( zFM}Aa<%*w~Gqx0=gnmi7KaN&XbgpE=V8+K*F%$)8$Lru>+;YIb+E-AiA-zk7a*F$L7%J=r5MRdkF=;v3W zyLt^hDa-~9z8N??l*=doCT%`fd~`d+m^Q64*1FM+rwy1{Zx=pMU}I_P6~uC!1}z^? z=CRgR3Sa*WjZ;=ZnuYcJRLUMyp{Gxz@|CruH9Ux&f*Y(SIlF|ME!0`~1+I`-tmr!O z?=V?y?n*4zJPCYUBhtM_J0!VGXf*Eja(Sn%jz)s(F|E2c2E?dm4<-^4JPgqj3nbXU z4_By8R)N8Ugo4v36DvaDUt6j;KB=^%0JU`dpndFHXbFgYwA{&?L52j8p_$GQ;^`TZ zl7|Bv2mI@k1PKH28RTK#0xhFgwK{wZrVh%$&$pn%c_`mTcsq~xv3OBGKGxDCS~JlHXX7bIzUBBK~FE?72gbZU`OEa zNZRTFf-bu^h&HuLaY`~`FGa~VS?R<&zyB~_gUcbUN0zQbA{gGfqP_fyR;t~KDc_dq zo8aS4@}b5nJo4#K}BLDQ>3rImP-!WC{r3}oT6rlJSa!b#%c{*2BNj?lC%}wK{6QE z&x(F%ds8v8CIu%@uhd8+tvbQ*cJH=&7rOg|~! z3z!O407+azK=0J8HmL-T>qR2i4V=+f3b!>eV}4q07npgL^+TN!9TYDrxSS_HBTm z3K;=7S~Scf(vQQr=Glb3;}R|)uEY|c?CA524I#!d7grIN+{q7|Le_ZJgh;0l(PRkqgPf=cqw9`x|{dEM2yYse!5of>wXhM$U5l8yYkK2NYVw zwh*&UsjWLW#oKgo@jnGIctWWhrMHAzNzxeBRA*yKe}a=zbI7E+fcplzf#3AbxT%+3tdIuw_4fFm^gzr%Bb#jq42X#*)1x0mO{64 zZYi3l1$nW;@M=N!WnAPS%;yZC7nyY%D20)VG)#1hYG6ei%37I{UQCZt)g!{`YYaE1 zWqV@l%~mM$-ci*8>EW0~mp4i=4m__k7ir`o6x48|j$I`)>CkAjNFpl3V(FP zDr=FQZHNiZ{oVCU(VS-ZDX*AbCb(xUh`+o%vQ0IqRf8s7LN;HV^WT=OWE zHIcv7`gM*SDIxQa-u?)d-H@1rQd{StC2?8)Q=*Hz3I~1NZ}&NeJJQpQYd)5i?8fCC zf3InGzW>ojqd+8ilp|?SG40ehw~+^--pZXi@bsMF@4H1eGj-uM#=$z4uTz9#33ZE$ z8?OZ4%xM?!4?x+(oq|^=cvr4}in|f-Nxz2q$)m|S3@9#H7sw+M;`e`d_y&mT1|(3O zFiEVG-MoBgxG^@}`(W+Q=G6iccMwZbtq3jYx~0b+6y?Y~0Gl8|PHqveZwqj0Qak_v zKL2{=Js|u9ooG8cgH3@5nIzs>v9z_Lva+Sp<86{@!ND=nq(HyT#%I zjjNA=(qH~P&DI67BX3`T!ZRFuiaY7Y$9n?4bUJrN{`-9Er)y`0>g}0YQ965b&lkRF zI;Yt}a`k4n1&xZ{XJOsIc3)IN*TMhAoLq824dz#1UF17t;(oVhs>4(AO7r~|r30s4 z?`a85)C_6SMelO>`uBOPH0a(J30Wtl_Vt*7Gb!2jc+zWO#xw4r=N`s2G5i<}%XQ*3 z=c{{+hy5oZa~deJg7s(Fx^zux+eZ$a__6f5n3%o`i&UEh*3OZa8GpW!7+e+fs>m(o zww{l`;4hpPKffi7sw0`<&@tj(6eaI4*~+y-;&jJOIrRMY1pfYcXVx4wa#cY2-}yfj zo%G4kz>qBEIE;nm^~!*bu`Juq?07kYE9L8Jku!V})8)^V`{Z)jehn>NyCSGU%Fss% zvPc+7qsgXy&im!b6;eCbgq}%3+GrP1f!R(bw4%t3$f+DU+1e@j51a`jN?6OACc4NA z5Sa6$%DOR-j1KPy32+T8dH9}bnX*Jc`VV>IhM0$Jq=SE-Bk#mXb8fv?^{U;9<^NX->g87uDNpqb3HFAN> zyPVI)A2uMLSKQ3=kp>3u&C@QRn{{aZk0n_*pZ2d<3xkGlgNsg&eU&a zQQThUaxdWdCg3_9xo>3;v+EUxtHaW6U;SEgT;@(@ z{%Sn?w@ww}k}}Kl{?lbwc+i!Mbj#b13}k-*Wm= z#m5s6dK3SE*0%hsYz}7^@ID}|gg~Ps;3Tr`L2=GG!kQvW!xid5k(US*u%;KXo`5f% zedVbK#21@rdWWn~i1Ff~H=^n&3^8tNXb*LLXo3LR5u1d@!Kl`N#nz^ow=_| z6$E)O(~vI_al*MPO(I6ID60c=NIbov`4}hb*Nv0xT|Q|w$5N_5^N)=kSgD)b?beP% zT4JOe|C?b)v-X_vs*Y}qGBuWD3#3W%nDMP~Ti+Gy`a8tZn13Gh9MCr?ERwb}fr6r% zCQ-BKCl^y+O<`Taq`Lwh#eG>r7YV56tEPUrLg}M({cqhL&6dV)%X#byv!iWOT&xy6y9Yg77-AIB%$F^8XM3J=^RH-n-s%Y4@@zyE2bQN$ z!E>gE0|W{P5Y}dM^ouX_*+K+1exP5p!V^rIP}QIy@`xMk1~bY{(P=SBNEKA6*2+>r z$6>T7XU;|za57}2oQyH_C;3sDE%X1vmwhs?EhZ+(x2`f2Q(X;T(3+MZ@2kFlRTb@(s-;&@PBCy# zg7f`Tz8a=sk$AH_aoLTs=O2)z!Yo^3X4LmHo}>ScjHpmn-{hs%)qPxO%=IcV14?2R;w8EM96#Vze)RgzX)hoz9^puhI+r-CX?<|lq@X$Z~KPkP7l z^x-ub(##Ok+1`Vyz>+VKPd=!fvp=nxmCZieTDPu$4&a@9Q$UPkbPe1S>2lh24UG=S zOWeag?KybYtkvnLvBODPn2UI*rETx=OS&5~!ob*zIly>8HCc-!__j$JWpG$w`cOL= zPjz?&Uzmp)TB;lmunm#)yFz#g5e8k-y zPLJ&;Q;2_#*9?D``25TP-IG{eSMkfe?T7DzI;AFu@k%1|*r9+_FPOiPS@FVQh_zm2 zw4jZc#-#o(S;MnQw`w&*M;*=#C6L(15@21A_gp?uR-mN_1TSXN5rfU=-H%yYVj{dNP~$cx%DOs{pM(e2;31h_aSvBD=iuC zL!Fc-z+D3me+q+{gX0N$w8xInpQe9~{I4xI@yj8Y6U^qkR&k%DX2SLBnZJ+kx7RHKC2tXlp$OM-G^m|IWo@h7ShO%lh5cS#LpE5Nc zG6`VrJWO__LNL&<-(5XrditB`Qx(J4Lk91wDED@X8~s9)iEc47!Fxq4#U-qme%4o2 ztoIL@I#yWBj#x#MgvUq>sq6Y`U7RVagxvW18Bgx32ysG^J+zeW7YwO(bgEj_b2N6^ zX0URA&BL~fIj8#BBX#eCHLo>4!3pVuJ&MA~P#_sYAj^OjR6uiBLfh5l>}X8f3a|&m z-B%-)htei_0tZP8r7SU;47S{l1qu-J%1ETbT{M&=R+4I>swsFW57t-{tV7TD` zJLVUfg}lN5mZkCZ@z+^Ux-IO?W__Dy(2vF%VX}|_n?V?p=*kc2PZHpq}eFfNvJ zz1!URjLAvX<6(#7=kRDB93LDH_2;DUJCk$qgt9ZEVa!3Yn-n^Jie!Y2u6Gk zzHNlU+%E-s$dHzj;#3Vog$C17u_uv^tasg2RF6`LCHfD#=#6vfqY4J9Ftx#9l6YKCI{_R9`lhVFf=_&H&E6Z!!{!Tp znQMX3iWsN@smTCeT4XD%=WU|+9rXDxqsrTL*?+{NxX!4>j+t7I7>VtmKy&bmkAAQx zrpt#QvJ8z05CN4()E1f1&cHc-!odZVp+2`~3U6Of%AR?OuwUiL5ojl-MeX01i=uCM z%36r6$}^9m{2Pr;T%NfZhgp-cnk-!A;KCf`B>9@r6^0xY;M~p7d?H7r z7ds_KhDy1bsDS& zWv@33`EGld*eUbxt~X|>Nq;=yry7%=gmAsA;xoKr%Dte0Q;_=rq#CCg*g`)(R8F?T zG)zAMT!26xVK$o*xbi9~s%4)(&z-v^kc+4O{EKN^Mt;ss-iydrtd*qd4n8_m zDgd$!07Oe+1wdpA9csKj^cGW(H+1;q`e{9$`Miqd<^hgf{oIbD!1(Pn?6zs{4Q;J z;huxkj6lFuVD|8O+SF}py>Typ?K3(qgUT+j90xL>C8=eOEg+~`AT*RFvR%%JVD1Zu zOwng&5`@jI|2KMqA|G_;2VvQ#&Y1$vjP&Kv9}ns8`!dhqnWa|fivwsKwP55qQ&<(yIh5vLTv@A|-iVELGU4%$9#-H(7GnfmPP&XWBCn7yC&QG!iR%v0A(_*UHec4bd&uK%+DGI*! zPXF=xz_j{+=2zd1%DiGbk<8Q|Ti9c&Kv}+QH@n*9Nx5tnK+C#!z7D~yw*IOmqYcweU>*FW#DBY2diaF?_ zdx%zuaS~4pB{5rNJlpVx>Ttn`c6X@`0nnT_pPw{7;ouWy&4B9Q6}@;Ei?6kc)e#dpzo7#WMkAaaH{HI>BAQ^Fo~T~pE!&s4sH#)XahomXE^`c( zQ{B)t@bfOhnh1@M4+y*RY9|0nG+_VsnZG)^UR=2$E(i3&!zc`+WmeB8U7uyUn)G^B z{pk_U4UQ+;d*I@5(Bw|JQ%iZKe_W>Rt>d^jn-OVpwK(ogqGuS;xl6538R)W2y)Qfv0I9 zDW%WlGq|hxUa~!$;`Zeo1-}9|ejJeRkL=p0v6+mBi!$k61v>uCdq0je@#^#{nEkFq%34D}@ zC}H2}me+6!zA>onEK`M*fPtVCzSv~X4hGJnq2ljTGdXxGr)SpiWL_fPrz>R=1CywN zBwyVq3AHpObGy2_tg(3x@xnb-5BPW3q=QbQz4xrgTJ8vDA^2&@iaj+bSme7 zDh=OXrL3n1G@pe9FTtr`hagOEd=N@@wJCwmN^`R{{@b(LVSh0!orqu4)P+;jq3v`d zE0kONd`=~xv%N$GZ`7N9)Qz7miccEy^sB&#N8dn?C}YvcenmC}kx_D*r2>x(V}N*n zBg2)zN1yqFQozJ(o?-OMIl;jogkeXuSJ-2*cf!~(YmHdb$ncR6Sn85Fo8GniLEXiR zx37*gURG7w+9`EtT<5YC3;SkgdvEo6eZHq=y(jpHYY4OIbrhqGVQ0;u3%---@I_=t z9PAPVHCYJ6Y5TayWLw9Lkj6=Hu9;AA;JbHL7>P`5??xnxo{*7Fr4}Xv;#p z`#*}##h>Z#kK?=9g<%+mvCW;3TcVWBu(^g1lI9XZLMlnw44eC0%Dvo@gb<}tZSJ=u zm!ec|iBcgIeV6_G{)ThT=W`yf^Ljs@SJh_JMQ0v-9@DoDj5;b>L;E<3)ZDB7_&zw8 z3lR8;2@It0GXApu@j=6Ws}KCnp!YlTL~m*WZ}w~46G8RweEVCV^X_^4g_u>8$=t^- z*3+F|-iA5KR$u{AXJ9KkphwMqCaJpCFV!~nshbTyxcbyyV|&#Ho3WDf&kGF%rwcmYw(4&xbn~CFEp%d-n9d zlM(8V65i{n+nb&B=}tG8em}Ei;M;Y|zzq7CmGr&u>I<`TDsEW`mpT_o;_JPc?o!E5 zFKpd=CiLGHZfpxyDZ(y+0{rNMQ2jZ=za8EJleMu+!E-66R zmnN8Z+{rxa8QX8g&RP$87!YWtS(Fg}%JJd*mVsV87A7Aet8`v=ZLwhP0WQZxW2()f@TVef+D{ z8FW6IuBeU*Vkzo`$S7;1=Usn{+gY!EI1`(9>(M9n@$a%<1Gg_6pO3VSuvv{0ROLJ| z99+&)(D3jqZ)N57lT+)qOu3@_ul>8-4AE`Bm(Bw4KNEq7>fzZPrMJC)LU*=57mI2@ zyE;X1AaZG@AgYFZbO*7Glfl!e^4ZU~Qx;i}1$DW^jQ>8DvwV!R2AI(i(G;bG%~N>w zD@hVIRA{Xgg>VxYeH!z0pf5eSuJnhx@rLWlP{v`9QFYqe#b`%1QTtkGiH)IMO?h2S zoo-(bl+v$H?uN6&J{rJkLyBdhkN#@B8SXGY8JyyBW3LcgN3iCgAO-Bq^_X z>%a`w1|9y9C072%fF1PC{CNq0{}KXJP>S?pV|Ic|P7`&oFWaH}RDW|KlgNlW2KE!RaS zpFQp+GeH^$B|a%fFl}w*UBHKCsu3a6@5p~INCESEv>V&gumUZjsW2q#D23?eF6J!M zQooMtF7GCr>!&=WIqfYzm?5M?gLU(n?ukW#?mFwRT@0u}4$mPZrssgqnI*!rzl#=y z^MER2b+79o*U7&P6W^h%y!&XD&>bN5+puQbq{^}AtS; z;S_KWsX2@awvu{`fRt8BJ+AoJsd>>G=gvhuf6 zCgZ501?niZLkJs%CgOzB!rqQ{M4I;)RVqPJP?Hg81x6dBIk_oolodkHgfI~fV{RH8 z$TcVb7=(+?F)-N!$`P<%DhwDxbZA1#hn9EyADU20gI~q&so8Ey{^<$T64>mJVMnDZ zX0oTU!PVyOyQ(X%6W}%t>2h5-rE_^N*&BJ)-C>QPtfCS(Jn|u&DH(H-+3WD)aC4N* z$=@x&vkq>@P3n>i*s_XCrorCDx;7E9mkxa?4ckBD=X>G7Pt-iGv#2i;1a{@-vo?uW zs+)sc>YzC(cB)Ii3DF4-Kvi935*+gTixlX^Zl4Pnh!q(P$!_Tn2 zIZ?FfOJmlZm%gn=4a0ohtQVp-nrWrqTQy2IC*ZM< zQZ@1O!eq?X2kwAHAolZqRcC9YZx34NLAj}w#9*;rP^h$rOLAQg<*06mwdXHls%4y% z@?c@+6&;rYJ=B(lnsh@er$4YwzjL0&b9Wp0p+y~oPKGF#o6XMWz%X-29^Bvker}H$ z$gJY2dSK`7PD7kouQ8ieUiw+ur5p9H$VUK|HGF=%M1?dj+gJX{g;t{Ud(xwGe-Y8^ z>rv_CWTIcbfsNpAUU@=km%vM-Fr{0}vzZl7mzNfX4dQ7nVWwSZRIW{fi@F%nu1Zwg zOAdlFOirM2JDdhn(ld08LRM-`iE#w$E1O+}L)GJQ{k^%dn$^X;5bBI+*OJcrLaUZ< zY~5W5#=ecb%))LJqi<9h_c2;&(w#HZeD+(mVUOXuts3Upeo3B$jG@)+u5ko(DAFH7 z^Z=H`Q$g-$$CzN+eOORxsKS5017s9{qMP_Fi#pC^fRHcI;m#d8K0Y?kqej>9Ve5lC>GFd&&yX~ zPQgV-%YGz>?Pm#mvo;I49WpCWF~cSu>`oNYiJCi%O3iqS9OY~e!zRSdI0!)CMbP$M zostx)mx2*PivN-{5&Ida7$b^`LduUs^11`h2%q)=lc&Bxp&|ffWD8wLoW@g8vQ$HZ zbW=b%pW?#Qb+^9vQ-*KI@wPmZ+iMkkLO7D#NK?z zGh_D)?lzTk>)_0rUFsEhgZ)1b@o9!E3}7x&N!Bk>nhj(38(EIuUuwSb8Q2}C92%`WxYyn-Y z2^bcdx(juS(`-LXcz*aV>^MGUN967xq3nYK_s%w$Z<^tmbM5*i_=F#KY*rO9oZ&K$ zd>RM;J;w`^6h31pTq&}T6(SSdOg|UGeyYg~)Nk;=pQDfi3g7@wPs_;o@IGlm>&I`@ ziL}yJMVDUH8k)*~*@9{4Gg)w^aG`Fvw&>Lrp`K20@m8?f4DQ(;lx95p-11Z9csBQP zVX$UL*lcH5Bv5v?&?pHW?_Vy;L!EBY(_V(ID$6;>(u)G<0UWZt%U=1S+IfQ5AAb|s z#Cy1kuAdtzo^&{~{egy-B-8ZX>}=_|*{9ucO-0Vn=HBIAkE<+voNLSw9M;d#FVfWB zKb?2jKv*ZI>2o^#;bQ86_EdQy?qO}gGXqm&fU4PAJJ{3WAxbrD9&9k0NW23ed#RAA z$-iUS+JzR0cZb;`P@QFZ0xGG_8GIUK^8EO21&Gl%1#ELY*uysKB)%x2{-0JX9A4Js zEQ$|Dl%(T^PwTkbnXzb0AU&z+iD%3Jn+&oi|{T z6~qjW-V|;J{%KAJPS7tP+y44>MuQs9B1Jxwh@Ya}PT+PX=$EH5I4}lE8wHc4bs%z} zS93XtW<9z#;EX0DsvR)Gq?^{}CdJ967-oGN=n5nc1|1bV-5Sfu8mp+vNi1j{!~?s@ z)vXu!qrlBUJ&9-ML`u}-l%0`z2C4f~F$(F0$^{t2?(Q;|sM1PHy|%@)DThR|Q<@@$ zu=Cn{{5PRkHK+^Y$YSr_~nAB)^I~nrC`xKSCib@d|wN4kKg~obu_gYNQZj zkA=(v_^2sRp$X_jVCr9iKR~&f%;zU?fo;ox7sg4-oFtWghI?$)R{IOF!lVR1a!EVZ zM1Nc>Bx5Y*jkX^XSaK6wdiR-vzEkdtQtf=aw+`$YovK!tCkuJ5mdvs4hB|%B zM}N!GcQ-w+fJL04DIu8&C}1iN92#=F_Z_E~3GZD%t)_AOlA%U{c-Z6A^xDLV77pfW7#_gq6W- z#~;0+R^N0#IN@T{rzwy=8(3EMHDju~EP7&myh-G5e|GTtQNHGZiy4PbeeK(REV|k) z!;N>W&0f{upSppr1dwPOO%gAs!chSkncYmN=IM@+U+`{>@b5Efaj@6)o@H! zD}bgjfCz!Y*vxB>5U};mlarXJa%dDr?nfLvFc}UlMwK}viA1Jc+=4OFz>i%Hl*rkg zkQ*#a4Pei9na5Pb9J)vBKw$jj@zJ@5e3@FtmLHhidIkWs$~Wi2_BY;9E(RckpTMct zA8LduRJpp`Y=N()CmO$9wDi{A{Ks;m!^wE&#y{AL_>)+G$Y$~ss}Pvup6*k~Xm%E1 zp8=ipsgltbKy+R(XaKJ^Joq>&IVP5EY{@d!o3Rtzpiq0(wQPpBsDgPB_m*{Gt09nXoFy^%LLZQ=2N3e{a zTU3!4apBghvHEk?{u6~(JfU^>4_`rSSRZxx~%od=|&9dN-lz*`AT@J_C+Q2DLW-=S@oPO*$HV_Rdx}jQggRy}-!) zX?`T)0jeo1{!jmaa#|+u*LX>7l4m;;J@NTIxh5gx-DQ)Sl}(+w4>HRuGWI93MjvM) z@$l~$F{EkI6JZ7aRIn};y!!%W9%8Q7q4C9k zg%z8!cS!?TWG=kz?zqLe$brK3bI#5J`Bcptja?=BgMm-F>h$d_#GWeNAsp-yoKah; zm7gd0AD2kf+LPJQ3cQSz90xd*zWFF5b+cU=W+a@4@twa`S%JyBA+Ie=249s+t+11l z6}=nO!BEvqRNq?cxC&>GSC3pc+1M}Ch5vIp+cdShS&sa8sV^_AUS;#? zG=^r%2Of!hUl`>^5&Hhoso?VUhL0 zrLjXMRSsjo1{hRfqRamAa~R7Xl0eRD5PJ&h(*xEsFL$@MJ$AE$j~2kPQaSwmlyAa0 zB3satchih&ivTZ+fG6)25+wil=bjRg|5N-KJueZ_bSnXqWa5!4Yj*4`2lQWgs7ZV4 z-!1T|=^9u`;CxBzg4TT#)G(pXVlCWA|-6+_A6f|1cz1>&hmE<8{N? z)D0Gw!4nf9SZt}iOzU)7)pq=pP!8B~?=4w#i6lav>mPmP^28!CLTFo0VOw`9xU<%yG6avL0U26Q?L{?8S-n{%>sp5^1 z?2Vi#v#fIx{gyZ}Z+S5d(=_9A^py)|K323scsHH;j6E8jsG0~0wWgv)r+_z+>Bgwj zP_uGa#gF6PFh4-xfhvyo&qA2x0_|eTi4y-i$U}+5dti$Td+zRGZ$V&NQqdWR@766FL zP*Y=Ag2l>bh($e)l!T|Am4YJzm460;8EowU0}R?l5zbn)b$oUP zi{g**fcsk)N5y1TXOo4Lyhc;W$jBrH8G}+q?Ij4Sa&$WKHHT6=u*@L?^(?kX8gyVI zJ)>uoEh1$yn5dG~r*EK4g8$nMd4jRO#pOGlTgldfDPSVvLZMX`x4dKBnq)Vr|;FKBsxi@+R>i6A!qfQS%ugUx~B3VoE-Afe& zA>~tn)sJk7fSfz8r>-{W`W2I#4+yTmz4w&nQ7=)UbufJIoq^DI7O&WUj-UloYeygx zj(=P(3gD<=3v_#~eGK-{XJ;vD`Q^rk@0JpoF;txbk*DJlw@)~(%>F(i+7>k-bXkWW zb4OEbFnGY?mse$zBK+K9c>++_be7UKkz1a)c8Y&Q2z5LrD@pO+eH&55oh|}vCURpT zTd>8RXM_I*RHo_78=gzu^E!Tb@BS`Rm9!rH^<6#X*z$Zfc%Ty^B|KSPtW$swU%FOAHn5icw*9Z<=-VQ+BjqGO!w z#u0_GSU-1?ciFFNcd~EnJM9ygEchjE*b><`H1F+^IO=npRCnFGYDXnmQO5et7l>@z z;1{t3<3)~t%V7_>)iz70-09xV-onr-xp&2(4gUhVL#h6+FW<}B=XM;=h_pgd#{T{6 zd>}@0@qvuo{qH5}IOM)HM1XdYlJVsK?4kS+i+UPmv}idK)wk~zi1MGdMF|B|^|0$M zSRS*2gWrD`*(q;xb27?`Bvw>N{>>^+FfIqd2bn;aOa;8z?vQE#7i&5XapE(j`nG1X z;}=_`5J}c55SP@H*OjHoY(z()7dz0Uwc;O>BBTY?i(9Iac(lrFiS)KtozGQI)(gjb zAt>6N`>bhqRK5SYO1=6ikW`wmaugIIC{D9AHx?lg;aI)PAq{)*-ZMi`Q^AsXy6 z3kvR_yS~L|-Z4#rSr7LbwDTmoqPpEqMTt<5%Gd2Ba---4FQF(QT;RxC3*`R3^H?&r(Cs)-Zw0HHrc)oc91GLE-~bJ$4_)^ zbT%dEm$F9(h++&PG@QP4_Q2=@`+@TLMkRg~7`vTC?q8D#F=Y7&B7LjZeuHv}~QJ=0Jv?40aTckJ`j_ZXF5P;G)*k={#3FZ|MVg!<7&AN5TZk#qA*?;74 zWL_}lljf<#e%I29)XD?aTJ&?h`%MjnjA*yiPvpEJ?Z9OmNP|&XlG2|>$5;tenoxni zn~&lznU^OZ5NB=wWbe+p?D_}~`N%Gm>twPDY@Ny<9+BuFA8LQ+wQ{_UxZ= z_3WI@(BGl6v#Iu%DmB&5=dP4oFoTFVqE3cQxVF={}ar zT-V_W*na7f_O3AVf6`ROZW+$er}6;k*?m*tXg!$D^&_z8q%vt52cp%VgN$aY5coo5 z_iE)^Ihs}a+VBkGpo(k_M^>lhhwy2)jMmQD5TRFoB#2?PxcVYa1vPw9Z(itmgrmKe zE8<$j-+g&8cSIccl)7RG*n#5IT6$#vTPl6xMx={VbYiG;E#r25Gz7%hZDOT>C|OjM zdNH{(qAfg8O0Q1CH3gC5%$e7Z`{#8+a(EV3IkHD4lcTHTRSh@W_dV~8F_DRyw02+> zA~-Ah&B}T8O8;T{mV{dx;?)9-!9ypnbYE;V5N&oncd&2u3+_B%$s$*yVLetHW+X6O z3ibO>T-BISJhYvKiTsK(nEH(yMuUwSdL%Yaj2}#)T34Hm9X=Mb7rDP5^0RWE?$1ao zJB9U595(;RzkOWdrN{_P%ej#))zz{9_9U=9z1%ypd=h(yE?Iv0?QV7rO^Ch5Ma8uw zEW0Fi%xWS6tST`;-D>eYzU)B^x zlVo@`gq>KEPr0j}Areo9JRVI3#pL8CaPXoYs_l*ulDESEpX6idUoe7-+f}CBaz5v& zlK!0X4QUIRpouwD+KbXL)cnqFhkRLe7!1~Yz@ z@n+KP9*+HgvUR5}mQSW~^ z2Z)qpm?c>Fc9Pl)z)y>qW)q>(S+ML)gsYU&q?BM${yVP9rCKnT5fQH{p@T{Lm-;vR zBHdj+TBrLaB&+`v^O&aNap|J!`WZ@wofkIi#j8@!STH4CxYk&1$VIHCkZVmH0GJ~# zJC9m7F=7;Z>qp;O>Gb;RdNg*3g?P$n{~FN&+OIX)H1h}j2a3WsbIIs}R!H8|3ea@9 z&%ziu=XX?+`<`MBJ2Tl0-0aiteou*%l`9?kh3Wi*C$!g#KdqNR?~suhea|xbo}5Zm zOd7!rzh9Q-^!R+RNMc-uE5w*3^$nLT8#qVB0)8&T3`%8yUUbg$_E7hq4v2 zP2U=AEu)T3^p7Pe=xx18Gqq~(>CZ&JxO^I&HQJvl*mNdEGV_HzP9}@y$2&SAxzuus z>?l_O^8VVbL2GQU5WSQPNCd?fimWk2>R-G?&6Lo2FgN%2t4U;Mo&_EFZ{Q9e^VxN>FzYvnT`}u8!>YH?ADrHn-DX z^>~b@NSk&``BsyC7NrhA+9SK8RY^1F7t0%tIC9GrGG6a?EN=1?;=-+V;M5D!{oT&K zmo=R+O-Hfb$EstwVsXSas%+gQpfV<(EQm#6RVzi>*h8jnBh|;!Oy{pki>C3BnWOCZ zSE#f|_8@Vj^@+V+#U;RIfPG!29D&(iRGkP;|du^c2U+DSvZf;++`wp z8E`K~j-m+_HdOpZwFz(Ptuob!`R(+5(={ADr1y>0L)@2v$j?_EPDupSgI@Zf3pYphLZP&ld-RR(o~cX@|-S-C=D1GvT~sfTMk*S|qo z%g-yPd|od;H?2B2X@x1sM=$qSO$`c$rw!O6j{!DA_cViswB5MTaH1slHW>q?jFeTT0%ulo$(~>XAxqH- z(jrfRf*N%gS**en>1c{>NAdYjKnrO~tPe<&Pd*d-SveqEca)HptCFAWGi(;sjqCTv?=+@Q?m-%k3UgnO3-aq zd)=5&3|%;~+j(+cdY9viozcMvIZj#4=r0)zo-97xlWvw2Q29b<_v|Efx)Q%b~Mf0^?PFD>*P3am*ZIdL_YfZ2nu^7H)9qHKXL6tB- zkJuhiESM@rBe`EC^ty@eA+lYeAw&WGQN!tzWBSmwVZjCQ*He8y@ije zd`NYIGQqZz*CY>=$AmROzb;zptzo&=P_o$Y=lf22hulcgn|;ykW#qz?uA~aG10&AV z3ig(QvE$m3&jGBUYu$eTHJn{NTJKWlnn8LUeIiy6F>O0unQ(k|9;M zIHqZMX*uW*tx)eX`$K!m4_KL7bsFF$;qQEEOMBF7?W*hc!jrgpqc3SslXg#Rh+;2o znfL_@P08?hZ*t-kH|10a9Vr7_cldm|Fy$L+=tphuIybaFZte#yP`|Ko*}vLm@bmKF ziYBqUpDe%6`|4rD_fR^^K107!Wy&%SL>2@w9!XXWN5FLoPj0&x=t$Vciat}*@>$BimXkUl?7k%zr2FLuVsoB?pDe+v zW6;(fW=yTwl^cArGSd3xgTkN#kg_pH1v(=*Q7R=~2%MYj%SoOPcE#NivQ%~Qaht8> z)6tu}<1^RZjFqaqXByq5O;{n4q3$}2W?&)`+143v{p2g>Mcw!ijPi``ao1x1>qdNTMvGYSE4UkHZG5aDlZb1 z>n)YpWNMd2yyRD#9q=Km#zke@?^lyvdxfc^#`SdpI|BZYw!IdG9hP+U1_Q%R#BdrJ zeIUAJ+v;>|NU$&#VHuTZ8PCx;tnhM+osKLViB(GtZ=oJSw_R#!_j{MIB|EzOhPuA2 zQhc1lX;?0&oK;%M?KR{-{;t%$^u`CWWlJ86?tTmCM9{PZ(U4NS``>j)fKwIUR^5Zd z{rBZ$xdXQ6+N+!RhSHrE=yJ8Gpb<_*@}1>ts($5Ft2v&gV>OWwlIj=JswU5Nl@qo^ zjhIe39ioN~-4s!F;_WAje(I~00DDZY4fF-*#6!7t4m7+}u2%S^BqtS> zR;HiRcXwxG@k200y|ZnWr%i0UKT`I|1E?-otQ0|$Bpf<*Lx74Lt}cg*pjk>&^&H7} z9rps)hC%Af7b)_0odRC-k>fondDkYB&RJ`IIw5<&tgSDDmy(#c5BVlm=Ys`GPph5; zGvh)3bq;bS=+erb37lg;e5qb7HmPnINoY?K<<6@c8U3}CWQEKzl$fJ(7QgG0XTSZU z?|-#VznL(k=xbeTMU%njc&AD7o0WM#Hz?sbD9|h*a`hHj3`%osLfm66ip~w!VdKIsHIyUZsC&Df|LTQoMN5*dM)k^|PM( z6oBsl!p(4-DuIWn5q9I+h*6#{mIR9-ieMRp7@|Z|oX8Bw{>PuD&|b;WF7#)*3f0wWug~X+J$?z5Z-Pjv^cIvu~SM9gFJ$zD1&OPMcS&Kkm=;8G9Z{nx8z&8 zZVZ1-Mg6Qio<7(gXI7cvaqEwKz38_O8Day!me}4JEmR4J6u#?|{?y+})(N9sL^NOO z&>)~%xWEkZ-eFtX+AKf8N}dC7RPEU-=sJ4h5g8rCzmgQtLR2oT2c4uF6UiQJB+q8z z(OQ?D=_w1hF^Ej}oS+^%;o1&= z-1)&y329H6xHSIwzL-NoA0a+U53imHrdcHs39OpRJ;J;rHkAVH+v~yCEpy>q2t?wMgya zFZ1o}cjO2qTw@_~4#!LgJN)mb;oL{DT4xFOUzH0tvn<4UhaA?|B)WZ{UJvn{4?@-kh*&B^FuaLY+VP@nm=a|@+$7pQ=0x& z)-WKCgKs7fi4v-yaJ|Fp%EWDslsqOL8Rd#5_ki|hk%}o0` zhv_Z&gbJJd`v$-Ld}*ZMC}`i3>*Iu1n~+3I=E2A4lQ}!*Hojt{d`n{$&%G2ml(+d^ z&?e{!TQJ}YuAbo^L*B{^ltUE&MOVddK?GXPT>n7cuA=_$o?YBb_V#BCM|IaIFLuzl zq6@fVFn3e$x2a3Rp&f}RfHc97(Ty0{reHvLNs_GY*cK(7MO|+0Q^dK>;s}#(mu{Y^ zxul5=Dc`0PCve#tgeZJFQL3%<5>+{U;s9GYxO2lYEih3&wf2O`0o&@r2{+q@B41ar z>a5(HQ#Ijh)u%^pTv$I(RJYDc|;_jT`=vJ_f$8>IbgZs6+BAFh|UqbgD;(}pluIgoErb2|4Wiz~d zxjgmw77^@e>|r*WB-4-TO3}6ZiWJe|Oo7sM&850fhFK^LSf~2&p0)(!%4~&9c;O59 zz;*aA;K_x%1lLav%atN~C(b`1^U#y0f3<@d==j1e1hRcQIqz@Q zc5=Yx{E+yq-@lKel)>Qq9$r2H&Ef8(F`6(12<#{iMCiWt9EMx`bPMURI+`ecES}vh zlWDt-L%7XU^#IYA*eR?LiKU9O!rPPrDKwryQxeRvAWf5Fpn-(;9l__KGqtvo#gR1- zrm%LIZPJhiw5~XrsXf2JU6zO%#a`ac-V_uV2F1F2JzFxe& zGiLwh8+UFK*BZZ1nWa2+MKck;^XL=ZU6dvx)qOzI5((sRJFl%(Wpl5=i!uHpqv042 z*)I5heQfZ=750gX0yCN~c=m`d3mr4=xOaAqE}K-rL~IQ~y0-hGv}ds}J6NJnp4fR) zq9WpOw`sSth^hSVGNM%#_B@p*y~6;e8}JaQ>8WJBq?^A5%fTwoW7#@ynS}iQrl3#* z)L?9TdHz5mqTO?vF1c8K60O1(|0|A)YQA}*umTCDfmy4xL=*?EtRDB);(;bZcZY24 zbace5JQ~R2GpRx2ozqIn2&I4QFx6S_YSV1g5sdb|(0U z;YDY0GsrVN!=8i;o>+O>dy8kTVonuZ@*NrPF_o1T9+P!?=U3j67eRM}YCgHkZ;g<5 z`tHt^`?>%8+ox?e3R?LGh6(gPNajVbQi)~m&!D;DOS+_lXhhA$4(44JbzI~Ed<5;uDowD0Rkg9RY&5n?6R@i| zyDb)s{feCsq9L!CVFQr=do3Zh3!%xU^m_D*sMGpuR<^B^#sbEF&(34oYhNY?N=y`O zv)DTC{1TLtZh|E=i2zIGA^P9!gM}nr;dlv|{hKIRhJ^BE=Wak^RcVkuBMqnJU54%BTDXD1Ed^I1U8`tB;W7ku+Q?p;l$}&xl3!=rhFz$+!HRY zWG0mGf}qjTezZgqBGq%}j-!r1;v2x0;^WEv;k9dE+B1n8ps2vp$y|&;O(> zGW!YR|AsxaU4ebgbyt+phq@Xo3(JlIyjDA9W_~rgM^_ZrMHh4g?Nsf#vdxO%Hq#s6 z(2_>mWdHItaphrRoCyaff1M~t##kcDrY*M?5l)wE0I zF1#Y3>1MSG)zf#MZT3{>Y$OUP`NRAVe@?`TVqoRtbZ_HwnxtxtuFQk?u&-(W;1+A2 zKPUCTFzK+-KOt;Hozkx+PRf^QDvBxpPU#)G)>ub7E@{|EZUbf6FfrF_veAW*pw`{` z2UFefg9%$ylT{Mg0=7qM&%G3h^y()qnG=?vw!=wTYLQjb!w>c~A5s;~Gv!%a&#!Kq zTPI8a0u>FS9@d>%0j0PN2}zcCDj8pg9V}lH_G!sI=4TMl=i@1lUf zRH~bY^p$mj<9cXh2+>c3XkE;M9TAT-#E;ORq#9>d2lg6sQA3Gtx~~&vrJ>Y(R6FH{=b}?)iUbgZ*8mW=WT6| zN~-nsr|j=w0qJ9+=%a>MDN|mvW};kw`1F`vI7A> zvsJ?@I)-IFTcmN4+=u6J?o`m}Ce+ivH;zqzE(3}+mVM;=lgfC2$jjoi?<^wWIm&EA zvh|XTK(v~8@Z&48;!cyci_RM1pZ7>ijTA}DVlh!r%e83yAsx0{7b`BN-lb^3+9qNT z{msc1Ig71e0Vb3GuB_92+Sg^S#FlfQRM2R&ufME54ZH)?4%DlU zI5ULr1dZ@ONhB}`raab9`M?gF5pK+{qFjY2_d}I`!|zp!DZ}q@_(Arb<)V1dswaI^ z+cHP#>tF}thmvUPfv-O%k{p)aB?oGjPUgs%@Wk3It_68(TN zUvYeVAqaqJv3u>W5qlMO@rHd-acZb<@?#}SST9}}K%IJ=cz)@WyT>Ph7y!|^BESub z2rF|@KH2(W3zr`>yjAjtvS)=FbtMYUOn)DW=Zcnil96cgui;=ll(udL5xnX@RY@q+ zBp2`SUnM-g7v^SAy+c9=(13|6Ke+)IxySwfhdfxuU)nA79}BED7syF8;qQjiJ(a@S7=Fhbv!Wi5}Sn<{wQDi z6mz{xJi_&=k|m;dR=k6T%nFdU7-p0%{AampRtwe_kRB3&NzYU^MoAA1d19u?vX`}c z&3h01CEt!fveOJm#V-AEI{j$|{VfI-@v9hf(lvd^Cud9G!3bjR8pf0X%<2W3b>E$s zm-2ypp76I(FPF`qBgFVoK`EI^m5^)T#&&hltJP)#px~a;ynM}1LQOU2!6Ht(Q%;y> zznPNrcnLH=3sQH8B+fepS~}@SsuLT=GMe&+f2yUSR5ByKzs<|@Ov&Yox!A$s@kCP( zq~a(~JZ4CKkbr!YD8e$lRBEr;-F@>}D#7!jEQP?n6+ zW-`P#*5D&Aq&a(a2=hWQXQxNLc0|(Q529~uuE^8@D0$^@)T@q~jjN%baSm!HFiSXp z0NO#@;2Ldjb^vlwqw7FMS0!3mewNnzgW#YI<@%{VWZLhxi13L(?eRmK;M?r_Ly0;2 z(hVN)f27csNyP?%s8N#l$eoBUxU00=D18Cjsh23M8VrJvO?gNn&2)q$r_M=_zOUsJ z=pxOt)^q@Cl*=Xk1cQ5*^TkHq!j((&8rF6dSVZj3`cVV|kLyIYI*o2D*@M1GpgW-o zLxcr-X9v(}WL$ZVYm{~-`|Z!D**p!O&awP}YrC4D9jCnbRluW)wcPo=ma{lI;*vjI zT|o+)xYF56EcQ`}jqm$OQ_~Bn*284%N&LnH`Tc!hsXJwFctZ`-gttoN#&^@jb7y6c z6>mFjZBDgHC`#Cz%%5)2eSUxor5Yo$09G%;{wP_ZnEB+d)-X(C*y3#&;uoA`_ACUY=_fCP60rM@H98 z1*FE+oUBDnPOX!h00@Q@kI1l&*$&;`dT`7r z)zMfq-!gq{%StuPoizdSGDtd{YpDy$mwDQRCDvjq=^|C!>I1Jfl-=~I5izn88y-E! z-*Y7$1~qXGy5Y>H^QR1V2pH!d*8fFbu016(qI;t<_fKaooaT1&#OBFc8{#iM8-{az zZ{6;wj;3Uj6MU)!mogFP6wxO{-YruZWu4yy`Tj1=TAJ~1nwSm3I0UFScaTn)3o;}L z=0fzja~B2=J4KKd!71OndR43C5{4HF4T8+ z`hefh`*N46fergszcUvq?1z1D!7QAfb)lO&RSTKOIC_536*w0D>h--Ho*W*JZmH(9 z>m4M34!$=zh^sjJfkfB~(53@ap1QP!SjwcFzn17pvf4Ogl&j)&dY|rSq#0v@W!#R&RaPi{DG&Ta zxS@vN00hE=B5}hkWC!B;&g8QYw|A{CBtWeVeTwaw^YBF++G zjCq0gmrP4CTWE%`tC^PqDR6`v$zAsbJ)&uUipqS35<(d}e>P|oy0EJOo>!&+QjxlN zs6;<9stx|RF_kp->H;+n^Dr>dLB{_yWQXTPUn7q@1 zJ=}KtR2Vvid44E$a}kEXm}U3f28ZAK6L;)g3jX-xQzDw&GNeKF<^2*5b9Dmbw_{}8 z@%~g%V!{qL;jiU>;%@-4F0bcD757J%tIL56Jb!RE7n)lqzLI}d+_OWF>VNpeXK*f- z5j+bx?D^aHuHE%Z;n)3Yum3sgxGfGmE=F=zmb4>5oy9(RJ1JZaak`=t&=S5Rc^(#2 zotU$Pi_2nY&_lz!39WO4#*`A$TFFy&p%oWNCt~9Vc-b530mOh@DGAFTe0hV5{=g&H z{f9@-=Twsvu$I`75JHm_SWw}>E{tBXVsBU9WH~!2HyjpytEMVCZXC9cWGfh4J4$re zcqF)}(!p?{){0@XW$Ygi2|v`OH(aYvKdu&uxy%T?aNC`eY9mwb{b!!JNsQYaeK^&# zudWN>r#b*t*OxWomUM-9gv$biQbhll!?#|h>`f`o6+GNnvYg4MtGj(YdS z+BBZ?2?jb7ZGu>yPV**}lC!+Nw6xlk3&CoEfNX-MWm5gKP~oJbHieIx1#^SN+a}K| zpg8}bN0(Mwxyxf*YD^&2iS}Yvhe|DX0~;n!>!O#{cR$;5wGidXKmq=fkNgIfh4U9C z&LW|}_S5t2q%d{mzHv^dMwU$Klb}O~hF-1fRqWTYxQ!w67c9Li*E)E}te__XMmpi% z9_$waoXs1?_-GUzFS0E*FQ@+F$^;9!n?yJYbTsn~KU^qfiXqG+R!u4;*3sEyb1@VESf>zUlr;er4P zdiMUusLbWsgJnP#B7Jm+(Pn!*dEvGFsLaD#sxMzsO`$|#x~W_)p*+fR^dM_&$>>=6 z!Kka1-`Mltw-zJwwbwkMKwM&Qa*K5I#1-x>;=fqe*-m-CJ%-su7y4dzgLD`D zkzZ(jV~o8I$dfU%wfAieegBYuT;8)@G951~JW4@I^0Y;}^y^*Mw`soFWgypli9g4) zc`XRnx#^6zh1#VB%Sz@QUAf{lb!OPE!`l4Kjetw1fvx&I(ne6W{r9hcb<|k^o;PRs zbF66g!Ud?J6xkL*PS4Y+aW_=Es!%&B&Yb0SN>LP zZIwE-*4LB6g@*eF57-=)xmVKSAM`V2?&G!0*|Ev(iv#lQ37@!%$*~hJEcQP&*}3a2 zUFlZuzHgNtiE$hr*BJ_~DMbbGWltoV_`FmRru=Ta+C_ zNV3YgJDh!FkFrCQm8{C%$|w}cYG_gv{ct~j!}obU&-eL!-tX6I{*15|U6<}E z(f@Rypm+V!iwEjs2kxI{zR(-DRs*>TEnEdQCx#~|pGGgYr5$_=6m@=?FqWn2<;jR= zLf4ENR-+@=4Ly$42Xx_rY?f0&XB2%@rwpn*>ym`WuZQb zm#x&|QDCgp8rVI@UsZ!5fLUL)V%Q{5VZzLqh6l>{p_46qK5Q*V~Q4)8dN(iR|>BBNDihJjqL~paMT)7-MdEzrj0-m;yj)9)3CXr|0 zv1<7D?oMDlubBiR9vWSgoGRAnay?!A$EtYx3pAV#&EPYrWE8Y+X=&3?U_Hvt(uYDM zX+|V3NrhRP^RwFTBfZ89?V4yX0{aY}(^#Pxf++5#L2~mIa(r`5<<3X?y$N~H{pWV@ zrh~mz;M6``o4#*PfYR5yx$(DMws3cED|$wAQ;)?g0tP*M>QT-iR`txGk+&wqoh<)x z3l?Md3mhxG5sK`$%FXZ%4`E(7R7+&W zA^iOB)hmWEe>WvjR0l=rFWoo;tpTpP>oxQX>$c0c>eacV$9+Nc7UGyzTe5ZhH&Ocv z4CrQ9mVbrObs;&5@+qP{0bC>Wmd30R8LrOz-GWs**NZ}s6z94%B*1ZKEbecsLG5EQ zegVkM%IZRh3QWj5;5c8qh-GTUZz0Io7swiXZu&S@g8OKtSXqKo(_=@%A2*7}VhJ71 zmYG^}7vKMjx_fumQd)3tw0H1Ro;fjFj^09moqBc2hi*qCiHkY5sPTAdd=*WvSTw9S z$Q~9;yLruaih$;UDbgC9o5?WVpqu)!9U!)XZ|8nQ|Lt|$6@xHV5EV$T^|lxk7?Vu~ z{qzTf=U1_QNU(@9`e!M$u%jZHav5mRs8^=Fy&*1Bu7e&#L)RqY*X zZ@q5km_x_%(8-(&8%Lti>xGX%ywjHYm8OwVdrXj3FX_{q>RX)ts>Q4J=T#^A3DA8G zVo(b@4orh60tO#}aRPjv7$LW)!E?3)CT~9{Yq%d~m*k+0grw#OSL=zKl+U6X6e@3r zCl9a2cTsp(zhTfmG5;3X*nGP{qoT7~`ji|*5T_!aMj6qFJK})`r6uB6>5@DpacQqF z%G=U)wFPoQ3J`m%?P$?wwGEbZppUz;-5VUq>gjaH{yA{(sgLUvX`EB*cecE*npy*C{1O$;pa|}dN(!8EGlL*B znvB|KB>Z^TTcA#DFO(a8v2u&U8H9EdZ-(qyxEa_lx6(!m5fR&lpX#b}H-Ky_-e}TWc5AKH<21Qu;7@R^UZ>nGErG!&GO={zc~MfEP#i zMBESids=^h25fy0F5g`a!EcplNSj$=51NTpCy-^vhL9=WkpmkEt`e7}WJ}9uaz%bl z)?ERo*_1HnEz)W(kZr|J!xLS*_N&aw8fc!|9qD>Q0>+%28V^S+)6G6thS-ovJs(lN zl*~~6z;SaYdc+fD0Y^)BlF3=hPg~>RUa+d%&)7}Mc1f9bNC2m>7LuuPcO0%1h9{dfM=)?+o#3`w0WT1N%u>Hz8N2!H88W|4BA0DQ zz$Q!tZYJ(!l$wSlOo`udjL@!p#+KnbZRdDU_e9qzPWr#26z$X2M7hfwFjju3o8?Ki zY)wD=jR5>o6+FpUL!8|W!&(VFNI7>XEm)fh*D=^-VJl)U4AgD9!WF{g+&9m2dmvGv z1r5F^DBWJ>tyHUBI+fWk6>ZuwP>&>&!CixJ#`Pfv9oC!|eMVo(6DRjYc9yKt-MD>P z(j;!&*4eN9DO#pOh{_XEp|WLk!*{IvBR8LUex>NYFMfEs4?>@Q176<1w`uzgSQq!x zH?Pgb{JGw<^2Ga?$pr>+DO79=b41V zJkR%wtZ#`_Y46Al@PkvVe^iD)9fW5jE z@PVd0#-lO?LS+9HSrNm~Y+~~6d#nPjroy>)f5*I#_P8eNF%}g&riH$>j=D7)`w@iR z-H2u-Uy6u?k<7K9;*9C?(86WUnr>{GKa<1D}Fi&qY z528)Vq@(F*rPS-Z<*nu=ZTcyd3R*277b57QCzDP+Ip~REFkLl(>}Xns2@C~Xaopt? zKr(9)fkrm^x?pm*{f(I+-cquKMM{!u0B<2YNIHPDI4gAZwQ?EJvJ8!S4mdZr$GR|g zF5@rL6`hpL$V;JBBHxBp=-wXJqPd>aJT`4nCt~Ppc`!pySgU8uFL6~0RU~CR0?@(u zp&tA@DMVyj625_)L`OR1NXl@Fzub+3i!sVDgQ5LmL|TEE_qD$S+}sr&*;;kCbr=XK zadw5!&jYK!z6P(5VBr(v0~skMr7PM13itA@7%w|(X!C|A9DYKmdPxcMaT@t+1@C9L zL^xc{)izCLz1l8Py+gPd$rT*HJeL>r87BkxVu_;9I&&383yu3l@|+K9aVq`jod1g> zubm|58gTW#8Qc`?9R}_fQCkw%ZP@En&(pX~$-^{)Hcx1!YpNqZy~T{2D3Ht$Kw0FywjozK;)WQc zO?UGz$M8b~U4wG;%x77{#7wgAxpSUu>FX>eVuHQ{k#jBFVo0Z*A;^y*(LVxrzNo5H zEeRL=?b2s*DTAAjfi)T(XG;zy92fM zy211o7MEfqz>h~CAjaV=#@l9hw?3W-NMqn-7e_L_1fnrBzd2YvS&BU&Lf z^qQ5<1B$g_s+G^a*BDrg_8ayj)e5qydcvkVf`+6G+b3i9eeyy%S$%6#Y~P3~%bj6C z?u9$t(>^lX#N@F?iasa8@=<`=Sr$1NsZY&jp(vgfZaNE1!G+*6o$Am{OZwkm76*^Ty-e^nC&$1a}y&1pWLbRpR z({9s|8AHo0LS8KSD6qY^kHFs|E(T7n-VAMC#7!ClhL;e<pZ`|1&QwAcEJ?ybMHt7n4SxfjTml$DxupyX59o zFa9=8h4~4&(j-M21xi7_UY7sykc%p?506I6@ z`L!DX>%Zsi7V}6O74+Fqxxw8t=-#Z~+3WCB7>jnR_6jQpJNuLcG z>J;5}P<>B-rLDB&F_akR@}}DZjkGu_c z>Esv|dDJHc!`{wI#!jBOaR*p(X&T8xPm~$g=(ZZWV<&}~}s7;(FkMyi2uTARZ zH_RZwLwF9@lo9a~&c0FMiRxfGr&i#ks*cRHRWW-+M?JdPhp_G+d$**ql128n7z*xInj%{m7>)_Fc1Ib`S5 zkGW4PZB|QE!!Q5b-b|tXRP&P})UnO1E@e6t|*pm^xv&->wI`RWO4D<*9 z`LcPTwfxC~YK+;(T!U#9Ht1#DBSS(TLj8!a!jn&ZAku}3g^OWoj-GwvV)U1|nqnfl zKZjOJoK-5M+O<#_YTp&M=F`K}-p{J>Mqoj_&@&LyI{@=5cp9|Ab2F$w`CYu0rTYAs z_;0gog#(Q5uM{nY@@+K^N&2>Uv=e!Gik4uqRa0KA$aL@;@cQ`-f_Ir zf<5~q8sks3k>-CQeBXoD!dIf=U@hyYz7UmZy#v4G8Xr~D?C%#JeapGZP~F#_g$a4?Nyg4M!w0xe%r6pX(jr!@&+pX@%^GC^IiuY2ixeg z#^)BKV19ea z>2L=tw+{>Jo0mae(_RqkT~U8=^F40<7CLih6De6|W53ZTf%5({*^A)Vy>|W;4%RzP ze~kEgHNnL3HJ(5)^ygt~g z<-!Nl7VEwH$r8Mu*?CKx`hQ3FUuo4X-4F<#^QTBX&Ku@6p~smP9=JKrxjKBExj0uE zP6TJKGvrW~Y{RYxnZtyZ8!iRpzED$YANdiYqw-5UMs481lGM5O64)NC+e+@!&vr+( zxtRStkGR}d`g=byikCrH zOXYuRdh^VH%AI@E1bU5!gb92Wo!n!^eS9D3J)j{ukM65a!edf}Sc8sOe%6u}t5sXQsy7MqDp4CYK*KeB<4d0}|Za7(sr_Zq&n2!6$*g5JO!%rg9zFJS=)AZz>8G!Vrzn?B1T~WU$L4B`< zs}neTq^JD(u61_b==PG;`Sd;?PM^&GFXe}5eF7D2)XMweJt?6W>GmI=(hW?QrtC?a z#%N-xR7a-bQA%$iLgVQy|IPu&Ozz5)ke`ImzCU5wLpkc&>mTot4S|9&o+ljcJ$i6U zGWMKhcJ%i~AM~r=)*K)C<))*zhAmaN{>6rWe>`Q*%hf-2dryxUP5(zVmg?Tu|5)_j z{(mVDkm&vcWcQDD6|^hALsJp+LDkp73ubb%oS`~%>5qDMb8CyZH1j}w(rI3sqV8Wx z(i!rZ5AU-I(_1UX{7F6=rPrYH6%}Hs!AvFiAhpl*dtp!#bJdml7=I*4B+Vo--9Q|Z zo-S+GP@#O@OwvMHEMhc?G#oxE1%9PiZ*d3dDOSr8wrMIQa=Qnabxv5eo|XFVP)4A> z>7SYWlV`u=r-R!tb=hXHp+>o`MJU(9hF6>Wk4Cxezb({vwp~@Rcq*qz##-(xpt`#f z4@4d85ASCM^bc6HTbL}cpPP$5`NVk@#VRTII<&d6LT41r^yFd1%Kks$uFdw;W z6+GRDrVF4E=jaRrSv3^M!||L`IlEG=GC8!6ff(bKC>jr_o+LHqeHN83e%J;CX^DGH ztEe=ZSeRn}-JeQuLCby1b%(YGQ`e4U^2$$RHWME&ef0XSBNDxRpCr6oiDb-Gljf`| zBgqRiLMNdLR!_gA%L|;o!H114xH%45z3%CF<+LLozSTqc@U_TeT9=u3s0nL(-KIU~ z5z-h7?-UMp=VKEOn=L+{_8Z!>E$ZpOivN?~&G+HuO2kF%+w~}kP@VNO+@uKHbMRuZ?Qa8LcbdW4Qtz!?iwPjLW$VH435jyHF= zgP1|FokjJ-l4cgexu1vYSPlARtYyKxqAfD_;1$+5F|)NOG?64LaZPHU*&!+s_PEbT z>8yQ!i5AZ53O8XCFp>DsC9XrbRcjw!Gxy>6Mt3h>3VYX_*4R1UHr4&ouU^5#@NAQ1 zSmS?_uEWvdpCb5bTiFGqx=0VIvL)YqEvD5j;Jg>w4h-^8(4J~9Kuq#ZZFA%Gs9o$= z*KR08zWOTqCwEENnF$eGQ5O$SP1dwqr;@^aRGxo!9@^(?VcBCu$0BA8H^sV=T&bS? z!7jrA6f{%gPf^(5a;jNUh9#P!dIIsCSYwg6vF7I&Ig#vIgiL}%8(F1}Q}{v`VG;K9Sq!j9f3mRh+no#HL>#UOekWMK@Ch;6*$~a zFhFt#_Y$GVHZBxtPc$x#%mhY{?#SY#&@R=2h_O-9bgL5K0BYa1$Kf+fD)mz@bM9&@ zcr-nJ#sI77AEXcSOnX_W+|wthZ6dNUk?cnIfQm^#v}SKQWAiZp7MZu@;q`0V zFgd@(rt_nR2$k){D!kcQi-Qdm&fDSJy&U40uC+ljk+{wG=vRidlr2BU;X2Qx&rX(l zckk6$Wv>kXWJS!w$*EiN(P9F{X`Zw0?g@v^mfgFb52S>vuR zE*}_96*)0RWP=eoihH6eeb>Z+B(5@91f$A9s%hi$d_q0C&Ioen{AL(K*{>Z@-*5I9 z`5{Ys)t^0O{|$%BRcgk*V0JJ_-oZw7#JK*Fylu>pbAx0N-?OmSwr*WBu&cWB;riwUOqwMOEI~-D>8?@r%`XgNmqlBfoOrcj(=Dx3vfBGT z9pm)iduZ+;c#`Zz!Xx?A#M}+dyFNFxia`xEW;VZlV8c42kpplQUk$_l$X&zU*X^($ z>u*mHVTPwd#i0z~oFh$>mk4#w%iPN;8=P5$yK2P|-y1M_<)3X=fwJIOU=GBk@ID-p za`Osf4wf5O3HO3vAyoij`Tjm)G&Nen+xBl7MDh}+>4MI`uhI`8QmOr$oI4J=500)batUOUhj8Xja^3@eQ=cU;CgJXxpOY~4+B()s2% zZp-e_Rm=KGtFUXz@_=6epxV$QFGez4S_cgPBqR9Pf>8z&@(hF_T?lNkEX8S%)34}- z;y>Z>skNs6HDfn#odO z{W1v~@Vgf)ywL(ln1@{59CbR$JoP}XibUl@85JECR(SG`ESS#QqyKD0Uv{T5p+cWJ zoAgXwD0Nf+nLfkwEoyTjH4_e@ao&-61ud3{-Dj4iz?T3JOvAwpb z=XO(j<{Hj-%P|%K><(Fw0cqFGVS4xswiOU`B@MSHK})38xkxNZly@%6Lw+^diV4^W z=(&TRHF)Q5Fy(Bq<_mAp9%9dNLJEo&%IV|_OI|uYtrrgZpB0+7|1{_L@2Vk{CbBIj zvN^yOIFOmMlBoJQ>+gSeU+HB363dj8$litA^ES;~+bQRF>CzfXRybrSR{)<^zzFCn zKsS2s;aVZ?66~oR3=uten54;n6$ytN=NHufx*}U?M7kLHj*WGz{Wj4ne8u@_|uCjTqJ(=-RJ=gJ@wWn;jDrC^zOY!*^3 z|1+n;FNpnNiIzZvR0K0G985Lod@P0%@l3pcnPB-^#ln<_@)~0KO(O9p@}M)q+tb*| z5)b7id-_p@UTMBM$u9jl?029T3boNCZ%smH^J;#A4NFRDn!pAthZ^G2IMI&3V|e3f z1FXh)Lgr|8c%RCgMt)|U(Vcu%$4ZfEL1nOsF5I6b%f|6(Qb~VG*{CR@OI2-1IJs|t zU9(H!1!D@oj`Y)>2PLdh`LYGx0#lx*=Je6yR?#g!D`H*GGCE992S224pQ}O*xuSY)#Z*>1D^n-neK?cK>Ua{j-JJGNYPx|1Gx0xM<8sS* zcV1eJc5Ni#$ue$boBu{og=jr~D$s1u6*hX9a{Ev+UuVi1#uZF_Myk-IuJd%cXAZ?v z2%d#1`b{4`C#~~0J%xtuMJ+9Kev5c!B+t!v^ONf!4#ufD2zEKl zNf?c83J31+t^Ufx>pxh@v*S}m(si19y3DBjl=%T|P%%H~UrJQ`z5dx|A1WmQEXGxV zIpfYjiU3|`p2iqW1l)ua1}3|yc<5Mt*&nC$Fr&_{ivzhr@>DLG2{l0QTgIc&1u$)y z>RWy14xscuH!W(k{`AGtDt^HiLt1y=HdM~`$th5e-FZw*pVrPppQGh9OC*NT=_DFa zAH^DO4{n1(!?Y)$tlO7C2UK6rb+d5X;%buTAC+c zSLhI~>#;~w)sdz@sZpecWo8O7F&1UMtH}J!Ur6F7_aNG%1+ovP6f8K|rm4D@BOi=t z6eOIa7|G5hcut!|0AVKgXc*;d)6J=J{EMV}ePJ{KRr^S2(aT7^OgZ~mg9>X5vj6}y zWCELNRjs+h4>PlW6sOk}Ad1aV7G_}J-t2SwFNJtKAf-5XUto*|@;7K9p0JPM*$u+4JZH9#$CI zO@3Z3j~+NOtk9nUh;Dl)W}95y1t32AG_|-4;q_N#6HnKTiOG}=n5&vqV+&*QIwb4R zoXw)ANN5(D-5PEe;!8knk;J@@*u3YeA?NpV(`c=l;oTdj1aCQWg$3g<0Fm{3xH*Q0h7#KN(X~eA_M+f|MXtxQdogxm>^`lNY4r zUc4oige*;MtH*;VVbRG71GxQaf8G(J&`s!gK_-Z@EK#pjsL@i%`)a7cemL{b9 zujE?2(8F_S=L085^)K5Ud3o!F^udUZ{t3E9UMA)VPOj{^Y5AP1&5(d_Xi)VNB9*?* zG<&|UvGa4INL~7cT?)(-e$xWK5V&gaiuD>XX%BJ_fV7G3@J+iwl9WiQHbn|m4&E{jNcI8^o=$x<~6Sfg=8{#{Wadf6uuJCb6s$aluE#V zwM!pp4tCJ!2g`6XAi>>suH!_OMvCPbV5C?YdkG0hTboC$KGR_qSSA-W8^i016N-o% z!Ayu{oM2>(}+7>HQPhUMtZp^X)zhO&mn4Xl~j6$90fz77wUvh7kczKa>X>&yeDu<__5{;pLLf50>wQ9c=* zR;}ZX$ov5Sxd|+pb50A_n)l07jj4=kN@obfxlygeo9Qecr`vO=U96 z5(<0M&P~`stfARI1KG|;KaY+vq$^Ol1Ax1`=YFBai1l%d%W+ zQLXZ!;j0nMJE3V$Y0EXuXr~TZLm)nDIR`Re3I7tM)|ETJVfy1z&*zT3oi*DSd+Spf zVbe6ouH2&}aEkC-uEQ^n?rKtzea+HfR9CVj&;az3Gu@ysYrWoG8JxOWQiVqSjwj|t z%U0Q}+powq-RflP6uSEsq9zWQQ7=8eA;-z9KY2HpK5@6&C}#!dr9I?QHn#VgkmI%S z``dHcO3f?N52cd{cNp4TDvbBnKDC{9*qb$9KDYij;+@!BaQ=5iYqoDOd{@zI-iIs` z@)nOX3f0Vc=UTch|8xJa$YkXmic}Bqw8*@j@BJnDS#sP3%Y7UDQqi|xmQ&w|V!3|a z8`^mptTfxv8s5Xtzo6vXMUMKHc!B?oc{Tr=e?77PE`D&Fe~uMm$X&+5e7 zWK9RF$+gy3qc+d^UD+Kr&36-Jm2E?wh9KiMZx?P_14VDjsn7La@K($${4Hr1Fr`OL z1O;&9n)8d~KrRrpGI{?}$ag3RKI{Z#I+gTPY)4~tySYB-#&4K(ZE$H7+ z0*6=8nuoISD|(lRbsm6CQWv?cu}JZjs-<&~n&v+YbwJoQ)~>hnaSW+X+OOv6hlJ%` zjd8&&okW$GTxSI=Ka>SszndCx_3_<7xo=jN?NcXvA?mt!jx4@a(cBoPATMz!=nsPq zr5n^1a#4u;^*higwd>ayZ-Ty4>_Tj2(Cbv%Aatbxg(Do`|d9Kji z;O~VUkz1sLcgg8L6s;NNb9tI1it6v!eb$WR-0mr1$uJ%|Di*bbLTbQe6_*c}qh-kg z>Ap9QdpOOCQ$(q*T*#-tlh}Bp85K7ZU6%zMwZ|2XzLcErdw_@|9^PMn zZ|FhCACsjS#Rj;55OztiT`?o7q@)~e-)7TL33z(GYj<6gLgUy%!(%oWvsuDVTltTE zelTRT_O#pHvj!R6r{Bk3RQ3W0RT}gz%}kPWXN=`FD+Pz~j>M z(05<*Jd;J4Hc!ZJx#gF91A9aB58z*)`v6;GGp8JXC^y|6iZ~g&Y2)b{@d2t8KfUXf zmgW~T1o)bE4=|_b^IxIBMF6SL&?hU|GO{C~-@=E91tzSMws?svBYej$Eurvqr2j<4 ztS6zFp8sOiHTu&FI%B@_>C3JUfJ8lspYg8h7(=o6<00hok5-?)BH^Le9({_J+IUMj zvQO_t|4N^@r;@OCKIYHI@Zx(3MSKGzH^Bv*rkB$v#_lS1q5S!|+7y>vNXcSUPj4H) z_CaYn(UTDzb-XoV_{qRw9A_Qy>keHyMACN_4=tPB#0B;jjpDg#D~XIR4J2sb4fGv2 zKYd-+2Euf{CWk-|bFejX%Q&31=A&Tkgr!e@b7Ay0@Lc)anZtut90+tjlRhwz`{Lbh;(p$eGRqgVCp+w%9z<#{FU}HV$j+MsK6aN{*w$nRS*YXbkz_0dZ z6}*Y4I|*8SRC)qk4J!~R{bu-~Cd?@6-OQ(VG4GQ8TE}LeOJ=K1z@v*ria3t5oWA_}ml3ukZ{rO$opL2u_WRAnHYzR{kItRoZV@k8oK4D_!XP!8G z3BjkKB{63@Swse0H=YKT`F1!9kve8lGZrwlpyBbPU^OFCtp`uxkzjzD8d)LyBoMC_K7(JUb2L9q$ z?$tfT_omRSXZh8wgB#AV*De38S3wG%{!#r{w4rD;sHFYndOyVYZ9r`?R1p;mC}h`Z z$lv<%klIe5iOh8@uK3ql@c~fv%(KGpD-gzAjdvNiaA06KqY`FEsq^m=(}Ic|{10=DnK3y9h@`j}HUEQbei+k%wRdH|UwMdVcj{A3N#f|kq#+I_SUZ%o^rS6?I_H*TaVhlHDH z{ln8PWJuXaG~IzB@*BFwmF-emxs>5~LtV#7s)c%Y;cP=%R^wZu<&*Bn*n$D4)jJMnlgu?O&>diZ^xswjy4w=jj3o%xWHNOSExKz8IBszT^G%^mq z#cJCJx9$?AOXB}!m|xn!z`yanPH{1*xEwlB?U{z@?Vih2J(|lFrO#pIOo-whhhzQH zw5QnygXU{-RO!Y%Y66uk#vTYxchjJyi1xDd1JmzH zvSn^}eWQ8q`>tbCrvWe@6RqKIyTO@Wd2csK=?xRubFmQf_>cZ_@mguBy)CTHKbX^3 zWo6(Ko6`{{2k<%1oUne`G5;2fqO)muMmMAI4XnMeZAj>Kpe_%A;ijE$d7ikcNut2% zKq@^+P~*bZd_nY@e<9_UMoMmm^^eWpJ<*O+$`haM4a7Hi34sc>5!Fk-C!L0oj9yG} zo(q7}0Wo$jYXN=gkVM#KEekh5`o3f;t9TUcN>hXn#~E))(J4oxi4z|Fn5v@AY4Ln< z+)G@)4xKB|U!Bn)ZWuFY%`VGbkmq}4lSy=-1(;Z205lis(L-t&QM_DJ$_66_ z8yoHf#ULEiYElYrA5_XE%|ll{Zx(gFR;tX=dvz&jN_KV4DC$O}^JVVe9E3h`?Ic!S zjNa1lY)wwu;x1X#|AydJq(d6ebcX+yW|^m^twZ`Q4mK&oWT?35FpPEbuZw=CzD8%} zyixoJ9-eYHz{C7Jh1=pbl&kmOq3wn9l6*J+!66;MCFRYSmwP2wkSymPRXAF*I*Yd^ zKK%E9Rrr^u3q;GTxc+gTa#t zI|VKWG;X)?cquZ55N3*|ElhQ@@=JiJ$5cFUmM@&ieVnaUSk5+R0 zmy+F*U$36cLhfB9uY@viiFNKsQA65UZ68Z%>)|5wZ?;?dNt=~CE8bUsmTMVIG!p*e z9vZ&l@uITyPT91>6LGmNifBOo=+v%KSb4X2XwYxk2z(sX*H!(s2K43a$kmJV-?@vO zRj-x0J5pY7blVvD&NP2IVa$aWOj+N|%(!*H3S|1q2<}6nSib?{4Cf{!2osFu(Wb(( z;@~Xd!KyI38gG6fs$@hY<5R5ft!Kxz1he%?AvyF&(m@g0zw=v|w16~A z%Zn1=VugOpWqTn+);t>H&S-v`fbvuX9$CBLO$ry7{gWFH%wYE#?>7|PvEsc6Tb*9y z(dT$&bvxCbC?}Q=X;jWgkS`0P$ z0rSy;#`g%0PZ+RYWEsDhZ@e*x+jfeT>=1EX9pJ)69yI$Yt$&v!FB*>10ouFqU&!pX z*upe<+uD-M3!b@1UY!(BlQHcZq*{DUf%p>Y1mMODaa(s0Xn80s?$#lQQgS(GIu?_}3b&t`eemS(Z4V)zC3QuyG>aC(n zIL8Z5@~Y3Nenc4~Gi*J)1+MCmS5^$AJv_cR4GTVt9Y5w{Tkv9=66`)=k-hzcZtgqS z6wO_C1Pu%pY{|pH_{jnRDGN$z6bESBDu}ql*Nva#puKsrN_Tz@VeWG=_mCuW1HeC) zZ69}~cOH|fG$w0Y?Czc!`COI(EfCCXIVd0-weH!rQ~vk!ur_`chos0IGl{I)je_yn zUKU1W{RqW1E%zZqn<0El_GDNoK42JafaCs1%>*z$*1TjS+Yd-AxyxnjjADqZ%K?IdyoRLFFJ0tu01qaXfu$Uy3iUKLu zB=CW$6e`KSh30U$bPY54_La>_{~HHkoMgw8D$@Jbcfkvgr4GeRy4)1*P?*%bTO$gKMf%z7EG3>60red$=(xHo`?XrtK9pO87C-`?Nh?&N;; zWB9GtcuC^=;LnMEvV&)L7)#x;!%d5qXB??>j*|-$M!)%!^={vLE5@w0k8$L_{M>-@ z$AZYbUCw+=R(oa9eu9$;vo?3&0%q7{m(6FlpbrH{Quy1?zh=p2nZyP z$WtPOK@A&5iqUqdW3rN8j9l{-ojxT3jf8E?(q1yv1HN`|D@K}nC*r@<`p&>cv=PDs zuC7url0ES95N1Y9aB}yAN#cvqB0fnM~hpFb7w)`0h z8SHS9KNPY$Dh}KFKue2+5A&=t%gj1sFjG%lSSMT0-P|oi?6F|_heVAeSiGmY_Av%T z^cgYojVi+{TUOVHTYsfYg3QM4_l?!?g91bVcouC}MrP;4t4Nm4aZelOIr@V#3w3Z} z6<}NuBfq454`Nu$KtvOD<=EFERd-r_9m9F4CY|AqB#oHOFsP2kWqFFwqHB5(GN!?I zOn^zI{TYSBlw3S~XXoOJRlFIuh57UbQ#6DFcEzY{f_+s46FDG_nz1jk-<_BM7cV+l zBsrJa=zC?>AaRnjB{D>MtyEOfWgr2Yx|%YWRD-_U>+oSOZw8*^hD1%15I!kR6M^iG&Oij~>!oZ9yaO01>Hro5FBApe`6g!{7HTc@P*U(mhB>;weS?x9IHNr> z@O=a$%GnQWp(m7OZR@u zJUVCd8cc{2Xx)OA-5oZ2Hht#AO7OSIr=-sB2U&)>6tHa~qekpa6q4jVtn+$1@jtwX z6fYB2?sUTT9@hl9Dn9W~`?LFC8E>0;2q)524C}=IAcm?T>B|!mv zTI01M`|qV{CNFh5e?+})q1ftv#)Ou=KZdCA7n=p*-Cz}ypWO)qBDgmQ>v{o4r0bSr z;(P4(K@NvOtYi9Z$^1CHw5#k0N8Y(x8|e>nLMP8rdkB_yebK9q6bS!{;d-FD>P1GI zVeNpSdlzVj!29Ytmj;h<3#T~kW2QPzijE1BhAidc0T>$!#OJL%e)BRD0Koz1q&1gxc8 zc)wR^zvv69R?LM#pe~n+fm9^y!-$jOkf9=3QE^CFBb;jm9(wCx>G9A{qd{-v=AxH5 zR_rXcRMwt{)8#e>%$kMvCtvyO*W5(NtwTeR>_V*HvgJ+ry=;v^#t)m4BN6xz$+5sZ zvgqn+yw^vcdRau$m?Tf0lYh8Dz&VG?rU#bmK9ablVWu~8e2JAMMVkwIlG*IfK`n@3 z7^kcY1g8!w3mg0-AxTVNZVsEzi2tX82_Vxq+tfd|sYl{e>@nJh^lJ>eYCN*Tj9Yf= zsT_(@cU2u_;C7|U4=w&J`#$(FY&lMmHh41=DIzpknSTb?-?jj>Sq``^d1CW>=2!6ble?pcVoP~@esZ4_~T#;vhFK=Ha@vJ)T zS<7(8*L%WwSG|+8qler^(YgS00KP;sPn9obKk8sIpk^q@<*mpkZ{-4Rqt63D`|r(S z2yvB{-GlbeEu6_NGK=jLA2~wB?bQ$?R(oC#Na15+zihvjl_>UOB>Ble{2=yuI)vRN z=GcMqxQ-kG!Wbs)`*J3;^-Cl0$3LVo0Lj@uE4VzzFmL*V;j3S=x^%S{S(#<(>;CByZ0GlH8{AJ3~(?t5*`dp)wq4YrTJKz#ZkL)xVP@?`sIZK}q` zwA+I#W?92-|5?n*n!djcWaAo!O4gF(KM$K`!UVhzFaeb=MrzSRs*+3r`3s)w=D5MSW&BlM({6%8lnV|cd~NJ>S({g^j&%R7_=hkQ;B!tVUA7s5HlR8 z9W8a!;@#KJg?q=Hn@#~zK=1xz9A6Q-KWrRWMjrx7u6)mdynNB7C__AT!n^UH8RCa7 zw%;(4Y}TTJF^4~2n&HIu!IfAheuGN5RtC-lI1bm^W=QqY#~c63P7gOVr2@5Xip2jZ z{gOkm%EJRsfDDqP{BbX%7J%=BSO+MRIfQ0qkUDL_zz}%^nr-1OiV7To6jt8Zn_2W| z%3P+F*)JP83@-@a1DCe{9H>rWvTGeYK+9n3rEhNdi}|6N$wv0W1S6)er|!Cne63Wd zmWGucpyHBXMfMLVwY4AD3@A;s$%~C?0_6ng_$J{9lXtA7)RCys!GJvuR$5ZK%mw7%2OANEymWymDf_I*N-+A;NXtC7y%PZqEd2~XXkaSM^#6p%|lzyw2K57p!GgI01KG%RlY zS#z2FV>s;e9Nz$*^a(NAM1ze+^fhG?F{sNSRnLpU~^T$ z7;?rav3lS_o}EZD4p|aWapQkHI+IA!jO5#BS~>}N8v<5~f)7Us{o2w(}|=Z_p%yU1VU>IrW9FFhGPJdg;+SS0p) zOjz%KYCo@EOpWefZPco91g0JuQo(85*!feO+l{!in;FXPOwtBoqX1nL<~m5Ua6SPpFJ_iqgHL4)0VY?l}j(2NEq`z`7L>M$+%F_wsDx+y8ZkD z`+um3Mb!@@8T=PZIwo3uW_)ken1t!{Q*d0;Jq~_7K4aXB;mX6QLpdWZZ3ejU9-;>3EuxR zbl;Cq|8X3^@6K^&pRF$ zmiH`NyDRLqzEgH9(_yE!Dnv$F>?m$pf^2!l2T0kj{jx3$)2OH}XDnM8MtAIK$p%j#>b`bHN@bO#sFX!H zt3ZXCzX{uT_-3ZQHXc9~BnQy-bkn>96TdwUm`FtBdTmhN$mOnTeFvR`ltG!y#mF~k zX{EB({x3u1Jk0Ob=K=chT^0=Qe=R)V)%)GKwsn{LD0}B_!cmcbbIsu8G>MA^+6?6h zXFw6FqvFoPy4-}5iON#HeTgTGfA@hFLT1*`vJ{HIzA)}ALqbrWr&nx@h1NI9u3tDf zy2<5Wth8nAKmis@VLI%`*abx>p9<`9m(Upw`1-raa_d2&a^Jox-OF;Awg_dok!c1} zRbEyS^}x*E8?MS-5|`k+$(L!MY43oIkc+=0q2u@Kt8&V=!1Ofev)u9LS;4Zuf$17$ z%*<`=iy~RleNX$GR2r%M#q>0L!9r#Q>;PKM*@IS-#vbAo3w<{^pFdhN4XgXv8OSBI zc9g7hJNr^@0;|cqGxBq@%HYkWhmYQSC}*j+54pdoSxiEP_KcL$LYPd(=tKIyj2qeM z#BbW9{T)d6Y4bXoKYXck^@CL!@|dCT$43@+_aHF}i@A8cwjg=Z zmpAy(OKEUF&lUg4OvvyOKnH9Ph5{RBC6pTx#tSO1(8zYLej$^?r@o?^6u^k!Q_yIve~(#e~QjndO$9;$8ME;seyzcGe>)%6zF-D9shK z1!DFu3#y&t&IkZZPofP~8R0om9s(mKl^Ozp_IV>!?g_BI|NL_0i;A9yoS}2=zHJJV z`?#7=fV2rI3@{ivp>I7bL*E_ZY|RHzZC=ZVYc%E$Un51x<67pZsTUshVfF-+vazSn zfO&qybQIo7<~@RIc)3mJnYYf8N9Z*+;s=+r8UaJ+S`TtQZ^EiG=eLAc`h%bD3`~{^ z=`=`cZ$_FA&X;2N6yzj!=(e=B)fIDBU!m_nfusZn&c5&>3Z1dVvd+40t_Pa)okz+f z*0h^bNX&w^M>pHjK`8aCtzx@J$4pZ+9F#*bv3gH4nz_{0k2O~*ninbx08^wiQ+Nl5 z(6+_%9K9K|PnH+ZD5S3KnH6N@XRl7HT=FQ^@9demn+V5ZpX7C~43-5zLvC`vfmgug=rOBT;S+07uY^6ZR`HV zp^{mpHFEe~Rs(=14>73W#o+JAb{7TzvM1aq z*Z;`(F;U`+;6Zg$*)}=F2vzX(T?LOJn6zkj#*q=mVuEX9tzO-#f6<5=y*^-0qrN^f zVB+qe^vn8DRMT#gtjeHV7mU8^*A8*uhs=A~G9t99ZnGtC&G2Vo`K2&w@3ls$@NzeR z@vN1&z@u1s_|MuR7(%&9p zI|t4jC1yBAQnN1W3R*py9U-r`7iLFF6E^X`*#lR{F1&=XAXUdW0zHhtlDHXVpPzZY z|DGw{@DuaDL!86|K#Jhc6ko9#KzD!C<;lXFE>k+QCHyE*MkhWk_p~;(S zPOq?q_Gz%N5hR`Ht7LHZ{pR27dXSh&s9#9B;8M2q{!EkV?vUtR#nIPPfT^bC8yIKF zxkwjrJ?39|4dS@yt(H;x#4Z+c>!0uAJ5?){I{gl{&=s*>jHB^II3puMlxGU6U`|i_ zh+6+tXKHXyuJ9j9MYF@_yEou&nM1fXnC;@Ul)zf^=Zy^I&6U`I6*LnKdsj+h{oR8+l#B189LBKZ!=F*$LYBDHZ@cdCBN9Zx5(Y^}apb1_pmHS}y zG~0WyQ0BdN1)G6Sl=y|->4VEh7r*W02BzeIssGl?rya z_j<_twa`O!P=KjG_&3bMbCSC^AIvrKyt;+h*8&s_+G1*@S`dbiz33}Kqf6hJtpd!& z^V_X;YHg>w@5JAnlZRRdgX{iZ1((xI`=ioL42J6$3$ zBGm0~k2R7LqQCITFlx&XN!h)?1dy+v(koE;6*Gp|a@L|d7dFJss=X{`+_N!jhUHZJ?z%a5yWLh1&_C7y2@2E(>bx*b5F1oty?6`!JxJnhZ)?>l06_qvmpMeN>ygf2uGtzP zJ3nA64(R1QC_qBWwLyPp4Hd8N^?#~`U4+XUGYxt(ipq!vNQpUFibgfR|6s@qf?~a@kaKnffHuXVr%fZ%xNj>huNDz_^|pa z$#oSZ&WJ+@5ZX-{Ir(y@912hGq)U%X0;y3Oi?3v*ta+SsrME85d`f*0l6#h9pc7~R z$RP`p-=TxgvbkA(9XkFJ4tq4{O_5n6ng6k)H0kzd?$Dd&}umF z0YDrWF%$6``;eSJTCE4O;2{bcF(4|ef?zsVnuVqT|i+TciuL$~A`oJ9(|o68a_|KY>(bQlBk?_;2M=xiH|fZ7_=p z2TSqC?=R3$HrUxirA7l8qdQC0n-VLQx#j* zx9pnkik#8Yz@7$KE)&h>7ogS`dMCKE_PSD%Q&K{KF1VCip*aPY^evr|9%tZ zVRYrPU3Yw{6Fn|t*lwcDYXzV607OPB|G(KsyX|zS4qsuycoB5tYB?xqBj?- zB&V+mK8WL}exoDPDTxr=HBP5+FXPn*qy40H;E(jrN{*Jw0k-Bf`@gLDm|aSFdS0GR zlm)FL^qA?kHXt3XY&?^8k8;tdk;k=YijE z)DC=TW#)TZ<2GR=>xc`Q;UattYY>JZO(PLv*@E?I3PmAm4>RqUKC9G}fo#u}4w^$J z8qf(CA{N=@*|YG5k*o!&E<2pRmPItazINQ*))!pIGj7N`RjZze00|qHU`EP1pM}qU z2_H#KL|KEG$O=Iw$`ZvN#cC!f$H$5HOOAGh)ui3}HyKAdTji@;olu2LP1DxA7hByp z3j$Be=py@5$q#4bdUNE12@GdMhSnE|pvN9s5A*|CuQS}zeMwfl7N5Sp#eb@1HkIjgMpJ~m2=FQ`Vief?m zg@!808%i-XSB*Zqr5-TC(dp_&(#d5C#q-B(5lZk}yfC3ysCrUn=j495Xc}=O_bZ6a z{nT{dM7d+lr4#ScdHJabS6>77gv*M9gXZj2rqNrXm};5YI=C8!K?z_z)1sZGXz`rx zv(h1Ns0e&|NG~8)LMF~C57fnG;wMN$CRbO!(H)oMsqgq!DLj^Kx5ebX+hxLYr`|>Hu7agQg z^XRs&2DVr&^%9Z&bzO;LCz>}%j*7ii{!gyAHKpe7OIXUqAtAluu!}8|Gu!I!&zjxq z*uCc^BNP5~JE*+;Q}i;tCF~fSt?G2eUy0#h8d!CyjJeCi%gA(K8wRGW7C{4y2pcaB@zIPK5 zSHYFilpdU@d_JnAwR_!REdO02BAjsH;%vP=Cj(nAA|t&>FS(G>Oq>Tb?e6N~_0O34_& zQf@rGU|9AeeEkTCQnE9 zNnY*yl2*0W_Qt=?I}ZI#n17fd&2B>(H5_L>4o1M~Ps?f3qTaon9Z1q4zI-VuQ&a=-`gT-Bb} zuoD0y z0n|Ppi;Bxli0gPIlaSNKJ;Dn+?E*mVM@xY8mCd+?TWq&0rTc#fySrv(+#cznkIdA* z;3?f#?&DEj%j=MLxpKn2h%US3a+Av(TX(IU3T&HZ&JRDK>W)9t?S1q1FIoFL!RG0k z&eO8^Z!L%?Ymn9#Ix4cd_c<2){{hHaTB@&gKhVK(=;7(^cXMptEWsR3_5K<@0RfG7 zy5&up6GyE4fM16YyBJXRC8iUAf?bj{wGnmoh1BS{J$egw$LG!d#_e&jh}v6=jZe@I z>NO-A-|r8I0yKu@2rPg3n?hnnSiTsS`;K(xgw@$~Of;_=cdT_>l|Pt8f5+w~eV#y3 z9Sj>-Rh~w(@TRk?U&@+SveqtW2C$5K|C!k35F}L<6n;bmtF5lfUN?K(WU}e{xLqN` zW579bzQhBWc)F}4EB!Jw^a!-B%K5^u-&aqL_UTHU@4bGY*_Uo97MeF5h8-36?c}lS z(tl)d*SEq3eCbH?jqj+USp2)uR0IwEx73=7A%76z`|Enot|#n#|J1_*yMRfNbJPmc z%tssLF0q&{qiSA0NM=6srelzE2jDha#>|P^S2C?^FJ36CSijQ9`QvV8y34r@`BlC%k|KjtR2EGL#4Z#9jXdtg~LcVT~ zu0~}lk5J6AKKsD}G7@#h2}z|{M-Fp68JoM!3r^^GhV-&7@Co7}07eWjT-^?4O!S(w zCdhx}LVvbP^&COaKV$Yv9}+m9>(hlPnJdLHokD3kzpE!787#8zpLQ|AXl^oTJB?Mrmb{(?Hfi_0G7QG~>hF4gau5z&+|Q%GH!Ly3 zrro~95A3{miyzoFHeQ|5wKR^49Qs%E-eA;Z;3J(J7wTc$tcm1%gLQ;P7V)*oM5Qv! z!)PpV&Or2q{<=WDkg5~*kOd`@LI=G8(N+GafB3%X%1x9Wo|ybP?H2%y7Fo0+@wg;h z&Y;-@*#&XBARuYah?LrNF!V5wj{QzhR7y{k`GpMWJ3)0JU~6Ct@72>ohPHt%ITM2$ z9u;sUOVE@0hX}?$mkbrrMJ(zV6`2qQ*#s58GA|du`nQG*(u)gIEhr;C*V9ACpaql( zm%~Ce-5dSV)u}jV8fKW)(jMZ?$?8yDkIQJKy{Y+S;A@n#sTdu;yIWt>1Ad@B&@yW# zrX(One2uAmv$zH^k+8ZF{?j?QzRSI5XR9RD>!;b2x#CdkNO9aO+cfwKA$5F2=X%H! zDdn|fxZ{@<`bj>I%BUV`$yN19?tF!4=o94f!2ICK3xk(~+P2rJXLRHG6*Kd!2&b^~ zl-z7(JeD6)0%*ShecN=d$B8)%;#WXr0_RFY4Jec|ep_xF)@d`UHL88~qLKH(j&qQy zp9oXUHb@6iki+I93L2F%R5o1DAIz^1q-JxKpx&CbFm&gL1zR*5nllz1<&zO&iuC)Y zN~VNc$)_|F{Khr~)9d$$Q|O_%h9ad&q=5JcQ_B*FePaNtdCYZO6SmolTs{Nl>I$Yv z+cIXl46c_1xrs{C6r6&52vS`hYYH&Mc`KZF|E0HL@>7^`n14Ib(i|le*gUE)(m@hc zlu)d=ZFOZ=$$UOCSE;5b>pn_cH4h=pN7f+IC#Y6LpVql`{KbBQ)mFTdGD1ABaQk6c zhX$QWVC-7B?K5V_QkU+Az!(AgC%@0izyXGl#gF}zT;%Z-zAloa7WSl5b%{xJb{*WvwGP2 zU&|}~(+*aWkiv_97*qn}WCG5wqQr2I0C+F9873d6$mD6=yCEQMc;PuNB!A;e}0@e&O7 zlkv(5;?GW>1=q~g3f_;Jr}r3P527<&Y8l2cyxy@>2*yIZz7JjEnGD)>ZeC{DkrUOr zaBcJr(t%lbmA5LsvY*f)SHeYeu500F4x^ZUS1Rzwe1*{lJxnnG9@J%xa@`E*t1fOl2mhxJZUu^ zcpX4)O#-qC;@i8?Y*$6@RrrLzLA z?i>3n9A`eH5)ilcvC6X>19+wpwL*mQuJFKjDY<$;mhj?jv3*KKtjZ97ZJ+(r67+Z1Nb!vR7b43a)nHLXKzMPts3;y)aH4gj1Q{< zaD_$Ck47oI`=LR@j~_+t43eeN_!gA>0%k}SVW9TnLxF^2r+PpU+kmY?D&*Z*@C0T9CNkFu7!$hDf|~2^Z4|R_7TC@?c98!xrtN|Gnm|n=4sr7ZQk0bi^iwKy*|^=;s)2^c&IP7!h#hTn&F}M(d*TTis7#HX!8VzMR^WXIUvH0kev&xWb=-TWNxI#e}I;;boV|@?M zg%$`h9t#_(3%{wB|0)97t^@oU(LL&l;l%_Y6M!}O=uR;YpLnDIDV94LC>s#QVw!z5Hfm5K0^ zSgp$>6{R^2`*~GbJBXzndy)q8$1)0VGirQFdJzw^FJURb(~A~d>#}FNt1hWoqDE6- zD94Ap$H2mi9Ii&eK5WsQ=RR-dOX3e{?AFEHIhaHUFfoKK@hUVi_k1S_#}Wu&-eb7> zYuuiOqO0gN!@%%2j@TLLy^3qZoyg{yhX`F$X}qGWG@92$LaS<@{W$=;3S*Q|0BS_v zT-tOM3^bTr=bwmjbqheVI4JRpU}WfG;c(CA2Pg(5{WCP{>^|#z5^Q;%RlP}Yt_CIl zoBcgr==0h|BlVbm-NleqIM{m_w+crcYLP^^3%>}7#4Lo{#{MR7ob>r z%e3^GBY+a&DtyL0nbmc?5o6##n3S41KL|&B$Dvkj5AwE(HjfVm46}UhP&Qy-`&$wX z#<8n4BmH@;X7-bpJ1=Bl0q^nhi`zN0cB}6qLLoe;?S1xdH9~(uxw~sZP$2Yk*~Qgp zh4yPUAcx@eI27+5_({8*i+afWH>6eLZ1^F{wFmhheewfh*>bTB^QhA7BU0mj|Z3{-oO^AjL*_WIR+{xF%b zz$EkftD#Uy5{VVk!Ts>FLvmDlmw}_>oP;#K8X6*L6;mCDOH4_C+2?az9yYPR#~pp3 zKKF~AZ-`MT2KE7ai)Ei@wOz%EjAb?8yE})+#PK$7o92EJYn;ry?yVZBnDrdzYZ(9> z1Ifj1`Vpk<#tQj`mFj&1^JO#y1e878C!+cl1D5yYPv2R+R|tHM1_8*P_1`UHn@ZbKd(*2e&~Tvv=j&KtUc|HK5KC%$Jzk(!Bg>*N~#U*)JU2 z#=L2Hz-#=i`)GZ4Pn~9d&Le)FA%@@r`^Zm*mInn&z|?ft?Z38@al;dt$YL$j_ zIaIR(p;_QtG){$Fw&z+Tux`%Y$B&qtNvD_Qb2DSoUC&wdMiWGjH#9Naw_3LDx1`-p z5fBfX2vMKdb=bt3+)RMw1Hj@I1z-GqL%`R{LwJj7eFHOUi)v-$DmY{^`NF|jor^qA zXL)2%7LY%#%5d!t&Unb5QbmLulLg?30*itMl0zx^Wd?bWKCf$-E~Dhai;d(b#Q`D! zR%iYS@G-ml{eb8=SQqLXi0Y{6xVWciy&2uH(k{e=7s9C9@E>FY*0U26kwH`%s$eVo zk7TpV<0dFN?zGF6!-za;@3s~rN9sc=z(dmS-_$r@?*}nn?lR;?t0)f}dac`$>^Vkw zIFr$^Ei9~dG5*^lP&EbX{YT7lJbVGKZp6WQy5nHB=Ag;QwvBZF-YS-cfp{BD&^Smv z9+u<_sotxelaQ+60GwWHnUvvjihPOY8zGlz zzQvSf4wqK9PUOYcoEL#BLpFnT>Ula`Sn;nUZxBOX! z=;JVamO}rwV1O#!wot{uA)zY`S9-2$b zqgC@C_y+yVyz0bxgUT2-XSBWfu-u(PJ+baluV{aQg)UQDcTs=S{>{YcNRGnWql-b} zwO!J`QS1juTX(BvY?m{z)s!0OO;Xq5{e`ESmN- zA+f~6nD?+)CWp*7(}t#noYn_oRQg-_Fg48v96n0j=gVWy;k#a7wyW!ou`v2;NPhft z0E+_XAL@lDmTnveu^~eHeImSIT+HJpI$>OXq0iztC27@ZSrAnaQ5-JG8Nt_DcH6M0 zR$3L!oYHaQB)d<9pwOWc^TLHK&8M4Q({ke(%`^ila(UAYfP~(NSxeqUI+_u#Y zjd}q`_19bo-)moRyBxBs^8*yrj~7})xt;9OE{=A$e*qC`2kdL8%Yt;choRhD?-P3-}LLAM#d(@t@BJAq9n)IYtWpK^)379NzOcct$7*=J(}s1_hS8cdm*u206fz*LY=# zqYt3GzgJCd8*a!)o*%tT;G=>9hp~3u)Grtg*>;BLpMLc#rQb7eP9C^I*H$Wz^7^8m z$}FPQY84TrDL}pdn>SX=H5Zn#G$DG}MTpd3X{rz}1%|`0ZmxlD@bKSP)nnEcTEnpZ z8mV{%Ac3qb)3f~@)D>?!_FA7apd5C=RZTDdn-jVi zi{>&RuH&O>^cb18an@v-W)<4rn3H!4MHgfst+M<|g+C$E+jkv;^!aTu6n?V_sO_MO6ydBl znWq2a{^pN4O>sWfgWC{;^o`;x41iT%Abbeu?}&aucM*=vEqW?MBSApstUncF3ZLrf zHsyZlVm(w8&Tm1@!9BX#JK5Vitsv-&K`1k=kT$DI2!~rr@Ef4TTtod1bS_wJd#dkD z_}6q~TL<09>@00h;pt6S0j5BPlsWew$D63s} zx=SQuyO}!Fo5deRuNlL_oV}Uz++}5Y$l4<_GLj_#$Kvsw&qZ|EOj)eUWrbOlzc1PX zH9;FgHL-Zi)}>aIvL5h@sIxr7|0Hj=wXL*ylm^Yi45g3}K$UJx9lYQ*htt z68d#sZa@XKitiNX3Q?gjNr3KOvu_`VCBbZG1MPcMax$1pFJT?G22W=}>RoCcsWAkx zahYZZ*{70=(9Q`li&l%1WL~IBv2?WeGqUgc%&NEIr(r>GoE(?TQMIAJi6Hc^dhyO| zBb%tWRgdh7{-w#zGov_s)!Ss(0{fgtu=2N_w4v{XV{zV>9}RRj+8|3G%Au5AdR&Wb z7to4XI2%JHNx%LtXlR;V18Ti*s7>QJwNkuB%;TUb;M@{eWL++OD7<#q$mbwPOD|w& zzzeV$#NqO3b<3-_*Ow&z`K&Kj zWsJAFE~eFtDLW%>6Vno~W%L&sBGW{JB|Msufz~=u7|&&~AT-9-d=3@l#-%xiH}=>D z1A;|lYGipW{hlNX1Z)c<1q*k^3v=N+fq4~R#)j9vtjyl?Xh0KFm!>@C-{JIluA9m+CGGoJdEhUim353n;+(>S%=d=zT?PyCn5|NdMGE;FykYwW+ z8uJ8PY*K4srj8@z{1dhj++P#+&AtaN3~Ex2*D|bfv|af|Ei}>jL<1U!pY8cW0H5S1V%o0bRRyzBk zZG0C0GlwfkbEN88Gi4awH&&KEwYE}KbSngRt29qytzF@nWwrBMb;e>+)37ueFR-#agaJ@V(RlC{wfAxs3#vbindY*bda^P}oT)gJsY z1Nc3Lt^C{)_Rj~#HglFf=4jHKt@ASVGmyIKNe{oeE<)&beB$83uIX)&#wUj(sy@lG zu$HNPwYqUf;pblZ?`8iIr)s_0H=m?|ZUQ4l-w&R~k$yIYh{&ucYpUKl}AL7lZ*`0f=vJ%`k<**}zas#{(-=DO8Q{AN3_ z?Nj|pMACeZQOi!p%7e<#x+5hSPF8s4l|-eBnSFURaR_8hkYzBv%aB_$%=g$rYk#o$HIt$M?4yb1wPQrutSR9(C^^=GK zp{kOJFAZEhJ0mUKBSnj3<8eGYLLZ!dd*qJUxUu=ffC7DF8 zSqi`!>ZHTP=EKaDH`5h@wNfBjom*!D2meW`m~+zd5t?Ib)v4Xe#$N?EGTfX*80EpF zFQ@4sCWpGzBTBk%q4~^gJG@GKJBZ<*#j9n1V;T&VCi!Q3zLLvRbOEAgYCl;)GXHR# za#3VgbG>4jyId-+K_&|19)1;js<7&c8TzYJo$F6Y%lBA?`684#yHe7Kk!97 zdoVP&b#8Euf{HCO$#O&Lw0sX|#?66|n@#Rd4;j?Yh6!3JnA|(dTx+EJviim$mrUOl zi)%l+N8WqLmQy(7TIWV94byFsZ})rIV|&fAf69m6fA-@0JFfDe(s|ycW-qm}BA$rs zAuuOG`fti`u}GpZpYIvGJQvi+(`u}%7EB?))r#t*!4-^u&4xo771Fse$uy}z^r#{; zlO~1{FaoP=eo!?j4Kei11ZI<$6d5(lB%ur?GSC*1x5M3MR|y zRhDVlQvi1d^U5WN#LTbu;0;yFKc<%-DgOst`0hS==hN-^jwSA}sB$IO-Z_X1)8{s) zdJ*>s_g15hBjNVrba2FPm;dG>)I&W(sVbZ4h}Fi(ILZw7Io8{J|GrA{h1|$i|K{M1 zhWkrY2KA>Q_sBHZO9=JOi~-K0&Ou2V5Mc-F%+A$H3m0cXV@a^X_m2Xj{Hok2pGp>zF-tpobHf{oq(&FIvr&4h| zSfePhkg)G|NE#ppsnSuM{jbfSyMa_Ec*Jr&&5c-JJ22VD_X4881 zmtO#sk5M#+M}2}DsB2ERhuoK#gL8b(*2h;Y02@l%My`507Ms}##Z)wxbI6}Oj?ql) ztK0t4|E=%2F+rT|mw*a_2b+-Q_%?d%8 zhvgseC1ElA&u5n4-n8W23qWIz^%IB;fuZF&im9TStkB9k%$IT8pHi0LGC-L&q$@7# z>+6#0{oBgyR#Pyx4Eox5y8A@njvCNA4p+}-81KoUC+V^T@ z3Yk%(ocD(amWy}#_?Y`S*ipqI!^(P(q;lFU6>8k;-kaC|*EeEQz{*Rf>rkF;c27N% zE{mO(b!S9GXrq90V~+tf$ub{J`L<8TUMDE&YcHi4>HfKG^HW7`zYRiK(X}o-dKROeOSqnt*m0`V!*?0O;dm$w;^nt>=EruZ|KCn2R%VWXYKbgNqypK zv;=zgkq~>paq6V!y=NBlJwxSO8+1TG(3xbI$cNp!4Hx=o>g7bkPK%^D$3d)=moo)ly;{2<14 zb@mP!0d76!M;WtyIyUITrReqE9t6Vd6lG)wM@EBWu~Y3gwJB_MGLi#Kn?|LN6uCkQ zayKl}T2In%D%3x9BYgSv#LFOCd+=FZCztIX=KnCFb*Bn2BvRA^w(?D4JK!jHC-t$!a1*jAtYB$Z00t+n5hj0)hr8u0a6239eAO%y7-n0*vHF@~* zH0)T~dU>`6e=deGf4hmS^@zNknpS94y)v^4XG~OiEKR_nf@>KkNMj(MVJgHqbT zF@!T($q7`N?WPNh&K+oHy2_U`Pi1+DAZjIWt{BP0%$ko&RT|T*$jj5{Iw>FgiF=+N0s+G=u^3Ae8NWVcX%%1?h?jCP(4UyceC? z$rjH~n)w$~7@4s;f8gdqpKLDjXZcMMlmSH3ueGbjX#<&AlFRg$hi|V;^}JY_Y_E9v zzAR^?ARB>~c|DNnTZYVJ&ayJ$4PSe3FB=(N!2RvGQfrUZ@e*`tuUOmoEx_$ZzC5=OCNQ*Wc#xyHRST-fHyFkSLg}4GEHPCEVoNg3C%{G7g zutD%jM$Wtei`7X9vs&+9FweTtr~aUO8()j`7E{Rh%N`emHt3L@d`Rn*+(PX$kFN}~hFA!pGh@#H50d=_vddKE`Xg2;TQ* z|C$kgR-RtV_x3BJXT_uDwVMT|V;e@D;=`ln( zpYXfNNHZ+^cx4)Ew^`%|?M7HOeLbtRSUZN}lYOz{W<9DXb>e9vT*yQZzk zm~rW_1{3!}9N4J4r6f(At{4yQbMvcw>Wmsf8knp}pNJX$Au8{>dreuGEc;mjE0++? z4w@Yg?%%I8^mkp@eRBWJUJUXTg1U}i-3)%|N+Es$GlXIASMG=*Z&$5~zJZQck@G>{ zN|0R!cZyA}GL~iP(zDw^+e`R+I&0Wk!LSo*z|g;dCZMuJWQIdavwCCmWxBf`*S6Z;ny|bT;qd^xdVtB>dVV8^ z5nG}mEX-FXlwT4CLFWtbp0;?yaIur0Pk1gvNAmF~EVt&hPN&@4cunL6ly`%%^Cf__ z^i$noOp2@q-JhFw3>T{yN~;`MdsDH4@pPhV+jK{_kf;{fnEl!i??FtThBBQ#)RT#u z_EaMBEAPir`EWG%dGxg-=%c?`?G!W(c#w@pP|E4wX+b5vdr%e|uf;(*2;UgJJ&mo# z!6~L-5<|MF+l%6P$!*i`hVW#|z!VDK)T8;r-t8N@P&&6?4S(|7z;hY?73vX(9Q;KZ zw5AA%=l(e~toj1*?dDUI>cjvM$nBy04tS+fPs(o&G zbZjQO0bk25H{D?Sn|b>OwB1EW(JlAR59=qh8kAQ{02M9Dio#E=Q*NGU3RLe+ssddn zJ=ZWN+of$f<>exaw#+{yA^n2axQ$_sZIJDomMfpd--17X4&N`P^O*AT;yK~Vf106n zHk&q23H9+gMn=h8=?CwPN!h$lo)2euL-==UWy>b=w0@{ltvpp z*7vV93$``%uLmcyYU)%i&J~ZEr{{607l=L4E#&;0o7;}Sda{apnpify%d~zM^XaM{ zERFkUkaawRXaeIo4ijtlWUV`3CX?T085!0)^C~Z==pkkcp7c8GRgIICw99WBXwWee zhP_Tsu*PIB5XFmjrU^5}x?O|>rD&ow_STF*tb&Fq<~S5llJ26;AU?7#Gn1dD+Y4Cp zo+3)pf-APadJ`dZmvnIU`*+m>-}hVmedoyEUF~#cUn3IkQ4^`x;7R`VgJjArd_jvv z6aVRLPKCzV>tiudw`_iKxERhkmE6Vd>=A@01bE~j7$=`-JX&Xiz~gbWRqvAZQLk%g zEyPSI?pX%AtDn*Nz`E0~K-9|CWhwGg@-4H|qH4Ya-^oeTKLmdBt$rvs@m$7aS@{AO z@*6ly|0dj@%*?Qm;a#De&;22e{Tu3nb?r&n~gwFi*>tTQZj* zpWl*sz2fqSzuBWFTQ-=l<%)|6#odne9@b~!Q^6WBC?3I#f>w|6Fq!t<>zToQ8u?7B zXTIEK!b@Zh<1pQyb(ZW^Vz2dKGbBgg#~{Zcea1yU&LXjYhv!irnt9y|ecQF8JgBj! z@*9jhTYa$`Uuwq|Z44H%$VKF<$kq zHNyMOh)dTG+(Zo0xlI@KvxHQL^d&8LoL`P-z>*)G_~C4G;OU`JGrNhyxc>OyI#lb| zTgc_m)1~EQqai}wiV-uyHw2Gyg4~Ipwm0|iCcWsmb6ry-e#;f%1qxdkbly!g96x0!b%#&5}aIK#lt&bKB(M;)^U!xMTe4t~Gh345B)sAY zr_#>->s!ZQh}hamxDn!Z{!a_QwA*e;_dhGwulEnCzZ@bGyu$x!mm*gL2a0fah+5iW z3FS%887)5-zJKAnW2!0|(PvOAnOsz)0OjWL&{5}6iiagCh=LtS$?mG?X(=(@rSuD% z)kXmFIBV{;Vj?J-LX|?g%q1+V=K_-VHG>; zs)glYSE7+x0DK7~!FvWOGR%K6HEMo9QMXa?#zmo_RNAVx>weFCd-KKbr>{)#>d42a zU^OsDRvZ4OP_qAoCJb-h=Czlgbj0HJUpkY71vfjKv~(FQHM9&hz(I<$+=qUcM>1v; zfTa9YZpDUyA3S}A7k5WgTKnd7K_Sht$W4)g)IqHMFLvnz_uIon)G^KT44+#d@7Iv} zv%-$TUcJ+{=m`^d<@(7KE(HU4C<)j{=4e;1EM@H(XDU|Z_l`3`(0=8OZaXBif{?Iv^ zm}H<_JnaAt9+9^4CV+-h^^}qk95G`o_?D_Ux%&v@Tv;GCSx!(*u7<(4tza6m|A8ib z-95`?k%(wXNV+8CkZf!lZx}duJu&I`0g4(qdmNIBb>!PcQO5Cb;W+-8!)yv9<)bcVzj(7_EK#N z<^u75EJ&XASg2`yxn;(_lQ(OC&r=0Hs@cdKCDo@6HZG7XG{sgjyMA*-nu`asJ{@)E z7teX7VCm`7@_}2_4wXP(MozwQcrIA;u+CtGnNPhw`OQGFF+%|W^PxLD*vXswOC!60 zfy^69k4l*m-HLRD%tLG2My)ap-MHSPA~AibM1^X0+F=UP3!4;6wr?C$AO3sN$C~`> zH0omSYlMfE_91Rm0JCSHeKRBpb2Jd{JiLBOAw_oUWA3BeGtA8=R>?S)uZk7bt?|ia zov;ged(zSJn6e3*SFwMM|?P2x+6HOxOkS5RNkSS?^wHDmkZ{5^)RQZpl4R#81N`(gZoTAmuV*oansov@d>1&`7C_LTOk5C*t9Ydr^tDs>uW9^YA;PP1N?rnNB`k?DJC2pOx@3BJ$j%~ z#8DO>yutTZDD}MH3Y$N3^^=4n3%&LBE5pB2K8Rb7m#vz581edX>y>|#zWS6hZh4Jo z$+as{wJlB6{+OntK47F!V=X}E5|@YsOVk8&PT61Cj?EYH^S(<$ok!~^EBrMK-#%Rg zZ7pzqy9v?{s&q`f#ZzB;M$AlX^s`Dox4Y;UiQA0xFU>CYAE4a#Jr*KT7tuB}?I6w2 zYW`6^CbiVi)d%$J^3M9Mik0_Yp#|coY9fI%cgGslp%ra!5uJ}ftrCXAr%BwH6lZv# zc!T<@Xr4W;+ME20U3KLY>I<#5vl~ZqE2w+XF%;UmtTP^(+@37refHglMF@s^%=}Ck z1eqq)>;5x_PJq8W`>waSUgH$UWlF!2_Qx4>lQ{aCIL`2T$v}bu*6ALjqlT~wkCrhf z>TK~8yi(bWRQ{HW!wBewEo=vJQxhk9k&|v?Y6@x4c%*q0?}tASE3SUa2rx!@@Sf3F zu^f;eIUu(*W#g8l)t9E1gSnT=a!>?o znfzy{qFaxrODkKd8cHjec8nw`39>bN=?dUdWlK_)_9nF}3mK6)0+z06VoUAI9z#b7 zK%GQD!O&XHEJx00mC>NXXwo=63336ORST(qH)v#;rs_O~Y=pehGZH{1=E@l%E)K$U zX0315SYY&7j|&&QhjNZ|8TuFE5MGm|L69;?4ni-9gmtB#M^qi?TvaT2aIAmpDVO6* zE;|7EBMOs7P0bjceY=9#wQ7sAHTaHkG#lWvm*Qv}e z1hYF8*2TU0wGWBL@t=D_S9-WN-K&QAMhR_R!(VOVA%i+fAp`%sN1g*bJjI+VCOs_H zS5gnNPg3((6^{!*D-A4(X`*4V3YO>f>dlj9K=^(9IDeyXA6R0Wr4 z8xEsSLg;zr=$o?m2?T%@v= zjEn0hEU=)|GhCnI!%b7{Pbekg&SdG&qNfSJmLi#d3M?v^uZiXU(03O92z)VWaRbDj zi7Sh#)J4bKv^~{6_fH;3LGjwR#s^BQ31SKJ`h*)MAY~w$5RsM4A7aJ^j?`2V(=9(_r*nP z+0d`|5`S`aq*aGqdRkB^d~L!2x$XsHtJ^kE=7RgF8@50 zC^t?$aCQT@sXeWC3~D}`TVEr#W7d-YImy=T3ldH=Qq$^{H&~4LcFT6#)>xhjP75Ct&DW1-anf zJ-y-mU)$r}1iCO-t#>6#&}P8uisupTRiDLF|Lea++esG(znOlEw)~vG-V)yefQkj+ z&QM}Oo1@F)Mn131AOt&By$t<+Si_~~Eh2NR_wT{3h;0@#!4j0*J_SzSH+@$Rx7Sni z7d9EHznDw~FFK$faoUFrwuUCHL=H|&B^pQZ8%5{>{o*C^0~v6;!E9rBk865BI={sz z3N4aZ+EPE?J_da1>4fBX^2+Ge@ls$qYv13M*bDq~??kDj3jKrKwog3rPA!aI>}!pM zro=?}Xm6PYNnd`G03h4S~oel|h2U0&$~ZvEWSc;YCOX85&*r zP2pEujNrQg?iHz96MqH4yI5~A>!u@P=0vHxkIN8Bg1Sv*uL$|~+M*KXbo3-{^NmgB zo=I~|T}}p@^ez}vEE~^=wp~e*$Y><@b|^ybT2<6-zJ|#r6JTKep1SG#W(Wsc*4EL> zhe9EBdWcK;*>e7`25oX?2Wc&$5L>hSg@%aCk0?gfCRw(8Pb5GJK z%lBAFeelQowu|&dO^N}arPpKs)_sMnowvF89i3z5?&&sM z5;@c{@KqLdGc3)i^Txd*Se}hr$!(!Se~0nm5{hoekK;8A@Wv)$M-vi3Yl=JlW~CcZ zSnmO=VZ}wFIKl8*h|-gr8K-Fu6CWXyd@9jc!wv!-{|h`{50F|Ec#H5$liriNlb~Yj z?Mo#<^K&8}$xaj|sR91~79ZotDaocKU8Kk8DPGxwA&S*Dl;g?ndvB0YDLFi1 z*_3ABKnnTE8PcY8Mj{I#U9ba>GV`%Uu`;j`a0f3l$nB(AIw>X^`TRGH>)x)z+D|9Q z!P}*}n{#QOrKQUk1hX_^^WqvM_lzijXP6WXSIfX8Wx|mkCVb9 zyUQ1i+FS!E0}lQV6_LeiUM$i=8Sw;`!=QM2VnGaKe&&HyR6~waQhR>{^GngIhbm$ z*E&9X#=xCV)~O`S#^U~H)#15O<)4?II~Y3WyM{Bdd(Cpa+l;sXFBcpGQxhZR20eyt zKns4J!DF0UlzDM!ZYo$g0*-A^WgxrdS)Y&gZvR4g=o^RySN8b0paqCKQ%psZIa;(# z%WzkDmkOki`Q!Ye*4cK|ugTUgEh6ckf++9CvkVy5$ZQZ8CPA?sQ&cq(9`jA4O;{tu zQ6lPf=$MkdCuN&HPNC}n)G7b>*w_MX@0gwn#Jtxk%;`cA4Ya@rWxTu_VnMGMYs0d zGx`t?pZFw)2nh~E5aBG1VW99-Vll}DS+(n=-jNjw}rVM+M2wERpOia#AS_ z^Xk!6+}&8=!_nS*7bEXI$caMD7kGD-U+3FM{Cp;j{HM`CXtwWakTKq!C4@2XD4xPr zJD;&MNjM_5WHi>98<4;my zTs$?fazHo^)5aC)LooP{=DsoKu_}Eb;qneQQ8@9O+$iZ&>+wMTr*M7eV>2&Hwl^WK ziVof6zi9N2wXRB?l(scY(xw54?STx~2`7-XBt4!X$YtArntb615l|8%?De0n=tz#Q#Z7$hXEuF}wqNWFM{sMS2%!{Cddl;W(yx-9CuCFFzgg3qO(E za#ab+WWy_AR^qnRf6KuQ@Z;)0JQs`n{a>rxn2=JY$qI3@^c&yq)Eri)3rn9N<=WSk zBldpY6=2ppvDDO6i{zaRA0d;f4%=)oyOvR&ED{+ z$+Bhm?TciQsa8XG%)=T)|3f%=ROQ|@4ei8AdyHzthpp_3c*{8XYg8k~8}H%a9;pBy38nl#BH0ecjc+kfzns?+5#0FvE8zdHOI-L^clfR zI}bECFHtRLxqg0Oeu?@p^y-A)cM5tb}R)F8<<*<@Q{C{CxojtM1`)T+pesq0L zMz+$p(Mzb+&baJnrDSa2bHr|QQ8#Epw)N!?PoO5%5u3>L66=>_vB7oatTV4keYVI- zTrIty>(bwIL=_uk@gz?h%XBQoZTD5rAlr@hR`_<1Dk*O)aQc~`=oR_yzxYknhz0ax zTWx1nv|Y>2wM48ZK=b~C`jZ@jBtAvxjRvjz(&)2M|2O+uLg3q~@5fZq6>EYvCgz{V z{_XC#cm;99LkO1q%N)-l5p&u}LP#x@#s=Ko`xuMH!9Zp^#~UTS{c&ITey1}uIKtAo zJEWdx2nU3^6?-5W2XBh}*cTCVOwV4aPL$hx_Z4&54=#Ln&Zs!+#&*%?%Qn{#CoYvb zV|EXABxTDTemxm3`K;&Ci@EAFz9^`FuU6dPY)XQ@2)a|*P%+(#%<{n#FPbs7lg4o+ z!3^xVn&A>P#e0`%>h86i=^16Dke7xfLgz4-Q(0B=9Q%+C1BDpW8oHkYJxPgUi%?(T zW=QEmx8qB=TZTcLA!s_8i^AQ*okduRj@yO{c8WoGR6OE1jkQ_Q;Q@9a!FF-dM9-x#Jf&(@$(avG;! zfEN*39>*iGAFbkhuztT>lJm=ahT_8sk2-x}klu@7=WNxuE1E7NTknG8lq5+mK`m8!#&May$KS{TX5PH9?&#_b*bBP5pSH z*gP;skddYJ0~*?&+b6%4e1G2Fu#QBN3A1ayJG-d8l=n0r-779%Jl3(DATq^Kd+~2^ zV~5i?7#8vs&NMX)#`l9Fhd8p}7`B@%VJN>;0s|P2Ore>#CKlMrd05BH4>rhH8`rOS z6dzO=2Ih{ZyY6e%&jPYztd?PSM>O9|34PqG;`2+u zBvpn9*D!G93vR%_$3 z_M_0G?p#^!Evnq2PZ9T#CmVrmCJ~_dp}Si8fznJ>8eB=QzKkfXx3zF>ac{ZWc0;4N z1%92Dl#6g!Xco@o$t#-GGE|j~kY+l&g+Mba&r51cReir{`h*sFyqd+?FHKn(OCFV{l}Z9M7EDVz6Ix+P|U(fld%XvipkFLi!^$e}O0;kFqZWJvR@I}{F5 z61o+oY5t~fx$<|R?923`A+ayoKSo{@f1HaAG$Qi)==XLjccyXOyz6TFGCXIyXgUHJ5|Wdo+M&bq z(?fk1eG|_5*#gbwxd|*CiOU9$iU}p8)FWnq1 zc9}I>_Ut$H*8_Bwq{j-+9vVG!%Vi7~**o9M-n)g7&Fke?r|-mftQguQZ0mQ4h# z4l0|p&)oFjd0l7=nGWPLpy$_ec$rK9%{b1XvZK5JDo&a&zq}pmz>a*0V}6+-Dz>-& z%+Fp~IG+{f{qs*FZZfcC`H9y(o_E)*w~Nn}4ZtY&w8wIKladcQm(Nw0NVnk9r1EaW zl;!&tnS^Sw;tFpp_9}WX!!INltm*s+e^Yq#0H`iW5+u7B@XY%5oLdJ9-^$8mUJR{U zD)>JA?lAL1-&gqiC75#UGKlRMj5>)!;EgUP>oLt|0iM3n3tPJ()%aDga;k_W-`)d* zrN8YM&&MNK%OFN4a{U?-l8X$ABtgMAN(a+4kgt(!Ow#Z0sZ$Tji1o09UYCAIND5Au z2;S{aU`q`MSy$4EQ$R2&Etrk#tv<4rRXn9w(c%f_7S@|<3Bun$cYKlm0WU)g+-(B= zJY;>G3oQ$#TBrLYpTzx#PCp}0T#5I5pO~#~DW2K|fYh$0(^J=aJtYoXG!0&9Skhjb zSMy3yaV=kO$V_$ddx*`ib_Y~<>)q3ywQ%tZ*gRPUs#gP)s)@8%3&eFq?i$4EbA6SK$@07eT=nJ=2Btdf4d0s9UZ?SLCy{x61vsOHK zB&%H^MVNWryt3;lC zG9_mApcm8tnIijRgnC(1@s5O-7tGnT>d$_w6kFq`Vxiyu{`S5&1W>8zFE_*WITfBaF&aCbPZa!ZSv6UDnm8%8!PG#nBoQ@Ix0ZY@ zIgM(5D=9^8qeU6?_!euN%sB|CSeol!c5y)i_;sOOcMczbu4W;vWqf@}Bh%PU@ge z8acg^AhW7Zn=OQI@0%ErUkkhb3#wvljsg7%2?lW^+;7g&1=kGQu_o8FqQf*y;#Ax{ z4FBZzj>VbXNoT}1&cvBlaEEGOzPW9UPET=HfPtWZTbc~HvVYfY-P~FI#M;NTnZ0

{<-CYuhs&Q_Gsa|mkoJT{+Oeg*!MftXTQkE&vTT>aH zd93+^+BX;J>Go`}n$Ox2l&K_mpiHgl9XJKIcku#um-SfZ#u&`D$CWC=Wl36%#)={-EIjXu7GA{$;3kM!*aQ4Z0^_ zWF)RD;A*!+FsRH~G`r9$g(3&D&U$7N*41ms=YKcIcW*!=PdfmL(p0x21cd)GPZY3D zA!yAvE$NcRyh$?Px3wJqdqhowLY_ap%HA~w^FTpUs!hZX94FV}AC?=$Uw$2J><5f} zv5aEj?=Bz_^aGJQq(f%9qQN*UL-NeRfk z09p8HVCeW_+1gM#@?O(*+2g?^u+SNAn|AhJwRuWUw<9|4t6yp7Y9p|MKcCm!VKAff zZ?nvrx(91%_{2#Bx04fg#5?nqUL&}^y(>DYk$~|i4_qdteuTW!306rBrH#twt# zs!{2`;=O|%0kotCgn&mXmRbJCx;48V-xfr7w?Tih_Z+^c6uB`7^#dZV%8b`;HV5KU z5KtSiXz-Fe(&~|eO99=Dmh_y)(JWO(`y-5(czIhHrU$lVEb$%_A`6ElA-^R7z=`@JD`&)NEyL4Dgv#xFrMNYu-_xz+VEa{(Bb=Kwz!|%&pv$ve!C3tjyT;9D7s`7m1WGlaNOvD&ILXBi87sW>SFi-z`o*v8AQydKaac<>6kIh7Ygb}bI>hpPt@{Rd~i~@yhZ?1`c zPFohBe^I&LHyG$lx<|U*j~;&~CMh~#q?H1*!1E|pm>F4Gzp7!-p7*hvU8^K={h6l@ z`0a!YE9tDXE~uYw`ifqyv70flDf2n4oU`ANHg@p2uc|`b7e&icARdXK<)elJEE8R? z^hK94#bEXn={+B18s9gPn2hl$V{i_5OK3}^aAAT?t~~*ctT&?U_d{BIF9?yg6qc=F zc84`_2CDVyP=v06^?tUX*se6As2@_|PH{!*YO*VsO^|sc0e>y-9L?l86LaTXK_Rg* z@k`}h`vlWpgMNrmDl_wqIh3#&b|QL~V5*M3p?4+Vt_QAR)IZlYGu&3946gY?l@mM8 zDd{U(eSyd#i%%A-AX?$|VwB=G=QbXHyHSyZ0q1x!geW)H&e1}W&mqA$k``R)%6$wo z`bf(Ml=MhOMMXz3GuKKNGL}9}f^(gZA9R)%${>YB@D$BkVc#*Gp6<>OSY(R6@U4+; zo@Lf>mk@4~I>1%Ji%}(efQ-UqB+n%zpCH7}B^4LhorBa1*H$yTA3jXU!pk+Zv&-lM z=ap~q_2z#3RWus0<()yC~9hY!`sJ4gt?(Z1N}e2F`-a5Af~m{__un`P7Qc8j=e zn@@`O%%-;5GyO)y1Hi!_)BOf>o|&cU4Sq!;}8Hl)`i{W3%Q3ebS8KsBibH zi=%#-*iMtYiQ;_y=T{1~IcT(agshyIqs)+P_&P;W#~S|V-*v%N@qY8-pNTzqTCail zz8B-Q5gUSKZw|8lQL&LE6y=Waqz~krR3^+{^I%P})Rok*x^T4H+h`q@z%OFom1+D) z+lG&%0EtfnUD=HW9dB@%qApA-COfisijR}*`CVpSzYVa1-E|WUavrx+ZGFdQoj#yk zZB~mv9jDC@h@`E{;fCYDooKz4R{QE!~%cGG=DV_BeR6xjs6dR_rRrWL!a<(e}_} z@-|gh&&aVEdtX9vYJFKGaU-yNJD;CzU`OP^yT_^f?QK03MGE{+ZSQo6aU@U!L*F?s z8eCLT8IIi$lkdqg>ZF*{AFoNi=NNrI`%%C1>Bdi1v^;v*=lk9i>tk%=^=I6YG>LdO zQT_c7Uc(0lUO5L=CQ~bY;`i2GHD7yi#o~TlI0CeXk_l! z;F>#MM`)GK?ht`|uOIp+06)vX>MDKW=Ia77I{Prf({Mf0zAr`fLxNu{GE}gj6aCMB zpaH!A=xim6nG!IS4h^6~1NT_VmjJ=Nm)}TUG*HrqVY3(_|nBL?r928l!}D; zsWi}{Un`p5)d?p5WdC9-bTLN=>EN^*%2~strfDnN^ftN!t>>cbbxFz$N(Wz604nc! zffX^5k3^6hScx!zodUk73e%GW?WwjPRB@&%YR^HmGrc4nR8Zx4H}k4+OntbDgEPe( zcz`b;Y#u+arX|S0YJHJByX9d+fx1fa+7wCoE!ByN5?uwS0Nj<3Ol*X1NQnM2LSmC$RILJb6=Ps%FRt2)y-+UggTGwv$#M9x4_Jx#sy>~A=)T5IM!vjw$ReVut{*g4Gvo-a2Q* zQ5k#U$958yJobTjh(y)-WZrDVvaoVE|B4iW(F+Q2i2vANvLYp^xZr@Og0mCOy{JVM zKMleW$?$2;p-`fLyY*WMPy3mulT97{hD5})ip(_Hbb+G@9I6eh&EvM(LT#q&DPa9mZD9U2cXN+-9J`Z;* z9f;<_<=57ed3g=9VPT2NemIA;BEK*R$=h#-($@ZCuPg%J?=9%=1f^I)1ptswy{rT} z&}E4UT?4iVgNiD^PW7`-6kvESKmbq`P6sX0L9z<5fpqYjx3brX2CP+<47%NIf2^Pl zSDOzg`CSZ~B46n=vVRJx?id7`4(v!Y`70{PJ1?oWd>xc?U6Y^dH5d9|0izQxJ(C|L zkwnbyH&cSh*wvs`l~L+nI8<|F?rflCDmmQ|7NX&@f>oUCxfU1qRkJ*0o9uG<#_HE| zlD+_AoSH;lJ>}SN3v{UB_6(CdAe1-WDYtJGuAjeX`W9(>MT{jAF$JvRdi9 zFRTNHn%A{*xc-Em?MqxPq(5BI{bq1J@+bCYk=uP1?_EduU7%y6iA31s`KG`WEKmy;uq9m6w38|#h87)(gc<7R3I6i2J z%zxclo8V-c1xJTSqS$FLy}w$99N8npirMzsj8MUyG%~3#n??Ly}O&FKjcjWK#0&%Eu!HhcON zkgglJt{4^h)|ffKBJhs-Ql?bs;lpiIfHZy2DnR;az(^x_Rbh?gS1|R+w5Or z;ht)@_{z#CMIL`;9Ron_YXAgj;gBNshA5b{0>E1VbZf8o9iY#nlr@JAM_a=a z{%93M*piSsdtJ2$B#!?&x$XPg48sMTExTaDb!UZhh zzF4O2hIC{l%$HWBO#`-~`YwHZ0So90ls6(#&BE2pRxHm{n}uQo8@VEy%3vNdp*s!fAaoIt zUF@s(t({0`gFKeyT(^4Q=VdiRMZOq}H*);ED8IZDkKn5-a!O_zJ5SGE{T;e3>zddx zgIm@lF(zihmC<@;Z~_JR2@CG;nf*iqTJM1!E`UVRfx%Ckcr-v@8au%fK8#{H>7Hw) zu`U9Za@>&Gl<^*!!e55rD}M}%6j1If!ckIhZv+66Hc-!|o@H5C&a8TG^wcbpz&Mcx^{rp5Is)0M2XfXdPwY14*aNuLt>df+nrdC+Hyf`U31bpg{ z7ItHq0>|J!-9R)Qn4{3LI8}w|Z7vXQMgt(`Q%#tjxskokJuyh|33zeu#j}7a6*pj3 zDDNd5j?KK=84D6}N6>#nX%h;D?at&L`R%h_zP(Yo$6Q%C@P)g@CU`dG;5qG*KS~1o zkR_P6ml*kHfHaD%$Q7KxYQ+>rseU^tCEi?jW?2W?j4t{wO2rCI^IT*7p#uK)zE<*l z%a!g1R_9;P7+9DE`l|#=|B|Zz`A}*D!iotT251fePxd?%{Wb0S-^iN}BW#Qj$s09j zUkt*1v?TnDs1|AnG&5yn9otl~ab7-yJXXH4ad-P;{DZse{XZ^-ZCU{!R~VWwbO@Ay zg6GJC`2dh-GLKs;XRl~3xygRkRsbl4Am7mrj6QcLxg8W_jkj8EsqFcGqskf6=1unf zKsbo9Tak$v8EJ)y{~)^;wJf}<_P$SA=)tw6oy{)11mFu5lP01fTj`nT@zjpU#v`^@wzqVo-PHxq}RX9xuhpv{&YH#=nhx!$|_ zQwDh-WehzZ`B!c%n(K~EvZc?piF>GQ%ivIu=dO zp+m%6$cl|a426kQUSUa8j zxyS%t^SJkZ*Ek!tYOGYH3lN;ip>8UymUXwO{BynNx&!1?o{j!eCb&6R&h+VEul9Me z_WM;23;j#-AiJLEf7uj59)C*l(Pr<1%P*V-d-j`6i{0c?WZ9t9w{c+aY%_zbuh%i3 zo=HnG!WEEVzEd|DXRPL_P*#+Lb;997%boXjt_&e({qJ`f_R0e>th%D~Rqc;_Ni4Si zzV8@<`H=@`2^tp0?|Nc(_#|Ol-#s|Le#d2qRABe{h5*siD~%(*Uk-jG`cDMJ2D1jy z7>_Q3{(ktyf&{t6j-UMfH=+%+X%f%Y4j|)-z3jV!)68g;)LkW&Yy^C;Ja7C zHz^&LSWLi(-|(+LpZn@*MpeR#sE_uW@ zE?V=~P%H=9^|K};|J?42e73ONrnXJTc;PU?RL0i_YCC^YSwQd6Qu1+4jmd&}pAgne zLp-Y%0~7RE93D}AfQUZ$?oI-?qZ=_n_S{@KwlqpkoD$!WIwPa3d z(^|{I_;dMqh$r+BZP*i|KENWROnB?~Fn_daA-0DwHlMB+_+t`oYqwn%-cTA(3Gj09 z?=EtR-;yi~_VAaiH!~tk=7-=Bl4imrhChfUf0kgr4S54J61&?Pj(xj(lv(KtTQ_db zmm%q-n6KD zUfeDJTy#x^)09qV|8(LgzVI&Q+)|79HI^`C=Ay`Fzm^~x!7tWcwGz6vA^rAXs&WYU z3ixGnb@vZFLvYXct~0;YP3TO&DbR}^|A{iTIbkoN?%$34d<;3r{&aimSdv{FJy^NK zU5jx8J4!7cVy;>%cT$aC7ST$P&!h*6;CbhhJ(IoN0Js{OT2>tJTvQyZ8L$F6g)J1j zImp6O)Zb3c@2vCk3ToeeZ&Q@ZI42$oG;#OnGbH2dA;@py42luLqM=`Dz19y@yxbxe z(#HCUCUd>XVByf~Ue?-{T-!OoM{Bg4xSpj&YhD5&Xn-efQ9Rw)YFJ@53^Vv>Tja(+ z!&O*br`ir7J<7S$kG<0! z#JXO*xwC^u8E*MvL>F0Vk93M&h_Qtv3m}1mLlRn{gjHs>Pc zrD@Aa))C9r6%Oh5fWKOHL|Icm-KIM{JDM`N@nDhn`C&jMVz33e=r)az0g6hHQ$6L3 z4WJ%YX;H{t29mIsoFt=%_YCS4*k4^>7@p~a zMT3ifrZNt1H;UdVk+u4L&n zF(N@$phA8laqCL8gaw(FmAVO5qed^LdS8*K^~i^aFQG3E!U1Xv?vULZmVB@d{N+|Z z-dm@#9MUT!S;Vck73rxn?Bz!A6HXA|%ZpD?Qb=WUbc>sGPsdIZ3b(0>c*Do}Zx;q6 zja_A_vrk7Wvcz)3E9idGNKV3QWo6D*n*Z6xu;4iSIfa_g1+CTA}=e(ncT2Mg!%^LKtEq?a#|>U(y<7mfrJF5PoOBm3IA9X+6@=( z?}BbrMvK+h{bZlNnJDbxm_z_wipoYP-nRz@f;*fu_P z+FIS9l9Wq8|FNPV4;NgKd4J-aMq0TD!VM#s>O+7W* zMYCp}W->-EN_yb4tGOKexL#pYygM`ctP1{>fb=U>U2ysBE^ z&Mpb5g#b*WinELO0>Ej~d}+dCoZfy}0d0l0(X7VZ4;MYku+k6yd|?0kLGoCt>h|B< z;)UF745F+E#6lk0>co9*sPz!j-nPtjP0Rg+Ywonlc(nESw_AUC97 zj9$iA(yW`p(PnV_>VS(_mWv7?Q8$65BGSiBWF_b!Lwb}O_q3`ttykPSJq44teJFTu zLA_V&ral)*DZ$-TtR5_A~k7DOWZM zs1-~+3yv2d6{QsC#JU0CoAD?*;FMK4C2F{PDd&3uPNOO2m=d z?wdJ7$epv$Md!?&`zlIur&1wFCHwjP51+^9^LZc7=PMt>fajU}H8id3vtJ2!MsPHm z-nTq>S5XMaFPrA>Zb@zTeZXZt_~}*o+mj#UK2j?S`8i9kjQ_)4)(|u`Q3wvgZ!DNIWT5z=VrC= z#aa!ClBtjoH=x#Nidr#EZ4U6bnMZXs8Bv}P6P?J-mZ#4sZwVOwRGDWs2dqy`mw>;# z+$oh<>(X(I2d-+ITc~*Hnr_hpulk$%;h6}gj?32*-(l|haW30D?0JC`$h09|i8>{7 zsH3)2U6^;W5Y^3#ZtrZaClq&T(Lw6SX^qHwR)RqSNhnTLCysZT7g-FD2-N{C)wm{R zj$+PSsfp9;EDvZ0f7mQqbOPCvNpVz}mBB38g2E;dE|b8qt+JXAq}G2JIMCD3=4at0 zm-IpvJEn3s!6z72X;)8?RnTB7pR!G*QKQP;Jq|?`;cd7#H7y+*PSU+chz~Agr#R4<` znr?lnpg@RmbhGtQ1D($YqnnEdN_2XTv!S(0_+!(n+VF3O@X)g<3rQG z{mE%nD8No}KMvPe5t=I?BUoxY$(J`F6Ei2ZEbC+c8{m)DOml#K99=b?gTMj2zFr9v z_D^*h;w&nYCX5p=N9edNXI6BGZm-{3EZ_(8r0I38e6m$7@ML=|B~Fvge$!T+9+UNI z{rDwI4J6zPI-Aa3)+lPo&esF;(a%oj$YF8{*puSPQr!RCxFdvzqps>*om6hDBma7) zfw2?x0Z@1q>tBV}n^SVfP7>?dcn%x=uPZN`HVpJAB_M_33_|0RhUsJR8~)EHx}HjX zn|>tEJH&O;G7@ysXRGwPvM8!)a_MB-T8EiBkVE8;Wo*4LpKo3d=PYNnMi)E=l(_~Ze`$TlPqjw4#UV*fP#}onMkgD#klf;Mk={@P zA$TeT(*Q}kuk-kwZK=R0KR-18D`5cmVR22=RqfGd&Vr1> zz_;tZ&v!zKbc*}Y?))9)i|h16P4TIwnK5Q^y36>hNn)J($hw24Jq262m0U)XrETt; zonct_eT83cNvzCt-RIr*?8SK}iWAoARtIeI+=!GTwFZIx#=~%L6bra1S z&bR2^^n37C7%J=lKo91SJU44I8t6{`=W5nS?M*yrJkdm&!s`)8bhsL$*zZE zjMFU;QosV-sh%p1kHLjZdpcb)Zjl7U$Ntru6$1#n1r+%wBJ@=h=|Ut14eHKw;d98e z*lPnfFo>G%4SqDZq`erk)_B@I$QfBM0l3EFxX1r2XA^0T&3>Ks;R6rIvTbc)F?)l~ zcXlYeZacmseciMJIpvIvRpp)qp^Jkb&l-IZf11;Amu%3~9MPCPISoA3cl?29(Rlv+ z#$QQ+3p@Nf7US_7v7tgC3O)(*vm~6UnB)Zkn--N(_VXjBF~2@IbF6%7%MP8D9v`Z4 z4K|QpKK;TOh<4kRJ(GUTunLz3ub8_({+_$Bi$6{P!`H}`IrligV(c8# zJuYu{6lRytg&NdlMraT?&`!vrML@`v`=M9T1ZQq>e+?5vnwcOzzVVSX@#sj1S%p0O zd7F|Ng^u0t`)n~@x-W30cz=>k0=$0yZ9fcZ8bSu750=Xd%g*>^?{L0RejR-A{)4+= zNHwZ&^Htigms;u5kLI@~UyF{UbdF&>l}rpnRVqHP6hs^5veU3j+u=EP0ka%~K$dKA zQV_VwH8TjanSdZA%NqUuxSs3~ox8%)vl2R@7sclfoVSWzXR^N{ME6d_J5#%))U7Awz-0Zi=H*~TfJ=(ji6 zuCu!d7KLd~?Tlx|Rvl%zGst~rmliu@hl{?R({*QM59L|TD!p!UW%(5reifQ+Bj?*R z7660Kw0n2?6yHxC?GgvcAKn6-tjvxT!My!<#gS~m19muck!w4n^)y+E^%gZKz4j84 zBW*$R6b@e-VdE&ZrEQ-xkHf%Hw0(d-%(_seNiFq0n4lEs(WDmsUiwAwsWsO|bEhG^ z_I)<4%M8n6HZ5hW&8PdrXF4_~wR%Ojr4u43%0+gmH-pb;Ql`!nXb7YcWZ{L_pF`PO zCWri&$6yJYOCQ>N=o!EMykTwgvC4G&&cWR=f)p!PLJppK__!DMoY>hL3t~IyM{6rw zo*>ZS(<3R8B73FckaD?+w0{8;8BnW(wOja@UWFQb53kIKWJ3}WWoyhuak0_;S?of` z5t93nra<`#Ay%0N2s&WTU+&To`h#1~E5PrRo7=^Q>l)|6N>~Jy3t_Xer|nad;)L&f z#h=Lr&s)ARYk^zHm$M3}=7N=VZpd@27`G}5xG|YZ$-gQvRb6~>!@Y(rvRj^d41F@G z99dD-7O}!|4cXVa1Tcgrvz?eg1BGSYMvwt6PW(3+lLXPEv3o}-1Edlle8G7m9Lqo3 zy{-vI-{i3kzVUAZCJ1*N00 zrmGf$gUy6y4!X!WqrllPEB^UC8Z15X9$x%(?cDNwa7NJ4%h}_dudGCmXEhU2R*Q^Qzbi`LsJmz|t6c9WQ;2nZKZ<0@X z@I6V7q^q2Xv(!p9s6;&J{df7>_1{&u4h`Tzwju-*bfUV*t{Si^@%NuMyh*8rs^86e z2#Ic~QT;^+wJx-Yk7mg7{!juBcdzq4*E)>FM>H*YaDKp7Q^YbP}KssVU75zk-g}0(X(E-d?v#o+`Npd;q*;m z>;+8zA9ffuwEB_>1na3VW45GAAJ`^e!Xsw!d~L$IPvKh{ZjfM#uJtGMHwqveK=+TcTD}>v;}+9V5*yICDBfAEk|04b1ElvMOs>_ z9i!art*TE0aLr)-t9jTUUqh3TnE==VeM+s3S=a<~uqA%sGwh1!=FIz{L^Vh@(ANMN zZIX{W0m}%&nys*Jy{F)u)5=jdxU+jn@)BwgoHsY}L|7y3S`Px9w9TD%QYkcEbdZW? zDBknhA!^Rxm2OQ|wX`;1{*{&qIp?R)sSMEXZ75h*DozrhE*whi$K?A!;Y6mWo&%I; z%n3dRTsB#t$7|HKfuN+S#t~rv>_%If(So)q-mM2Rvah&i^4;{+^cw!iH%`xucC9bs z=7wRO4gC70f&&%-scd=j&Qn;E#T2+34fO@m*OD;%f7wHEK8c6ReSBP>l0;R0nMt*- zQKXbhawL>_xDIPT{fGn`x04xxhB!3Ombttxh7FV1r|ih06c zc|Lb$m{RZ_7Z+%HrzcWK3sQUu$2{AUlBeFS1P%;toFj()w!Uqv$l#YG&Ej9LnZ?ce zbj<59-Uz_!)`crMa!n8eiTB~@?;gde>7`Am z!YstYAAhm(Pow8j1rsOqmIP#KXwqeAQEg}Tn8~$lJCX@xV*g74naeHJ-$hO{viJuv zCYodpv5Tc~yT=8b<{VyDw=E8+kYZ4%-y>X0f67a$;uLwv03Zwiiis(?S7OFbK4k&0 zmwsFK%KBHNjjv1M%PqX4=8p_U|^Af|5dyeHuKpiX;UPL zYWvk_r);cb82iXA_qPVF4XC??@o}x;y!4xS^Fw?;}69#w6ozpNYR@t4GhXTZV1p8ID-s<1qI%5!gt4dT= zdLh&uY^V>GY_@DZrxDK6lb)zSl~2%8fk42pZaErW**=aO1)gJw+!eB51zWsRxq=q& zs9qINr@1+C@j&HVZrh|R$QOQP`XoSoO`QJ7)urT~dGXEi)#dd|aNW<`$Dc4;r$Q@j zPw4?jZaNH`{p10%r?-x+koFnO=j%Kd946BLhnL*ZvIOF`5<=W`AjGhjLN3o%;JpJ+#2)r2P|nm9Gf)I8)p?-L%v8W!{v`*8*D%{Y?Vj`k z*Q~=7AcEbbGH%fzQS`t|UW`r5k6~Zv7)LNS#fx5>IBgxrfZ|vTfE(?$0{|v8Zx#{H zMa}vU8Ta7poBwDMnyFlEIK1FYie^CuF43(^3@WE1#tr{v>n;-o;4@(44_Sh<69A>O zzAr$4^E~hxyiMtB60zPH>mRd(-y)#WJZ{s)191?aiL~3JeOwV)xJ^b#mxxV?gF26` ztuW9;sM^An#z!`H+62Hd0gh5VZESouF5-oQr?*^Lx7gNBhqQMK7stWv8T=uOfLa=y zj=m0Dgq0DD+foGMs~)w)31;5SSbh zt-{iu(C&S@#k(0<0s^gJ2%QBwYX(-Eb08XrfyH(F0)xF1+xZv!+|0Yc%~|D^~fM-?S^+nYjfu`@a7u3C58?Q5Z~gZ3sd^ zlJ+bKF&qJjUL+yB&NCn{oR>S3$#%N*J*D4_T6xD(Fe-`sA*K=wi<=WB!_$+=nNSX{ z#Pfy_uV+K<`?L7AwcH1+5iXA2Jj{SF1)nV2vtsk^;qQ{wNFY8CaMKYlSVj z#pe|s$&ktoo7+TZH9m;JoCWSKmB+zjHYxpr{hI|$?xL|R0X;)BkVbAcXobJ2i@r%k ze7eft8;DLsRIut0W91hS5Cg8$1R>OyPYgg?*~2}8NnilN0U#RDEy9qy`6OAer5mPT zp7O`ruC-gNGpXqGmafgLTw96?L|zle=hA5zRwq@n==Q6&;`!bHgkES)I^p+|257ta zQ&H&`Jsmu8>-p6}??m~XovK(^LKb46>J$9umSx`oC%LY0mPqXJJK=jOThqh0ft0UJ z@GX9*^jilHcBP5-Dv8qT#4+doUIhFXV|mxt>7FmoX?<$&Kd}0q{enMUc95Y#w~^Eb zLdyurMRg>|2qZM)*`g&?As#hrsih*$A=J-(U7Vnrj;jVBJUt+M#B=cA-81)XOjM$#Z1lC4DAg1S(uu^Hpbl%9klQ7Z=jco48&RH1UReBcU?@ zP;!I;w-EY^0!#tjVmJm=rZPX6COpS0_SIaMCKZG0<5=tAD)8Q2N;%^tViZX9P1nEcV#LKczhVv_9kaqT%p|w5|L(#9n*EVOzi@*Ihdc}FtQ=0`g zjQE)RaSVV(c?jk%GA~*ne2C%dAwomChI%2alO5=D2 z_FZWUhNCTKacxT3B_(f(tywS&B%+JXs;`ajFHWTu@cYl@#QYqHx^|+=Y0zgbHE+?f zHaibWpL`MCg^M*BHII>GaS%ii-xRa~!v*#1taQ+!!=??(zRPUjTB)&k6?{(MK}pA#Eau$ClJheT%_S$Lle$@GVIS_yVRcZhXgxD3bNjB?yWp8uLc-iMxW%l_j;2C&!oj5E{WMQ&}9Tu9DesTzPOZO z2_`@@8BpRov+c=;4C@Y>go_MVZw4=~7?8Td?q)goWQ~WZPm{XQZ6*`v`+hN1WB1vd z=nH)@Wwwwy4zM!(T(luq9n1>+v5?F7^E!=Bp^H=*fG>k3Nc}mnzqQ1QKX=@)r)m;vU zsNJ7n0_BY;&Gq~t7)bQ+vi0DZ4_(h@;=7S`8n&6b=$cK|Xaq8>4(*?_>wx0zM;3nb zjaLg^W5fH_@V%URhqOlj^*(W6<-f>nsML*&=!PY-yB_vzI}#A(K=9Vvz7V{83xV{} zes?1wT#~Is=Ij{6t|Qn@JmuIMJ;Fgg<+B~ul~ueemTW(sl-@4O&j}H;<$7La7xngC zVnN2(wVaoI{FBFs+0(+5g=!ikfWE9pi0&>EOD8|jv6=|)qRqk0X%o_qJpfGWqyt*PyL z8Vv4U@U5#oSIQ#9kK=Uf_rfEX-sL{O5EjBkXP@uzoK3o5)wPU|=^n}cj(?DT;!Z`v zh+adlWnEKf+KYbCL@ReM{ShTa?$0vWc&Ivm?-`@cgaP^AxsmQYO~2MR~A`co=mXtIVaJmxa21l`}pIA$oed&&*a7RX+&v365|_w z?Y)^>_DczT$bUepT#wn{q;SURu(Ovmad^@D&cX$8h~D2(fV?=UVaGwdIQJ8ET%OoP z08HVDX2Ei_dd>|6i|j~lwbLVLIpR_6FNNIN*af@iYj$H+gV#r_VzmocK{Ni__8^K=H|$~#sGGvhC12rDJo{NB?bT#M#DWwqcIBu( ztrK)DIj*UZ&u)UV2pnq(<1MdCBGz4D_e*Opw*RozvWORkC7zu)UA_q*sg(R|JhiW{ z93Yh384cyK?(aHl$_+U0<-yXeb$neVJwWD+ z7j1^~_oi74xVJ49di3wh&r8f~e^#RE)_Q*>!H1pmbW1}OCQFe;jn?oP=r!Xs;>lBS ztMko^rK%iVu2~*8lU6zVsKp6&!Li)vst0j99}MCW6LHlK?a32V$Kc<7+I9WJrN>^d z)?RucQ&wvsd+3!SC+m%dr+xI0;r{nI4Y`+4n}!0Fk^;&CZtcDnu&PZQb!1Omt&|l> zXR3rA3U*X?up5&rmz;NYjytA4+o7JkmLRl%mf36LF2DE+xP*Mw{7Qx0~{jpw`r*lx3&czH?EHiN(6Oy|x=L;{6H@AlGA zID3Wi^wIZ~?3Vv-I~3L;Yz00I_dH@&JU^4N7+$w7Ah9#DSZvD>8VJ#jo#y?cX>NjQ%IGT{i?X03i=9(E{8 z)IR{;Uqpr3Ji1)8w4yPfvg(5#B$LQ41DgK^I^xY+wY5&ju1du@JAP@Tosdx`Jhswz`KPJC=W zRhlzSmCCNm5q=

RN9-6i;nR<+eX?TZaZW8$Xd%3~Q>x)GM1WNvb6AHN}CC4x|;0 zwkoAY)cO~elbb|@Y#n4(8qX))A7c(RcVxH!C_Z=OW8O-Vq5CXQ%9bDrIy`?8tOZy& zP!H9eZ^Q=`_$59gNBHlZ_ISC=Dq2EEU5Hb|veOIIc9#D>PM-=&Z>a-O9aQB}p(ggQ zUh3RxWFPnN5R)0B+)(15`t<7jT5wFRL_CLP^c)=pCd+K& zO;&BD14Vz8eRH{E{Kd}xyPTI^9dSWCis*KXlwYFM91(*2w%(n`iEYQg1Ns#CZ{OtE zBEUGDQ@tt2a4*sF6*)KE*tNZ47J@Zoq;f6Nn|mezGE$1ir3Fg5tdbBtY3S}y>Y5Lg zK?WE)dk{`jWh+A&hNPWiBBVjzQh^ynHo{AYIAv7Ura4@2BM?UTii#LtC7+Ro%Zv0= zB}s&f1C@5BZWJxfHYqtf!;>jTf|_|tBwgNWG)2>|Ys6&ZWPwGupBj6KPPkQg?Qq&A zAEp$t<{#Cnk@@-HKw+No&6%|c@-=rtz276bi7+^at}VCRdPp&tVwDc%-u=m~@NX6H zHoZ!GglzI7cp!hX%KJ(2l}!FajKl>xUgk-H`gS3=7vS?4hgD_jj6%qrk?lVo~J zS9q{>ocxx`emNXqO4hVh%zW#Z$TY9OJJ_nuOd*eQb#m;1Ddxo>;6x}`B3o5Ktby&8 z09`ku#lexrcH9-bhfGg*mMAk9h< zw)F}9^uf?3%q^+H1~IUR!K-P=V+}%}lXbKEtP(xW)qy49)2BSMyeT#n3nlQ!xTZHa%xdx4M#ZUE(}jHJ*|&_KZ2G5yId(?#)9Uww=A zWxEx6be^5dY09DV&wZ<-yVzoqIh+-S`aDycwA(^5ud*7yW7GJU*!}vX@os@=USp;2 z(>$lwdnsxFsr1nJd^ZmB_iYiDs^NxEP0hCQb4vkS7sy~6&MAl6SyWk?59ST_RF-#W z6*h~|T_4DyG&HdnUVH0%fGt~-YnAP(+sC||JL`VF^>X0phgEzzQ7KQ;NhBeW)KKaF z+Qp<*2!={VjLY!_HnIvpMRW9e^k$RV#!!60hBC?cTmQY7<>r_{>X+Y#Y#`1u3!!g4 z^us839^O+jK9W;u;DWSPuJY6yqn_~dpHlAUirHDJ%(vNZp5a|SMPv_2ZLnprTBXnGvJ;;z zTDOh{TdYkoO&-p+SNOw_ZC|~(NU^jjnZRO zzWDxjq+wP0&Y{J@k9|E^$`Z38nLbAdzq;9@QrFn!MT*dEvm<8E>M$X8 zSX908`*VY!=`u22UTKfp@ho;m>{qm<&>Wl8mJi3P10lze)F1<^4b5AhJR75(zBuj3 z096hWHxX&YRfeyI?@tZ|mWe2b39#10WZX$9u)OLdOi;=3=wc^5s?~mX+0ctlxJqig zces{e{C?gNs+$XBty!O2f8E@*)o&WkA-yP9O6tK0*k`yyi0sPO6A_N}6|L1AQ1TF^ zT=?L^o7&=EJ(S#u(B(LVcgu*b!S87Muwx^g5S7jqF@Z;cpHZxyKVCD-tK_4zz^LM2 z2Hd&xUCqv;zkd#G9PE#{e;n1I!iu{p|07X58b3ip+Q8r$uiU%TUy|YZNqlJ)MH!mQ z)iX?$nH3>@A1>Wi^TB3J2!uja7U;Mk&qZ`TQ)iTd`cP{=r&-yMXa6GNe=LJvQAj zbXk(zE~I3uax^Y5>#I5wf61znbB=~qNko$3sU7!ZVOA^0=SfALYs+VrV+ADKb9rJg z%Xk|cgnY@eN|0BEzwIP&JfU$q^b!kz#RAYhTlFOm>`wt`hYD2P ze?8&b1s{nui_h#~V(U@nUp*eZx*tEVeB;9KcJ}yw(!Mnnco!OFndqQbC(J+&NRKZA=(L^_EpD0xe^@9YK zp#ps;1&YZ6>gj@;^#Gl(lKmNSw6lex23 zpovnD+Dh)NI&Jnmo!o9g3PL9z*cU~B4-aVXtj5sr9QI4^@!We$tvuINC`xuj&qU(2 zp;)&ZY4k8`F#ygH^5Jc$5P;e&mMkOGj44IS+jqzTcJ5DUWJ;_{yPbEq-jDp8;_m5t zK2@GH*2u1eb#WgKSh|k^08HFrCi7aI+l}%e7+-Xi;+t*Bn8AM?iAo7X@ZBlV`V2={ zgmOOV;-;L6dirGQT@?+Qpehs8L4se~Eq4Q1pohat;{ZxEnscebCGA_R-5_;kX1#~D z3r_xy)%qz{wy#PnAXl29iw$r zqWQr>WMJu-e@p_m_MW6U@By>kul#3t^}{b^y!;WiIJ-Wum-b7ROiH+R&r9<*`P$_r z?6+?Za&7r|{jb!?$zhs8RP+=3+|-z*ScH`!M7D_yHc1u;0U)Coe3?Fw7WsRSsMftc zjvp59d_R##EjdSB7uQ+^{k1;Wes_*s8HjJW5loSWohaY@TGBhRI2KAeRfBuImq)EO zFdhcfq6}0(`mPXZGPqKOsffq;LUc~Pul(;amb+c{0?Q{xH9jC)Gbyt};Xe>cb;U5E z*3@UC+%fg6T;6F8 z`()nu>xgeomsj+0SgpPhxio0R({=9DP}|&zoe>AG-h_oI+jsrvCExh@@M?aS^VMBf z)r)MzV2dm`zLbh&U^A0S`^mED#QoCy9aKhTe*22BT)UF`iOyLqp9N^Hn9k-Lv+`w%U#=TxG_LG#2wVc&w~#GseM zj^7{xUnn6fa;z7-HHKB#g7~<+%GkFj-40YTwTly69$22r61K+4tE^@%!nEWrSUMMf zJ@OHKv@BWiTau$Y;;2d(`^|9t=iHML9hS-<{iL*8Z<>r|bN9kzlR_q@cHBlKjXJSZ zOB95xf#9Ro*KjAi23PH#7T8Tniz<9IK9b_oWk5oMb%n!)!W*N0I5>(zyf`RwZ*Tv)^$b~}FCO~!TN8!M3MMw0Wspn2=Yw!xEi!w3c!WvSy$lVnedB$zL9 z@`9&86rQ3r5cuYHq{;_ZA_n@T@_HsAaAlut<@b#}%$RRz3rTrF;fQ*NJzm-@JL4ht z?RQ?nt;9a@JAi~UPF>cYou&DgN5;jU3+7kUV%v-?jtFC;?{bq)oBVfLK1AeU)Y~yp z1NjmAhH0yd91$0p%v53*E89^OTOfF2d9t$`zEyp@cNiP$|DRh8}s1sYOK0*!dpEd61adMjjqbkSvgSp;;y!3Gt_irUQ0^ZMO*j@GQ9^S{f@wlDy6g0hVO`ISS$_K)BM`tanz;XzZ+bc) zWq>LYILT4)t2wB#JABgV_fw~m5Qz3m)ow1(!$;RJ{4XjumZKbGo#Zz-vxqYvS@d@K~=q8gE4;ZWHYcG8Di{~O%eXIBsgje?()c@B*BO2 zVaT&ldFEa$lSSDV+5i5bt;FEJKZo!Kcc4g+b0ErSS(lkDu3mIxfG=IYomF83{TrODwAoOBbJ| zi(!Vr9iUh?Av#)dxKXM?CYP3~PvS{`cm#kD5}nJr zKXZV@e>E2Ims$(`9B~nomX6xSn=a~(S4*ZP4Z4>5@y+%{w)uoG1`w1~4W(6@u6NG| z)Xm1b`&T6GS_>ld+cSbn_Y*l$z3+?igPvS%Qbe(E-5DW`5~~m}r_+F2r9skEK0Hj< z!Z8Y8H6e~LKKT5_Y%@-;;;up3n`wp3_1+$ZojAeUC*HlkdAp6Zc9Ws|@1OSQBNm`I zpQUZiJ3CThHaUSpx~LT2JYi&IdEtb>TH^BApm5SmO2)s-J!VwIp0zw(CLfid{^!xK zA-C*aj*9StMymPX+S)tJd@w`W#;l??&$dV5$IbwAbFbY&&)^RAm6=Xq{+Sc)o^r#6 zZf)yc2C~jTNqk%(YFzD1qr6Y>1X|Yf(p(t|wrRVbFKWj#`&v?3vS=C^tNB{U$=&)- z4#iH`P#BbUtvp-;fAwN%fZ|i1MpVadnX?xg>y?Hzo-9XwZVX?|YI^TT4z-%J{x_Y# zuM0^}i}Whqw$l2zB^Vt29BWt6j%&E^^(0QF?f1q{_$`KfL%>sHteLZg!-U@~wE-_+X<#_v}n{zxl#$vEo&T%o;c*jaNa^aahj9(^n2@pu`7-$30g z98!h?GP)w#ir~j`NJF~F^O*R%Wf4B!-a;ex3r=;hTQr61e*^=o^Yb}EFFM_v%sdR& zG{3JHn3z8lC%VM|=pXp&K4>4+NOaMYTbs}cpS~4xW3uhs91<*D{0dSZ3S^JAI?ZDA zem&Ai8Yo{4?`L}6D~>eqZyYe%_{}cpgsIr`Y`#1Ik`}u6Q|0!iyu$lZE7qrY5cK~x z{gnfFQFu7lhY|+Y~T`;mQ?dcqL~HhuZxfYOQh#+AdyTOnl>*bsqE(LC^Xe( z|7`_3@pr)T299PVv&z-JX`xHW%?xf?LKx}~a3yQk?(n9O42R%$QNAeU@(Fn>4Vtr+ zT!Xg=o4-lPs~6Y$`LAswR47l*sgKnP=#m;OWDL%ftxciGZ_{v>CZ}gi4|3EG6Vg;v zkaI3{HvM`&g-eco5ZAu? zOAmz$QM8G)*fMFOmf;~mP$3u;9hZY%VXl*%EJw2B!+ZXu35~OUdSWIFyH12R~9kY z(w)2Id}ua>(ET;;=eP{sy8>wCH6p-4>4GBwquY_YE&ny*!iktKsCP%;N32RT;tKe& z(@?$sOL|hHz2D=7?JU88L5C0Nml22=1}G;Z;iVE!CJSF@j%ICU2#!r=>K$6Z2t&TM z?yr=+BiS=pZR~>K)y?_UnEJAe6Av_Rq6F;gA@6oyKMuFex@ZiJWWm_^kGh}K<88cI zfZC-0!bdrNQLc#9hdORN1TS@4T7;DhY2BQ8Yw`)e`|duaCuWCUQTfwK{}l})L#wgv z#R}iGsY6@s+PdxP6cKID9Xc7KWWUO283`w!j5i;-L#wmRWkaO$ot1+x`?2dyl$`z# z%l<09q=P)g7>wWNw%a6AgTpvIH+H!r6hS&7B>Kz(BVU~*&3(s>1K=!F68`y75Z=Jm ziS{=qS~pG0pzFfRrhRrJA`>3@->u+p(8ppcEZ^k%CWpw3d?hK(IMLS6Oapp7f7nJM zAp8~F;N%nUnUTlxi-hr*F6vZlu)N9Navh<``&I&(@oclZL)y0ZRFv3Xh{=3E`?V?6 zij7*)v`&VKzwh1J5)6bk(^eg|W~4Z!Oz2LqtN4%CW}*TJn^*YaaoajK-a$oP-xewZ z-3DKxR3bF>t$uZAz_$IU_lO%os>j)~GQWJUkGY3CaRt@M|Ju4>`X%j5#o=6Rv1N%! zwAl0lj&nRzR^AtCTlk(C3Lw zGWh;x#AGkb>igzZrcm3qmCOnd?y8#!%xy=5CI*n&gaa1HgDM8BDToKjP8E^6IT1&B%z{P=uxvTo{iV zWFT|NAn@Ze7WD{If}KjPsly`~lQ2z1=`a(5z2wC~#`m zFKH4rT7@sK6L>>lc|htAPSIP|YsN^^8-Lh1Yl?3g2sS{RcN*hB&(pxjX^?!N*RT1UF6y z8H5`Bw`}?Irf?X$oI1U|NK!sBfGequF?Q1kfn z7iED7bVM75jbqY8dHek4LycAGv{&q!`6QH1F6Ge!JAseaeX(-%^)&a5G)#42-MV^~ zAH*2U;&;&vmuBZTqWK5xx(96B2$~c)CZ+do`aGEnH`5TRo-H(S29|YS?l0yzl8>;F z&oUSE+$e|$NTA}w0P}OGe;rxU47f zk5YKBa*?$S)jB}^iB{2b3wpPs{4CxeKg_E5vSdMmsU>A z1!HU2XnTRjG&j1yVg=PvD+6Z_YgoRHUv{IjX*oC-X8BqFo=2TO5Tq`l&1%9oXdjgG&R!so2EPLMLgPCRj2qv0giXf%?O#cA5oKT z)C(?DwI^qx2i#?00CuNvRwX}`7A`>NcC5^gToo*{i+N4H@9nOE`K5FWL`2lJo&^LW;nnn3Q!($wR}ApZ@9!J+6K~}!lHra z+3%+WuiLQN;NWR?;n4qlbYSU~XCMO_qrXrM4`bvWNiM#*Z6}3Su}F`Cu!OSw0Jl3K zAfmYRc>tx}{(k^=K#9LoRwlqI6vDs;b%`cHp=J=8AwBU1G{BnkGM6@|C%=dWt8f51 z^JhSjq6*j(Mpr_X0j|`@ttn$$gJu?rnFl3;6dF*0Zj(upxf=uJ8bA~qS4SM_h;_7~ zP|ekuow)`_)ERkDj}@YvK*3(B`GYa#CSv58I(Sm|^-;reM$STOx>k7G5m7$Tg#UnS zG!>CbsBBnRg;>}h)39LSq76^gJ9P4q(77NSR;U`*7ZYF|8ImEw(-vtWFy26^6EP7D zb9)UjZ~xE-aFq-`c~*DT1+By}NhwP-0VKC%VokP*_)~o_b8&Xmp9eaXuG;^7Wg?(- z*(U`wK(tC_^5O*wG=?B*0idNP6Yxyupk8|rezcksmS73Hn4meKb0-vyg#x$=D5El} zG8Iq(Rp1%+$aHQXHQY#PLi$f|(@?h26Q=S{_v%BZa&?@5f?e6E|Eb+xGC)I;4#T|YJI`uIhJ%Mae zyoBDegy5otZUk+TcMUE8kv?%I2?9tX$(0a7Al0i$3PJ3;IYF^oBss7qrp#h2*|99NR%?d`&&*N+jTfiLHqUbU56ngg&u|3YqHLyP zQ$8^c-QW!0;2c%uI~wLxx5HG<>AXO3A%PmHliIVI8gKI!5iVlU*{~4=vkos3aIgc& z1jm%<+j1a7OG}YK{<|MfCaY8eGp?GIFGE0bQ9(+PtDk5VjZ7v{l7DPrz(ccUXa>ri zM{_2ZiD{uhc*~%X)VF&nKZfEpQq94GLb!yM!RK@ui2MH!YS(B~!+ugQ%qV=!8u-i& zB^#pVb;nG@Odx7@qZ^&+j)-vsPBaETF^Q(DYg+2MLZGm2;wG(o&b;;y4`54QbW+n& zM(w=UIRSYb+k_>{gwSSeTAUf(P)CK369dep?pS?8SP8l)d#CF0~Ie_wp59g!UUi` zF%?IKWm1NfIqH514WR_g1_K2xtpSOqC$e=s2jv_Lz;V}#gYGx((DB7*a^b0!?%MwccIrp+;-#q&EHJe z=iwceqiZYW#N%PunFDz;uDg*v+1^43#7W}}rV2R$4L(5tt~2Bl(ruo7h7ZD=Y&by) z-4n|27K(rnVzmn}24jJk6Y!?dug%&q;v2G1Q1XW3uLH;styWs5!v~Q&GU245<*Be)(c^&gv8KqXCOS6r7A^ zXSNlw)f4piTS7Ok9;`CdnBRlxbBn2VeVaUk!3IV?)^B4fK;+gAeozWk9L}sN&Lu>- z;iLpL>rPMuuk;yl_CQkrQX76bUK;;v?WH;K^^c5=ALQ&(@IfD9&v?J7crxx2iPsZ( z>Mf;%?;1ku96Wn&14Fcq%{hUBC0oy34Q=a6m9dDMJvLXUZ@YvG2ID z;9MK{HpP&RG|YiXx+<$O8$cvCqGH2O&j5%Z7`?L>=kPdL&!rtM#9QkAyRsC&c5BO0 zQQ1K`;-M@6@ejp6f(Z!zBSin`--#pBj{Ga}ZyKQz6c1jUw(Z}xjQ^;$`*+R7wQC2d zK(P0(*F}ORLi{s9aDa&}EMKxbsEptQ9bFi3%a%;mE?>Kd?jq>xUr}U6ku_~f(CATF z*^nj6CdEQC4ZW%<(=U>^}vgO177CMmoLZ3LHR%E7Y`V5oa5gCf^Pmf zP&~y88#^PM$U64#7?c0?|Cs!fC5s??CcM`3QhyBBG5x=e`TvE9U))R2JtmlN!U+u4 z(Cr@@G&`pmf~FASA|YTPsKS3L#E?UrP=H||oI(`nA09*$D5Zl?Kw%Vt581b`s*!L4oCdgv%73gR2m~yr&};`b zbeLj;6|jo0DW{Ht>YrVZDN3rSmYQmpWS&}K0}Fc5K!a+`B8V}@{!wO_vv4Aet-0`e zt5BTYIxI56?jk6$ND=ERFum;BE3d>12+T6d@Up;*P&*s%A20sVjg5cU_#?ASSLy{% z31=e+R)TKRL5Kex{%G)@+Kk|iIROaDBf5X4BdEIOmZgp*>7G5Pf`bmY;Q(p>F#?SV zz35^JHcT)NJ^2peMHcqVT@M%a_IuC2`sn*-K>Gk(k3If4QSgv1Mx{Z@bN+z=h6*pl z=|Y1s+>lv;n8l}dC|8dbEhg>vx4l-e;nXc4QOPDy@VTTDOIOC^mve{)Qql)syOr@fBO3gRp zjQXj6203Gbt_;w#wy?(12`!fHDs(Qj@k)DH!w3uPFTpm=Yp=WF?hP)&94n0P!YosC z)r&?0%?tl2**JC8KiJrzwyxa3Q>A~J@q){Xdhrf7f_UW()g9c>mY{17%HoSu6kb+Y zyq-($SZEvAj-VT4CkTz&D*wk0JH8F~m~q=v&%J-_`%n0C`P)xF-`}gRKv{m_g+2s1 znWGNP5}z<(f-^kmV1NxC5ygu_gcw5*O%(qhdr$!w#(!Xp(M5@mAmju>jwoT8h%j16 zNBvdykw;kOvS*y&A9y$j1hhegHZ;csAi&AavXrF<=IkFjutPrPFeh0Eg-vH-%0J%J zCabL}PI7umHW<(~JQ)CNtWimAzQdAUxCJh3LsX;`cB#Vn&!^9^SHVZ|ND4h_IL$Z)dokJAwZ zcE)O!>X;=R>L4Il$hxC+-iWPf@ey-nBuE!H^A83bC_XGHUh@2t9{aFok;L=QMc%_7 z0R@OX3Bo`=y5O&W{7VSi(-5;TgfP@GtV9cA(TUhcu@$+yk7)Js=sgo^*YhXtvHA!q_mL0MhQNaFpNZ^JjhwQSP)odOqQ^OWyl=DKZHDk zM-E}gEkA(3dJwRH1Uz8O67dgZ5=0ySxIpAOdJ!5}zyzJKrUefoAur^E8$el;EX-sS zHIdLwN--f+PKd%}FrW)v(SSP*uo8AUfHg~4iCY3UDZiq5qT0GytpVI^e*svWv89-la2c}RWFQU5dG`!G+bQpHd6k{7RE zn9Gs~!hjU|^{+RCGGPEy7+9M#O0$}hB|KOqK~NDQ`PIn8JsR0R>Oqi_-I8P%S(3_J zrhttc#AVKKMw1}m3?YJqC}lH|Gb*FC z$~H$}hp)NNHH0|}4DDvy+FnF${%jkw_!75633qLgA*etNm6#u5%ToV@kwX0i^!2m^CeR||!;H(9Pbh#m0) zIxqT)Ao}eB1cTa%WUL20?ip9S{;}X=5U;65RZk-iG9djN$dCd$5WX0o2n+m6n*UIQ zg_#q|?TwFP=Q|}TOB6AQ4Fm-$_(#Q5lz@v}>_t3q@jsAskP<{BBH<=T{ti--f4FQR zENfXv#DU{(9E1fIP=S~?y6@(kBWEVa$C$X;w9QW0O;!V~XbWzXWbA?)8?Z#4T$rgF z>U*9PA1*!ZX|pzNxEYs5wxJYXOU=wer;trXa|J?28R& z;Kq8wP9WyVB6|FB*MM=&NG6~sdg%F2+Z+7gdL?KLovAQ$G>p29F^feEi&%(als;6N z4}Al92$FuZ;&hLU5Gix9E&pMPc(CZ4blHK%3V`8Cyb81O@jdGQ(_Th zjmhD_dzYo0>97utEV+(S`zTX^iLSE)!`T3CIFI-!kODCt^k^PNGM9Lnz~Movs!9*J zYdiW_j|4dcMK}i2hydCSjf6#=Kpf=W+0A;X%H}C=|d;`mwf(Owr&FF%F%Ldrn!ZW$D zFPuH8Ac!=P6BEjSGthw+nyngQi@-s?-?|8nD?Uuo3q~0tAL6Yzyc8FL43^*G*Z)y>CDOdq- z5SMO32F=o}5?Y5f5g{v^y{4EGicAI|Py`)l26r;P+B!3egG0G6B9%Cz2a%s5Kl=#2rK zEE-=0aUPuvx22= zlMxKLK^L>ZgZL$t!2>)Pi4TCcL{Np8p#p+v5*Yi3C80c)&;%14mHqB`^^$5;!TluGZE%18e>IZjzmOPLJ7c+Z2F0D;`E&j?EL6C6um6&V64 z4RA`2suedd05@=zNhAm*z=Y`%MN~P@YpH;&?6g*_sXO{K^$NAEtQ~D}G#CPbU5p7{ z6cr2b33vIO{4l%psI>v}4_=TLWpq1Pd#ZV{O95FAOLBraID^7Gwmm2qgy|5mBCE3M zyCEf?h4E1q6tRE6wzXOqMZ>{##E5!85^7jS7l^SXxdsuShHI#|BTRs67>9p2&G)g# zL=Zjoxd0RS2SsCv2MQ#Z;KkkihYi4h;{46ndq`wh1~U<@e@F&DRg;523i&uEKRt#m zi_Ymh8}@@Ow|EV;SexH+jWPe@PMKtze-ffLQ=9d~mQ7uPKBTz4FgfOMPsyl^?9({x z!I|+m6^Am72Dyzx^OXUVA;RD;|E#o&xPjTxja{+ID)@&A;Ddu0R+$RR>X@kswIf*6 zKb8O;$Z-Sw!c95N9nP?TGgtxtf@?Q|P=g zJbPqpyRKoZDR85M}C z&CtLep|g#s1W=D+Ey#hQJV1_rm>pj24m1cB?if~=VntSjDV!3^SiA_>;f^@-J}PV#+S8{gA%bW)i&PV)87DQtfDQNq*Al)^STwjab=^4`vQr zbY%*L&`vwnIocyWG6Gf>1`*cHI2{i*SO6eMVHG|q0UO-=sL=qKm*Iio0b@}V^&Jb8 z(WkP}VY6IirCu)rWp!fXp^Z~iZp2stiXz^ zXf+^MfE{=O*UC7Yy__~P?4knuadfWl#z+d>T3yii#YU<1Eg9zyJn7gfR#O`@%z&wAr<& zA=n7txnK>Grc{6OqLH)I;$xITk>r6o-%+LR_ywZ6`J02VFG_rn+yo?oNSx020}KE7 zF5D=zSUm`!h9b=A1tWk~=761I@hDqF1Na7(VJ*ecX=trZkNM=5W(|$OfQ@ zV_BHU*z<*D5L9pyRHIN%t00J_Y3Qr)h7s^>OaPD93O;(eitC(gAMkxuEv>w|C}xfes8Y*G*16pj#{8^ zmMB}hog??M<*IvX0>sUou@Wir65KS8W8j2eD0Q>;owIwF3^Y6Yz=aiEodQaHoYE&2l;E9==BT%D{ki8Za$?ECm#_3JbM@voB-G#VQ0EJ*mYzXKp+Sguty&h zawh50&Z{?RfCdP_$1aW-6nKCnUzmYTJy5<)*9(f{WoX%}V?ph#b0VQ}TAGKn=;o{e z-_8L_gCUi4?!GYT>`cirKVKV)GaE8XHg&YV4w8=*6dJVNvbMj?w?+hIC zv_nOSJ{+3KOkwGx-PApl3g^eW2n4Ms0fLne#<;Mw}_r8nB%?ToioMVGESOLJrK)C-C##%#{wnN+#1x)0r z;rvh+7-cZ3uc`n^5FZ}!ZQi?WE-Qo?M}YYEkB2}M0=WzmxWvGce@lA(oAyDXz=|&Z z;rU1HpDH{Pw@v%^Odz#xrmj(=(yoAuZY~P^Gga{)ghbB%U6?5_jzEGlcl?_m(8LBH z+_K3^nsisTY`X;Riu&*ARA2wBPTl3}X*Oi*)-h$8EZf)>HU22c5em&ei1aEN(790O zKZXq3))ffR?!vxs{|bz2MBw1T|LDbCA}8?7hHnl39V|F+-6cjWFG2j)z*{+p|DoyS zrfsjd&45-!ZRejI3wFG$wp3BWXn`8&R&*np?ULGbIpF`EsifkK)PIEi39cMBhX(%+ z{BwgwI>a4byiBpv#g2(C0zrWIm;BEkOZhN3Ii~*DuYbqfALAv92`?t`1u~HppFWdc z@%{gT%YMB4!^<)B{lZIo1RnTHf4}(W1Oq0R@Q)AzU09J27s8Msh8_OXp@>3sh>A`y z{9{mvGogrLiYUNfqC*Ph#anRK$dpk;9DPtxNdH_^5fwlRL@a~278t|8QL-R)j8d{N<?h!O^9 zzQu*xU5)*t!9t}y6j(!a82}NDhgNjxXL2wqM_m7JImB6F2Jn?>Uq(z89HpHZvDl%) z2!w_P5H*vU1p=+AlNSO31<`H7jHW|H$+CTZEO7!!8C|0z9v;m!%{@ny zP=Z9;{6n%vGYvTsAl>|P%|BF#12dErvG4#v7sNrp1yjH}g#{Cskk1O-c!Sg~N*!(H zKiT{PW|~i0<)#y>sHmWf#Enrr(+6N+1cr`u z5h73tgjjq+2`4-=(lZ;WQjr20ZA2i@5W$dW0uzDEY$vd~642J*rKae|DPx*bQsUIK zIH~C=zCcq{o^mxag#}CuSilXw=79gv{7E@(gA1Y7wuB|DEp0IDi=tGB7)UMWEe+|@ zT{6=UA3EeR47m&uF~g}Fex^~g=?$aK@sBC+OenANLU;tyIG|WgFSohUt8CSa8ZLn? zZZJ$01X2qGZ~%1wP~&e#_YYzTq&TA~jvK?qfm*0eAi@w0sT%hWMEs*WCRo)DbVtWf z{9z5~QICBZ`HwziplR`e*Sz>;pZVBFL7Fts^9lqYbJgcP|EL}ue&Gaetbqmro5>!& z*DM*~h9LHk(sFvyD}jsz4`84MHG1>`kjQT)B?yiJ?jaI^Oe27}tOy{W;S880a3(m5 zgO##$hC>WZOiIzF2bb~>WPJZKO|pnu2c;GjroG8(5@ZSx5K+P~U_v0i@m|^5(zb@I z4N*#T)Qohv7!_9JGdujp-o_Rgx(sGBh+)(%b&{$#Sn(gLf{hp^4 z9olnA=dG(=1hP+iWC5Ubsmnp>GSGXh$DUG}(g_<7$SS+%%9)TTtS*{K!VF>%6>x!3 zis~U=Dzc3aC{vjgiQoL_`j2}2=zj8>#z?40C$#9rNTI$TmrK~8Qw)uiK+~2 zYsi=t)`c_6`Qc>T*0#SG5i>)C%MckQv0fM1Rc*o4!)j$j^TuSNVI#c{Hj1uaj=TOlB_(ihYE&~ zkVc`QPg~T>GmenU9{uAZ(-`J7JQlKP#P0}qonJ-nVZVRi?-|A%3Cb=~h=1HfCp=m5 zKi*&g2XZ4B+8qDWQqq}}riD;VTj`)xN*me_9xWNCO>GWtaD#b_h|Ej_D(98%olu}3!+g8uIyqC?2Iv=Jy z^ESJTJfMf+%ibU}nN8uGQ;NwmY zXs;|Ym{o8%Sf$H@sh4v{)eYuy&7#~tREpmlAOZ;eU9c#V5eq4ht9t!f@UainpZxSI zUGC8jgW;90Q9h_%6>;JeGW20)g>^**n@NZ(Vvlc!Tx<}jp^hu^%V2h_1Ru+B{MgNI zWYTz<72zc`uHgb_bi)}*?soy86bBwa$+1%qT2qjcjbm7r%43RC2(^X`t6+s`Ga09x z$dvyzS$Jav9KS&Y=x7^)4YbWaq}b$v-k7`aFo|>Ni(Ohv&_g9AFTD+7aE0i7+c=;X zdt5PH)c+4&C< z$__x=KPW;Ma1Xf^M;_Ed2G)WS+`s~;UAPFuF=Rj`WPrWA$FNNmd$5O7y-BYn2q#4q zgw)6NC>y@~0~}z$8caapb(ORgOokYYhJ;AskqEQEzy)|)seqB)2o7Cr10K`^{K#8G z#Lqw2RTW~M$V9}DS67Yfb=@USOoD4nRB2rBF=@ZTsR|>hA`7O%bR0?JU zQJ*M^slbpP>4h+yN^mqoH8dHkp$4;nB7F%3b)6z;G{hJYAYGhM)_qUw7zX8dL07Gs zc>II&nNbFEU>#t<+QrA4{DTz4Lo)bB@g$XjfYOBNOZCX0gAGXE@yD`#lJaoEKj>g{ zH4BGykF{MJhImzn(S-^uA?L6K5jm7l%$rUeK^3kWG7-Ww=-B6hUP#Q?34{bc9@7_w z12ibfKNNsAAlZ|!Kmj~L0dztfSb!o34L6L*?NQm6Sq(W6UzS}N({Loig@ylAU_cFU zoIt3~4FsJ))D2*~P|CefTh-87iA`VpQ=K&m-qhTj85CpSByHhELp8+Yk;+NVL^Egr z{7nG?vmlID#W?xQMoe2)E%x5|#>}%mgVqU2qr^yH(!&$eWJgSP2|~Jz62V>7zbI#53TJ zK#0IUfI$I40AjJgGqJ!oJi;SP0S#=x9B@N^D2468A=V^?1X)nl@E-ruaMsfJg4Ilg zne<`C0pbl{g0U3G=CEH*k{rqn3S~S*pwWeK;nsKQjrcJJWSC!gDMa8j#4QTlOx%Y1 zH49Migfgs(YY4<^*a3AhWh&lE(p?wGNr&qc;91flU8s=)5)^JM=1iDn8_n7n_!Ke9 z0)g~_1V{lKz(;}LCA$n;u(8W1Y0`g)$uImGCD8}qEr_z23moKw1q9569NyxAmE!3{ z<1wB+aKR=jggS=N3Xz>VS{^@^=Df9=k658<>O}lZ13&sBy5Z6n7S>FBgEV}@H!#){ zO+hDki9}A299m5}X=G?6CsT+PI91ToOhqyzLrCsG2|-68hFkwd{7rED5&G2 z8)#HSd+7uK9B6rYWv{@WKy(2N)XrE|*RYt;uYgW1Gytvf*LuKZBp5>^z(FI)9lZ!h z+}TUJAXS5j2~+jUC^^qEZm5Kqz#Gs&;gO?Gc!*L6m57G)m zSAHv4HK2DAqXp3AS*U5G9SFL089;$T*H;T9rye@CN5#+JYh~UDVk9%ow~?9=l1xOjyhmCTT<* z0m)cQ$>dLy<`0p*=4;+-A=GOdv;me9z!X5)oa7!Hrc)dqY)1B>njk}Ig%DJb30TMp zGW6j;{Gk_IC$tppc!nom&?I>J30#C1OLZSjei#4u0j`ZO;m4we253M~&PaCpiZHwZ zDm2$bN!L;e8m;snQ$}hN{#Qhzs$#g02_hb-M&awUNY4QRu}~L3x&1I8BD+%Y{=Y} z~hT#5g!?BB#7mrz2)ve?!sDudCjMtDdUeQ`@4 zRm3XQ7Xk9hr7eg0&V>3REglISSSo@LY|oX*q5|gc*L5HRMDTmS0T4()B`}h@yooc) z$Jp|RQx#Z&`A3D8=vyMsCoQXk1!R+~F!s*`dS){{4#nYrN9ZtpR zHnGETLk-~WEZhJZpaC5jM%(c2^`(zqtn58khGbykdFlk8>CK@)24<+IUI>w=3XU*r zROHZXUf9+g+i#S})N>`}A8%Ag`Ke4EpN69CRW*FtD zl&PVV88tv2{6QzIzDc6M!wK|={_Kx3 z4bVU6D>Qt=&Ro+`;NBaCvz3Kum0?gFR>aY~ZeTl{oLuo3^kN~hRDp&KK4ENl9wK3M zta;Y+%0`|}q!`bUXUal`kUAaB=Gj;3hR-!2s8$2~g&23_ryogLa%_=VV#j-y@b@qv z=(I0_mUOWc2h&x=1pWgJSb+aB*h<+&EmumdnF-_7GJzAA0Z#|px)hIpOwYMg4>qzg z+0KVE)?FxB_wh_H99)Fb#M%fEnf(>$;gR`G+0kZYTt%23lna9W<^9HSqH}l z_@h8r3=ZqWkbr?W+;t%of-_Lld3eJyFoo{@wUu4mKlq+GgVV#JlV&w>Xx*+6+yFdV z+I-Um-S|q-y%0bDmO!&7(dyZP`fXd-T(7_}5q)Y6p$6HZ#?H<5Y+rk%}Q3Mz4r!MUdA?2ggaPuXCu^l|bMV(bR4V1=%TqbBGQa z{mPlAr4sxBIt*ily@~%5#Dg&m0vu2l^8{;u5Qu;rb+FZkyzIyFm@T|K@C;5EVLE}c zo_7Xou)ut|i%v}76gXKHCHG96l$lHIto^|TZ~_zbSFkxj8OV7~7l>0S zwV*3VQ@;zkNId_%fYNvG3ss>DfN;Vb_>7{@D76kDIqq_#o6lswr}a%VJkC|(`bY_U zzT-mm;IjW@*W16GwLSA zSTs#Gcmo6Y(L4{v34Q8VFA?-Y4nPXAeUNEyJf7s0DgUH(}j~zN<+nGCxYB; zkP4*AP8Hb!5`o89D}}Es_8<91*g4;CiEL#sOF{El3Khl(e;54=ZpRuIXcSivQ4ByVLF49ML4!Lc z{F`F%UygsX{w-O;P{R@<6E7NJ*vm+>f-n^R^8%uv7dsa0c==~($DfuO82+>KZ-+sG zZYCNuav0*M zJ)QRZ-?M)w{!M#!Gwtw6hK%^=(ZXgJafeKnewtwi61GRq)SfZB*Ni$$V1v#3? zA_XDLNuviPDu|;5V>(U3j5r#RAk#vWjlzF)C`b{SAi8Ciix`@SBLg%#?KFc*`{#|2 z7D~wknM6uSB&vk@BSs5UTBx9!Ry)uLG_EYDiU$o!swmX5VyGslpz5LvgKl6b%&q@8 zK!B@%xT>WAp6n3I3Bmqx0*C?5;KZ0@W?jKuY()AzW zel1S9+wj>_h_x(mZX&m=?_GsS72HNZ$o>q;SIs>kW}1BZAZyRf~GjFeild-4Muw4bmV; zR|~@AA3Xlis9@7HKx088J~U_vm_9r69}7fM$sjLO-a%y@C?#Pae;%bY(SP>B z%dAK3N}E!<94k!G$IQ}{(>E#`QnZ*jGYBM7{}BRI(;O%$&4FX0Eg@l=sfOfG2?37R zZE)i?*MhdOZMSWZtGv15sQCa4YOYz<8B_jY;hzTxn4$&@xREbW^Q?_szx8O3&vyE< z*(LY)4C3}58SZc$y=>6q=ATafa6n!x4GP=-JAk1+n>W6PLU>!p{|px24{e>9Lfg_!@$4l^+b8@vD| zOL*ZA!dP5Ss$!aqrRsi1Q_4{oqBA%dgeNp1(?4d!kQ<014^OEXQEJAOtlZ!aPxBL? zWZ{HCgkS?vlZ#u-f+(~o>QITQi=c3bD8Z!7P?GA)TrSm@x;ViboSK5ChQv3mfTlB< zfx#QNKn@WWWECPK*sG?*kY+3>RtA{{AxiKRG#)281@YBCN?^IlZQ~igF^+SPLydn> z0}R&ygEO2_x`GrS4$xu%269&(MZ)eawX5B>X!noY|8(1i=k0uv75 zLzXt;1_!K$RYs^FstjVcIFv*oGx35YR>F`!Xy6YC@xm0kpt6EgshJ2e$eM;S6rUKy zW(-kb&!`f?GtFoRMMF)}xYDMs{G%y6Y2gUUQkS#*f&nya%z6+dP(^_a7hoHk)V!9c zNFmBBgE35C9P^kU9_AQMn1d5NRhf@qMpS)c2xtDr1%sW24+MgyMSx^DG4|qt3_%WR zPEdjqOv6@qu+2?(L%DVsgpLK#;~uvGjq6AQJEr@_H{zf=)_FrdT3e(e(=|J8wTD~Z z`WCe$sk?t9;|D+Zmb#F!j!x2N1#4)aj|P{cP!+^NIVt}bhBShc3|VMy|MA$!OZc*&5VF~lSY%qBx% zri_AsvnT7r(m+zhkf40YOQ>O5Ou7)lrVXxy`&7yX*IASk{zC~YWJ3)&5jCm7Arb>H zgIV5^Hoc%_E{v*fr1Y{Fz9dR&eR1eqz*d;R{OK`z>cTlt@kCFpRv|F>+o<$b5CV_` zvV6F~0lH|K()2+^iVbtQBAi=hAu(0%a)MrGP8sRXAG?e;{ zW_^PPUO>blxe>ebpyw>GLr;1(Y033`%OI2d79;;E@dMiVhj{B)2Qsp;f(_L3G;#Ip zTqR;J3+ZdWILX+41(wSoLH0+s#3e>X1X!2?){7g;i1Z9HOiTo0memu68-%e;X#QhM zXjlbLa)1W@onstR@51jbbd*Nw{^)sruiy@ zT;(bFT;~b($q*$dVF#8lLsBQ8to zPCn%l>pvJ#R`~GSKk8XWGQ5!wmAD~DX1)PL7<9_%1*|k9vtGm^DP7=Z&09wBL-_ zj|dCTrhG{$On=jDW0Gh$xWQsQLE2WVo=<+(A`iv=!v;7Z(8Dly05WjGTMqM=@+>sG zzrcm0z&6mg`7lyryOiC3!I-HPh6zk?0s~BN4jN1W6}sLv-$><|ya{O-thz5BjD3Ur zx&c8Hb5$AnYyb`1IfB4>N;{}=)T95#B*Dhv&2!96xeGurTvZ^)hn;LHa% zvN3N)GLqiy@qO&l!{UPQr{HA$L4T6LAA=(!8QjQ+9Xck$F28YQhS7NDON48Kn64{k%OJY$2E1Sc zFu*JTg$bC;7|c%Xxa(5xqSOW>FX|%i1|_@-1u+K0)Gmrqe&|yE&H^j|*Ld#NrYI)f zzyVn0%=kbF>(Gp1lFqDe{=k5mXCHDaO(?TaB?(HPqag0#8K&VRn?tFX${E`5AG)Cc{y`H~?;m1M!?q(1 z>|g`pjd-f1_i6_y*~2}6&plXdk|gOplEDlPab0jscf!RHy8vdi!hBEzdLWDYDr7?R zhha!jmO^Cw)J*-pXkaSti};VP1}lBCQWINcvz8!m=I;jXhrgBy|5~NLPUAzMk&8TQ z3Sa=vWp_Gdgp`3*{^87=umaMgFQ<(ro=qbqjv)?gj|?ItDd%E>3MK#H9-6~B%q@`K zP#e;)ARxdQx}gnbuK)^wJGy`Y>@fH6P%awjJ{##N*h6^o&6AL!-zbSG;UgK60V>yJ z7rcSi?rdIqqJL1sMJgm;dO~6*VqS>N$VMcxY)O{X(#X&!mo{XuK*T|pKo~CI;a+Ng zzN|@Hg;sXpNA8Ok|DozOrVFM?B~<2`g065}gI0P%FS8`JI`f-|vimPo?^i0vT> zVE)FefhYzM8sQNdAr7vQ{w|9lWJ4M3Mj1n57!6_=^Nb6?J`7FXrL}3!+TnqC6bdhoDO^48tG$PCdvA z86x2Z<5c0!0oRJNWC?Fbpva^^WnOvW04m_vByY^7^FavJBTgkF&FC2tY*(%U5AFAFvk?0~`qF7k(0ZTtO z%k}q`nz!{oKvkH|<%&U82G(f;VLlq~*s)_BFA5Z&wUG@!gB0t6(`nvv130<;$!ZpE zh59$LbOLP)_SATq9WpHtG^N6$(jx05tCtld6a!LkMszpOyMvr(AQJ+^xavA*hQ_tw z7!8w6wdfn9c$Q1)>5XCw&=lZw$TC5*TcPb!J0RPB1v>QyoEQq&tIS&HLpPH;$ZJcsK57M!BaMiUE<4k;Rt8wx;E?&L4?}yYn zO)60UFF0sI(v6>CBMp}2ad+eAZVr9W8~#a8oC5c4b8(InLZ|r-A8+%=D*k!U+SYGT zJl9=<_=)|nzZoM|9Zj&hW!N zjt~GkpnV?qbgwp6k_XDxlUNxw2yQ)~kc$QjtpL303@~*nxoq5sL8t23Q|c`7>f1QD z;$FxTu=8j{dIllEcCqOWK zN8S@C)IMN1JUhS~B96KijJTS%EK806U+oP*Ys5WyG``jZz9}-)*2%n6(Ce0R(6^iB zA6LEeI&o{kC#JeFgF{8k%s=@+@C!iL&lzOmol6tfwJJCkgd>I#YJr*|W%jF9bD8B# zCrh&s%d@B;jFpDll=MRS0hT;vp0x^9H91VD|G0e{Jw0U{x< zrVcLJ9%_G5*Qya*`#Gp1hd&5dTtC8JLq=*YA>l{WF}g1sLhPq#f^bqJ-7 zxHpZxlVj*#e;{HAgh_LouD!$qLZk{81?nnjjhsut;ZO1w8IF*v7LZGlQio?$%jh^#MAQjCut0LH z{o+WXmdjFB3fa8Cz4bd(%(KeA^~C;!?{Fz`7% z`uuw9AgKJ0zVHhK3TBbWnN=;s*>bWcX+teVR)_d$F>Sbl$0xdF8tBUwV z|9%g7P9RKCV@VxF;vcNu@*Y;a78EeQ6!PqE+sSz<|6IhG_mIP^RITgy)deB~BsCIv ztFiXi3W4qX5u{DR_S4hC?M@FQsP?`Pg8uwHm|ar1jQtBZbEJnVmS&B;!6hAixuuk5_ zT1q6>!3EO95G%3*pls)<{v`-yyX{HnWPo4rz>^QuFDHD?D5gdgohE|y&fc*TtnV$X#t+YO%q_{sRBNP54Y|j^S9T0 znwO*wV-Nx?qyqIdt|uN2I8J3%NnGf71+HsVzR{Kub^Xt<`_5=8M`oKhbbfDHEffg0 z<$(O5-GCe2@{qB@b0P}(?tkhOhoZT`ViCV0ap;E}OV5gDJr~@Er=_NF?`phr{X`LI zC`+4nf}l8L8o;$3FJ32TiPI^;tF+erfNiu0TISp=3Cm}kKQw#(D4c8+=UnKV_ZW|9 z83Z-3wCbMl|K(sW0q9+V$r8|qIrhw4)#g)$eSlDwVtFK<_381-H&W|MzJo8l1wG+p ztbMd1uaxfoXsb2ul3?adXOa#w4L_Q`n%pZU-=|k{!yl zzdre6XahQ}L`k>oo$1Oa+U;&wQxm;@W|lVLMYUh{xG8~L>!H}RX`Vuy9_)_D;Cpjb zWzmS~<3VeeX@-M4IPX<}iE4%-GDH7R-g3`*CFIG=wiBC}z3K~mpeXrb>2^MADgN30%Hx@Yc7=w%~`A`1BHsG|>alWdgw>cwAIUEQ9=PrvRqz zIbOc&cW(8q`C+lr%H8vTJn2AQa#eco>hw52PqWRx<;OKybb=F=(BhHlAYs???>`c9 z+PslhSUnuPe)4{Tj?jv(u$;&BxlaIV z%8jAb@mv&xARZy%IQ;Y;N94he+Dc8^vaI-l(O>CsFwH#K3SX7i29nyjf0?*{s>d}f&p zj!L3JP!UV1|7hMh4t$mPx8_29wI@+^X!00HlH|$Nsj&}qV!*z~qD3yq(giL0;Xef} zHQRd>4?1xw>vCdlHggX?i$sUhMgI5@9!X2`eDyQX8ns)t_Dxm8@~JA{J4(QW zfAm?@vEzSOq5NqCs_cn5w|R)IafY3T%0;f2oO{*nGBp?HA3}1c)qjcUPyIaL?W^hZ z6bgR4`~0QF`A1%gwP&CHIu?*(pk5h}R_$4d2we;;^veyco;>tJx)tE)q>SNxR z@<@VG2@4sFA@g=??y&i@FCjS&1-q*p^AEl16DaXSVAI9*FAXKm27#yEL%gd0E?wFK z?g)YY`}_DT4RCAtmIDN{dsHi_?HwtFFz@-z8OO^daTyeAT`EE&+CIqAi4e#=$YUku z=IZ48>*lGA6SvF-WxHsQi==nMI;Y`DZZ^^JahkbGjh5n^nwFj{>*FQOzobe| zb$t4;-uh9CCV57O|8nc6cbyE$FvZ@GztQY_7XB@~T;a?(*3+jq9bQ)dwA9w4dYf(X zw|>9b^WpyA0u-w${F?0NJ9oj-TS+=JPtx^fnOf{2{WP`8|cXHD4eVEz!!*?5N$tiq2T;W^*|-A{`f`Kw(JgE4%oC9>7A z3*!5H-K*93wYlq$DpIShQEwG5q$4e3w2}q%DQL)xnW9Vo*_^_sp&@s@`Ku*%gJJFZ zZ!}Ii^s7YOFwetLBM7VeF$)#R;`fknazAv09e5~}`{u%j68&ErWQ3`2Ax8jK4eXe% z=2P@Bl!yl}!RrGajbC%v3>WDc7>KT+SSsV$pn#}R9K_Au%*q?>kH9PFWfz&9^c*<3 zxsvvfVrkU?;7bP8iimS0Z3(drsr>^bDlxSsb@5exF%GA!$C@2#!ma_M^!3eUf~v2k z8I%xLHSAA`cwSurNsu_AGd>Pi`+B-TW-B1^W}uPba|z);lh$bttBz7&wTF^-@=|HR ztHsNCA-h(fTzIJiFJZZI+nUh!Rg|<+dd6a?;^BO*1b*SaC4wcQLgg8a?-A_Tw6gTacnNZzQ%D_n z^9#i3@b@g)%CLz)Nr9=R?2X+I2Xf7wI4VChyd4tKa8#tz<g+1lWj=N{(2^sI^88>v0f1N7C3ejWFnz*C5T64aJ4TWLZmK#$6TSY3Ww6 z)K>3T*I+koRfOn;NV|K=+{z_+aN=<|3wEch|F^v2IY*D?4(n5ugOb`5#zMgONweH9 zs^K|m3lEBT*Hhy=av=7CFh#48-nGeSX#u44+nx&BI~}-tWNXQl0p>80aj+mp(5Qh` zQa=DI8r8)|5D%!^j$3;-6>WUbOfq@46lSDjEbDJ~-dG<$gRIxzI?G&zZEZB*OK?_v zZS}H>GTZX>M~h|QzCK^{#m$xkGQi2@`p*Pr@k2Ff4jW|tD?Wgf&%kTS-FKysbCIz; zN^O#J5Y8ho?qWw{lm3`NN#&e6VpVfYg4Y3zCZ&t;$V{C})WTG8qqM^UlDPt%r!E%f z+DgV9I43-~h8!Hwb}0nGG09ySGgN^8=A`C^gj8wa6Jo3JmoiN~py24^3UPo+L~@Tv z5bsvHb0T@xvo2P&r2EwAlWL#Lw@MFmG)xYtEYs*oY`S(XRU{c-T5smx9BUrw8s+J~ z`eqLMe`aYJ8sYVPRMnRfyibgJ)5g>rdqZt9A|r3yC^Wor!Z)@!BX>+$*jIgi&ZYRu z?a$t?aSu~n2k|pHS%7Rpx_{4BqOW08TK=)QWvGy#AA7(>NYF{EZ$Ld3`O1|SbxK#V&-}wSl2=^ zC}nue{;5Tj=TX|I<+TKls&UfA`c%i?@+4-a=%z)Teb?OcH_&g=g>m4+stP&t=! zg`u~ySwLh{p#dZam@`?SKlKPFIYNjaJQ}gMaxc&2++C~wB4TTMy4L6@3}(2Ws=n~X za$3sTErX>zeXkj1g?Rb;(!I@IMHed$I7LxbrH>Rd^fE*ph~ZZDv!`(FE_;uIoh@Ny z-$GzJ62_W$HJ_|blHvbq9eHR0xlOpXo!?Y2Mup6GvNJ5wR9}( z43ZFG8t9xNF7*NuarC8pPcVkb;8&r+eT{&)O|&{l*qv;*mrmk+4bj!~uoa5eJrJy` z8*4#OL_4AR6CixJ5+sEH61Bh^1q+Xp)VqrO+_!`rIJk7%OWv^}#{>AvMCl-d4Rkr>S%$S;TUMtB6Xg_-ck)?Hj!pa&=Ua2 zA}OUJ?Sd)I4%>7=`h`&nm`Hi&c?QUj!GYaFy$WcEr~xdN4IAthA<$$>px(5QjJ6f8 z)nn540UEb-E;Y_l{@V^-%Cv7*$nM)Rq5X7d8j@;zDa+doX2jnL&OeP*7vW|D2WL=p)^jWgR_?MMHOjh(22IwqnO7xMK;))>)<^&&Jq3lYfar zwf+{0izt~PA)`hawflQB10XavXCMDZqS$6(JX2?}ch3j?Xi~j@7n{FhOBbe>+hflx zix>AuXaFpm+_U$(F`4=f&6Xi`?!l$OrV$Q~%|WKMo*9+t4rL(#+inNevRU8Dkg4HAR z>F1;qcS$;1f|6*VB2xO?BZv|J6hIbM{0W0>I!B-dDhc^Fl=Umz)bHaE*hl-fNn{(y zV7Z;F)D!io7x4QbYSfEa0l>~Pt&Nh9FS+C?SAfQMB$NZ1;bx+?lW!UUkCB)UGMO4N zMzv4Hvv!P9>Qz67VbWs|V`P{aMxuHYTRkT(&{u|bjLodD=jY<&|T`Bt9Nn%ZGAgZc9j%S~=Jya*&fcXk}I#cx0hY_AVrM z>!k^dOZcHw^=Y~GldycVm{fgMyzJ=l-C}3C#C(1aIWUuET6=`I1YKJZk)~b_!-LJa zeELC0D_X!sV2OjDQyGTLgbwtk>rX(#R<0!BP>Bjeq>O%hnb-GKp*JY1eIA+9q z+cwpii3jiJi4KdAChM7$jvsuQzlG{3R2~(=qCw|lwagbB|1)Z+6+WV|EU=w?#D)qn zbcJNMI7YgHc_hI2(^~IA*vt>1z|JsJg>Vf8TK5ba!7bOTmG)=K`0fp>Dsct1b>)q*Ca_Rlt$)%>*b4 zr|`Q=}GMmD#%sXdiRdIW4d*8BC;tm zd5Vi98K2=VK9^kxTx8kJYcVzJ_qLbZ?&bwz5rDH7>+Ny_CC2UwKBlVYAl*>!Fgufv1?8Tz)uV@et{-X6PDVWo}Y^Jdrg+HO0v(cqCRK5Rw34GuPd zMqK|Z1lknxVPi(@f$jKP8EUaS(yfeNeWp4YrWUewlo-?&MrFB8-k|Z94($nEK9v$L ze6LabbpEZf!?%>Jj7d#>&qX3yMYQI04BXEv%=oK)cHvn^>i$O(!7FLqIBL8lBuPa` zq@Q@qt>ONGodsO{O^;aVk$2?WvzIQ`Ye=k(DfalR?B*A>uZ9cMLq6Lqc5g6;6eV~3=B=q*Xqnc@g`lh-w z&*|$=6sIzjATbDN#!()JAHnu@QbnLBU}H3l`2w881=kkIyiFG&=!F0KF6ZXK*?j-R z{e~M+2TAV2yh2;o{Y5CEQFSIIp49FY7BVIR1LX{a=Ufv4DDX^xsy{}M_DQVnzpD96 zyA*H_kBUg8A=0dBmDs99TiECe=%QetZQ$`Y{8FEwCih~cTXZP5zYmxFv_CU%mgsef zJxg(fQ34Dq+C=1*0R7wNNB#*T#<96szfxJX?HU%qb^vufyH8{JQRJ@B?q8v|G3Rv8 zi!_Cb@&HazY~S_Rrt?ltQQov*O1OV@w}*sb+_ixtzc;QN>rBi% zl4NDUIXP;jS%ve6l$_l}49iHJ5JoMtCwNqAHw6^W699`#L<+oMpsClgZX%yz*z)~= zB&)%t{tL;0s}W@ecwuv>3JxI03zyk=?v=bdDdtRb`E_#I&Sbl zTk+_j<_-7&e_a#?XaEA{!+1xj{C5FQ0x1Fnygna6kY_6I(P~xQhvDQ}O*V`^D19mK zDipp2j9(EWQ~9Fc$X``(q@fLYb2Z}ff&70$XNEvWxyfm8)s+|Tj;Ku?nZK}#sHK+J zrLQWu7rvM8M&;^YGcL$`N=Vtw+V3UUJL_iW|13MKb&u>>Kh&mFU3kh>dtB98IA_w>_`V5o$R<4+SGB~H~ z>lD)@^5eCWiZ#LLPOxtzNg_w+l% z{eMtVtzTDBICfBgqb@L(-s6YUP|Li_9fTRJXoL!=&8Fr5ksGYr+ zGJ@tWT9L?}5g)UA59Xe}*ZuWh2PGo^Lwz7d>12*+Os~~0g%WByTCD!!p?aO_$G;YR zOQbh^g#vdQ`XW<4y$_TjJ;YcY1nl0!+>!=r6$%`1gO*2Qev)M>-n>o$pzZgN`0b<9 zBuAzSVmF+&Y#s>a?FI*bS|$mKA3R*s^K{h)BD0r)_~ZKSab^TgK%~3AwjxzzPJiQe z(})5L8y~y6{h2p+P~~vy_Z2Zw)my>+6lLdZjkS){!%0A~zj|sVb>1{N)9s(vN`%GJ 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 Q8h%3>v_TrAAs_$%JC2a~PXGV_ literal 0 HcmV?d00001 diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/step-development-guideline.md b/vendor/github.com/bitrise-io/bitrise/_docs/step-development-guideline.md new file mode 100644 index 00000000..7ff9d458 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_docs/step-development-guideline.md @@ -0,0 +1,118 @@ +# Step Development Guideline + +## Never depend on Environment Variables in your Step + +You should expose every outside variable as an input of your step, +and just set the default value to the Environment Variable you want to use in the `step.yml`. + +An example: + +The Xcode Archive step generates a `$BITRISE_IPA_PATH` output environment variable. +**You should not** use this environment variable in your Step's code directly, +instead you should declare an input for your Step in `step.yml` and just set the default +value to `$BITRISE_IPA_PATH`. Example: + +``` +- ipa_path: "$BITRISE_IPA_PATH" + opts: + title: "IPA path" +``` + +After this, in your Step's code you can expect that the `$ipa_path` Environment Variable will +contain the value of the IPA path. + +By declaring every option as an input you make it easier to test your Step, +and you also let the user of your Step to easily declare these inputs, +instead of searching in the code for the required Environment Variable. + + +## Do not use submodules, or require any other resource downloaded on-demand + +As a Step runs frequently in a CI / automation environment you should try to make your Step as stable as possible. +This includes the resources / tools used by your Step as well, not just the core code. + +If your Step depends on another tool, which have to be downloaded on-demand, during the execution +of your Step, there's a chance that even your Step was retrieved correctly but the +resource it tries to download just fails because of a network, authorization or other error. + +You should try to include everything what's required for your Step into the Step's repository. +In case of submodules, you should rather include the content of the other repository, +instead of actually using it as a submodule. + +The only exception is the dependencies you can fetch from an OS dependency manager, +on Debian systems you can use `apt-get` and on OS X you can use `brew`. +You can declare these dependencies in your `step.yml`, with the `deps` property, +and `bitrise` will manage to call the dependency manager to install the dependency, +and will fail before the Step execution in case it can't retrieve the dependency. + + +## Step id naming convention + +Use hyphen (`-`) separated words for you step id, like: `set-ios-bundle-identifier`, `xcode-archive-mac`, ... + + +## Input naming convention + +Use lower case [snake case](https://en.wikipedia.org/wiki/Snake_case) style input IDs, e.g. `input_path`. + +### Inputs which can accept a list of values + +You should postfix the input ID with `_list` (e.g. `input_path_list`), and expect the values to be provided as a pipe character separated list (e.g. `first value|second value`). This is not a hard requirement, but a strong suggestion. This means that you should prefer this solution unless you really need to use another character for separating values. Based on our experience the pipe character (`|`) works really well as a universal separator character, as it's quite rare in input values (compared to `,`, `;`, `=` or other more common separator characters). + +**As a best practice you should filter out empty items**, so that `first value||second value` or even + +``` +first value| |second value +``` + +is treated the same way as `first value|second value`. Again, not a hard requirement, but based on our experience this is the most reliable long term solution. + + +## Output naming convention + +Use all-upper-case [snake case](https://en.wikipedia.org/wiki/Snake_case) style output IDs, e.g. `OUTPUT_PATH`. + +### List of values in outputs + +You should postfix the output ID with `_LIST` (e.g. `OUTPUT_PATH_LIST`), and provide the values as a pipe separated list (e.g. `first value|second value`). This is not a hard requirement, but a strong suggestion. This means that you should prefer this solution unless you really need to use another character for separating values. Based on our experience the pipe character (`|`) works really well as a universal separator character, as it's quite rare in output values (compared to `,`, `;`, `=` or other more common separator characters). + + +## Version naming convention + +You should use [semantic versioning](http://semver.org/) (MAJOR.MINOR.PATCH) for your step. For example: `1.2.3`. + + +## Step Grouping convention + +You can use `project_type_tags` and `type_tags` to group/categorise your steps. + +`project_type_tags` are used to control if the step is available/useful for the given project type. + +Available `project_type_tags`: + +- ios +- macos +- android +- xamarin +- react-native +- cordova +- ionic + +_If step is available for all project types, do not specify project_type_tags, otherwise specify every project types, with which the step can work._ + +`type_tags` are used to categorise the steps based on it's functionality. + +Available `type_tags`: + +- access-control +- artifact-info +- installer +- deploy +- utility +- dependency +- code-sign +- build +- test +- notification + +_Every step should have at least one type_tag, if you feel you would need a new one, or update an existing's name, please [create a github issue](https://github.com/bitrise-io/bitrise/issues/new), with your suggestion._ diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/README.md b/vendor/github.com/bitrise-io/bitrise/_examples/README.md new file mode 100644 index 00000000..26d66221 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/README.md @@ -0,0 +1,4 @@ +# Bitrise CLI Examples + +* If you're just starting with Bitrise CLI head over to the `tutorials` folder/section. +* The `experimentals` folder contains concepts / experimental examples which might or might not work with the current release version (most likely won't), but might provide you a hint of the future ;) diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/before-after/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/before-after/bitrise.yml new file mode 100644 index 00000000..8156bb48 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/before-after/bitrise.yml @@ -0,0 +1,73 @@ +format_version: 0.9.8 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +app: + envs: + - BITRISE_PROJECT_TITLE: MyTit1 + - BITRISE_DEV_BRANCH: develop + opts: + is_expand: false +workflows: + after: + steps: + - script@0.9.1: + title: Running the After script + inputs: + - content: | + #!/bin/bash + echo "STEPLIB_BUILD_STATUS: ${STEPLIB_BUILD_STATUS}" + echo "BITRISE_BUILD_STATUS: ${BITRISE_BUILD_STATUS}" + exit 1 + before: + envs: + - BITRISE_PROJECT: MyTit1 + steps: + - script: + title: Running the Before script + inputs: + - content: | + #!/bin/bash + set -v + echo 'This is a before workflow' + echo "MY_TEST_ENV: ${MY_TEST_ENV}" + echo "STEPLIB_BUILD_STATUS: ${STEPLIB_BUILD_STATUS}" + echo "BITRISE_BUILD_STATUS: ${BITRISE_BUILD_STATUS}" + before2: + steps: + - script: + title: Running the Before2 script + inputs: + - content: | + #!/bin/bash + set -v + echo 'This is a before2 workflow' + target: + title: test title + summary: | + This is a workflow summary. + Check this out! + before_run: + - before + - before2 + after_run: + - after + envs: + - MY_TEST_ENV: My test value + opts: + is_expand: false + steps: + - script: + title: Running the target script + inputs: + - content: | + #!/bin/bash + set -v + echo 'This is a before workflow' + - https://github.com/bitrise-io/bitrise-steplib.git::timestamp: + outputs: + - UNIX_TIMESTAMP: null + opts: + title: unix style + - ISO_DATETIME: null + opts: + title: iso 8601 (RFC3339Nano) + - script: {} diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/create-new-step/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/create-new-step/bitrise.yml new file mode 100644 index 00000000..946ae64f --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/create-new-step/bitrise.yml @@ -0,0 +1,18 @@ +format_version: 0.9.8 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +app: + envs: + - STEP_DIR_PATH: ~/develop/ + opts: + is_expand: true + is_required: true +workflows: + create: + envs: [] + steps: + - script: + title: Hello Bitrise! + inputs: + - content: |- + #!/bin/bash + echo "Welcome to Bitrise!" diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/dependencies/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/dependencies/bitrise.yml new file mode 100644 index 00000000..f2c89629 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/dependencies/bitrise.yml @@ -0,0 +1,103 @@ +format_version: 1.1.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +app: + envs: + - BITRISE_PROJECT_TITLE: MyTit1 + - BITRISE_DEV_BRANCH: develop + opts: + is_expand: no + +workflows: + _cleanup: + title: Cleanup workflow + steps: + - script: + inputs: + - content: |- + #!/bin/bash + + OUTPUT="$(which apt-get)" + if [ "$OUTPUT" != "" ]; then + echo "apt-get remove cmake" + sudo apt-get -y remove cmake + fi + + OUTPUT="$(which brew)" + if [ "$OUTPUT" != "" ]; then + echo "brew remove cmake" + brew uninstall cmake + fi + + echo $(which cmake) + + test: + before_run: + - _cleanup + title: Test dependencies workflow + steps: + - script: + deps: + brew: + - name: cmake + apt_get: + - name: cmake + inputs: + - content: |- + #!/bin/bash + set -v + + OUTPUT="$(which cmake)" + echo "$OUTPUT" + if [ "$OUTPUT" == "" ]; then + exit 1 + fi + + dependencies_test: + title: "test title" + summary: | + This is a workflow summary. + Check this out! + envs: + - MY_TEST_ENV: My test value + opts: + is_expand: false + before_run: + - before + steps: + - script: + title: Running the target script + dependencies: + - manager: brew + name: cmake + inputs: + - content: | + #!/bin/bash + set -v + echo 'This is a target workflow' + - https://github.com/bitrise-io/bitrise-steplib.git::timestamp@0.9.0: + is_always_run: true + + before: + envs: + - BITRISE_PROJECT: MyTit1 + steps: + - script: + title: Running the Before script + inputs: + - content: | + #!/bin/bash + set -v + echo 'This is a before workflow' + OUTPUT="$(which cmake)" + echo "$OUTPUT" + if [ "$OUTPUT" != "" ]; then + brew uninstall cmake + fi + + dependencies_xcode: + steps: + - script: + dependencies: + - manager: _ + name: xcode diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/bitrise.yml new file mode 100644 index 00000000..2a74c522 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/bitrise.yml @@ -0,0 +1,24 @@ +format_version: 0.9.8 +workflows: + build: + steps: + # - https://github.com/bitrise-io/bitrise-steplib.git::git-clone: + # run_if: "{{ IS_TOOL == true }}" + - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: + inputs: + - content: | + bundle install + middleman build + - https://github.com/bitrise-io/bitrise-steplib.git::slack: + + deploy: + envs: + - S3_BUCKET: middleman-prod + steps: + - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: + inputs: + - content: | + bundle install + middleman build + - https://github.com/bitrise-io/bitrise-steplib.git::slack: + - https://github.com/bitrise-io/bitrise-steplib.git::amazon-s3-bucket-sync: diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/v2.bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/v2.bitrise.yml new file mode 100644 index 00000000..29145adb --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/v2.bitrise.yml @@ -0,0 +1,40 @@ +format_version: 1.3.0 +workflows: + + git_clone: + steps: + - https://github.com/bitrise-io/bitrise-steplib.git::git-clone: + run_if: "{{ IS_TOOL == true }}" + + build: + steps: + - https://github.com/bitrise-io/bitrise-steplib.git::bash-script-runner@1.1.1: + inputs: + - __INPUT_FILE__: | + bundle install + middleman build + + stage: + envs: + - S3_BUCKET: middleman-stage + before_run: + - build + after_run: + - notifications + steps: + - https://github.com/bitrise-io/bitrise-steplib.git::amazon-s3-bucket-sync: + + deploy: + envs: + - S3_BUCKET: middleman-prod + before_run: + - build + after_run: + - notifications + steps: + - https://github.com/bitrise-io/bitrise-steplib.git::amazon-s3-bucket-sync: + + notifications: + steps: + - https://github.com/bitrise-io/bitrise-steplib.git::send-hipchat-msg: + - https://github.com/bitrise-io/bitrise-steplib.git::send-slack-msg: diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/templates/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/templates/bitrise.yml new file mode 100644 index 00000000..ca16d4e6 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/templates/bitrise.yml @@ -0,0 +1,131 @@ +# +# Demonstrates the Run-If template expressions. +# The templates / expressions you can use are the official +# Go template expressions, you can find the full documentation +# on Go's text/template doc page: https://golang.org/pkg/text/template/ +format_version: 0.9.8 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +workflows: + primary: + steps: + # + # Get and compare envs + - script: + title: Run-If expression + run_if: |- + {{getenv "TEST_KEY" | eq "test value"}} + inputs: + - content: |- + #!/bin/bash + echo "RunIf expression was true" + # + # Or if that's all you want to do just use the enveq function + - script: + title: Run-If expression + run_if: '{{enveq "TEST_KEY" "test value"}}' + inputs: + - content: |- + #!/bin/bash + echo "RunIf expression was true" + + # + # Env not empty + - script: + title: Run-If expression + run_if: '{{getenv "TEST_KEY" | ne ""}}' + inputs: + - content: |- + #!/bin/bash + echo "RunIf expression was true" + + # + # Another env not empty + - script: + title: Run-If expression + run_if: '{{getenv "TEST_KEY" | eq "" | not}}' + inputs: + - content: |- + #!/bin/bash + echo "RunIf expression was true" + + # + # Env equal to env + - script: + title: Run-If expression + run_if: '{{getenv "TEST_KEY_1" | eq (getenv "TEST_KEY_2")}}' + inputs: + - content: |- + #!/bin/bash + echo "RunIf expression was true" + + # + # Use the available expression data properties + # like IsCI or IsBuildFailed directly + - script: + title: Run-If expression + run_if: |- + {{.IsCI}} + inputs: + - content: |- + #!/bin/bash + echo "RunIf expression was true" + # + # You don't have to wrap the expression in {{...}} if it's a simple + # oneliner + - script: + title: Run-If expression + run_if: $.IsCI + inputs: + - content: |- + #!/bin/bash + echo "RunIf expression was true" + # + # You can even remove the $ sign, it's optional in a simple + # expression like this + - script: + title: Run-If expression + run_if: .IsCI + inputs: + - content: |- + #!/bin/bash + echo "RunIf expression was true" + # + # If-Else condition + - script: + title: Run-If expression + run_if: |- + {{if .IsCI}} + true + {{else}} + false + {{end}} + inputs: + - content: |- + #!/bin/bash + echo "RunIf expression was true" + # + # Multi condition + - script: + title: CI and Not Failed + run_if: |- + {{.IsCI | and (not .IsBuildFailed)}} + inputs: + - content: |- + #!/bin/bash + echo "RunIf expression was true" + # + # Pull Request condition + - script: + title: Only if NOT a Pull Request + run_if: not .IsPR + inputs: + - content: |- + #!/bin/bash + echo "RunIf expression was true" + - script: + title: Only if it was a Pull Request + run_if: .IsPR + inputs: + - content: |- + #!/bin/bash + echo "Pull Request ID: ${PULL_REQUEST_ID}" diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/timestamp-gen/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/timestamp-gen/bitrise.yml new file mode 100644 index 00000000..4eca75f4 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/timestamp-gen/bitrise.yml @@ -0,0 +1,22 @@ +format_version: 0.9.8 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" +app: + envs: + - BITRISE_PROJECT_TITLE: MyTit1 + opts: + is_expand: no + - BITRISE_DEV_BRANCH: develop + opts: + is_expand: no +workflows: + _: + envs: [] + steps: + - timestamp@0.9.0: + - script: + inputs: + - content: | + #!/bin/bash + set -v + echo ${UNIX_TIMESTAMP} + echo ${ISO_DATETIME} diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/trigger-map/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/trigger-map/bitrise.yml new file mode 100644 index 00000000..82eefbab --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/trigger-map/bitrise.yml @@ -0,0 +1,58 @@ +format_version: 0.9.8 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + +trigger_map: +- pattern: master + is_pull_request_allowed: false + workflow: master +- pattern: feature/* + is_pull_request_allowed: true + workflow: feature +- pattern: "*" + is_pull_request_allowed: true + workflow: primary + +workflows: + master: + title: Master workflow + summary: Shuld triggered by master branches + steps: + - script: + inputs: + - content: | + #!/bin/bash + echo 'This the master workflow' + + feature: + title: Feature workflow + summary: Shoould triggered by feature branches + steps: + - script: + inputs: + - content: | + #!/bin/bash + echo 'This is a feature workflow' + + primary: + title: Primary workflow + before_run: + - before + steps: + - script: + inputs: + - content: | + #!/bin/bash + echo 'This is the primary workflow' + echo 'time: ${ISO_DATETIME}' + + - script: + inputs: + - content: | + #!/bin/bash + echo 'This is the primary workflow' + echo 'time: ${ISO_DATETIME}' + + before: + title: Primary workflow + steps: + - https://github.com/bitrise-io/bitrise-steplib.git::timestamp: diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/.gitignore b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/.gitignore new file mode 100644 index 00000000..f94187cd --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/.gitignore @@ -0,0 +1,2 @@ +.bitrise* +tmp-bitrise.yml diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/bitrise.yml new file mode 100644 index 00000000..c00de59d --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/bitrise.yml @@ -0,0 +1,32 @@ +# +# This experimental upload&download will only work with the new bitrise.io API! +# +format_version: 1.1.0 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + +app: + envs: + - BITRISE_YML_PATH: ./tmp-bitrise.yml + # define these in your .bitrise.secrets.yml + - BITRISE_APP_SLUG: $BITRISE_APP_SLUG + - BITRISE_APP_API_TOKEN: $BITRISE_APP_API_TOKEN + +workflows: + download_from_bitrise_io: + steps: + - script: + title: Downloading bitrise.yml ... + inputs: + - content: |- + #!/bin/bash + set -e + ret_content=$(curl --fail https://www.bitrise.io/api/app/${BITRISE_APP_SLUG}/config/download.yml?api_token=${BITRISE_APP_API_TOKEN}) + echo "${ret_content}" > ${BITRISE_YML_PATH} + upload_to_bitrise_io: + steps: + - script: + title: Uploading bitrise.yml ... + inputs: + - content: |- + #!/bin/bash + curl --fail -X POST --data-urlencode "app_config_datastore_yaml=$(cat ${BITRISE_YML_PATH})" https://www.bitrise.io/api/app/${BITRISE_APP_SLUG}/config/upload.yml?api_token=${BITRISE_APP_API_TOKEN} diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/README.md b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/README.md new file mode 100644 index 00000000..e9c9ed3d --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/README.md @@ -0,0 +1,21 @@ +# Tutorial Bitrise.yml configurations + +This folder contains examples for those who are just getting +started with Bitrise CLI. + + +## steps-and-workflows + +You should start your experimenting with the `steps_and_workflows` examples. +You can find an annotated `bitrise.yml` in the folder which will guide you +through the basic concepts of bitrise CLI and the `bitrise.yml` config +file format. + +## inputs-outputs-envs + +This folder contains examples of how inputs, outputs and environment +variables are handled. + +A quick note: every input and output is actually an environment +variable, you can handle those in your scripts just like you would any +regular environment variable. diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/errors-force-run-and-skippable/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/errors-force-run-and-skippable/bitrise.yml new file mode 100644 index 00000000..d3c4510f --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/errors-force-run-and-skippable/bitrise.yml @@ -0,0 +1,46 @@ +format_version: 0.9.8 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + +workflows: + fail: + # this workflow is a test for failed steps/workflows, + # it'll fail + steps: + - script: + title: "ok" + - script: + title: "ls" + inputs: + - content: | + #/bin/bash + ls -alh + - script: + title: "fail" + is_skippable: true + inputs: + - content: | + #/bin/bash + set -v + exit 1 + - script: + title: "fail 2" + is_skippable: false + inputs: + - content: | + #/bin/bash + set -v + exit 1 + - script: + title: "ok" + inputs: + - content: | + #/bin/bash + echo "-----> This should NOT be printed!!" + is_always_run: false + - script: + title: "ok" + inputs: + - content: | + #/bin/bash + echo "-----> This should be printed!!" + is_always_run: true diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/inputs-outputs-envs/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/inputs-outputs-envs/bitrise.yml new file mode 100644 index 00000000..39629401 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/inputs-outputs-envs/bitrise.yml @@ -0,0 +1,43 @@ +format_version: 0.9.8 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + +app: + envs: + - BITRISE_PROJECT_TITLE: EnvTest $HOME + opts: + is_expand: no + - BITRISE_DEV_BRANCH: develop + opts: + is_expand: no + +workflows: + example-envs: + # You can run this workflow with: + # bitrise run example-envs + envs: [] + steps: + - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: + inputs: + - content: | + #!/bin/bash + echo "Hello world!" + echo "BITRISE_PROJECT_TITLE (HOME should NOT be expanded): ${BITRISE_PROJECT_TITLE}" + export EXP_TEST='Exported value' + echo "EXP_TEST: ${EXP_TEST}" + - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: + inputs: + - content: | + #!/bin/bash + echo "Second script/step" + echo "BITRISE_DEV_BRANCH: ${BITRISE_DEV_BRANCH}" + echo "EXP_TEST (should be empty): ${EXP_TEST}" + - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: + inputs: + - content: | + #!/bin/bash + echo "Read from .bitrise.secrets.yml: ${BITRISE_SECRET_TEST1}" + - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: + inputs: + - content: | + #!/bin/bash + echo 'This ENV should NOT be expanded: ${BITRISE_PROJECT_TITLE}' diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/react-native/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/react-native/bitrise.yml new file mode 100644 index 00000000..40c2cad3 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/react-native/bitrise.yml @@ -0,0 +1,36 @@ +--- +format_version: 1.1.0 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +app: + envs: + - BITRISE_PROJECT_PATH: ios/ReactNativeSample.xcodeproj + opts: + is_expand: false + - BITRISE_SCHEME: ReactNativeSample + opts: + is_expand: false + +trigger_map: +- pattern: "*" + is_pull_request_allowed: true + workflow: build-react-app + +workflows: + build-react-app: + steps: + - install-react-native: {} + - npm: + inputs: + - command: install + - react-native-bundle: {} + - xcode-archive: + title: 'Xcode: Create Archive' + inputs: + - output_dir: "${BITRISE_DEPLOY_DIR}" + outputs: + - BITRISE_IPA_PATH: + opts: + title: The created .ipa file's path + - BITRISE_DSYM_PATH: + opts: + title: The created .dSYM.zip file's path diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/bitrise.yml new file mode 100644 index 00000000..af518108 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/bitrise.yml @@ -0,0 +1,139 @@ +format_version: 0.9.8 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + +workflows: + + basics: + # You can run this workflow with: + # bitrise run basics + # + # All of the following steps in this workflow will do exactly the + # same thing, it demonstrates how you can define a step's ID + # in different ways. + steps: + # If you use a step from a step collection / library then + # a step's ID consists of three parts: + # 1. The step-lib source + # 2. The step's ID in the step-lib + # 3. The step's version, registered in the step-lib + # A full ID looks like this: + # step-lib-source::step-id@version + - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: + title: "Full ID" + inputs: + - content: | + #/bin/bash + echo "Welcome to Bitrise!" + # If you define a default_step_lib_source (just like you can see it + # at the top of this bitrise.yml file) then you don't have to + # specify it again for the steps if you want to use + # the default_step_lib_source + # You can include the :: separator or if you want to you can remove it + # completely. + - ::script@0.9.0: + title: "Using default_step_lib_source" + inputs: + - content: | + #/bin/bash + echo "Welcome to Bitrise!" + - script@0.9.0: + title: "Using default_step_lib_source, without ::" + inputs: + - content: | + #/bin/bash + echo "Welcome to Bitrise!" + # If you want to use the latest version of the step + # you can even remove the version from the ID. + # Once again you can include the separator (@ for the version) + # but you can remove it completely. + # Note that the trailing colon is still required, even + # if you don't specify the version! + - script@: + title: "Using default_step_lib_source, without ::" + inputs: + - content: | + #/bin/bash + echo "Welcome to Bitrise!" + - script: + title: "Using default_step_lib_source, without ::" + inputs: + - content: | + #/bin/bash + echo "Welcome to Bitrise!" + + + direct-url: + # You can run this workflow with: + # bitrise run direct-url + # + # This workflow shows how to use steps with specifying the + # step's git clone URL directly. + # This way the step will always be git cloned from the specified + # URL and not used from a step library/collection. + # To do this you have to construct the ID in this way: + # git::git-clone-url-of-the-step-repo@branch-or-tag + steps: + - script: + title: "ok" + - git::https://github.com/bitrise-io/steps-timestamp.git@master: + title: "remote_git-stamp-test" + - git::git@github.com:bitrise-io/steps-timestamp.git@master: + title: "remote_git-stamp-test" + - script: + title: "print time" + inputs: + - content: | + #/bin/bash + set -e + echo "ISO_DATETIME: ${ISO_DATETIME}" + + relative: + # You can run this workflow with: + # bitrise run relative + # + # You can specify local path for a step as well. + # The path can be any kind of path (even absolute path) + # but the best way is to use relative paths + # if you want to run your workflow on a Continuous Integration + # service or want to share with someone else. Absolute paths + # and relative-to-home paths most likely won't work anywhere + # else except on your machine. + # To do this you have to construct the ID in this way: + # path::local-path-of-the-step-folder + steps: + - script: + title: "ok" + - path::./steps-timestamp: + title: "relative_pth-stamp-test" + - script: + title: "print time" + inputs: + - content: | + #/bin/bash + set -e + echo "ISO_DATETIME: ${ISO_DATETIME}" + + local: + # You can run this workflow with: + # bitrise run local + # + # This is the same as the 'relative' workflow example + # just demonstrating that you can use all the common + # patterns to define the path of the step. + # You can define even absolute paths but keep in mind + # that if you do it most likely won't work at someone, + # or on your Continuous Integration service (like your favorite Bitrise.io) + steps: + - script: + title: "ok" + - path::~/develop/go/src/github.com/bitrise-io/steps-timestamp: + title: "local_time-stamp-test" + - path::$HOME/develop/go/src/github.com/bitrise-io/steps-timestamp: + title: "local_time-stamp-test" + - script: + title: "print time" + inputs: + - content: | + #/bin/bash + set -e + echo "ISO_DATETIME: ${ISO_DATETIME}" diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/LICENSE b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/LICENSE new file mode 100644 index 00000000..a6a5c39a --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Bitrise + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/README.md b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/README.md new file mode 100644 index 00000000..a77de2b2 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/README.md @@ -0,0 +1,2 @@ +# steps-timestamp +Generates a timestamp and stores it into env diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/_scripts/ci.sh b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/_scripts/ci.sh new file mode 100644 index 00000000..490966c5 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/_scripts/ci.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -e + +THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "${THIS_SCRIPT_DIR}/.." + +export PATH="$PATH:$GOPATH/bin" + +# +# Script for Continuous Integration +# + +set -v + +# Check for unhandled errors +go get github.com/kisielk/errcheck +go install github.com/kisielk/errcheck +errcheck -asserts=true -blank=true ./... + +go test -v ./... + +# +# ==> DONE - OK +# \ No newline at end of file diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.go b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.go new file mode 100644 index 00000000..43b38aaa --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "time" +) + +func EnvmanAdd(key, value string) error { + args := []string{"add", "-k", key, "-v", value} + return RunCommand("envman", args...) +} +func RunCommand(name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func main() { + now := time.Now() + + // unix timestamp + // ex: 1436279645 + timestamp := now.Unix() + timestampString := fmt.Sprintf("%d", timestamp) + if err := EnvmanAdd("UNIX_TIMESTAMP", timestampString); err != nil { + fmt.Println("Failed to store UNIX_TIMESTAMP:", err) + os.Exit(1) + } + + // iso8601 time format (timezone: RFC3339Nano) + // ex: 2015-07-07T16:34:05.51843664+02:00 + timeString := fmt.Sprintf("%v", now.Format(time.RFC3339Nano)) + if err := EnvmanAdd("ISO_DATETIME", timeString); err != nil { + fmt.Println("Failed to store ISO_DATETIME:", err) + os.Exit(1) + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.sh b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.sh new file mode 100755 index 00000000..4d43276a --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +THIS_SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Start go program +cd "${THIS_SCRIPTDIR}" + +set -v + +go run ./step.go diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.yml b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.yml new file mode 100644 index 00000000..851fea2f --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.yml @@ -0,0 +1,17 @@ +title: Generate time +summary: Generates current timestamp +description: | + Generates current timestamp +website: https://github.com/bitrise-io/steps-timestamp +fork_url: https://github.com/bitrise-io/steps-timestamp +source: + git: https://github.com/bitrise-io/steps-timestamp.git +is_requires_admin_user: false +is_always_run: false +outputs: + - UNIX_TIMESTAMP: + opts: + title: unix style + - ISO_DATETIME: + opts: + title: iso 8601 (RFC3339Nano) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/README.md new file mode 100644 index 00000000..2b134024 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/README.md @@ -0,0 +1,22 @@ +# Welcome to Bitrise CLI + +[Lesson 1 - The first Steps](./lesson1_steps) + +Captain's log, bitdate 110912.5. +We begin our mission discovering a new universe, full of new opportunities and new ways to improve ourselves and our day-to-day routines. We’ve prepared for this day for a long time. + +- The first step was a simple command to make sure we have what it takes to start our adventures: `curl -L https://github.com/bitrise-io/bitrise/releases/download/VERSION/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise` + +- Next we made sure that we are in the right mode `chmod +x /usr/local/bin/bitrise` + +- And finally we checked that everything is present and we are using the latest technologies by running the `chmod +x /usr/local/bin/bitrise` command and after it `bitrise setup` + +A journey has never had such an easy start. We’ve just traveled to the planet (or if you prefer folder) typed `bitrise init` in our computer and a new Workflow was created that we could use right away to automate a part of our day-to-day routine. We learned a lots of things on our voyage and we are here to help you get started in this automated universe. The lessons section is all about getting familiar with the how-to’s of the [Bitrise CLI](https://github.com/bitrise-io/bitrise). Every lesson folder contains a README.md that gives you an overview of the topic and a bitrise.yml that has a complete Workflow ready to run. + +- Explore the Steps (including the Steps in our [StepLib](https://github.com/bitrise-io/bitrise-steplib)) in [lesson1](./lesson1_steps) - Look, the final frontier! +- Create an army of Steps by adding them to your Workflow to conquer your automation needs in [lesson2](./lesson2_workflow) - Battle formation! +- Make sure your army of Steps get and pass on to each other in the right order in [lesson3](./lesson3_input_output_env) - Set phasers to stun! +- Stay in control even in hard times when the engines are on fire (due to errors) in [lesson4](./lesson4_errors) - Scotty, where are you?! +- And take a look at one of our journeys through a complete Workflow in [lesson5](./lesson5_complex_wf) - Are you ready for the Kobayashi Maru? + +[Lesson 1 - The first Steps](./lesson1_steps) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/.gitignore b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/.gitignore new file mode 100644 index 00000000..9e7527f5 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/.gitignore @@ -0,0 +1,2 @@ + +.bitrise* diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/README.md new file mode 100644 index 00000000..e188b0ec --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/README.md @@ -0,0 +1,37 @@ +# Lesson 1 - The first Steps + +[Back to the CLI Lessons Introduction page](../) + +[Lesson 2 - The flow of work in your Workflow](../lesson2_workflow) + +First of all let's talk about Steps. Steps are the building blocks of a [Bitrise](https://bitrise.io) Workflow. At [Bitrise](https://bitrise.io) we know how important it is to have plenty of opportunities to customize the automation process as we also worked as a mobile app development agency. The need for a wide variety of customization in the automation of the development and deployment workflows is what leads to the creation of [Bitrise](https://bitrise.io). We are eager to provide you with Steps that can help you with automation throughout the application lifecycle and this is where our Step Library comes into view. We created the open source [StepLib](https://github.com/bitrise-io/bitrise-steplib) to give you the basic Steps that you need in creating a Workflow to boost your productivity. Yes, it is open source, you can fork it, add your own Steps or even use another public fork of it! Also when you add a useful Step to your fork and you think other developers could make good use of it, don’t hesitate to send us a pull request! + +Now that you created your first local project (by calling the `bitrise setup` and after it the `bitrise init`) we can have some fun with the Steps! Open the bitrise.yml and let's add some steps! + +## StepID +SetpID is a unique identifier of a step. In your Workflow you have to include this ID to tell [Bitrise](https://bitrise.io) which Step you'd like to run. In our [StepLib](https://github.com/bitrise-io/bitrise-steplib) if you open the [steps folder](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps) you can see that every Step folder's name is the StepID. + +### StepID format in the .yml + +For Steps from the [StepLib](https://github.com/bitrise-io/bitrise-steplib): + - You can use the full StepID format. (step-lib-source::StepID@version:) + - `https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.1:` + - If the `default_step_lib_source` is defined (by default it is and refers to our [StepLib](https://github.com/bitrise-io/bitrise-steplib)), you can simply omit the step-lib-source and even the `::` separator. (::StepID@version:) + - `::script@0.9.0:` and `script@0.9.0:` + - If there is only one version of a step or if you always want to use the latest version you can even remove the version and the `@` separator, too. And if you take a look at the generated bitrise.yml you can see that this is the format it uses (StepID@: - the only step in the generated Workflow is `- script:`) + - `script@:` and `script:` +For Steps that are not in the [StepLib](https://github.com/bitrise-io/bitrise-steplib) and are stored online: + - The format to download and run a step is git::clone-url@branch + - `git::https://github.com/bitrise-io/steps-timestamp.git@master` + - In this case we are using the HTTPS clone url to clone the master branch in the Step's repository + - `git::git@github.com:bitrise-io/steps-timestamp.git@master` + - In this case we are using the SSH clone url to clone the master branch in the Step's repository +For Steps on your machine: + - In this case the Step is already stored on your computer and we only need to know the exact path to the step.sh + - relative-path::./steps-timestamp + - path::~/develop/go/src/github.com/bitrise-io/steps-timestamp + - path::$HOME/develop/go/src/github.com/bitrise-io/steps-timestamp + +[Back to the CLI Lessons Introduction page](../) + +[Lesson 2 - The flow of work in your Workflow](../lesson2_workflow) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/bitrise.yml new file mode 100644 index 00000000..f6aa8a0b --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/bitrise.yml @@ -0,0 +1,91 @@ +format_version: 1.3.0 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +app: + envs: + - BITRISE_PROJECT_TITLE: lesson_1 + opts: + is_expand: true + - BITRISE_DEV_BRANCH: master + opts: + is_expand: true +workflows: + steplib_steps: + steps: + - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: + title: "Full ID" + inputs: + - content: | + #/bin/bash + echo "Welcome to Bitrise!" + - ::script@0.9.0: + title: "Using default_step_lib_source" + inputs: + - content: | + #/bin/bash + echo "Welcome to Bitrise!" + - script@0.9.0: + title: "Using default_step_lib_source, without ::" + inputs: + - content: | + #/bin/bash + echo "Welcome to Bitrise!" + - script@: + title: "Using default_step_lib_source, without ::" + inputs: + - content: | + #/bin/bash + echo "Welcome to Bitrise!" + - script: + title: "Using default_step_lib_source, without ::" + inputs: + - content: | + #/bin/bash + echo "Welcome to Bitrise!" + - script: + title: "ok" + - git::https://github.com/bitrise-io/steps-timestamp.git@master: + title: "remote_git-stamp-test" + - script: + title: "print time" + inputs: + - content: | + #/bin/bash + set -e + echo "ISO_DATETIME: ${ISO_DATETIME}" + - script: + title: "ok" + - script: + title: "print time" + inputs: + - content: | + #/bin/bash + + - git-clone: + title: "Clone timestamp repo for local use" + run_if: true + inputs: + - repository_url: https://github.com/bitrise-io/steps-timestamp + - clone_into_dir: steps-timestamp + - branch: master + - path::./steps-timestamp: + title: "relative_pth-stamp-test" + - script: + title: "print time" + inputs: + - content: | + #/bin/bash + set -e + echo "ISO_DATETIME: ${ISO_DATETIME}" + - script: + title: "ok" + - path::~/Documents/Development/bitrise-cli/_lessons/lesson1_steps/steps-timestamp: + title: "local_time-stamp-test" + - path::$HOME/Documents/Development/bitrise-cli/_lessons/lesson1_steps/steps-timestamp: + title: "local_time-stamp-test" + - script: + title: "print time" + inputs: + - content: | + #/bin/bash + set -e + echo "ISO_DATETIME: ${ISO_DATETIME}" diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/.gitignore b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/.gitignore new file mode 100644 index 00000000..9e7527f5 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/.gitignore @@ -0,0 +1,2 @@ + +.bitrise* diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/README.md new file mode 100644 index 00000000..5345fcf0 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/README.md @@ -0,0 +1,88 @@ +# Lesson 2 - The flow of work in your Workflow + +[Back to Lesson 1](../lesson1_steps) + +[Lesson 3 - The ins and outs with environment variables](../lesson3_input_output_env) + +Basically Workflows are groups of steps. There are main Workflows, that contain the Steps which provide the main functionality. There are utility Workflows that we use to prepare everything for the main Workflow, to clean up or to send notification containing the build status. The utility Workflows begin with '_' and these Workflows can't be run using the `bitrise run ` command. + +What could be a better example to show how Workflows work, than to create an iOS Unit Test Workflow? Let's get down to business! +First of all, what do we need in the Unit Test Workflow? +- Xcode: Test is all that we need to run + +And what are the needed setup steps to accomplish these objectives, what should be added to the utility Workflows? +- The project should be on the machine running the Workflow, so there should be a git-clone Step +- There should be a notification Step to make sure you don't have to sit in front of your computer and watch the terminal the whole time + +So let's create our first utility Workflow called _setup to make sure that the project is present on the current machine and is up-to-date. +We'll use a simple bash script to achieve this (just for the fun of it ;) ) The _setup Workflow should look something like this: + +``` +_setup: + description: Clone repo + steps: + - script: + title: clone + run_if: |- + {{enveq "XCODE_PROJECT_PATH" ""}} + inputs: + - content: |- + #!/bin/bash + echo $XCODE_PROJECT_PATH + if [ ! -d $PROJECT_FOLDER ] ; then + git clone ${REPO_URL} + else + cd $PROJECT_FOLDER + git pull + fi +``` + +Great! Now let's jump to the main Workflow. It will only contain an Xcode: Test step so let's keep it simple and call it `test`. You can add Workflows to the after_run and before_run of Workflow. This will run the given Workflow just before or after the given Workflow. So here is the main Workflow with the before_run and after_run sections: + +``` +test: + before_run: + - _setup + after_run: + - _cleanup + steps: + - xcode-test: + title: Run Xcode test + inputs: + - project_path: ${XCODE_PROJECT_PATH} + - scheme: ${XCODE_PROJECT_SCHEME} + - simulator_device: iPhone 6 + - simulator_os_version: latest + - is_clean_build: "no" +``` + +Awesome! Now we are almost done! only one more Workflow to create! _cleanup should contain simply be another bash script that just delete's the directory. + +``` +_cleanup: + description: |- + This is a utility workflow. It runs a script to delete the folders created in the setup. + steps: + - script: + title: Cleanup folder + description: |- + A script step to delete the downloaded Step folder. + inputs: + - content: |- + #!/bin/bash + rm -rf $PROJECT_TITLE +``` + +Wow! We're done! Weeell not quite. If you try to run the Workflow you can see, that it fails. Currently the environment variables aren't added that are needed. Add these environment variables to your .bitrise.secrets.yml: + +- REPO_URL: +- PROJECT_TITLE: +- PROJECT_FOLDER: +- XCODE_PROJECT_PATH: +- XCODE_PROJECT_SCHEME: ${PROJECT_TITLE} + +Aaaaand yeah! All done! Great job! *Drop mic* + +[Back to Lesson 1](../lesson1_steps) + +[Lesson 3 - The ins and outs with environment variables](../lesson3_input_output_env) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/bitrise.yml new file mode 100644 index 00000000..5cfbe8e4 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/bitrise.yml @@ -0,0 +1,53 @@ +format_version: 1.3.0 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +app: + envs: + - BITRISE_PROJECT_TITLE: lesson_2 + opts: + is_expand: true + - BITRISE_DEV_BRANCH: master + opts: + is_expand: true +workflows: + _setup: + description: Clone repo + steps: + - script: + title: clone + inputs: + - content: |- + #!/bin/bash + if [ ! -d $PROJECT_FOLDER ] ; then + git clone ${REPO_URL} + envman add --key XCODE_PROJECT_PATH --value $BITRISE_SOURCE_DIR/$PROJECT_FOLDER/${PROJECT_TITLE}.xcodeproj + else + cd $PROJECT_FOLDER + git pull + envman add --key XCODE_PROJECT_PATH --value $BITRISE_SOURCE_DIR/$PROJECT_FOLDER/${PROJECT_TITLE}.xcodeproj + fi + test: + before_run: + - _setup + after_run: + - _cleanup + steps: + - xcode-test: + title: Run Xcode test + inputs: + - project_path: ${XCODE_PROJECT_PATH} + - scheme: ${XCODE_PROJECT_SCHEME} + - simulator_device: iPhone 6 + - simulator_os_version: latest + - is_clean_build: "no" + _cleanup: + description: |- + This is a utility workflow. It runs a script to delete the folders created in the setup. + steps: + - script: + title: Cleanup folder + description: |- + A script step to delete the downloaded Step folder. + inputs: + - content: |- + #!/bin/bash + rm -rf $PROJECT_TITLE diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/.gitignore b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/.gitignore new file mode 100644 index 00000000..9e7527f5 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/.gitignore @@ -0,0 +1,2 @@ + +.bitrise* diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/README.md new file mode 100644 index 00000000..6dc2d586 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/README.md @@ -0,0 +1,19 @@ +# Lesson 3 - The ins and outs with environment variables + +[Back to Lesson 2](../lesson2_workflow) + +[Lesson 4 - Keeping the control even when the engines are on fire](../lesson4_errors) + +You are probably familiar with environment variables. These are crucial part of [Bitrise](https://bitrise.io), because our Steps communicate using Environment Variables. We created [envman](https://github.com/bitrise-io/envman) to make Environment Variable management a whole lot easier. Also for security reasons we added a .bitrise.secrets.yml to store all your secret passwords and any other local machine- or user related data. At every `bitrise init` we create a .gitignore file to make sure that the top secret data you are storing in this file is not added to git. + +There are multiple ways to create Environment Variables + +- You can add them to the `.bitrise.secrets.yml` - these variables will be accessible throughout the whole app (every Workflow). +- You can add them to the envs section of the app, just like the BITRISE_PROJECT_TITLE and BITRES_DEV_BRANCH - these variables will be accessible throughout the whole app (every Workflow). +- You can add them to the envs section of the given Workflow you would like to use it in - these variables will be accessible throughout the Workflow. +- You can export them in your own Workflow by using the [script step from the StepLib](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/script) - + - or to make it visible in the whole Workflow you can use [envman](https://github.com/bitrise-io/envman) (`envman add --key SOME_KEY --value 'some value'`) + +[Back to Lesson 2](../lesson2_workflow) + +[Lesson 4 - Keeping the control even when the engines are on fire](../lesson4_errors) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/bitrise.yml new file mode 100644 index 00000000..83e86dcc --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/bitrise.yml @@ -0,0 +1,39 @@ +format_version: 1.3.0 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +app: + envs: + - BITRISE_PROJECT_TITLE: lesson_3 $HOME + opts: + is_expand: true + - BITRISE_DEV_BRANCH: master + opts: + is_expand: true +workflows: + example-envs: + envs: [] + steps: + - script: + inputs: + - content: | + #!/bin/bash + echo "Hello world!" + echo "BITRISE_PROJECT_TITLE (HOME should NOT be expanded): ${BITRISE_PROJECT_TITLE}" + export EXP_TEST='Exported value' + echo "EXP_TEST: ${EXP_TEST}" + - script: + inputs: + - content: | + #!/bin/bash + echo "Second script/step" + echo "BITRISE_DEV_BRANCH: ${BITRISE_DEV_BRANCH}" + echo "EXP_TEST (should be empty): ${EXP_TEST}" + - script: + inputs: + - content: | + #!/bin/bash + echo "Read from .bitrise.secrets.yml: ${BITRISE_SECRET_TEST1}" + - script: + inputs: + - content: | + #!/bin/bash + echo 'This ENV should NOT be expanded: ${BITRISE_PROJECT_TITLE}' diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/.gitignore b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/.gitignore new file mode 100644 index 00000000..9e7527f5 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/.gitignore @@ -0,0 +1,2 @@ + +.bitrise* diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/README.md new file mode 100644 index 00000000..ec79ec7a --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/README.md @@ -0,0 +1,17 @@ +# Lesson 4 - Keeping the control even when the engines are on fire a.k.a. Error management + +[Back to Lesson 3](../lesson3_input_output_env) + +[Lesson 5 - A complex Workflow](../lesson5_complex_wf) + +So here's one of the most common part of development - Errors. Yeah we all know that guy who configures everything and writes every line of code flawlessly at the first time... Of course we are not that guy. When working on a complex workflow, it happens at least once that an old code stays in the project making the tests fail or that one little option in the configuration that messes up the whole thing and makes it fail. + +We are following the bash conventions about error handling. Every Step that runs successfully exits with the error code 0 and if the exit code is different the Step fails and (if the Step wasn't skippable the whole Workflow fails). + +There are two ways to keep the Workflow up and running even after a failed Step. +- If the Step was marked skippable, the following Steps will also run. This is great if you want to notify the team that the build started but the used service is currently offline. +- If the Step is marked always run the given Step will be run even if the build fails. This can be used to notify the team of errors. + +[Back to Lesson 3](../lesson3_input_output_env) + +[Lesson 5 - A complex Workflow](../lesson5_complex_wf) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/bitrise.yml new file mode 100644 index 00000000..6d09d161 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/bitrise.yml @@ -0,0 +1,51 @@ +format_version: 1.3.0 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +app: + envs: + - BITRISE_PROJECT_TITLE: lesson_4 + opts: + is_expand: true + - BITRISE_DEV_BRANCH: master + opts: + is_expand: true +workflows: + fail: + steps: + - script: + title: "ok" + - script: + title: "ls" + inputs: + - content: | + #/bin/bash + ls -alh + - script: + title: "fail" + is_skippable: true + inputs: + - content: | + #/bin/bash + set -v + exit 1 + - script: + title: "fail 2" + is_skippable: false + inputs: + - content: | + #/bin/bash + set -v + exit 1 + - script: + title: "ok" + inputs: + - content: | + #/bin/bash + echo "-----> This should NOT be printed!!" + is_always_run: false + - script: + title: "ok" + inputs: + - content: | + #/bin/bash + echo "-----> This should be printed!!" + is_always_run: true diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/.gitignore b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/.gitignore new file mode 100644 index 00000000..9e7527f5 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/.gitignore @@ -0,0 +1,2 @@ + +.bitrise* diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/README.md new file mode 100644 index 00000000..b15de51f --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/README.md @@ -0,0 +1,133 @@ +# Lesson 5 - A complex Workflow + +[Back to Lesson 4](../lesson4_errors) + +[Lesson 6 - Pull the trigger on the Workflow](../lesson6_triggers) + +Let's spice things up a little bit with a more complex bitrise.yml. We will create a Workflow for an iOS project just like in [lesson2](../lesson2_workflow), but this time we'll prepare it for running on our local machine and also on [Bitrise](https://bitrise.io) (Yeah, just for fun we'll run different Steps locally and on the CI server), also we'll add some more main Workflows so that we can use the Archive, Analyze and Test features of Xcode and combine these into a single Workflow by using the before_run / after_run fields. + +First of all let's summarize what we want. +- Utility + - _setup + - git clone or pull to get the source code on the local machine + - _cleanup + - remove source from the local machine + - _download_certs + - to download the needed certificates on the CI Server +- Main Workflows + - analyze + - archive + - test + - master - to create the archive, deploy it and notify the Users about it + +Move the Workflow from [lesson2](../lesson2_workflow) to the current bitrise.yml. Now we have a *_setup*, *_cleanup* and a *test* Workflow. + +Let's add the _download_certs Workflow. It will only have one step, the certificate-and-profile-installer. We have to pass two inputs to it - keychain_path and keychain_password. These are the only two parameters that we'll need. We also want to set it to run only on the CI server so we have to set the run_if to .IsCI. +The Workflow should look something like this: + + _download_certs: + description: This is a utility workflow, used by other workflows. + summary: This workflow downloads the needed certificates on the CI server and adds them to the keychain. + steps: + - git::https://github.com/bitrise-io/steps-certificate-and-profile-installer.git@master: + description: |- + This step will only be used in CI mode, on continuous integration + servers / services (because of the `run_if` statement), + but **NOT** when you run it on your own machine. + run_if: .IsCI + inputs: + - keychain_path: $BITRISE_KEYCHAIN_PATH + - keychain_password: $BITRISE_KEYCHAIN_PASSWORD + +Now we should add the remaining two Xcode Workflows. For both Workflows the _setup and _download_certs Workflows have to be added to the before_run section, to make sure the source is on the machine and the needed signing tools are also present. The only difference between these two Workflows is that before the archive is created we want to run a Unit Tests to make sure nothing went wrong since the previous deployed version. + + analyze: + before_run: + - _setup + - _download_certs + description: |- + This workflow will run Xcode analyze on this project, + but first it'll run the workflows listed in + the `before_run` section. + steps: + - script: + title: Run Xcode analyze + inputs: + - content: xcodebuild -project "${XCODE_PROJECT_PATH}" -scheme "${XCODE_PROJECT_SCHEME}" + analyze + archive: + description: |- + This workflow will run Xcode archive on this project, + but first it'll run the workflows listed in + the `before_run` section. + before_run: + - _setup + - test + - _download_certs + steps: + - xcode-archive: + title: Run Xcode archive + inputs: + - project_path: ${XCODE_PROJECT_PATH} + - scheme: ${XCODE_PROJECT_SCHEME} + - output_dir: $output_dir + outputs: + - BITRISE_IPA_PATH: null + opts: + title: The created .ipa file's path + +And now the master Workflow. This Workflow will deploy the created archive, clean up and send a notification to slack. So the before_run section should contain the archive and the after_run should contain the _cleanup Workflow. And just to make sure no one uploads a broken version, we will set the run_if to only run the Steps if the build is running on a CI server. By adding the correct input variables the Workflow should look like this: + +master: + description: |- + This workflow is meant to be used on a CI server (like bitrise.io), for continuous + deployment, but of course you can run it on your own Mac as well, + except the Step which deploys to Bitrise.io - that's marked with + a Run-If statement to be skipped, unless you run bitrise in --ci mode. + before_run: + - archive + after_run: + - _cleanup + steps: + - script: + inputs: + - content: |- + #!/bin/bash + echo "-> BITRISE_IPA_PATH: ${BITRISE_IPA_PATH}" + - bitrise-ios-deploy: + description: |- + The long `run_if` here is a workaround. At the moment Bitrise.io + defines the BITRISE_PULL_REQUEST environment + in case the build was started by a Pull Request, and not the + required PULL_REQUEST_ID - so we'll check for that instead. + run_if: enveq "BITRISE_PULL_REQUEST" "" | and .IsCI + inputs: + - notify_user_groups: none + - is_enable_public_page: "yes" + outputs: + - BITRISE_PUBLIC_INSTALL_PAGE_URL: null + opts: + title: Public Install Page URL + description: |- + Public Install Page's URL, if the + *Enable public page for the App?* option was *enabled*. + - slack: + run_if: .IsCI + inputs: + - webhook_url: ${SLACK_WEBHOOK_URL} + - channel: ${SLACK_CHANNEL} + - from_username: ${PROJECT_TITLE} - OK + - from_username_on_error: ${PROJECT_TITLE} - ERROR + - message: |- + CI check - OK + PULL_REQUEST_ID : ${PULL_REQUEST_ID} + BITRISE_PUBLIC_INSTALL_PAGE_URL: ${BITRISE_PUBLIC_INSTALL_PAGE_URL} + - message_on_error: |- + CI check - FAILED + PULL_REQUEST_ID : ${PULL_REQUEST_ID} + +This lesson showed you how to handle the local and the CI server Workflows. Move on to the next lesson and see how you can define triggers to run a Workflow using the given trigger. For example trigger a build using the `test` Workflow when a push comes any feature branch. + +[Back to Lesson 4](../lesson4_errors) + +[Lesson 6 - Pull the trigger on the Workflow](../lesson6_triggers) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/bitrise.yml new file mode 100644 index 00000000..cbce92c0 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/bitrise.yml @@ -0,0 +1,148 @@ +format_version: 1.3.0 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +app: + envs: + - BITRISE_PROJECT_TITLE: lesson_5 + opts: + is_expand: true + - BITRISE_DEV_BRANCH: master + opts: + is_expand: true + - PROJECT_TITLE: BitriseSampleWithYML + - XCODE_PROJECT_PATH: ./sample-apps-ios-with-bitrise-yml/${PROJECT_TITLE}.xcodeproj + - XCODE_PROJECT_SCHEME: ${PROJECT_TITLE} + - BITRISE_KEYCHAIN_PATH: $HOME/Library/Keychains/login.keychain + - BITRISE_KEYCHAIN_PASSWORD: vagrant +workflows: + _download_certs: + description: This is a utility workflow, used by other workflows. + summary: This workflow downloads the needed certificates on the CI server and adds them to the keychain. + steps: + - git::https://github.com/bitrise-io/steps-certificate-and-profile-installer.git@master: + description: |- + This step will only be used in CI mode, on continuous integration + servers / services (because of the `run_if` statement), + but **NOT** when you run it on your own machine. + run_if: .IsCI + inputs: + - keychain_path: $BITRISE_KEYCHAIN_PATH + - keychain_password: $BITRISE_KEYCHAIN_PASSWORD + _setup: + description: Clone repo + steps: + - script: + title: clone + inputs: + - content: |- + #!/bin/bash + echo $XCODE_PROJECT_PATH + if [ ! -d $PROJECT_FOLDER ] ; then + git clone ${REPO_URL} + else + cd $PROJECT_FOLDER + git pull + fi + _cleanup: + description: |- + This is a utility workflow. It runs a script to delete the folders created in the setup. + steps: + - script: + title: Cleanup folder + description: |- + A script step to delete the downloaded Step folder. + run_if: not .IsCI + inputs: + - content: |- + #!/bin/bash + rm -rf $PROJECT_TITLE + analyze: + before_run: + - _setup + - _download_certs + description: |- + This workflow will run Xcode analyze on this project, + but first it'll run the workflows listed in + the `before_run` section. + steps: + - script: + title: Run Xcode analyze + inputs: + - content: xcodebuild -project "${XCODE_PROJECT_PATH}" -scheme "${XCODE_PROJECT_SCHEME}" + analyze + archive: + description: |- + This workflow will run Xcode archive on this project, + but first it'll run the workflows listed in + the `before_run` section. + before_run: + - _setup + - test + - _download_certs + steps: + - xcode-archive: + title: Run Xcode archive + inputs: + - project_path: ${XCODE_PROJECT_PATH} + - scheme: ${XCODE_PROJECT_SCHEME} + - output_dir: $output_dir + outputs: + - BITRISE_IPA_PATH: null + opts: + title: The created .ipa file's path + test: + steps: + - xcode-test: + title: Run Xcode test + inputs: + - project_path: ${XCODE_PROJECT_PATH} + - scheme: ${XCODE_PROJECT_SCHEME} + - simulator_device: iPhone 6 + - simulator_os_version: latest + - is_clean_build: "no" + master: + description: |- + This workflow is meant to be used on a CI server (like bitrise.io), for continuous + deployment, but of course you can run it on your own Mac as well, + except the Step which deploys to Bitrise.io - that's marked with + a Run-If statement to be skipped, unless you run bitrise in --ci mode. + before_run: + - archive + after_run: + - _cleanup + steps: + - script: + inputs: + - content: |- + #!/bin/bash + echo "-> BITRISE_IPA_PATH: ${BITRISE_IPA_PATH}" + - bitrise-ios-deploy: + description: |- + The long `run_if` here is a workaround. At the moment Bitrise.io + defines the BITRISE_PULL_REQUEST environment + in case the build was started by a Pull Request, and not the + required PULL_REQUEST_ID - so we'll check for that instead. + run_if: enveq "BITRISE_PULL_REQUEST" "" | and .IsCI + inputs: + - notify_user_groups: none + - is_enable_public_page: "yes" + outputs: + - BITRISE_PUBLIC_INSTALL_PAGE_URL: null + opts: + title: Public Install Page URL + description: |- + Public Install Page's URL, if the + *Enable public page for the App?* option was *enabled*. + - slack: + run_if: .IsCI + inputs: + - webhook_url: ${SLACK_WEBHOOK_URL} + - channel: ${SLACK_CHANNEL} + - from_username: ${PROJECT_TITLE} - OK + - from_username_on_error: ${PROJECT_TITLE} - ERROR + - message: |- + CI check - OK + PULL_REQUEST_ID : ${PULL_REQUEST_ID} + BITRISE_PUBLIC_INSTALL_PAGE_URL: ${BITRISE_PUBLIC_INSTALL_PAGE_URL} + - message_on_error: |- + CI check - FAILED + PULL_REQUEST_ID : ${PULL_REQUEST_ID} diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/.gitignore b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/.gitignore new file mode 100644 index 00000000..9e7527f5 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/.gitignore @@ -0,0 +1,2 @@ + +.bitrise* diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/README.md new file mode 100644 index 00000000..e6a7ad2a --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/README.md @@ -0,0 +1,31 @@ +# Lesson 6 - Pull the trigger on the Workflow + +[Back to Lesson 5](../lesson5_complex_wf) + +Using Git Flow you have multiple branches and need to do different things according to these branch types. Let's try the triggers with an example: +There are some developers working on your project. Each one of them works on a different feature branch developing different features. When a developer finishes a feature and merges the given branch, you want to notify the lead developer that it's time for a code review. When a feature set is merged on the development branch you may want to add the changes to the master branch, deploy the application and send notification emails to some employees of the client. Triggers can be added to the trigger map section in your bitrise.yml. You set a pattern and which workflow should the given pattern trigger. Here is a sample trigger map for the example development process: + + trigger_map: + - pattern: test** + is_pull_request_allowed: true + workflow: test + - pattern: "**feature**" + is_pull_request_allowed: true + workflow: feature + - pattern: "**develop" + is_pull_request_allowed: true + workflow: develop + - pattern: master + is_pull_request_allowed: true + workflow: master + - pattern: "*" + is_pull_request_allowed: true + workflow: fallback + +You can notice that there is a fallback workflow at the end of the trigger map. This Workflow runs if the trigger expression didn't match any of the defined trigger patterns. For example if a developer creates a new branch with the name `develop_awesome_important_change` it wouldn't match the `**develop` trigger pattern. In this case the fallback Workflow would run. You can use this Workflow to get notified about the wrong branch name. As you can see you can add wildcard to your pattern but make sure to add the `""` if you want to start the pattern with the wildcard (in yml the value can't start with *). + +You can notice on the [Bitrise website](https://bitrise.io) that the triggers there are the names of the branch that received the push or pull request. + +You can try the samples in the bitrise.yml. Just run the `bitrise trigger` command to view the full list of triggers in the .yml and try running the given workflow with the `bitrise trigger ` command. + +[Back to Lesson 5](../lesson5_complex_wf) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/bitrise.yml new file mode 100644 index 00000000..5e2d46ef --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/bitrise.yml @@ -0,0 +1,97 @@ +format_version: 1.3.0 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + +title: Template configuration. +summary: |- + Template 'bitrise.yml', generated by 'bitrise init'. +description: |- + Configuration (environments) specified in 'app' will be available + for every workflow. + + The Trigger Map ('trigger_map') defines mapping between trigger patterns + and workflows. + You can run workflows directly with bitrise: bitrise run workflow-name + Or you can 'trigger' a build: bitrise trigger some-pattern + + With this example 'trigger_map' if you 'bitrise trigger test' + or 'bitrise trigger test-1' or specify any other pattern + which starts with 'test' then the 'test' workflow will be used. + In any other case (ex: 'bitrise trigger something-else') the + workflow called 'fallback' will be used. + + Workflows ('workflows') are where you can define different, separate scenarios, + which you can then 'bitrise run' or 'bitrise trigger'. + +app: + envs: + - BITRISE_APP_TITLE: "lesson_6" + - BITRISE_DEV_BRANCH: "master" + +trigger_map: +- pattern: test** + is_pull_request_allowed: true + workflow: test +- pattern: "**feature**" + is_pull_request_allowed: true + workflow: feature +- pattern: "**develop" + is_pull_request_allowed: true + workflow: develop +- pattern: master + is_pull_request_allowed: true + workflow: master +- pattern: "*" + is_pull_request_allowed: true + workflow: fallback + +workflows: + test: + steps: + - script: + title: Fallback + inputs: + - content: |- + #!/bin/bash + echo "This is the test workflow, used" + echo " if you 'bitrise trigger' a build and the pattern" + echo " starts with "test"" + feature: + steps: + - script: + title: Fallback + inputs: + - content: |- + #!/bin/bash + echo "This is the feature workflow, used" + echo " if you 'bitrise trigger' a build and the pattern" + echo " contains the "feature" expression" + develop: + steps: + - script: + title: Fallback + inputs: + - content: |- + #!/bin/bash + echo "This is a the develop workflow, used" + echo " if you 'bitrise trigger' a build and the pattern" + echo " ends with the "develop" expression" + master: + steps: + - script: + title: Fallback + inputs: + - content: |- + #!/bin/bash + echo "This is a the master workflow, used" + echo " if you 'bitrise trigger' a build and the pattern" + echo " matches the "master" pattern in the trigger_map" + fallback: + steps: + - script: + title: Fallback + inputs: + - content: |- + #!/bin/bash + echo "This is a the fallback workflow, used" + echo " if you 'bitrise trigger' a build but the pattern" + echo " does not match any other pattern in the trigger_map" diff --git a/vendor/github.com/bitrise-io/bitrise/_scripts/build_tools_in_docker.sh b/vendor/github.com/bitrise-io/bitrise/_scripts/build_tools_in_docker.sh new file mode 100644 index 00000000..c8ff3959 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_scripts/build_tools_in_docker.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -e + +THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +export REPO_ROOT_DIR="${THIS_SCRIPT_DIR}/.." +cd "${REPO_ROOT_DIR}" + +CONFIG_tool_bin_path="${REPO_ROOT_DIR}/_temp/bin" +echo " (i) CONFIG_tool_bin_path: ${CONFIG_tool_bin_path}" + +if [ ! -d "${ENVMAN_REPO_DIR_PATH}" ] ; then + echo "[!] ENVMAN_REPO_DIR_PATH not defined or not a dir - required!" + exit 1 +fi + +if [ ! -d "${STEPMAN_REPO_DIR_PATH}" ] ; then + echo "[!] STEPMAN_REPO_DIR_PATH not defined or not a dir - required!" + exit 1 +fi + +set -v + +mkdir -p "${CONFIG_tool_bin_path}" + +# build envman +cd "${ENVMAN_REPO_DIR_PATH}" +docker-compose run --rm app go build -o bin-envman +mv ./bin-envman "${CONFIG_tool_bin_path}/envman" + +# build stepman +cd "${STEPMAN_REPO_DIR_PATH}" +docker-compose run --rm app go build -o bin-stepman +mv ./bin-stepman "${CONFIG_tool_bin_path}/stepman" + +# => DONE [OK] diff --git a/vendor/github.com/bitrise-io/bitrise/_scripts/ci.sh b/vendor/github.com/bitrise-io/bitrise/_scripts/ci.sh new file mode 100644 index 00000000..fca661e4 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_scripts/ci.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e + +THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +export REPO_ROOT_DIR="${THIS_SCRIPT_DIR}/.." + +set -v + +# Install dependencies +go get -u github.com/tools/godep +go install github.com/tools/godep +godep restore + +# Build a test version +go build -o tmpbin +./tmpbin setup +rm ./tmpbin + +bash "${THIS_SCRIPT_DIR}/common/ci.sh" + +# ===> DONE diff --git a/vendor/github.com/bitrise-io/bitrise/_scripts/create_release_with_docker_compose.sh b/vendor/github.com/bitrise-io/bitrise/_scripts/create_release_with_docker_compose.sh new file mode 100644 index 00000000..efaf8933 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_scripts/create_release_with_docker_compose.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +export REPO_ROOT_DIR="${THIS_SCRIPT_DIR}/.." +cd "${REPO_ROOT_DIR}" + +set -v + +docker-compose build --no-cache app + +docker-compose run --rm app bitrise run create-release diff --git a/vendor/github.com/bitrise-io/bitrise/_scripts/get_version.go b/vendor/github.com/bitrise-io/bitrise/_scripts/get_version.go new file mode 100644 index 00000000..7f3b0a09 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_scripts/get_version.go @@ -0,0 +1,42 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "regexp" +) + +func main() { + // Inputs + var ( + versionFilePathParam = flag.String("file", "", `Version file path`) + ) + + flag.Parse() + + if versionFilePathParam == nil || *versionFilePathParam == "" { + log.Fatalf(" [!] No version file parameter specified") + } + versionFilePath := *versionFilePathParam + + // Main + versionFileBytes, err := ioutil.ReadFile(versionFilePath) + if err != nil { + log.Fatalf("Failed to read version file: %s", err) + } + versionFileContent := string(versionFileBytes) + + re := regexp.MustCompile(`const VERSION = "(?P[0-9]+\.[0-9-]+\.[0-9-]+)"`) + results := re.FindAllStringSubmatch(versionFileContent, -1) + versionStr := "" + for _, v := range results { + versionStr = v[1] + } + if versionStr == "" { + log.Fatalf("Failed to determine version") + } + + fmt.Println(versionStr) +} diff --git a/vendor/github.com/bitrise-io/bitrise/_scripts/go_install_tools.sh b/vendor/github.com/bitrise-io/bitrise/_scripts/go_install_tools.sh new file mode 100644 index 00000000..f50825c2 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_scripts/go_install_tools.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +set -e + +THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +export REPO_ROOT_DIR="${THIS_SCRIPT_DIR}/.." +cd "${REPO_ROOT_DIR}" + +if [ ! -d "${ENVMAN_REPO_DIR_PATH}" ] ; then + echo "[!] ENVMAN_REPO_DIR_PATH not defined or not a dir - required!" + exit 1 +fi + +if [ ! -d "${STEPMAN_REPO_DIR_PATH}" ] ; then + echo "[!] STEPMAN_REPO_DIR_PATH not defined or not a dir - required!" + exit 1 +fi + +set -v + +# go install envman +cd "${ENVMAN_REPO_DIR_PATH}" +godep restore +go install + +# go install stepman +cd "${STEPMAN_REPO_DIR_PATH}" +godep restore +go install + +# godep restore for bitrise +cd "${REPO_ROOT_DIR}" +godep restore + +# => DONE [OK] diff --git a/vendor/github.com/bitrise-io/bitrise/_scripts/set_version.sh b/vendor/github.com/bitrise-io/bitrise/_scripts/set_version.sh new file mode 100644 index 00000000..901c1438 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/_scripts/set_version.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -x + +version_file_path="$1" +if [ ! -f "$version_file_path" ] ; then + echo " [!] version_file_path not provided, or file doesn't exist at path: $version_file_path" + exit 1 +fi +versionNumber=$next_version +if [[ "$versionNumber" == "" ]] ; then + echo " [!] versionNumber not provided" + exit 1 +fi + +cat >"${version_file_path}" < Linting: $line" + golint_out="$(golint $line)" + if [[ "${golint_out}" != "" ]] ; then + echo "=> Golint issues found:" + echo "${golint_out}" + exit 1 + fi + done <<< "$GOLIST_WITHOUT_VENDOR" + - script: + title: Go test + inputs: + - content: go test ./... + + _bitrise-run-setup: + steps: + - script: + title: Setup system bitrise + run_if: ".IsCI" + inputs: + - content: |- + #!/bin/bash + + bitrise setup + + _prepare-and-setup: + title: Prepare bitrise and install testing tools + description: | + Prepares the environment for testing + steps: + - script: + is_skippable: true + inputs: + - content: brew update + - script: + title: Install testing tools + inputs: + - content: |- + #!/bin/bash + set -ex + + # Check for unhandled errors + go get -u -v github.com/kisielk/errcheck + + # Go lint + go get -u -v github.com/golang/lint/golint + - script: + title: Install bitrise tools + run_if: ".IsCI" + inputs: + - content: |- + #!/bin/bash + set -e + set -x + + # Install envman + envman -v + curl -fL https://github.com/bitrise-io/envman/releases/download/1.1.0/envman-$(uname -s)-$(uname -m) > /usr/local/bin/envman + chmod +x /usr/local/bin/envman + envman -v + + # Install stepman + stepman -v + curl -fL https://github.com/bitrise-io/stepman/releases/download/0.9.18/stepman-$(uname -s)-$(uname -m) > /usr/local/bin/stepman + chmod +x /usr/local/bin/stepman + stepman -v + + # ---------------------------------------------------------------- + # --- workflows for Releasing + create-release: + title: Create Release version + description: |- + Creates new version with specified RELEASE_VERSION environment + + 1, Create CHANGELOG and git release + 2, Export RELEASE_VERSION + 3, Create binaries + after_run: + - create-binaries + steps: + - script: + title: Create CHANGELOG and git release + inputs: + - content: |- + #!/bin/bash + set -ex + + go get github.com/bitrise-tools/releaseman + + export CI=true + + releaseman create-changelog \ + --version $RELEASE_VERSION \ + --set-version-script "bash _scripts/set_version.sh version/version.go" + + announce-release: + title: Announce Release + description: |- + Notifying about new version of bitrise + + Send Slack notifications + steps: + - slack: + title: Announce on Internal Slack channel + inputs: + - webhook_url: "$INTERNAL_DEV_SLACK_WEBHOOK_URL" + - channel: "$INTERNAL_DEV_SLACK_CHANNEL" + - from_username: ${BIN_NAME} + - message: | + Release v${RELEASE_VERSION} was just published! :tada: + + You can find it at ${GITHUB_RELEASES_URL} + - emoji: ":rocket:" + - slack: + title: Announce on Public Slack channel + inputs: + - webhook_url: "$PUBLIC_SLACK_WEBHOOK_URL" + - channel: "$PUBLIC_SLACK_CHANNEL" + - from_username: ${BIN_NAME} + - message: | + Release v${RELEASE_VERSION} was just published! :tada: + + You can find it at ${GITHUB_RELEASES_URL} + - emoji: ":rocket:" + + create-binaries: + title: Create binaries + description: | + Creates Linux and Darwin binaries + steps: + - script: + title: Create binaries + inputs: + - content: | + #!/bin/bash + set -e + set -x + + echo + echo "Create final binaries" + echo " Build number: $BITRISE_BUILD_NUMBER" + + export ARCH=x86_64 + export GOARCH=amd64 + + # Create Darwin bin + export OS=Darwin + export GOOS=darwin + + DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" + echo " Create final Darwin binary at: $DEPLOY_PATH" + + version_package="github.com/bitrise-io/bitrise/version" + + go build \ + -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ + -o "$DEPLOY_PATH" + + envman add --key OSX_DEPLOY_PATH --value $DEPLOY_PATH + cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH + echo " Copy final Darwin binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" + + + # Create Linux binary + export OS=Linux + export GOOS=linux + + DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" + echo " Create final Linux binary at: $DEPLOY_PATH" + + go build \ + -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ + -o "$DEPLOY_PATH" + + envman add --key LINUX_DEPLOY_PATH --value $DEPLOY_PATH + cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH + echo " Copy final Linux binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" + + # ---------------------------------------------------------------- + # --- workflows for Utility + godeps-update: + title: Godeps update + description: | + Used for updating bitrise dependencies with godep + steps: + - script: + title: Dependency update + inputs: + - content: | + #!/bin/bash + set -ex + go get -u -v github.com/tools/godep + + rm -rf ./Godeps + rm -rf ./vendor + + go get -t -d ./... + go get golang.org/x/sys/unix + go get github.com/davecgh/go-spew/spew + go get github.com/pmezard/go-difflib/difflib + godep save ./... + + noop: + title: Noop + description: Empty workflow for quick testing + + fail-test: + title: Fails + description: Workflow will fail + steps: + - script: + title: Success + inputs: + - content: exit 0 + - script: + title: Fail wit exit code 2 + inputs: + - content: exit 2 + - script: + title: Skippable fail with exit code 2 + is_always_run: true + is_skippable: true + inputs: + - content: exit 2 + - script: + title: Skipping success + is_always_run: false + inputs: + - content: exit 0 diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/dependencies.go b/vendor/github.com/bitrise-io/bitrise/bitrise/dependencies.go new file mode 100644 index 00000000..a6bdb231 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/bitrise/dependencies.go @@ -0,0 +1,420 @@ +package bitrise + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/bitrise-io/bitrise/plugins" + "github.com/bitrise-io/bitrise/tools" + "github.com/bitrise-io/bitrise/utils" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/progress" + "github.com/bitrise-io/go-utils/retry" + "github.com/bitrise-io/go-utils/versions" + "github.com/bitrise-io/goinp/goinp" + stepmanModels "github.com/bitrise-io/stepman/models" + ver "github.com/hashicorp/go-version" +) + +func removeEmptyNewLines(text string) string { + split := strings.Split(text, "\n") + cleanedLines := []string{} + for _, line := range split { + if strings.TrimSpace(line) != "" { + cleanedLines = append(cleanedLines, line) + } + } + return strings.Join(cleanedLines, "\n") +} + +// CheckIsPluginInstalled ... +func CheckIsPluginInstalled(name string, dependency PluginDependency) error { + _, found, err := plugins.LoadPlugin(name) + if err != nil { + return err + } + + currentVersion := "" + installOrUpdate := false + + if !found { + log.Warnf("Default plugin (%s) NOT found, installing...", name) + installOrUpdate = true + currentVersion = dependency.MinVersion + } else { + installedVersion, err := plugins.GetPluginVersion(name) + if err != nil { + return err + } + + if installedVersion == nil { + log.Warnf("Default plugin (%s) is not installed from git, no version info available.", name) + currentVersion = "" + } else { + currentVersion = installedVersion.String() + + minVersion, err := ver.NewVersion(dependency.MinVersion) + if err != nil { + return err + } + + if installedVersion.LessThan(minVersion) { + log.Warnf("Default plugin (%s) version (%s) is lower than required (%s), updating...", name, installedVersion.String(), minVersion.String()) + installOrUpdate = true + currentVersion = dependency.MinVersion + } + } + } + + if installOrUpdate { + var plugin plugins.Plugin + err := retry.Times(2).Wait(5 * time.Second).Try(func(attempt uint) error { + if attempt > 0 { + log.Warnf("Download failed, retrying ...") + } + p, _, err := plugins.InstallPlugin(dependency.Source, dependency.MinVersion) + plugin = p + return err + }) + if err != nil { + return fmt.Errorf("Failed to install plugin, error: %s", err) + } + + if len(plugin.Description) > 0 { + fmt.Println(removeEmptyNewLines(plugin.Description)) + } + } + + pluginDir := plugins.GetPluginDir(name) + + log.Printf("%s Plugin %s (%s): %s", colorstring.Green("[OK]"), name, currentVersion, pluginDir) + + return nil +} + +// CheckIsHomebrewInstalled ... +func CheckIsHomebrewInstalled(isFullSetupMode bool) error { + brewRubyInstallCmdString := `$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"` + officialSiteURL := "http://brew.sh/" + + progInstallPth, err := utils.CheckProgramInstalledPath("brew") + if err != nil { + fmt.Println() + log.Warnf("It seems that Homebrew is not installed on your system.") + log.Infof("Homebrew (short: brew) is required in order to be able to auto-install all the bitrise dependencies.") + log.Infof("You should be able to install brew by copying this command and running it in your Terminal:") + log.Infof(brewRubyInstallCmdString) + log.Infof("You can find more information about Homebrew on its official site at:", officialSiteURL) + log.Warnf("Once the installation of brew is finished you should call the bitrise setup again.") + return err + } + verStr, err := command.RunCommandAndReturnStdout("brew", "--version") + if err != nil { + log.Infof("") + return errors.New("Failed to get version") + } + + if isFullSetupMode { + // brew doctor + doctorOutput := "" + var err error + progress.NewDefaultWrapper("brew doctor").WrapAction(func() { + doctorOutput, err = command.RunCommandAndReturnCombinedStdoutAndStderr("brew", "doctor") + }) + if err != nil { + fmt.Println("") + log.Warnf("brew doctor returned an error:") + log.Warnf("%s", doctorOutput) + return errors.New("command failed: brew doctor") + } + } + + verSplit := strings.Split(verStr, "\n") + if len(verSplit) == 2 { + log.Printf("%s %s: %s", colorstring.Green("[OK]"), verSplit[0], progInstallPth) + log.Printf("%s %s", colorstring.Green("[OK]"), verSplit[1]) + } else { + log.Printf("%s %s: %s", colorstring.Green("[OK]"), verStr, progInstallPth) + } + + return nil +} + +// PrintInstalledXcodeInfos ... +func PrintInstalledXcodeInfos() error { + xcodeSelectPth, err := command.RunCommandAndReturnStdout("xcode-select", "--print-path") + if err != nil { + xcodeSelectPth = "xcode-select --print-path failed to detect the location of activate Xcode Command Line Tools path" + } + + progInstallPth, err := utils.CheckProgramInstalledPath("xcodebuild") + if err != nil { + return errors.New("xcodebuild is not installed") + } + + isFullXcodeAvailable := false + verStr, err := command.RunCommandAndReturnCombinedStdoutAndStderr("xcodebuild", "-version") + if err != nil { + // No full Xcode available, only the Command Line Tools + // verStr is something like "xcode-select: error: tool 'xcodebuild' requires Xcode, but active developer directory '/Library/Developer/CommandLineTools' is a command line tools instance" + isFullXcodeAvailable = false + } else { + // version OK - full Xcode available + // we'll just format it a bit to fit into one line + isFullXcodeAvailable = true + verStr = strings.Join(strings.Split(verStr, "\n"), " | ") + } + + if !isFullXcodeAvailable { + log.Printf("%s xcodebuild (%s): %s", colorstring.Green("[OK]"), colorstring.Yellow(verStr), progInstallPth) + } else { + log.Printf("%s xcodebuild (%s): %s", colorstring.Green("[OK]"), verStr, progInstallPth) + } + + log.Printf("%s active Xcode (Command Line Tools) path (xcode-select --print-path): %s", colorstring.Green("[OK]"), xcodeSelectPth) + + if !isFullXcodeAvailable { + log.Warnf("No Xcode found, only the Xcode Command Line Tools are available!") + log.Warnf("Full Xcode is required to build, test and archive iOS apps!") + } + + return nil +} + +func checkIsBitriseToolInstalled(toolname, minVersion string, isInstall bool) error { + doInstall := func() error { + officialGithub := "https://github.com/bitrise-io/" + toolname + log.Warnf("No supported %s version found", toolname) + log.Printf("You can find more information about %s on its official GitHub page: %s", toolname, officialGithub) + + // Install + var err error + progress.NewDefaultWrapper("Installing").WrapAction(func() { + err = retry.Times(2).Wait(5 * time.Second).Try(func(attempt uint) error { + if attempt > 0 { + log.Warnf("Download failed, retrying ...") + } + return tools.InstallToolFromGitHub(toolname, "bitrise-io", minVersion) + }) + }) + + if err != nil { + return err + } + + // check again + return checkIsBitriseToolInstalled(toolname, minVersion, false) + } + + // check whether installed + progInstallPth, err := utils.CheckProgramInstalledPath(toolname) + if err != nil { + if !isInstall { + return err + } + return doInstall() + } + verStr, err := command.RunCommandAndReturnStdout(toolname, "-version") + if err != nil { + log.Infof("") + return errors.New("Failed to get version") + } + + // version check + isVersionOk, err := versions.IsVersionGreaterOrEqual(verStr, minVersion) + if err != nil { + log.Errorf("Failed to validate installed version") + return err + } + if !isVersionOk { + if !isInstall { + log.Warnf("Installed %s found, but not a supported version (%s)", toolname, verStr) + return errors.New("Failed to install required version") + } + return doInstall() + } + + log.Printf("%s %s (%s): %s", colorstring.Green("[OK]"), toolname, verStr, progInstallPth) + return nil +} + +// CheckIsEnvmanInstalled ... +func CheckIsEnvmanInstalled(minEnvmanVersion string) error { + toolname := "envman" + minVersion := minEnvmanVersion + if err := checkIsBitriseToolInstalled(toolname, minVersion, true); err != nil { + return err + } + return nil +} + +// CheckIsStepmanInstalled ... +func CheckIsStepmanInstalled(minStepmanVersion string) error { + toolname := "stepman" + minVersion := minStepmanVersion + if err := checkIsBitriseToolInstalled(toolname, minVersion, true); err != nil { + return err + } + return nil +} + +func checkIfBrewPackageInstalled(packageName string) bool { + out, err := command.New("brew", "list", packageName).RunAndReturnTrimmedCombinedOutput() + if err != nil { + return false + } + return len(out) > 0 +} + +func checkIfAptPackageInstalled(packageName string) bool { + err := command.New("dpkg", "-s", packageName).Run() + return (err == nil) +} + +// DependencyTryCheckTool ... +func DependencyTryCheckTool(tool string) error { + var cmd *command.Model + errMsg := "" + + switch tool { + case "xcode": + cmd = command.New("xcodebuild", "-version") + errMsg = "The full Xcode app is not installed, required for this step. You can install it from the App Store." + break + default: + cmdFields := strings.Fields(tool) + if len(cmdFields) >= 2 { + cmd = command.New(cmdFields[0], cmdFields[1:]...) + } else if len(cmdFields) == 1 { + cmd = command.New(cmdFields[0]) + } else { + return fmt.Errorf("Invalid tool name (%s)", tool) + } + } + + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + if err != nil { + if errMsg != "" { + return errors.New(errMsg) + } + log.Infof("Output was: %s", out) + return fmt.Errorf("Dependency check failed for: %s", tool) + } + + return nil +} + +// InstallWithBrewIfNeeded ... +func InstallWithBrewIfNeeded(brewDep stepmanModels.BrewDepModel, isCIMode bool) error { + isDepInstalled := false + // First do a "which", to see if the binary is available. + // Can be available from another source, not just from brew, + // e.g. it's common to use NVM or similar to install and manage the Node.js version. + { + if out, err := command.RunCommandAndReturnCombinedStdoutAndStderr("which", brewDep.GetBinaryName()); err != nil { + if err.Error() == "exit status 1" && out == "" { + isDepInstalled = false + } else { + // unexpected `which` error + return fmt.Errorf("which (%s) failed -- out: (%s) err: (%s)", brewDep.Name, out, err) + } + } else if out != "" { + isDepInstalled = true + } else { + // no error but which's output was empty + return fmt.Errorf("which (%s) failed -- no error (exit code 0) but output was empty", brewDep.Name) + } + } + + // then do a package manager specific lookup + { + if !isDepInstalled { + // which did not find the binary, also check in brew, + // whether the package is installed + isDepInstalled = checkIfBrewPackageInstalled(brewDep.Name) + } + } + + if !isDepInstalled { + // Tool isn't installed -- install it... + if !isCIMode { + log.Infof(`This step requires "%s" to be available, but it is not installed.`, brewDep.GetBinaryName()) + allow, err := goinp.AskForBoolWithDefault(`Would you like to install the "`+brewDep.Name+`" package with brew?`, true) + if err != nil { + return err + } + if !allow { + return errors.New("(" + brewDep.Name + ") is required for step") + } + } + + log.Infof("(%s) isn't installed, installing...", brewDep.Name) + if cmdOut, err := command.RunCommandAndReturnCombinedStdoutAndStderr("brew", "install", brewDep.Name); err != nil { + log.Errorf("brew install %s failed -- out: (%s) err: (%s)", brewDep.Name, cmdOut, err) + return err + } + log.Infof(" * "+colorstring.Green("[OK]")+" %s installed", brewDep.Name) + } + + return nil +} + +// InstallWithAptGetIfNeeded ... +func InstallWithAptGetIfNeeded(aptGetDep stepmanModels.AptGetDepModel, isCIMode bool) error { + isDepInstalled := false + // First do a "which", to see if the binary is available. + // Can be available from another source, not just from brew, + // e.g. it's common to use NVM or similar to install and manage the Node.js version. + { + if out, err := command.RunCommandAndReturnCombinedStdoutAndStderr("which", aptGetDep.GetBinaryName()); err != nil { + if err.Error() == "exit status 1" && out == "" { + isDepInstalled = false + } else { + // unexpected `which` error + return fmt.Errorf("which (%s) failed -- out: (%s) err: (%s)", aptGetDep.Name, out, err) + } + } else if out != "" { + isDepInstalled = true + } else { + // no error but which's output was empty + return fmt.Errorf("which (%s) failed -- no error (exit code 0) but output was empty", aptGetDep.Name) + } + } + + // then do a package manager specific lookup + { + if !isDepInstalled { + // which did not find the binary, also check in brew, + // whether the package is installed + isDepInstalled = checkIfAptPackageInstalled(aptGetDep.Name) + } + } + + if !isDepInstalled { + // Tool isn't installed -- install it... + if !isCIMode { + log.Infof(`This step requires "%s" to be available, but it is not installed.`, aptGetDep.GetBinaryName()) + allow, err := goinp.AskForBoolWithDefault(`Would you like to install the "`+aptGetDep.Name+`" package with apt-get?`, true) + if err != nil { + return err + } + if !allow { + return errors.New("(" + aptGetDep.Name + ") is required for step") + } + } + + log.Infof("(%s) isn't installed, installing...", aptGetDep.Name) + if cmdOut, err := command.RunCommandAndReturnCombinedStdoutAndStderr("sudo", "apt-get", "-y", "install", aptGetDep.Name); err != nil { + log.Errorf("sudo apt-get -y install %s failed -- out: (%s) err: (%s)", aptGetDep.Name, cmdOut, err) + return err + } + + log.Infof(" * "+colorstring.Green("[OK]")+" %s installed", aptGetDep.Name) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/print.go b/vendor/github.com/bitrise-io/bitrise/bitrise/print.go new file mode 100644 index 00000000..539e381b --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/bitrise/print.go @@ -0,0 +1,614 @@ +package bitrise + +import ( + "fmt" + "strings" + "time" + "unicode/utf8" + + "github.com/bitrise-io/bitrise/models" + "github.com/bitrise-io/bitrise/toolkits" + "github.com/bitrise-io/go-utils/colorstring" + log "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/stringutil" + "github.com/bitrise-io/go-utils/versions" + stepmanModels "github.com/bitrise-io/stepman/models" +) + +const ( + // should not be under ~45 + stepRunSummaryBoxWidthInChars = 80 +) + +//------------------------------ +// Util methods +//------------------------------ + +func isUpdateAvailable(stepInfo stepmanModels.StepInfoModel) bool { + if stepInfo.LatestVersion == "" { + return false + } + + res, err := versions.CompareVersions(stepInfo.Version, stepInfo.LatestVersion) + if err != nil { + log.Errorf("Failed to compare versions, err: %s", err) + } + + return (res == 1) +} + +func getTrimmedStepName(stepRunResult models.StepRunResultsModel) string { + iconBoxWidth := len(" ") + timeBoxWidth := len(" time (s) ") + titleBoxWidth := stepRunSummaryBoxWidthInChars - 4 - iconBoxWidth - timeBoxWidth - 1 + + stepInfo := stepRunResult.StepInfo + + title := "" + if stepInfo.Step.Title != nil && *stepInfo.Step.Title != "" { + title = *stepInfo.Step.Title + } + + if stepInfo.GroupInfo.RemovalDate != "" { + title = fmt.Sprintf("[Deprecated] %s", title) + } + + titleBox := "" + switch stepRunResult.Status { + case models.StepRunStatusCodeSuccess, models.StepRunStatusCodeSkipped, models.StepRunStatusCodeSkippedWithRunIf: + titleBox = fmt.Sprintf("%s", title) + if len(titleBox) > titleBoxWidth { + dif := len(titleBox) - titleBoxWidth + title = stringutil.MaxFirstCharsWithDots(title, len(title)-dif) + titleBox = fmt.Sprintf("%s", title) + } + break + case models.StepRunStatusCodeFailed, models.StepRunStatusCodeFailedSkippable: + titleBox = fmt.Sprintf("%s (exit code: %d)", title, stepRunResult.ExitCode) + if len(titleBox) > titleBoxWidth { + dif := len(titleBox) - titleBoxWidth + title = stringutil.MaxFirstCharsWithDots(title, len(title)-dif) + titleBox = fmt.Sprintf("%s (exit code: %d)", title, stepRunResult.ExitCode) + } + break + default: + log.Errorf("Unkown result code") + return "" + } + + return titleBox +} + +func getRunningStepHeaderMainSection(stepInfo stepmanModels.StepInfoModel, idx int) string { + title := "" + if stepInfo.Step.Title != nil && *stepInfo.Step.Title != "" { + title = *stepInfo.Step.Title + } + + content := fmt.Sprintf("| (%d) %s |", idx, title) + charDiff := len(content) - stepRunSummaryBoxWidthInChars + + if charDiff < 0 { + // shorter than desired - fill with space + content = fmt.Sprintf("| (%d) %s%s |", idx, title, strings.Repeat(" ", -charDiff)) + } else if charDiff > 0 { + // longer than desired - trim title + trimmedTitleWidth := len(title) - charDiff + if trimmedTitleWidth < 4 { + log.Errorf("Step title too long, can't present title at all! : %s", title) + } else { + content = fmt.Sprintf("| (%d) %s |", idx, stringutil.MaxFirstCharsWithDots(title, trimmedTitleWidth)) + } + } + return content +} + +func getRunningStepHeaderSubSection(step stepmanModels.StepModel, stepInfo stepmanModels.StepInfoModel) string { + + idRow := "" + { + id := stepInfo.ID + idRow = fmt.Sprintf("| id: %s |", id) + charDiff := len(idRow) - stepRunSummaryBoxWidthInChars + if charDiff < 0 { + // shorter than desired - fill with space + idRow = fmt.Sprintf("| id: %s%s |", id, strings.Repeat(" ", -charDiff)) + } else if charDiff > 0 { + // longer than desired - trim title + trimmedWidth := len(id) - charDiff + if trimmedWidth < 4 { + log.Errorf("Step id too long, can't present id at all! : %s", id) + } else { + idRow = fmt.Sprintf("| id: %s |", stringutil.MaxFirstCharsWithDots(id, trimmedWidth)) + } + } + } + + versionRow := "" + { + version := stepInfo.Version + versionRow = fmt.Sprintf("| version: %s |", version) + charDiff := len(versionRow) - stepRunSummaryBoxWidthInChars + if charDiff < 0 { + // shorter than desired - fill with space + versionRow = fmt.Sprintf("| version: %s%s |", version, strings.Repeat(" ", -charDiff)) + } else if charDiff > 0 { + // longer than desired - trim title + trimmedWidth := len(version) - charDiff + if trimmedWidth < 4 { + log.Errorf("Step version too long, can't present version at all! : %s", version) + } else { + versionRow = fmt.Sprintf("| id: %s |", stringutil.MaxFirstCharsWithDots(version, trimmedWidth)) + } + } + } + + collectionRow := "" + { + collection := stepInfo.Library + collectionRow = fmt.Sprintf("| collection: %s |", collection) + charDiff := len(collectionRow) - stepRunSummaryBoxWidthInChars + if charDiff < 0 { + // shorter than desired - fill with space + collectionRow = fmt.Sprintf("| collection: %s%s |", collection, strings.Repeat(" ", -charDiff)) + } else if charDiff > 0 { + // longer than desired - trim title + trimmedWidth := len(collection) - charDiff + if trimmedWidth < 4 { + log.Errorf("Step collection too long, can't present collection at all! : %s", collection) + } else { + collectionRow = fmt.Sprintf("| collection: %s |", stringutil.MaxLastCharsWithDots(collection, trimmedWidth)) + } + } + } + + toolkitRow := "" + { + toolkitForStep := toolkits.ToolkitForStep(step) + toolkitName := toolkitForStep.ToolkitName() + toolkitRow = fmt.Sprintf("| toolkit: %s |", toolkitName) + charDiff := len(toolkitRow) - stepRunSummaryBoxWidthInChars + if charDiff < 0 { + // shorter than desired - fill with space + toolkitRow = fmt.Sprintf("| toolkit: %s%s |", toolkitName, strings.Repeat(" ", -charDiff)) + } else if charDiff > 0 { + // longer than desired - trim title + trimmedWidth := len(toolkitName) - charDiff + if trimmedWidth < 4 { + log.Errorf("Step toolkitName too long, can't present toolkitName at all! : %s", toolkitName) + } else { + toolkitRow = fmt.Sprintf("| toolkit: %s |", stringutil.MaxLastCharsWithDots(toolkitName, trimmedWidth)) + } + } + } + + timeRow := "" + { + logTime := time.Now().Format(time.RFC3339) + timeRow = fmt.Sprintf("| time: %s |", logTime) + charDiff := len(timeRow) - stepRunSummaryBoxWidthInChars + if charDiff < 0 { + // shorter than desired - fill with space + timeRow = fmt.Sprintf("| time: %s%s |", logTime, strings.Repeat(" ", -charDiff)) + } else if charDiff > 0 { + // longer than desired - trim title + trimmedWidth := len(logTime) - charDiff + if trimmedWidth < 4 { + log.Errorf("Time too long, can't present time at all! : %s", logTime) + } else { + timeRow = fmt.Sprintf("| time: %s |", stringutil.MaxFirstCharsWithDots(logTime, trimmedWidth)) + } + } + } + + return fmt.Sprintf("%s\n%s\n%s\n%s\n%s", idRow, versionRow, collectionRow, toolkitRow, timeRow) +} + +func getRunningStepFooterMainSection(stepRunResult models.StepRunResultsModel) string { + iconBoxWidth := len(" ") + timeBoxWidth := len(" time (s) ") + titleBoxWidth := stepRunSummaryBoxWidthInChars - 4 - iconBoxWidth - timeBoxWidth - 1 + + icon := "" + title := getTrimmedStepName(stepRunResult) + coloringFunc := colorstring.Green + + switch stepRunResult.Status { + case models.StepRunStatusCodeSuccess: + icon = "✓" + coloringFunc = colorstring.Green + break + case models.StepRunStatusCodeFailed: + icon = "x" + coloringFunc = colorstring.Red + break + case models.StepRunStatusCodeFailedSkippable: + icon = "!" + coloringFunc = colorstring.Yellow + break + case models.StepRunStatusCodeSkipped, models.StepRunStatusCodeSkippedWithRunIf: + icon = "-" + coloringFunc = colorstring.Blue + break + default: + log.Errorf("Unkown result code") + return "" + } + + iconBox := fmt.Sprintf(" %s ", coloringFunc(icon)) + + titleWhiteSpaceWidth := titleBoxWidth - len(title) + coloredTitle := title + if strings.HasPrefix(title, "[Deprecated]") { + title := strings.TrimPrefix(title, "[Deprecated]") + coloredTitle = fmt.Sprintf("%s%s", colorstring.Red("[Deprecated]"), coloringFunc(title)) + } else { + coloredTitle = coloringFunc(title) + } + + titleBox := fmt.Sprintf(" %s%s", coloredTitle, strings.Repeat(" ", titleWhiteSpaceWidth)) + + runTimeStr, err := FormattedSecondsToMax8Chars(stepRunResult.RunTime) + if err != nil { + log.Errorf("Failed to format time, error: %s", err) + runTimeStr = "999+ hour" + } + + timeWhiteSpaceWidth := timeBoxWidth - len(runTimeStr) - 1 + if timeWhiteSpaceWidth < 0 { + log.Errorf("Invalid time box size for RunTime: %#v", stepRunResult.RunTime) + timeWhiteSpaceWidth = 0 + } + timeBox := fmt.Sprintf(" %s%s", runTimeStr, strings.Repeat(" ", timeWhiteSpaceWidth)) + + return fmt.Sprintf("|%s|%s|%s|", iconBox, titleBox, timeBox) +} + +func getDeprecateNotesRows(notes string) string { + colorDeprecateNote := func(line string) string { + if strings.HasPrefix(line, "Removal notes:") { + line = strings.TrimPrefix(line, "Removal notes:") + line = fmt.Sprintf("%s%s", colorstring.Red("Removal notes:"), line) + } + return line + } + + boxContentWidth := stepRunSummaryBoxWidthInChars - 4 + + notesWithoutNewLine := strings.Replace(notes, "\n", " ", -1) + words := strings.Split(notesWithoutNewLine, " ") + if len(words) == 0 { + return "" + } + + formattedNote := "" + line := "" + + for i, word := range words { + isLastLine := (i == len(words)-1) + + expectedLine := "" + if line == "" { + expectedLine = word + } else { + expectedLine = line + " " + word + } + + if utf8.RuneCountInString(expectedLine) > boxContentWidth { + // expected line would be to long, so print the previous line, and start a new with the last word. + noteRow := fmt.Sprintf("| %s |", line) + charDiff := len(noteRow) - stepRunSummaryBoxWidthInChars + if charDiff <= 0 { + // shorter than desired - fill with space + line = colorDeprecateNote(line) + noteRow = fmt.Sprintf("| %s%s |", line, strings.Repeat(" ", -charDiff)) + } else if charDiff > 0 { + // longer than desired - should not + log.Errorf("Should not be longer then expected") + } + + if formattedNote == "" { + formattedNote = noteRow + } else { + formattedNote = fmt.Sprintf("%s\n%s", formattedNote, noteRow) + } + + line = word + + if isLastLine { + noteRow := fmt.Sprintf("| %s |", line) + charDiff := len(noteRow) - stepRunSummaryBoxWidthInChars + if charDiff < 0 { + // shorter than desired - fill with space + line = colorDeprecateNote(line) + noteRow = fmt.Sprintf("| %s%s |", line, strings.Repeat(" ", -charDiff)) + } else if charDiff > 0 { + // longer than desired - should not + log.Errorf("Should not be longer then expected") + } + + if formattedNote == "" { + formattedNote = noteRow + } else { + formattedNote = fmt.Sprintf("%s\n%s", formattedNote, noteRow) + } + } + } else { + // expected line is not to long, just keep growing the line + line = expectedLine + + if isLastLine { + noteRow := fmt.Sprintf("| %s |", line) + charDiff := len(noteRow) - stepRunSummaryBoxWidthInChars + if charDiff <= 0 { + // shorter than desired - fill with space + line = colorDeprecateNote(line) + noteRow = fmt.Sprintf("| %s%s |", line, strings.Repeat(" ", -charDiff)) + } else if charDiff > 0 { + // longer than desired - should not + log.Errorf("Should not be longer then expected") + } + + if formattedNote == "" { + formattedNote = noteRow + } else { + formattedNote = fmt.Sprintf("%s\n%s", formattedNote, noteRow) + } + } + } + } + + return formattedNote +} + +func getRunningStepFooterSubSection(stepRunResult models.StepRunResultsModel) string { + stepInfo := stepRunResult.StepInfo + + removalDate := stepInfo.GroupInfo.RemovalDate + deprecateNotes := stepInfo.GroupInfo.DeprecateNotes + removalDateRow := "" + deprecateNotesRow := "" + if removalDate != "" { + removalDateValue := removalDate + removalDateKey := colorstring.Red("Removal date:") + + removalDateRow = fmt.Sprintf("| Removal date: %s |", removalDateValue) + charDiff := len(removalDateRow) - stepRunSummaryBoxWidthInChars + removalDateRow = fmt.Sprintf("| %s %s%s |", removalDateKey, removalDateValue, strings.Repeat(" ", -charDiff)) + + if deprecateNotes != "" { + deprecateNotesStr := fmt.Sprintf("Removal notes: %s", deprecateNotes) + deprecateNotesRow = getDeprecateNotesRows(deprecateNotesStr) + } + } + + isUpdateAvailable := isUpdateAvailable(stepRunResult.StepInfo) + updateRow := "" + if isUpdateAvailable { + updateRow = fmt.Sprintf("| Update available: %s -> %s |", stepInfo.Version, stepInfo.LatestVersion) + charDiff := len(updateRow) - stepRunSummaryBoxWidthInChars + if charDiff < 0 { + // shorter than desired - fill with space + updateRow = fmt.Sprintf("| Update available: %s -> %s%s |", stepInfo.Version, stepInfo.LatestVersion, strings.Repeat(" ", -charDiff)) + } else if charDiff > 0 { + // longer than desired - trim title + if charDiff > 6 { + updateRow = fmt.Sprintf("| Update available!%s |", strings.Repeat(" ", -len("| Update available! |")-stepRunSummaryBoxWidthInChars)) + } else { + updateRow = fmt.Sprintf("| Update available: -> %s%s |", stepInfo.LatestVersion, strings.Repeat(" ", -len("| Update available: -> %s |")-stepRunSummaryBoxWidthInChars)) + } + } + } + + issueRow := "" + sourceRow := "" + if stepRunResult.ErrorStr != "" { + // Support URL + var coloringFunc func(...interface{}) string + supportURL := "" + if stepInfo.Step.SupportURL != nil && *stepInfo.Step.SupportURL != "" { + supportURL = *stepInfo.Step.SupportURL + } + if supportURL == "" { + coloringFunc = colorstring.Yellow + supportURL = "Not provided" + } + + issueRow = fmt.Sprintf("| Issue tracker: %s |", supportURL) + + charDiff := len(issueRow) - stepRunSummaryBoxWidthInChars + if charDiff <= 0 { + // shorter than desired - fill with space + + if coloringFunc != nil { + // We need to do this after charDiff calculation, + // because of coloring characters increase the text length, but they do not printed + supportURL = coloringFunc("Not provided") + } + + issueRow = fmt.Sprintf("| Issue tracker: %s%s |", supportURL, strings.Repeat(" ", -charDiff)) + } else if charDiff > 0 { + // longer than desired - trim title + trimmedWidth := len(supportURL) - charDiff + if trimmedWidth < 4 { + log.Errorf("Support url too long, can't present support url at all! : %s", supportURL) + } else { + issueRow = fmt.Sprintf("| Issue tracker: %s |", stringutil.MaxLastCharsWithDots(supportURL, trimmedWidth)) + } + } + + // Source Code URL + coloringFunc = nil + sourceCodeURL := "" + if stepInfo.Step.SourceCodeURL != nil && *stepInfo.Step.SourceCodeURL != "" { + sourceCodeURL = *stepInfo.Step.SourceCodeURL + } + if sourceCodeURL == "" { + coloringFunc = colorstring.Yellow + sourceCodeURL = "Not provided" + } + + sourceRow = fmt.Sprintf("| Source: %s |", sourceCodeURL) + + charDiff = len(sourceRow) - stepRunSummaryBoxWidthInChars + if charDiff <= 0 { + // shorter than desired - fill with space + + if coloringFunc != nil { + // We need to do this after charDiff calculation, + // because of coloring characters increase the text length, but they do not printed + sourceCodeURL = coloringFunc("Not provided") + } + + sourceRow = fmt.Sprintf("| Source: %s%s |", sourceCodeURL, strings.Repeat(" ", -charDiff)) + } else if charDiff > 0 { + // longer than desired - trim title + trimmedWidth := len(sourceCodeURL) - charDiff + if trimmedWidth < 4 { + log.Errorf("Source url too long, can't present source url at all! : %s", sourceCodeURL) + } else { + sourceRow = fmt.Sprintf("| Source: %s |", stringutil.MaxLastCharsWithDots(sourceCodeURL, trimmedWidth)) + } + } + } + + // Update available + content := "" + if isUpdateAvailable { + content = fmt.Sprintf("%s", updateRow) + } + + // Support URL + if issueRow != "" { + if content != "" { + content = fmt.Sprintf("%s\n%s", content, issueRow) + } else { + content = fmt.Sprintf("%s", issueRow) + } + } + + // Source Code URL + if sourceRow != "" { + if content != "" { + content = fmt.Sprintf("%s\n%s", content, sourceRow) + } else { + content = fmt.Sprintf("%s", sourceRow) + } + } + + // Deprecation + if removalDate != "" { + if content != "" { + content = fmt.Sprintf("%s\n%s", content, removalDateRow) + } else { + content = fmt.Sprintf("%s", removalDateRow) + } + + if deprecateNotes != "" { + if content != "" { + content = fmt.Sprintf("%s\n%s", content, deprecateNotesRow) + } else { + content = fmt.Sprintf("%s", deprecateNotesRow) + } + } + } + + return content +} + +// PrintRunningStepHeader ... +func PrintRunningStepHeader(stepInfo stepmanModels.StepInfoModel, step stepmanModels.StepModel, idx int) { + sep := fmt.Sprintf("+%s+", strings.Repeat("-", stepRunSummaryBoxWidthInChars-2)) + + fmt.Println(sep) + fmt.Println(getRunningStepHeaderMainSection(stepInfo, idx)) + fmt.Println(sep) + fmt.Println(getRunningStepHeaderSubSection(step, stepInfo)) + fmt.Println(sep) + fmt.Println("|" + strings.Repeat(" ", stepRunSummaryBoxWidthInChars-2) + "|") +} + +// PrintRunningStepFooter .. +func PrintRunningStepFooter(stepRunResult models.StepRunResultsModel, isLastStepInWorkflow bool) { + iconBoxWidth := len(" ") + timeBoxWidth := len(" time (s) ") + titleBoxWidth := stepRunSummaryBoxWidthInChars - 4 - iconBoxWidth - timeBoxWidth + sep := fmt.Sprintf("+%s+%s+%s+", strings.Repeat("-", iconBoxWidth), strings.Repeat("-", titleBoxWidth), strings.Repeat("-", timeBoxWidth)) + + fmt.Println("|" + strings.Repeat(" ", stepRunSummaryBoxWidthInChars-2) + "|") + + fmt.Println(sep) + fmt.Println(getRunningStepFooterMainSection(stepRunResult)) + fmt.Println(sep) + if stepRunResult.ErrorStr != "" || stepRunResult.StepInfo.GroupInfo.RemovalDate != "" || isUpdateAvailable(stepRunResult.StepInfo) { + footerSubSection := getRunningStepFooterSubSection(stepRunResult) + if footerSubSection != "" { + fmt.Println(footerSubSection) + fmt.Println(sep) + } + } + + if !isLastStepInWorkflow { + fmt.Println() + fmt.Println(strings.Repeat(" ", 42) + "▼") + fmt.Println() + } +} + +// PrintRunningWorkflow ... +func PrintRunningWorkflow(title string) { + fmt.Println() + log.Printf("%s %s", colorstring.Blue("Switching to workflow:"), title) + fmt.Println() +} + +// PrintSummary ... +func PrintSummary(buildRunResults models.BuildRunResultsModel) { + iconBoxWidth := len(" ") + timeBoxWidth := len(" time (s) ") + titleBoxWidth := stepRunSummaryBoxWidthInChars - 4 - iconBoxWidth - timeBoxWidth + + fmt.Println() + fmt.Println() + fmt.Printf("+%s+\n", strings.Repeat("-", stepRunSummaryBoxWidthInChars-2)) + whitespaceWidth := (stepRunSummaryBoxWidthInChars - 2 - len("bitrise summary ")) / 2 + fmt.Printf("|%sbitrise summary %s|\n", strings.Repeat(" ", whitespaceWidth), strings.Repeat(" ", whitespaceWidth)) + fmt.Printf("+%s+%s+%s+\n", strings.Repeat("-", iconBoxWidth), strings.Repeat("-", titleBoxWidth), strings.Repeat("-", timeBoxWidth)) + + whitespaceWidth = stepRunSummaryBoxWidthInChars - len("| | title") - len("| time (s) |") + fmt.Printf("| | title%s| time (s) |\n", strings.Repeat(" ", whitespaceWidth)) + fmt.Printf("+%s+%s+%s+\n", strings.Repeat("-", iconBoxWidth), strings.Repeat("-", titleBoxWidth), strings.Repeat("-", timeBoxWidth)) + + orderedResults := buildRunResults.OrderedResults() + tmpTime := time.Time{} + for _, stepRunResult := range orderedResults { + tmpTime = tmpTime.Add(stepRunResult.RunTime) + fmt.Println(getRunningStepFooterMainSection(stepRunResult)) + fmt.Printf("+%s+%s+%s+\n", strings.Repeat("-", iconBoxWidth), strings.Repeat("-", titleBoxWidth), strings.Repeat("-", timeBoxWidth)) + if stepRunResult.ErrorStr != "" || stepRunResult.StepInfo.GroupInfo.RemovalDate != "" || isUpdateAvailable(stepRunResult.StepInfo) { + footerSubSection := getRunningStepFooterSubSection(stepRunResult) + if footerSubSection != "" { + fmt.Println(footerSubSection) + fmt.Printf("+%s+%s+%s+\n", strings.Repeat("-", iconBoxWidth), strings.Repeat("-", titleBoxWidth), strings.Repeat("-", timeBoxWidth)) + } + } + } + runtime := tmpTime.Sub(time.Time{}) + + runTimeStr, err := FormattedSecondsToMax8Chars(runtime) + if err != nil { + log.Errorf("Failed to format time, error: %s", err) + runTimeStr = "999+ hour" + } + + whitespaceWidth = stepRunSummaryBoxWidthInChars - len(fmt.Sprintf("| Total runtime: %s|", runTimeStr)) + if whitespaceWidth < 0 { + log.Errorf("Invalid time box size for RunTime: %#v", runtime) + whitespaceWidth = 0 + } + + fmt.Printf("| Total runtime: %s%s|\n", runTimeStr, strings.Repeat(" ", whitespaceWidth)) + fmt.Printf("+%s+\n", strings.Repeat("-", stepRunSummaryBoxWidthInChars-2)) + + fmt.Println() +} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/print_test.go b/vendor/github.com/bitrise-io/bitrise/bitrise/print_test.go new file mode 100644 index 00000000..6ac5001b --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/bitrise/print_test.go @@ -0,0 +1,389 @@ +package bitrise + +import ( + "strings" + "testing" + "time" + + "github.com/bitrise-io/bitrise/models" + "github.com/bitrise-io/go-utils/pointers" + stepmanModels "github.com/bitrise-io/stepman/models" + "github.com/stretchr/testify/require" +) + +const longStr = "This is a very long string, this is a very long string, " + + "this is a very long string, this is a very long string," + + "this is a very long string, this is a very long string." + +func TestIsUpdateAvailable(t *testing.T) { + t.Log("simple compare versions - ture") + { + stepInfo1 := stepmanModels.StepInfoModel{ + Version: "1.0.0", + LatestVersion: "1.1.0", + } + + require.Equal(t, true, isUpdateAvailable(stepInfo1)) + } + + t.Log("simple compare versions - false") + { + stepInfo1 := stepmanModels.StepInfoModel{ + Version: "1.0.0", + LatestVersion: "1.0.0", + } + + require.Equal(t, false, isUpdateAvailable(stepInfo1)) + } + + t.Log("issue - no latest - false") + { + stepInfo1 := stepmanModels.StepInfoModel{ + Version: "1.0.0", + LatestVersion: "", + } + + require.Equal(t, false, isUpdateAvailable(stepInfo1)) + } + + t.Log("issue - no current - false") + { + stepInfo1 := stepmanModels.StepInfoModel{ + Version: "", + LatestVersion: "1.0.0", + } + + require.Equal(t, false, isUpdateAvailable(stepInfo1)) + } +} + +func TestGetTrimmedStepName(t *testing.T) { + t.Log("successful step") + { + stepInfo := stepmanModels.StepInfoModel{ + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(longStr), + }, + Version: longStr, + } + + result := models.StepRunResultsModel{ + StepInfo: stepInfo, + Status: models.StepRunStatusCodeSuccess, + Idx: 0, + RunTime: 10000000, + ErrorStr: longStr, + ExitCode: 1, + } + + actual := getTrimmedStepName(result) + expected := "This is a very long string, this is a very long string, thi..." + require.Equal(t, expected, actual) + } + + t.Log("failed step") + { + stepInfo := stepmanModels.StepInfoModel{ + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(""), + }, + Version: longStr, + } + + result := models.StepRunResultsModel{ + StepInfo: stepInfo, + Status: models.StepRunStatusCodeSuccess, + Idx: 0, + RunTime: 0, + ErrorStr: "", + ExitCode: 0, + } + + actual := getTrimmedStepName(result) + expected := "" + require.Equal(t, expected, actual) + } +} + +func TestGetRunningStepHeaderMainSection(t *testing.T) { + stepInfo := stepmanModels.StepInfoModel{ + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(longStr), + }, + Version: longStr, + } + + actual := getRunningStepHeaderMainSection(stepInfo, 0) + expected := "| (0) This is a very long string, this is a very long string, this is a ver... |" + require.Equal(t, expected, actual) +} + +func TestGetRunningStepHeaderSubSection(t *testing.T) { + stepInfo := stepmanModels.StepInfoModel{ + ID: longStr, + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(longStr), + }, + Version: longStr, + } + + actual := getRunningStepHeaderSubSection(stepmanModels.StepModel{}, stepInfo) + require.NotEqual(t, "", actual) +} + +func TestGetRunningStepFooterMainSection(t *testing.T) { + t.Log("failed step") + { + stepInfo := stepmanModels.StepInfoModel{ + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(longStr), + }, + Version: longStr, + } + + result := models.StepRunResultsModel{ + StepInfo: stepInfo, + Status: models.StepRunStatusCodeFailed, + Idx: 0, + RunTime: 10000000, + ErrorStr: longStr, + ExitCode: 1, + } + + actual := getRunningStepFooterMainSection(result) + expected := "| \x1b[31;1mx\x1b[0m | \x1b[31;1mThis is a very long string, this is a very l... (exit code: 1)\x1b[0m| 0.01 sec |" + require.Equal(t, expected, actual) + } + + t.Log("successful step") + { + stepInfo := stepmanModels.StepInfoModel{ + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(""), + }, + Version: longStr, + } + result := models.StepRunResultsModel{ + StepInfo: stepInfo, + Status: models.StepRunStatusCodeSuccess, + Idx: 0, + RunTime: 0, + ErrorStr: "", + ExitCode: 0, + } + + actual := getRunningStepFooterMainSection(result) + expected := "| \x1b[32;1m✓\x1b[0m | \x1b[32;1m\x1b[0m | 0.00 sec |" + require.Equal(t, expected, actual) + } + + t.Log("long Runtime") + { + stepInfo := stepmanModels.StepInfoModel{ + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(""), + }, + Version: longStr, + } + result := models.StepRunResultsModel{ + StepInfo: stepInfo, + Status: models.StepRunStatusCodeSuccess, + Idx: 0, + RunTime: 100 * 1000 * 1e9, // 100 * 1000 * 10^9 nanosec = 100 000 sec + ErrorStr: "", + ExitCode: 0, + } + + actual := getRunningStepFooterMainSection(result) + expected := "| \x1b[32;1m✓\x1b[0m | \x1b[32;1m\x1b[0m | 28 hour |" + require.Equal(t, expected, actual) + } + + t.Log("long Runtime") + { + stepInfo := stepmanModels.StepInfoModel{ + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(""), + }, + Version: longStr, + } + result := models.StepRunResultsModel{ + StepInfo: stepInfo, + Status: models.StepRunStatusCodeSuccess, + Idx: 0, + RunTime: hourToDuration(1000), + ErrorStr: "", + ExitCode: 0, + } + + actual := getRunningStepFooterMainSection(result) + expected := "| \x1b[32;1m✓\x1b[0m | \x1b[32;1m\x1b[0m | 999+ hour|" + require.Equal(t, expected, actual) + } +} + +func TestGetDeprecateNotesRows(t *testing.T) { + notes := "Removal notes: " + longStr + actual := getDeprecateNotesRows(notes) + expected := "| \x1b[31;1mRemoval notes:\x1b[0m This is a very long string, this is a very long string, this |" + "\n" + + "| is a very long string, this is a very long string,this is a very long |" + "\n" + + "| string, this is a very long string. |" + require.Equal(t, expected, actual) +} + +func TestGetRunningStepFooterSubSection(t *testing.T) { + t.Log("Update available, no support_url, no source_code_url") + { + stepInfo := stepmanModels.StepInfoModel{ + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(longStr), + }, + Version: "1.0.0", + LatestVersion: "1.1.0", + } + + result := models.StepRunResultsModel{ + StepInfo: stepInfo, + Status: models.StepRunStatusCodeSuccess, + Idx: 0, + RunTime: 10000000, + ErrorStr: longStr, + ExitCode: 1, + } + + actual := getRunningStepFooterSubSection(result) + expected := "| Update available: 1.0.0 -> 1.1.0 |" + "\n" + + "| Issue tracker: \x1b[33;1mNot provided\x1b[0m |" + "\n" + + "| Source: \x1b[33;1mNot provided\x1b[0m |" + require.Equal(t, expected, actual) + } + + t.Log("support url row length's chardiff = 0") + { + paddingCharCnt := 4 + placeholderCharCnt := len("Issue tracker: ") + supportURLCharCnt := stepRunSummaryBoxWidthInChars - paddingCharCnt - placeholderCharCnt + supportURL := strings.Repeat("a", supportURLCharCnt) + + // supportURL := + stepInfo := stepmanModels.StepInfoModel{ + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(longStr), + SupportURL: pointers.NewStringPtr(supportURL), + }, + Version: "1.0.0", + LatestVersion: "1.0.0", + } + + result := models.StepRunResultsModel{ + StepInfo: stepInfo, + Status: models.StepRunStatusCodeSuccess, + Idx: 0, + RunTime: 10000000, + ErrorStr: longStr, + ExitCode: 1, + } + + actual := getRunningStepFooterSubSection(result) + expected := "| Issue tracker: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |" + "\n" + + "| Source: \x1b[33;1mNot provided\x1b[0m |" + require.Equal(t, expected, actual) + } +} + +func TestPrintRunningWorkflow(t *testing.T) { + PrintRunningWorkflow(longStr) +} + +func TestPrintRunningStepHeader(t *testing.T) { + stepInfo := stepmanModels.StepInfoModel{ + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(""), + }, + Version: "", + } + step := stepmanModels.StepModel{} + PrintRunningStepHeader(stepInfo, step, 0) + + stepInfo.Step.Title = pointers.NewStringPtr(longStr) + stepInfo.Version = "" + PrintRunningStepHeader(stepInfo, step, 0) + + stepInfo.Step.Title = pointers.NewStringPtr("") + stepInfo.Version = longStr + PrintRunningStepHeader(stepInfo, step, 0) + + stepInfo.Step.Title = pointers.NewStringPtr(longStr) + stepInfo.Version = longStr + PrintRunningStepHeader(stepInfo, step, 0) +} + +func TestPrintRunningStepFooter(t *testing.T) { + stepInfo := stepmanModels.StepInfoModel{ + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(longStr), + }, + Version: longStr, + } + + result := models.StepRunResultsModel{ + StepInfo: stepInfo, + Status: models.StepRunStatusCodeSuccess, + Idx: 0, + RunTime: 10000000, + ErrorStr: longStr, + ExitCode: 1, + } + PrintRunningStepFooter(result, true) + PrintRunningStepFooter(result, false) + + stepInfo.Step.Title = pointers.NewStringPtr("") + result = models.StepRunResultsModel{ + StepInfo: stepInfo, + Status: models.StepRunStatusCodeSuccess, + Idx: 0, + RunTime: 0, + ErrorStr: "", + ExitCode: 0, + } + PrintRunningStepFooter(result, true) + PrintRunningStepFooter(result, false) +} + +func TestPrintSummary(t *testing.T) { + PrintSummary(models.BuildRunResultsModel{}) + + stepInfo := stepmanModels.StepInfoModel{ + Step: stepmanModels.StepModel{ + Title: pointers.NewStringPtr(longStr), + }, + Version: longStr, + } + + result1 := models.StepRunResultsModel{ + StepInfo: stepInfo, + Status: models.StepRunStatusCodeSuccess, + Idx: 0, + RunTime: 10000000, + ErrorStr: longStr, + ExitCode: 1, + } + + stepInfo.Step.Title = pointers.NewStringPtr("") + result2 := models.StepRunResultsModel{ + StepInfo: stepInfo, + Status: models.StepRunStatusCodeSuccess, + Idx: 0, + RunTime: 0, + ErrorStr: "", + ExitCode: 0, + } + + buildResults := models.BuildRunResultsModel{ + StartTime: time.Now(), + StepmanUpdates: map[string]int{}, + SuccessSteps: []models.StepRunResultsModel{result1, result2}, + } + + PrintSummary(buildResults) +} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/setup.go b/vendor/github.com/bitrise-io/bitrise/bitrise/setup.go new file mode 100644 index 00000000..d1bad775 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/bitrise/setup.go @@ -0,0 +1,184 @@ +package bitrise + +import ( + "errors" + "fmt" + "runtime" + + "github.com/bitrise-io/bitrise/configs" + "github.com/bitrise-io/bitrise/plugins" + "github.com/bitrise-io/bitrise/toolkits" + "github.com/bitrise-io/bitrise/version" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/log" +) + +const ( + minEnvmanVersion = "1.1.6" + minStepmanVersion = "0.9.33" +) + +// PluginDependency .. +type PluginDependency struct { + Source string + MinVersion string +} + +// PluginDependencyMap ... +var PluginDependencyMap = map[string]PluginDependency{ + "init": PluginDependency{ + Source: "https://github.com/bitrise-core/bitrise-plugins-init.git", + MinVersion: "0.9.7", + }, + "step": PluginDependency{ + Source: "https://github.com/bitrise-core/bitrise-plugins-step.git", + MinVersion: "0.9.4", + }, + "workflow-editor": PluginDependency{ + Source: "https://github.com/bitrise-io/bitrise-workflow-editor.git", + MinVersion: "1.0.13", + }, + "analytics": PluginDependency{ + Source: "https://github.com/bitrise-core/bitrise-plugins-analytics.git", + MinVersion: "0.9.10", + }, +} + +// RunSetupIfNeeded ... +func RunSetupIfNeeded(appVersion string, isFullSetupMode bool) error { + if !configs.CheckIsSetupWasDoneForVersion(version.VERSION) { + log.Warnf(colorstring.Yellow("Setup was not performed for this version of bitrise, doing it now...")) + return RunSetup(version.VERSION, false, false) + } + return nil +} + +// RunSetup ... +func RunSetup(appVersion string, isFullSetupMode bool, isCleanSetupMode bool) error { + log.Infof("Setup") + log.Printf("Full setup: %v", isFullSetupMode) + log.Printf("Clean setup: %v", isCleanSetupMode) + log.Printf("Detected OS: %s", runtime.GOOS) + + if isCleanSetupMode { + if err := configs.DeleteBitriseConfigDir(); err != nil { + return err + } + + if err := configs.InitPaths(); err != nil { + return err + } + + if err := plugins.InitPaths(); err != nil { + return err + } + } + + if err := doSetupBitriseCoreTools(); err != nil { + return fmt.Errorf("Failed to do common/platform independent setup, error: %s", err) + } + + switch runtime.GOOS { + case "darwin": + if err := doSetupOnOSX(isFullSetupMode); err != nil { + return fmt.Errorf("Failed to do MacOS specific setup, error: %s", err) + } + case "linux": + default: + return errors.New("unsupported platform :(") + } + + if err := doSetupPlugins(); err != nil { + return fmt.Errorf("Failed to do Plugins setup, error: %s", err) + } + + if err := doSetupToolkits(); err != nil { + return fmt.Errorf("Failed to do Toolkits setup, error: %s", err) + } + + fmt.Println() + log.Donef("All the required tools are installed! We're ready to rock!!") + + if err := configs.SaveSetupSuccessForVersion(appVersion); err != nil { + return fmt.Errorf("failed to save setup-success into config file, error: %s", err) + } + + return nil +} + +func doSetupToolkits() error { + fmt.Println() + log.Infof("Checking Bitrise Toolkits...") + + coreToolkits := toolkits.AllSupportedToolkits() + + for _, aCoreTK := range coreToolkits { + toolkitName := aCoreTK.ToolkitName() + isInstallRequired, checkResult, err := aCoreTK.Check() + if err != nil { + return fmt.Errorf("Failed to perform toolkit check (%s), error: %s", toolkitName, err) + } + + if isInstallRequired { + log.Warnf("No installed/suitable %s found, installing toolkit ...", toolkitName) + if err := aCoreTK.Install(); err != nil { + return fmt.Errorf("Failed to install toolkit (%s), error: %s", toolkitName, err) + } + + isInstallRequired, checkResult, err = aCoreTK.Check() + if err != nil { + return fmt.Errorf("Failed to perform toolkit check (%s), error: %s", toolkitName, err) + } + } + if isInstallRequired { + return fmt.Errorf("Toolkit (%s) still reports that it isn't (properly) installed", toolkitName) + } + + log.Printf("%s %s (%s): %s", colorstring.Green("[OK]"), toolkitName, checkResult.Version, checkResult.Path) + } + + return nil +} + +func doSetupPlugins() error { + fmt.Println() + log.Infof("Checking Bitrise Plugins...") + + for pluginName, pluginDependency := range PluginDependencyMap { + if err := CheckIsPluginInstalled(pluginName, pluginDependency); err != nil { + return fmt.Errorf("Plugin (%s) failed to install: %s", pluginName, err) + } + } + + return nil +} + +func doSetupBitriseCoreTools() error { + fmt.Println() + log.Infof("Checking Bitrise Core tools...") + + if err := CheckIsEnvmanInstalled(minEnvmanVersion); err != nil { + return fmt.Errorf("Envman failed to install: %s", err) + } + + if err := CheckIsStepmanInstalled(minStepmanVersion); err != nil { + return fmt.Errorf("Stepman failed to install: %s", err) + } + + return nil +} + +func doSetupOnOSX(isMinimalSetupMode bool) error { + fmt.Println() + log.Infof("Doing OS X specific setup") + log.Printf("Checking required tools...") + + if err := CheckIsHomebrewInstalled(isMinimalSetupMode); err != nil { + return errors.New(fmt.Sprint("Homebrew not installed or has some issues. Please fix these before calling setup again. Err:", err)) + } + + if err := PrintInstalledXcodeInfos(); err != nil { + return errors.New(fmt.Sprint("Failed to detect installed Xcode and Xcode Command Line Tools infos. Err:", err)) + } + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/template_util.go b/vendor/github.com/bitrise-io/bitrise/bitrise/template_util.go new file mode 100644 index 00000000..8bb34ecc --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/bitrise/template_util.go @@ -0,0 +1,90 @@ +package bitrise + +import ( + "bytes" + "errors" + "os" + "strings" + "text/template" + + "github.com/bitrise-io/bitrise/models" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/goinp/goinp" +) + +// TemplateDataModel ... +type TemplateDataModel struct { + BuildResults models.BuildRunResultsModel + IsBuildFailed bool + IsBuildOK bool + IsCI bool + IsPR bool + PullRequestID string +} + +func getEnv(key string, envList envmanModels.EnvsJSONListModel) string { + if len(envList) > 0 { + for aKey, value := range envList { + if aKey == key { + return value + } + } + } + return os.Getenv(key) +} + +func createTemplateDataModel(isCI, isPR bool, buildResults models.BuildRunResultsModel) TemplateDataModel { + isBuildOK := !buildResults.IsBuildFailed() + + return TemplateDataModel{ + BuildResults: buildResults, + IsBuildFailed: !isBuildOK, + IsBuildOK: isBuildOK, + IsCI: isCI, + IsPR: isPR, + } +} + +// EvaluateTemplateToString ... +func EvaluateTemplateToString(expStr string, isCI, isPR bool, buildResults models.BuildRunResultsModel, envList envmanModels.EnvsJSONListModel) (string, error) { + if expStr == "" { + return "", errors.New("EvaluateTemplateToBool: Invalid, empty input: expStr") + } + + if !strings.Contains(expStr, "{{") { + expStr = "{{" + expStr + "}}" + } + + var templateFuncMap = template.FuncMap{ + "getenv": func(key string) string { + return getEnv(key, envList) + }, + "enveq": func(key, expectedValue string) bool { + return (getEnv(key, envList) == expectedValue) + }, + } + + tmpl := template.New("EvaluateTemplateToBool").Funcs(templateFuncMap) + tmpl, err := tmpl.Parse(expStr) + if err != nil { + return "", err + } + + templateData := createTemplateDataModel(isCI, isPR, buildResults) + var resBuffer bytes.Buffer + if err := tmpl.Execute(&resBuffer, templateData); err != nil { + return "", err + } + + return resBuffer.String(), nil +} + +// EvaluateTemplateToBool ... +func EvaluateTemplateToBool(expStr string, isCI, isPR bool, buildResults models.BuildRunResultsModel, envList envmanModels.EnvsJSONListModel) (bool, error) { + resString, err := EvaluateTemplateToString(expStr, isCI, isPR, buildResults, envList) + if err != nil { + return false, err + } + + return goinp.ParseBool(resString) +} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/template_utils_test.go b/vendor/github.com/bitrise-io/bitrise/bitrise/template_utils_test.go new file mode 100644 index 00000000..0762cabe --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/bitrise/template_utils_test.go @@ -0,0 +1,408 @@ +package bitrise + +import ( + "os" + "strings" + "testing" + + "github.com/bitrise-io/bitrise/configs" + "github.com/bitrise-io/bitrise/models" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/stretchr/testify/require" +) + +func TestEvaluateStepTemplateToBool(t *testing.T) { + buildRes := models.BuildRunResultsModel{} + + propTempCont := `{{eq 1 1}}` + isYes, err := EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, isYes) + + propTempCont = `{{eq 1 2}}` + isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, false, isYes) + + isYes, err = EvaluateTemplateToBool("", false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.NotEqual(t, nil, err) + + // these all should be `true` + for _, expStr := range []string{ + "true", + "1", + `"yes"`, + `"YES"`, + `"Yes"`, + `"YeS"`, + `"TRUE"`, + `"True"`, + `"TrUe"`, + `"y"`, + } { + isYes, err = EvaluateTemplateToBool(expStr, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.NoError(t, err) + require.Equal(t, true, isYes) + } + + // these all should be `true` + for _, expStr := range []string{ + "false", + "0", + `"no"`, + `"NO"`, + `"No"`, + `"FALSE"`, + `"False"`, + `"FaLse"`, + `"n"`, + } { + isYes, err = EvaluateTemplateToBool(expStr, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.NoError(t, err) + require.Equal(t, false, isYes) + } +} + +func TestRegisteredFunctions(t *testing.T) { + defer func() { + // env cleanup + require.Equal(t, nil, os.Unsetenv("TEST_KEY")) + }() + + buildRes := models.BuildRunResultsModel{} + + propTempCont := `{{getenv "TEST_KEY" | eq "Test value"}}` + require.Equal(t, nil, os.Setenv("TEST_KEY", "Test value")) + isYes, err := EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, isYes) + + propTempCont = `{{getenv "TEST_KEY" | eq "A different value"}}` + require.Equal(t, nil, os.Setenv("TEST_KEY", "Test value")) + isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, false, isYes) + + propTempCont = `{{enveq "TEST_KEY" "enveq value"}}` + require.Equal(t, nil, os.Setenv("TEST_KEY", "enveq value")) + isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, isYes) + + propTempCont = `{{enveq "TEST_KEY" "different enveq value"}}` + require.Equal(t, nil, os.Setenv("TEST_KEY", "enveq value")) + isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, false, isYes) +} + +func TestCIFlagsAndEnvs(t *testing.T) { + defer func() { + // env cleanup + if err := os.Unsetenv(configs.CIModeEnvKey); err != nil { + t.Error("Failed to unset environment: ", err) + } + }() + + buildRes := models.BuildRunResultsModel{} + + propTempCont := `{{.IsCI}}` + t.Log("IsCI=true; propTempCont: ", propTempCont) + if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { + t.Fatal("Failed to set test env!") + } + isYes, err := EvaluateTemplateToBool(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if !isYes { + t.Fatal("Invalid result") + } + + propTempCont = `{{.IsCI}}` + t.Log("IsCI=fase; propTempCont: ", propTempCont) + if err := os.Setenv(configs.CIModeEnvKey, "false"); err != nil { + t.Fatal("Failed to set test env!") + } + isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if isYes { + t.Fatal("Invalid result") + } + + propTempCont = `{{.IsCI}}` + t.Log("[unset] IsCI; propTempCont: ", propTempCont) + if err := os.Unsetenv(configs.CIModeEnvKey); err != nil { + t.Fatal("Failed to set test env!") + } + isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if isYes { + t.Fatal("Invalid result") + } + + propTempCont = `$.IsCI` + t.Log("IsCI=true; short with $; propTempCont: ", propTempCont) + if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { + t.Fatal("Failed to set test env!") + } + isYes, err = EvaluateTemplateToBool(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if !isYes { + t.Fatal("Invalid result") + } + + propTempCont = `.IsCI` + t.Log("IsCI=true; short, no $; propTempCont: ", propTempCont) + if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { + t.Fatal("Failed to set test env!") + } + isYes, err = EvaluateTemplateToBool(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if !isYes { + t.Fatal("Invalid result") + } + + propTempCont = `not .IsCI` + t.Log("IsCI=true; NOT; propTempCont: ", propTempCont) + if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { + t.Fatal("Failed to set test env! : ", err) + } + isYes, err = EvaluateTemplateToBool(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if isYes { + t.Fatal("Invalid result") + } + + propTempCont = `not .IsCI` + t.Log("IsCI=false; NOT; propTempCont: ", propTempCont) + if err := os.Setenv(configs.CIModeEnvKey, "false"); err != nil { + t.Fatal("Failed to set test env! : ", err) + } + isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if !isYes { + t.Fatal("Invalid result") + } +} + +func TestPullRequestFlagsAndEnvs(t *testing.T) { + defer func() { + // env cleanup + if err := os.Unsetenv(configs.PullRequestIDEnvKey); err != nil { + t.Error("Failed to unset environment: ", err) + } + }() + + // env cleanup + if err := os.Unsetenv(configs.PullRequestIDEnvKey); err != nil { + t.Error("Failed to unset environment: ", err) + } + + buildRes := models.BuildRunResultsModel{} + + propTempCont := `{{.IsPR}}` + t.Log("IsPR [undefined]; propTempCont: ", propTempCont) + isYes, err := EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if isYes { + t.Fatal("Invalid result") + } + + propTempCont = `{{.IsPR}}` + t.Log("IsPR=true; propTempCont: ", propTempCont) + if err := os.Setenv(configs.PullRequestIDEnvKey, "123"); err != nil { + t.Fatal("Failed to set test env! : ", err) + } + isYes, err = EvaluateTemplateToBool(propTempCont, false, true, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if !isYes { + t.Fatal("Invalid result") + } +} + +func TestPullRequestAndCIFlagsAndEnvs(t *testing.T) { + defer func() { + // env cleanup + if err := os.Unsetenv(configs.PullRequestIDEnvKey); err != nil { + t.Error("Failed to unset environment: ", err) + } + if err := os.Unsetenv(configs.CIModeEnvKey); err != nil { + t.Error("Failed to unset environment: ", err) + } + }() + + buildRes := models.BuildRunResultsModel{} + + propTempCont := `not .IsPR | and (not .IsCI)` + t.Log("IsPR [undefined] & IsCI [undefined]; propTempCont: ", propTempCont) + isYes, err := EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if !isYes { + t.Fatal("Invalid result") + } + + propTempCont = `not .IsPR | and .IsCI` + t.Log("IsPR [undefined] & IsCI [undefined]; propTempCont: ", propTempCont) + isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if isYes { + t.Fatal("Invalid result") + } + + propTempCont = `not .IsPR | and .IsCI` + t.Log("IsPR [undefined] & IsCI=true; propTempCont: ", propTempCont) + if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { + t.Fatal("Failed to set test env! : ", err) + } + isYes, err = EvaluateTemplateToBool(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if !isYes { + t.Fatal("Invalid result") + } + + propTempCont = `.IsPR | and .IsCI` + t.Log("IsPR [undefined] & IsCI=true; propTempCont: ", propTempCont) + if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { + t.Fatal("Failed to set test env! : ", err) + } + isYes, err = EvaluateTemplateToBool(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if isYes { + t.Fatal("Invalid result") + } + + propTempCont = `.IsPR | and .IsCI` + t.Log("IsPR=true & IsCI=true; propTempCont: ", propTempCont) + if err := os.Setenv(configs.PullRequestIDEnvKey, "123"); err != nil { + t.Fatal("Failed to set test env! : ", err) + } + if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { + t.Fatal("Failed to set test env! : ", err) + } + isYes, err = EvaluateTemplateToBool(propTempCont, true, true, buildRes, envmanModels.EnvsJSONListModel{}) + if err != nil { + t.Fatal("Unexpected error:", err) + } + if !isYes { + t.Fatal("Invalid result") + } +} + +func TestEvaluateTemplateToString(t *testing.T) { + buildRes := models.BuildRunResultsModel{} + + propTempCont := "" + value, err := EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.NotEqual(t, nil, err) + + propTempCont = ` +{{ if .IsCI }} +value in case of IsCI +{{ end }} +` + value, err = EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, strings.Contains(value, "value in case of IsCI")) + + propTempCont = ` +{{ if .IsCI }} +value in case of IsCI +{{ else }} +value in case of not IsCI +{{ end }} +` + value, err = EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, strings.Contains(value, "value in case of IsCI")) + + value, err = EvaluateTemplateToString(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, strings.Contains(value, "value in case of not IsCI")) + + propTempCont = ` +This is +{{ if .IsCI }} +value in case of IsCI +{{ end }} +` + value, err = EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "value in case of IsCI"))) + + value, err = EvaluateTemplateToString(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, (strings.Contains(value, "This is"))) + + propTempCont = ` +This is +{{ if .IsCI }} +value in case of IsCI +{{ end }} +after end +` + value, err = EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "value in case of IsCI"))) + + value, err = EvaluateTemplateToString(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "after end"))) + + propTempCont = ` +This is +{{ if .IsCI }} +value in case of IsCI +{{ else }} +value in case of not IsCI +{{ end }} +` + value, err = EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "value in case of IsCI"))) + + value, err = EvaluateTemplateToString(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "value in case of not IsCI"))) + + propTempCont = ` +This is +{{ if .IsCI }} +value in case of IsCI +{{ else }} +value in case of not IsCI +{{ end }} +mode +` + value, err = EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "value in case of IsCI") && strings.Contains(value, "mode"))) + + value, err = EvaluateTemplateToString(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) + require.Equal(t, nil, err) + require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "value in case of not IsCI") && strings.Contains(value, "mode"))) +} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/util.go b/vendor/github.com/bitrise-io/bitrise/bitrise/util.go new file mode 100644 index 00000000..596b0a2f --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/bitrise/util.go @@ -0,0 +1,675 @@ +package bitrise + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "gopkg.in/yaml.v2" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/bitrise/configs" + "github.com/bitrise-io/bitrise/models" + "github.com/bitrise-io/bitrise/tools" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/command/git" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + stepmanModels "github.com/bitrise-io/stepman/models" +) + +// InventoryModelFromYAMLBytes ... +func InventoryModelFromYAMLBytes(inventoryBytes []byte) (inventory envmanModels.EnvsSerializeModel, err error) { + if err = yaml.Unmarshal(inventoryBytes, &inventory); err != nil { + return + } + + for _, env := range inventory.Envs { + if err := env.Normalize(); err != nil { + return envmanModels.EnvsSerializeModel{}, fmt.Errorf("Failed to normalize bitrise inventory, error: %s", err) + } + if err := env.FillMissingDefaults(); err != nil { + return envmanModels.EnvsSerializeModel{}, fmt.Errorf("Failed to fill bitrise inventory, error: %s", err) + } + if err := env.Validate(); err != nil { + return envmanModels.EnvsSerializeModel{}, fmt.Errorf("Failed to validate bitrise inventory, error: %s", err) + } + } + + return +} + +func searchEnvInSlice(searchForEnvKey string, searchIn []envmanModels.EnvironmentItemModel) (envmanModels.EnvironmentItemModel, int, error) { + for idx, env := range searchIn { + key, _, err := env.GetKeyValuePair() + if err != nil { + return envmanModels.EnvironmentItemModel{}, -1, err + } + + if key == searchForEnvKey { + return env, idx, nil + } + } + return envmanModels.EnvironmentItemModel{}, -1, nil +} + +// ApplyOutputAliases ... +func ApplyOutputAliases(onEnvs, basedOnEnvs []envmanModels.EnvironmentItemModel) ([]envmanModels.EnvironmentItemModel, error) { + for _, basedOnEnv := range basedOnEnvs { + envKey, envKeyAlias, err := basedOnEnv.GetKeyValuePair() + if err != nil { + return []envmanModels.EnvironmentItemModel{}, err + } + + envToAlias, idx, err := searchEnvInSlice(envKey, onEnvs) + if err != nil { + return []envmanModels.EnvironmentItemModel{}, err + } + + if idx > -1 && envKeyAlias != "" { + _, origValue, err := envToAlias.GetKeyValuePair() + if err != nil { + return []envmanModels.EnvironmentItemModel{}, err + } + + origOptions, err := envToAlias.GetOptions() + if err != nil { + return []envmanModels.EnvironmentItemModel{}, err + } + + onEnvs[idx] = envmanModels.EnvironmentItemModel{ + envKeyAlias: origValue, + envmanModels.OptionsKey: origOptions, + } + } + } + return onEnvs, nil +} + +// CollectEnvironmentsFromFile ... +func CollectEnvironmentsFromFile(pth string) ([]envmanModels.EnvironmentItemModel, error) { + bytes, err := fileutil.ReadBytesFromFile(pth) + if err != nil { + return []envmanModels.EnvironmentItemModel{}, err + } + + return CollectEnvironmentsFromFileContent(bytes) +} + +// CollectEnvironmentsFromFileContent ... +func CollectEnvironmentsFromFileContent(bytes []byte) ([]envmanModels.EnvironmentItemModel, error) { + var envstore envmanModels.EnvsSerializeModel + if err := yaml.Unmarshal(bytes, &envstore); err != nil { + return []envmanModels.EnvironmentItemModel{}, err + } + + for _, env := range envstore.Envs { + if err := env.Normalize(); err != nil { + return []envmanModels.EnvironmentItemModel{}, err + } + if err := env.FillMissingDefaults(); err != nil { + return []envmanModels.EnvironmentItemModel{}, err + } + if err := env.Validate(); err != nil { + return []envmanModels.EnvironmentItemModel{}, err + } + } + + return envstore.Envs, nil +} + +// ExportEnvironmentsList ... +func ExportEnvironmentsList(envsList []envmanModels.EnvironmentItemModel) error { + log.Debugln("[BITRISE_CLI] - Exporting environments:", envsList) + + for _, env := range envsList { + key, value, err := env.GetKeyValuePair() + if err != nil { + return err + } + + opts, err := env.GetOptions() + if err != nil { + return err + } + + isExpand := envmanModels.DefaultIsExpand + if opts.IsExpand != nil { + isExpand = *opts.IsExpand + } + + skipIfEmpty := envmanModels.DefaultSkipIfEmpty + if opts.SkipIfEmpty != nil { + skipIfEmpty = *opts.SkipIfEmpty + } + + if err := tools.EnvmanAdd(configs.InputEnvstorePath, key, value, isExpand, skipIfEmpty); err != nil { + log.Errorln("[BITRISE_CLI] - Failed to run envman add") + return err + } + } + return nil +} + +// CleanupStepWorkDir ... +func CleanupStepWorkDir() error { + stepYMLPth := filepath.Join(configs.BitriseWorkDirPath, "current_step.yml") + if err := command.RemoveFile(stepYMLPth); err != nil { + return errors.New(fmt.Sprint("Failed to remove step yml: ", err)) + } + + stepDir := configs.BitriseWorkStepsDirPath + if err := command.RemoveDir(stepDir); err != nil { + return errors.New(fmt.Sprint("Failed to remove step work dir: ", err)) + } + return nil +} + +// GetBuildFailedEnvironments ... +func GetBuildFailedEnvironments(failed bool) []string { + statusStr := "0" + if failed { + statusStr = "1" + } + + environments := []string{} + steplibBuildStatusEnv := "STEPLIB_BUILD_STATUS" + "=" + statusStr + environments = append(environments, steplibBuildStatusEnv) + + bitriseBuildStatusEnv := "BITRISE_BUILD_STATUS" + "=" + statusStr + environments = append(environments, bitriseBuildStatusEnv) + return environments +} + +// SetBuildFailedEnv ... +func SetBuildFailedEnv(failed bool) error { + statusStr := "0" + if failed { + statusStr = "1" + } + + if err := os.Setenv("STEPLIB_BUILD_STATUS", statusStr); err != nil { + return err + } + + if err := os.Setenv("BITRISE_BUILD_STATUS", statusStr); err != nil { + return err + } + return nil +} + +// FormattedSecondsToMax8Chars ... +func FormattedSecondsToMax8Chars(t time.Duration) (string, error) { + sec := t.Seconds() + min := t.Minutes() + hour := t.Hours() + + if sec < 1.0 { + // 0.999999 sec -> 0.99 sec + return fmt.Sprintf("%.2f sec", sec), nil // 8 + } else if sec < 10.0 { + // 9.99999 sec -> 9.99 sec + return fmt.Sprintf("%.2f sec", sec), nil // 8 + } else if sec < 600 { + // 599,999 sec -> 599 sec + return fmt.Sprintf("%.f sec", sec), nil // 7 + } else if min < 60 { + // 59,999 min -> 59.9 min + return fmt.Sprintf("%.1f min", min), nil // 8 + } else if hour < 10 { + // 9.999 hour -> 9.9 hour + return fmt.Sprintf("%.1f hour", hour), nil // 8 + } else if hour < 1000 { + // 999,999 hour -> 999 hour + return fmt.Sprintf("%.f hour", hour), nil // 8 + } + + return "", fmt.Errorf("time (%f hour) greater than max allowed (999 hour)", hour) +} + +// SaveConfigToFile ... +func SaveConfigToFile(pth string, bitriseConf models.BitriseDataModel) error { + contBytes, err := generateYAML(bitriseConf) + if err != nil { + return err + } + if err := fileutil.WriteBytesToFile(pth, contBytes); err != nil { + return err + } + return nil +} + +func generateYAML(v interface{}) ([]byte, error) { + bytes, err := yaml.Marshal(v) + if err != nil { + return []byte{}, err + } + return bytes, nil +} + +func normalizeValidateFillMissingDefaults(bitriseData *models.BitriseDataModel) ([]string, error) { + if err := bitriseData.Normalize(); err != nil { + return []string{}, err + } + warnings, err := bitriseData.Validate() + if err != nil { + return warnings, err + } + if err := bitriseData.FillMissingDefaults(); err != nil { + return warnings, err + } + return warnings, nil +} + +// ConfigModelFromYAMLBytes ... +func ConfigModelFromYAMLBytes(configBytes []byte) (bitriseData models.BitriseDataModel, warnings []string, err error) { + if err = yaml.Unmarshal(configBytes, &bitriseData); err != nil { + return + } + + warnings, err = normalizeValidateFillMissingDefaults(&bitriseData) + if err != nil { + return + } + + return +} + +// ConfigModelFromJSONBytes ... +func ConfigModelFromJSONBytes(configBytes []byte) (bitriseData models.BitriseDataModel, warnings []string, err error) { + if err = json.Unmarshal(configBytes, &bitriseData); err != nil { + return + } + warnings, err = normalizeValidateFillMissingDefaults(&bitriseData) + if err != nil { + return + } + + return +} + +// ReadBitriseConfig ... +func ReadBitriseConfig(pth string) (models.BitriseDataModel, []string, error) { + if isExists, err := pathutil.IsPathExists(pth); err != nil { + return models.BitriseDataModel{}, []string{}, err + } else if !isExists { + return models.BitriseDataModel{}, []string{}, fmt.Errorf("No file found at path: %s", pth) + } + + bytes, err := fileutil.ReadBytesFromFile(pth) + if err != nil { + return models.BitriseDataModel{}, []string{}, err + } + + if len(bytes) == 0 { + return models.BitriseDataModel{}, []string{}, errors.New("empty config") + } + + if strings.HasSuffix(pth, ".json") { + log.Debugln("=> Using JSON parser for: ", pth) + return ConfigModelFromJSONBytes(bytes) + } + + log.Debugln("=> Using YAML parser for: ", pth) + return ConfigModelFromYAMLBytes(bytes) +} + +// ReadSpecStep ... +func ReadSpecStep(pth string) (stepmanModels.StepModel, error) { + if isExists, err := pathutil.IsPathExists(pth); err != nil { + return stepmanModels.StepModel{}, err + } else if !isExists { + return stepmanModels.StepModel{}, fmt.Errorf("No file found at path: %s", pth) + } + + bytes, err := fileutil.ReadBytesFromFile(pth) + if err != nil { + return stepmanModels.StepModel{}, err + } + + var stepModel stepmanModels.StepModel + if err := yaml.Unmarshal(bytes, &stepModel); err != nil { + return stepmanModels.StepModel{}, err + } + + if err := stepModel.Normalize(); err != nil { + return stepmanModels.StepModel{}, err + } + + if err := stepModel.ValidateInputAndOutputEnvs(false); err != nil { + return stepmanModels.StepModel{}, err + } + + if err := stepModel.FillMissingDefaults(); err != nil { + return stepmanModels.StepModel{}, err + } + + return stepModel, nil +} + +func getInputByKey(inputs []envmanModels.EnvironmentItemModel, key string) (envmanModels.EnvironmentItemModel, error) { + for _, input := range inputs { + aKey, _, err := input.GetKeyValuePair() + if err != nil { + return envmanModels.EnvironmentItemModel{}, err + } + if aKey == key { + return input, nil + } + } + return envmanModels.EnvironmentItemModel{}, fmt.Errorf("No Environmnet found for key (%s)", key) +} + +func isStringSliceWithSameElements(s1, s2 []string) bool { + if len(s1) != len(s2) { + return false + } + + m := make(map[string]bool, len(s1)) + for _, s := range s1 { + m[s] = true + } + + for _, s := range s2 { + v, found := m[s] + if !found || !v { + return false + } + delete(m, s) + } + return len(m) == 0 +} + +func isDependecyEqual(d1, d2 stepmanModels.DependencyModel) bool { + return (d1.Manager == d2.Manager && d1.Name == d2.Name) +} + +func containsDependecy(m map[stepmanModels.DependencyModel]bool, d1 stepmanModels.DependencyModel) bool { + for d2 := range m { + if isDependecyEqual(d1, d2) { + return true + } + } + return false +} + +func isDependencySliceWithSameElements(s1, s2 []stepmanModels.DependencyModel) bool { + if len(s1) != len(s2) { + return false + } + + m := make(map[stepmanModels.DependencyModel]bool, len(s1)) + for _, s := range s1 { + m[s] = true + } + + for _, d := range s2 { + if containsDependecy(m, d) == false { + return false + } + delete(m, d) + } + return len(m) == 0 +} + +func removeStepDefaultsAndFillStepOutputs(stepListItem *models.StepListItemModel, defaultStepLibSource string) error { + // Create stepIDData + compositeStepIDStr, workflowStep, err := models.GetStepIDStepDataPair(*stepListItem) + if err != nil { + return err + } + stepIDData, err := models.CreateStepIDDataFromString(compositeStepIDStr, defaultStepLibSource) + if err != nil { + return err + } + + // Activate step - get step.yml + tempStepCloneDirPath, err := pathutil.NormalizedOSTempDirPath("step_clone") + if err != nil { + return err + } + tempStepYMLDirPath, err := pathutil.NormalizedOSTempDirPath("step_yml") + if err != nil { + return err + } + tempStepYMLFilePath := filepath.Join(tempStepYMLDirPath, "step.yml") + + if stepIDData.SteplibSource == "path" { + stepAbsLocalPth, err := pathutil.AbsPath(stepIDData.IDorURI) + if err != nil { + return err + } + if err := command.CopyFile(filepath.Join(stepAbsLocalPth, "step.yml"), tempStepYMLFilePath); err != nil { + return err + } + } else if stepIDData.SteplibSource == "git" { + if err := git.CloneTagOrBranch(stepIDData.IDorURI, tempStepCloneDirPath, stepIDData.Version); err != nil { + return err + } + if err := command.CopyFile(filepath.Join(tempStepCloneDirPath, "step.yml"), tempStepYMLFilePath); err != nil { + return err + } + } else if stepIDData.SteplibSource == "_" { + // Steplib independent steps are completly defined in workflow + tempStepYMLFilePath = "" + } else if stepIDData.SteplibSource != "" { + if err := tools.StepmanSetup(stepIDData.SteplibSource); err != nil { + return err + } + if err := tools.StepmanActivate(stepIDData.SteplibSource, stepIDData.IDorURI, stepIDData.Version, tempStepCloneDirPath, tempStepYMLFilePath); err != nil { + return err + } + } else { + return errors.New("Failed to fill step ouputs: unkown SteplibSource") + } + + // Fill outputs + if tempStepYMLFilePath != "" { + specStep, err := ReadSpecStep(tempStepYMLFilePath) + if err != nil { + return err + } + + if workflowStep.Title != nil && specStep.Title != nil && *workflowStep.Title == *specStep.Title { + workflowStep.Title = nil + } + if workflowStep.Description != nil && specStep.Description != nil && *workflowStep.Description == *specStep.Description { + workflowStep.Description = nil + } + if workflowStep.Summary != nil && specStep.Summary != nil && *workflowStep.Summary == *specStep.Summary { + workflowStep.Summary = nil + } + if workflowStep.Website != nil && specStep.Website != nil && *workflowStep.Website == *specStep.Website { + workflowStep.Website = nil + } + if workflowStep.SourceCodeURL != nil && specStep.SourceCodeURL != nil && *workflowStep.SourceCodeURL == *specStep.SourceCodeURL { + workflowStep.SourceCodeURL = nil + } + if workflowStep.SupportURL != nil && specStep.SupportURL != nil && *workflowStep.SupportURL == *specStep.SupportURL { + workflowStep.SupportURL = nil + } + workflowStep.PublishedAt = nil + if workflowStep.Source != nil && specStep.Source != nil { + if workflowStep.Source.Git == specStep.Source.Git { + workflowStep.Source.Git = "" + } + if workflowStep.Source.Commit == specStep.Source.Commit { + workflowStep.Source.Commit = "" + } + } + if isStringSliceWithSameElements(workflowStep.HostOsTags, specStep.HostOsTags) { + workflowStep.HostOsTags = []string{} + } + if isStringSliceWithSameElements(workflowStep.ProjectTypeTags, specStep.ProjectTypeTags) { + workflowStep.ProjectTypeTags = []string{} + } + if isStringSliceWithSameElements(workflowStep.TypeTags, specStep.TypeTags) { + workflowStep.TypeTags = []string{} + } + if isDependencySliceWithSameElements(workflowStep.Dependencies, specStep.Dependencies) { + workflowStep.Dependencies = []stepmanModels.DependencyModel{} + } + if workflowStep.IsRequiresAdminUser != nil && specStep.IsRequiresAdminUser != nil && *workflowStep.IsRequiresAdminUser == *specStep.IsRequiresAdminUser { + workflowStep.IsRequiresAdminUser = nil + } + if workflowStep.IsAlwaysRun != nil && specStep.IsAlwaysRun != nil && *workflowStep.IsAlwaysRun == *specStep.IsAlwaysRun { + workflowStep.IsAlwaysRun = nil + } + if workflowStep.IsSkippable != nil && specStep.IsSkippable != nil && *workflowStep.IsSkippable == *specStep.IsSkippable { + workflowStep.IsSkippable = nil + } + if workflowStep.RunIf != nil && specStep.RunIf != nil && *workflowStep.RunIf == *specStep.RunIf { + workflowStep.RunIf = nil + } + + inputs := []envmanModels.EnvironmentItemModel{} + for _, input := range workflowStep.Inputs { + sameValue := false + + wfKey, wfValue, err := input.GetKeyValuePair() + if err != nil { + return err + } + + wfOptions, err := input.GetOptions() + if err != nil { + return err + } + + sInput, err := getInputByKey(specStep.Inputs, wfKey) + if err != nil { + return err + } + + _, sValue, err := sInput.GetKeyValuePair() + if err != nil { + return err + } + + if wfValue == sValue { + sameValue = true + } + + sOptions, err := sInput.GetOptions() + if err != nil { + return err + } + + hasOptions := false + + if wfOptions.Title != nil && sOptions.Title != nil && *wfOptions.Title == *sOptions.Title { + wfOptions.Title = nil + } else { + hasOptions = true + } + + if wfOptions.Description != nil && sOptions.Description != nil && *wfOptions.Description == *sOptions.Description { + wfOptions.Description = nil + } else { + hasOptions = true + } + + if wfOptions.Summary != nil && sOptions.Summary != nil && *wfOptions.Summary == *sOptions.Summary { + wfOptions.Summary = nil + } else { + hasOptions = true + } + + if isStringSliceWithSameElements(wfOptions.ValueOptions, sOptions.ValueOptions) { + wfOptions.ValueOptions = []string{} + } else { + hasOptions = true + } + + if wfOptions.IsRequired != nil && sOptions.IsRequired != nil && *wfOptions.IsRequired == *sOptions.IsRequired { + wfOptions.IsRequired = nil + } else { + hasOptions = true + } + + if wfOptions.IsExpand != nil && sOptions.IsExpand != nil && *wfOptions.IsExpand == *sOptions.IsExpand { + wfOptions.IsExpand = nil + } else { + hasOptions = true + } + + if wfOptions.IsDontChangeValue != nil && sOptions.IsDontChangeValue != nil && *wfOptions.IsDontChangeValue == *sOptions.IsDontChangeValue { + wfOptions.IsDontChangeValue = nil + } else { + hasOptions = true + } + + if !hasOptions && sameValue { + // default env + } else { + if hasOptions { + input[envmanModels.OptionsKey] = wfOptions + } else { + delete(input, envmanModels.OptionsKey) + } + + inputs = append(inputs, input) + } + } + + workflowStep.Inputs = inputs + + // We need only key-value and title from spec outputs + outputs := []envmanModels.EnvironmentItemModel{} + for _, output := range specStep.Outputs { + sKey, sValue, err := output.GetKeyValuePair() + if err != nil { + return err + } + + sOptions, err := output.GetOptions() + if err != nil { + return err + } + + newOutput := envmanModels.EnvironmentItemModel{ + sKey: sValue, + envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ + Title: sOptions.Title, + }, + } + + outputs = append(outputs, newOutput) + } + + workflowStep.Outputs = outputs + + (*stepListItem)[compositeStepIDStr] = workflowStep + } + + // Cleanup + if err := command.RemoveDir(tempStepCloneDirPath); err != nil { + return errors.New(fmt.Sprint("Failed to remove step clone dir: ", err)) + } + if err := command.RemoveDir(tempStepYMLDirPath); err != nil { + return errors.New(fmt.Sprint("Failed to remove step clone dir: ", err)) + } + + return nil +} + +// RemoveConfigRedundantFieldsAndFillStepOutputs ... +func RemoveConfigRedundantFieldsAndFillStepOutputs(config *models.BitriseDataModel) error { + for _, workflow := range config.Workflows { + for _, stepListItem := range workflow.Steps { + if err := removeStepDefaultsAndFillStepOutputs(&stepListItem, config.DefaultStepLibSource); err != nil { + return err + } + } + } + if err := config.RemoveRedundantFields(); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/util_test.go b/vendor/github.com/bitrise-io/bitrise/bitrise/util_test.go new file mode 100644 index 00000000..08432678 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/bitrise/util_test.go @@ -0,0 +1,498 @@ +package bitrise + +import ( + "encoding/json" + "testing" + "time" + + "github.com/bitrise-io/bitrise/configs" + envmanModels "github.com/bitrise-io/envman/models" + stepmanModels "github.com/bitrise-io/stepman/models" + "github.com/stretchr/testify/require" +) + +func secToDuration(sec float64) time.Duration { + return time.Duration(sec * 1e9) +} + +func minToDuration(min float64) time.Duration { + return secToDuration(min * 60) +} + +func hourToDuration(hour float64) time.Duration { + return minToDuration(hour * 60) +} + +func TestApplyOutputAliases(t *testing.T) { + t.Log("apply alias on signle env") + { + envs := []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "ORIGINAL_KEY": "value", + }, + } + + outputEnvs := []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "ORIGINAL_KEY": "ALIAS_KEY", + }, + } + + updatedEnvs, err := ApplyOutputAliases(envs, outputEnvs) + require.NoError(t, err) + require.Equal(t, 1, len(updatedEnvs)) + + updatedKey, value, err := updatedEnvs[0].GetKeyValuePair() + require.Equal(t, "ALIAS_KEY", updatedKey) + require.Equal(t, "value", value) + } + + t.Log("apply alias on env list") + { + envs := []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "ORIGINAL_KEY": "value", + }, + envmanModels.EnvironmentItemModel{ + "SHOULD_NOT_CHANGE_KEY": "value", + }, + } + + outputEnvs := []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "ORIGINAL_KEY": "ALIAS_KEY", + }, + } + + updatedEnvs, err := ApplyOutputAliases(envs, outputEnvs) + require.NoError(t, err) + require.Equal(t, 2, len(updatedEnvs)) + + { + // this env should be updated + updatedKey, value, err := updatedEnvs[0].GetKeyValuePair() + require.NoError(t, err) + require.Equal(t, "ALIAS_KEY", updatedKey) + require.Equal(t, "value", value) + } + + { + // this env should NOT be updated + key, value, err := updatedEnvs[1].GetKeyValuePair() + require.NoError(t, err) + require.Equal(t, "SHOULD_NOT_CHANGE_KEY", key) + require.Equal(t, "value", value) + } + } +} + +func TestTimeToFormattedSeconds(t *testing.T) { + t.Log("formatted print rounds") + { + timeStr, err := FormattedSecondsToMax8Chars(secToDuration(0.999)) + require.NoError(t, err) + require.Equal(t, "1.00 sec", timeStr) + } + + t.Log("sec < 1.0") + { + timeStr, err := FormattedSecondsToMax8Chars(secToDuration(0.111)) + require.NoError(t, err) + require.Equal(t, "0.11 sec", timeStr) + } + + t.Log("sec < 10.0") + { + timeStr, err := FormattedSecondsToMax8Chars(secToDuration(9.111)) + require.NoError(t, err) + require.Equal(t, "9.11 sec", timeStr) + } + + t.Log("sec < 600 | min < 10") + { + timeStr, err := FormattedSecondsToMax8Chars(secToDuration(599.111)) + require.NoError(t, err) + require.Equal(t, "599 sec", timeStr) + } + + t.Log("min < 60") + { + timeStr, err := FormattedSecondsToMax8Chars(minToDuration(59.111)) + require.NoError(t, err) + require.Equal(t, "59.1 min", timeStr) + } + + t.Log("hour < 10") + { + timeStr, err := FormattedSecondsToMax8Chars(hourToDuration(9.111)) + require.NoError(t, err) + require.Equal(t, "9.1 hour", timeStr) + } + + t.Log("hour < 1000") + { + timeStr, err := FormattedSecondsToMax8Chars(hourToDuration(999.111)) + require.NoError(t, err) + require.Equal(t, "999 hour", timeStr) + } + + t.Log("hour >= 1000") + { + timeStr, err := FormattedSecondsToMax8Chars(hourToDuration(1000)) + require.EqualError(t, err, "time (1000.000000 hour) greater than max allowed (999 hour)") + require.Equal(t, "", timeStr) + } +} + +func TestRemoveConfigRedundantFieldsAndFillStepOutputs(t *testing.T) { + // setup + require.NoError(t, configs.InitPaths()) + + configStr := ` + format_version: 1.3.0 + default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + + workflows: + remove_test: + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + exit 2 + opts: + is_expand: true + - timestamp: + title: Generate timestamps + ` + + config, warnings, err := ConfigModelFromYAMLBytes([]byte(configStr)) + require.Equal(t, nil, err) + require.Equal(t, 0, len(warnings)) + + require.Equal(t, nil, RemoveConfigRedundantFieldsAndFillStepOutputs(&config)) + + for workflowID, workflow := range config.Workflows { + if workflowID == "remove_test" { + for _, stepListItem := range workflow.Steps { + for stepID, step := range stepListItem { + if stepID == "script" { + for _, input := range step.Inputs { + key, _, err := input.GetKeyValuePair() + require.Equal(t, nil, err) + + if key == "content" { + opts, err := input.GetOptions() + require.Equal(t, nil, err) + + // script content should keep is_expand: true, becouse it's diffenet from spec default + require.Equal(t, true, *opts.IsExpand) + } + } + } else if stepID == "timestamp" { + // timestamp title should be nil, becouse it's the same as spec value + require.Equal(t, (*string)(nil), step.Title) + + for _, output := range step.Outputs { + key, _, err := output.GetKeyValuePair() + require.Equal(t, nil, err) + + if key == "UNIX_TIMESTAMP" { + // timestamp outputs should filled with key-value & opts.Title + opts, err := output.GetOptions() + require.Equal(t, nil, err) + + require.Equal(t, "unix style", *opts.Title) + require.Equal(t, (*bool)(nil), opts.IsExpand) + require.Equal(t, (*bool)(nil), opts.IsDontChangeValue) + require.Equal(t, (*bool)(nil), opts.IsRequired) + } + } + } + } + } + } + } + + // timestamp outputs should filled with key-value & opts.Title + +} + +func TestSsStringSliceWithSameElements(t *testing.T) { + s1 := []string{} + s2 := []string{} + require.Equal(t, true, isStringSliceWithSameElements(s1, s2)) + + s1 = []string{"1", "2", "3"} + s2 = []string{"2", "1"} + require.Equal(t, false, isStringSliceWithSameElements(s1, s2)) + + s2 = append(s2, "3") + require.Equal(t, true, isStringSliceWithSameElements(s1, s2)) + + s2 = []string{"1,", "1,", "1"} + require.Equal(t, false, isStringSliceWithSameElements(s1, s2)) +} + +func TestIsDependecyEqual(t *testing.T) { + d1 := stepmanModels.DependencyModel{Manager: "manager", Name: "dep"} + d2 := stepmanModels.DependencyModel{Manager: "manager", Name: "dep"} + + require.Equal(t, true, isDependecyEqual(d1, d2)) + + d1 = stepmanModels.DependencyModel{Manager: "manager", Name: "dep1"} + d2 = stepmanModels.DependencyModel{Manager: "manager", Name: "dep"} + + require.Equal(t, false, isDependecyEqual(d1, d2)) + + d1 = stepmanModels.DependencyModel{Manager: "manager", Name: "dep"} + d2 = stepmanModels.DependencyModel{Manager: "manager1", Name: "dep"} + + require.Equal(t, false, isDependecyEqual(d1, d2)) +} + +func TestContainsDependecy(t *testing.T) { + d1 := stepmanModels.DependencyModel{Manager: "manager", Name: "dep1"} + d2 := stepmanModels.DependencyModel{Manager: "manager", Name: "dep2"} + d3 := stepmanModels.DependencyModel{Manager: "manager1", Name: "dep3"} + + m := map[stepmanModels.DependencyModel]bool{ + d1: false, + d2: true, + } + + require.Equal(t, true, containsDependecy(m, d1)) + + require.Equal(t, false, containsDependecy(m, d3)) +} + +func TestIsDependencySliceWithSameElements(t *testing.T) { + s1 := []stepmanModels.DependencyModel{} + s2 := []stepmanModels.DependencyModel{} + require.Equal(t, true, isDependencySliceWithSameElements(s1, s2)) + + d1 := stepmanModels.DependencyModel{Manager: "manager", Name: "dep1"} + d2 := stepmanModels.DependencyModel{Manager: "manager", Name: "dep2"} + d3 := stepmanModels.DependencyModel{Manager: "manager1", Name: "dep3"} + + s1 = []stepmanModels.DependencyModel{d1, d2, d3} + s2 = []stepmanModels.DependencyModel{d2, d1} + require.Equal(t, false, isDependencySliceWithSameElements(s1, s2)) + + s2 = append(s2, d3) + require.Equal(t, true, isDependencySliceWithSameElements(s1, s2)) + + s2 = []stepmanModels.DependencyModel{d1, d1, d1} + require.Equal(t, false, isDependencySliceWithSameElements(s1, s2)) +} + +func TestConfigModelFromYAMLBytes(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + trivial_fail: + steps: + - script: + title: Should success + - script: + title: Should fail, but skippable + is_skippable: true + inputs: + - content: | + #!/bin/bash + set -v + exit 2 + - script: + title: Should success + - script: + title: Should fail + inputs: + - content: | + #!/bin/bash + set -v + exit 2 + - script: + title: Should success + is_always_run: true + - script: + title: Should skipped + ` + config, warnings, err := ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + workflow, found := config.Workflows["trivial_fail"] + require.Equal(t, true, found) + require.Equal(t, 6, len(workflow.Steps)) +} + +func TestConfigModelFromJSONBytes(t *testing.T) { + configStr := ` +{ + "format_version": "1.0.0", + "default_step_lib_source": "https://github.com/bitrise-io/bitrise-steplib.git", + "app": { + "envs": null + }, + "workflows": { + "trivial_fail": { + "title": "", + "summary": "", + "before_run": null, + "after_run": null, + "envs": null, + "steps": [ + { + "script": { + "title": "Should success", + "source": {} + } + }, + { + "script": { + "title": "Should fail, but skippable", + "source": {}, + "is_skippable": true, + "inputs": [ + { + "content": "#!/bin/bash\nset -v\nexit 2\n", + "opts": {} + } + ] + } + }, + { + "script": { + "title": "Should success", + "source": {} + } + }, + { + "script": { + "title": "Should fail", + "source": {}, + "inputs": [ + { + "content": "#!/bin/bash\nset -v\nexit 2\n", + "opts": {} + } + ] + } + }, + { + "script": { + "title": "Should success", + "source": {}, + "is_always_run": true + } + }, + { + "script": { + "title": "Should skipped", + "source": {} + } + } + ] + } + } +} + ` + config, warnings, err := ConfigModelFromJSONBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + workflow, found := config.Workflows["trivial_fail"] + require.Equal(t, true, found) + require.Equal(t, 6, len(workflow.Steps)) +} + +func TestConfigModelFromYAMLBytesNormalize(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +app: + envs: + - BITRISE_BIN_NAME: bitrise + opts: + is_expand: false + - GITHUB_RELEASES_URL: https://github.com/bitrise-io/bitrise/releases + opts: + is_expand: false + +workflows: + test: + steps: + - script: + title: Should fail, but skippable + is_skippable: true + inputs: + - content: echo "Hello World" + opts: + is_expand: no +` + config, warnings, err := ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + // should be able to serialize into JSON + _, err = json.MarshalIndent(config, "", "\t") + require.NoError(t, err) +} + +func TestConfigModelFromJSONBytesNormalize(t *testing.T) { + configStr := ` +{ + "format_version": "1.0.0", + "default_step_lib_source": "https://github.com/bitrise-io/bitrise-steplib.git", + "app": { + "envs": [ + { + "BITRISE_BIN_NAME": "bitrise", + "opts": { + "is_expand": false + } + }, + { + "GITHUB_RELEASES_URL": "https://github.com/bitrise-io/bitrise/releases", + "opts": { + "is_expand": false + } + } + ] + }, + "workflows": { + "test": { + "steps": [ + { + "script": { + "title": "Should fail, but skippable", + "is_skippable": true, + "inputs": [ + { + "content": "echo \"Hello World\"", + "opts": { + "is_expand": false + } + } + ] + } + } + ] + } + } +} +` + config, warnings, err := ConfigModelFromJSONBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + t.Log("The ConfigModelFromJSONBytes method should call the required Normalize methods, so that no map[interface{}]interface{} is left - which would prevent the JSON serialization.") + t.Logf("Config: %#v", config) + // should be able to serialize into JSON + _, err = json.MarshalIndent(config, "", "\t") + require.NoError(t, err) +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/cli.go b/vendor/github.com/bitrise-io/bitrise/cli/cli.go new file mode 100644 index 00000000..c2106bd3 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/cli.go @@ -0,0 +1,163 @@ +package cli + +import ( + "errors" + "fmt" + "os" + "path" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/bitrise/bitrise" + "github.com/bitrise-io/bitrise/configs" + "github.com/bitrise-io/bitrise/plugins" + "github.com/bitrise-io/bitrise/version" + "github.com/urfave/cli" +) + +func initLogFormatter() { + log.SetFormatter(&log.TextFormatter{ + FullTimestamp: true, + ForceColors: true, + TimestampFormat: "15:04:05", + }) +} + +func before(c *cli.Context) error { + /* + return err will print app's help also, + use log.Fatal to avoid print help. + */ + + initLogFormatter() + initHelpAndVersionFlags() + + // Debug mode? + if c.Bool(DebugModeKey) { + // set for other tools, as an ENV + if err := os.Setenv(configs.DebugModeEnvKey, "true"); err != nil { + log.Fatalf("Failed to set DEBUG env, error: %s", err) + } + configs.IsDebugMode = true + log.Warn("=> Started in DEBUG mode") + } + + // Log level + // If log level defined - use it + logLevelStr := c.String(LogLevelKey) + if logLevelStr == "" && configs.IsDebugMode { + // if no Log Level defined and we're in Debug Mode - set loglevel to debug + logLevelStr = "debug" + log.Warn("=> LogLevel set to debug") + } + if logLevelStr == "" { + // if still empty: set the default + logLevelStr = "info" + } + + level, err := log.ParseLevel(logLevelStr) + if err != nil { + log.Fatalf("Failed parse log level, error: %s", err) + } + + if err := os.Setenv(configs.LogLevelEnvKey, level.String()); err != nil { + log.Fatalf("Failed to set LOGLEVEL env, error: %s", err) + } + log.SetLevel(level) + + // CI Mode check + if c.Bool(CIKey) { + // if CI mode indicated make sure we set the related env + // so all other tools we use will also get it + if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { + log.Fatalf("Failed to set CI env, error: %s", err) + } + configs.IsCIMode = true + } + + if err := configs.InitPaths(); err != nil { + log.Fatalf("Failed to initialize required paths, error: %s", err) + } + + // Pull Request Mode check + if c.Bool(PRKey) { + // if PR mode indicated make sure we set the related env + // so all other tools we use will also get it + if err := os.Setenv(configs.PRModeEnvKey, "true"); err != nil { + log.Fatalf("Failed to set PR env, error: %s", err) + } + configs.IsPullRequestMode = true + } + + pullReqID := os.Getenv(configs.PullRequestIDEnvKey) + if pullReqID != "" { + configs.IsPullRequestMode = true + } + + IsPR := os.Getenv(configs.PRModeEnvKey) + if IsPR == "true" { + configs.IsPullRequestMode = true + } + + return nil +} + +func printVersion(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "%v\n", c.App.Version) +} + +// Run ... +func Run() { + if err := plugins.InitPaths(); err != nil { + log.Fatalf("Failed to initialize plugin path, error: %s", err) + } + + initAppHelpTemplate() + + // Parse cl + cli.VersionPrinter = printVersion + + app := cli.NewApp() + app.Name = path.Base(os.Args[0]) + app.Usage = "Bitrise Automations Workflow Runner" + app.Version = version.VERSION + + app.Author = "" + app.Email = "" + + app.Before = before + + app.Flags = flags + app.Commands = commands + + app.Action = func(c *cli.Context) error { + pluginName, pluginArgs, isPlugin := plugins.ParseArgs(c.Args()) + if isPlugin { + plugin, found, err := plugins.LoadPlugin(pluginName) + if err != nil { + return fmt.Errorf("Failed to get plugin (%s), error: %s", pluginName, err) + } + if !found { + return fmt.Errorf("Plugin (%s) not installed", pluginName) + } + + if err := bitrise.RunSetupIfNeeded(version.VERSION, false); err != nil { + log.Fatalf("Setup failed, error: %s", err) + } + + if err := plugins.RunPluginByCommand(plugin, pluginArgs); err != nil { + return fmt.Errorf("Failed to run plugin (%s), error: %s", pluginName, err) + } + } else { + if err := cli.ShowAppHelp(c); err != nil { + return fmt.Errorf("Failed to show help, error: %s", err) + } + return errors.New("") + } + + return nil + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/commands.go b/vendor/github.com/bitrise-io/bitrise/cli/commands.go new file mode 100644 index 00000000..3ef7b848 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/commands.go @@ -0,0 +1,237 @@ +package cli + +import "github.com/urfave/cli" + +const ( + // JSONParamsKey ... + JSONParamsKey = "json-params" + // JSONParamsBase64Key ... + JSONParamsBase64Key = "json-params-base64" + + // WorkflowKey ... + WorkflowKey = "workflow" + + // PatternKey ... + PatternKey = "pattern" + // PushBranchKey ... + PushBranchKey = "push-branch" + // PRSourceBranchKey ... + PRSourceBranchKey = "pr-source-branch" + // PRTargetBranchKey ... + PRTargetBranchKey = "pr-target-branch" + + // ConfigKey ... + ConfigKey = "config" + // InventoryKey ... + InventoryKey = "inventory" + + // OuputFormatKey ... + OuputFormatKey = "format" +) + +var ( + commands = []cli.Command{ + initCmd, + setupCommand, + { + Name: "version", + Usage: "Prints the version", + Action: printVersionCmd, + Flags: []cli.Flag{ + flOutputFormat, + cli.BoolFlag{Name: "full", Usage: "Prints the build number as well."}, + }, + }, + { + Name: "validate", + Usage: "Validates a specified bitrise config.", + Action: validate, + Flags: []cli.Flag{ + flPath, + flConfig, + flConfigBase64, + flInventory, + flInventoryBase64, + flFormat, + }, + }, + { + Name: "run", + Aliases: []string{"r"}, + Usage: "Runs a specified Workflow.", + Action: run, + Flags: []cli.Flag{ + // cli params + cli.StringFlag{Name: WorkflowKey, Usage: "workflow id to run."}, + cli.StringFlag{Name: ConfigKey + ", " + configShortKey, Usage: "Path where the workflow config file is located."}, + cli.StringFlag{Name: InventoryKey + ", " + inventoryShortKey, Usage: "Path of the inventory file."}, + + // cli params used in CI mode + cli.StringFlag{Name: JSONParamsKey, Usage: "Specify command flags with json string-string hash."}, + cli.StringFlag{Name: JSONParamsBase64Key, Usage: "Specify command flags with base64 encoded json string-string hash."}, + + // deprecated + flPath, + + // should deprecate + cli.StringFlag{Name: ConfigBase64Key, Usage: "base64 encoded config data."}, + cli.StringFlag{Name: InventoryBase64Key, Usage: "base64 encoded inventory data."}, + }, + }, + { + Name: "trigger-check", + Usage: "Prints out which workflow will triggered by specified pattern.", + Action: triggerCheck, + Flags: []cli.Flag{ + // cli params + cli.StringFlag{Name: PatternKey, Usage: "trigger pattern."}, + cli.StringFlag{Name: ConfigKey + ", " + configShortKey, Usage: "Path where the workflow config file is located."}, + cli.StringFlag{Name: InventoryKey + ", " + inventoryShortKey, Usage: "Path of the inventory file."}, + + cli.StringFlag{Name: PushBranchKey, Usage: "Git push branch name."}, + cli.StringFlag{Name: PRSourceBranchKey, Usage: "Git pull request source branch name."}, + cli.StringFlag{Name: PRTargetBranchKey, Usage: "Git pull request target branch name."}, + cli.StringFlag{Name: TagKey, Usage: "Git tag name."}, + + cli.StringFlag{Name: OuputFormatKey, Usage: "Output format. Accepted: json, yml."}, + + // cli params used in CI mode + cli.StringFlag{Name: JSONParamsKey, Usage: "Specify command flags with json string-string hash."}, + cli.StringFlag{Name: JSONParamsBase64Key, Usage: "Specify command flags with base64 encoded json string-string hash."}, + + // deprecated + flPath, + + // should deprecate + cli.StringFlag{Name: ConfigBase64Key, Usage: "base64 encoded config data."}, + cli.StringFlag{Name: InventoryBase64Key, Usage: "base64 encoded inventory data."}, + }, + }, + { + Name: "trigger", + Aliases: []string{"t"}, + Usage: "Triggers a specified Workflow.", + Action: trigger, + Flags: []cli.Flag{ + // cli params + cli.StringFlag{Name: PatternKey, Usage: "trigger pattern."}, + cli.StringFlag{Name: ConfigKey + ", " + configShortKey, Usage: "Path where the workflow config file is located."}, + cli.StringFlag{Name: InventoryKey + ", " + inventoryShortKey, Usage: "Path of the inventory file."}, + + cli.StringFlag{Name: PushBranchKey, Usage: "Git push branch name."}, + cli.StringFlag{Name: PRSourceBranchKey, Usage: "Git pull request source branch name."}, + cli.StringFlag{Name: PRTargetBranchKey, Usage: "Git pull request target branch name."}, + cli.StringFlag{Name: TagKey, Usage: "Git tag name."}, + + // cli params used in CI mode + cli.StringFlag{Name: JSONParamsKey, Usage: "Specify command flags with json string-string hash."}, + cli.StringFlag{Name: JSONParamsBase64Key, Usage: "Specify command flags with base64 encoded json string-string hash."}, + + // deprecated + flPath, + + // should deprecate + cli.StringFlag{Name: ConfigBase64Key, Usage: "base64 encoded config data."}, + cli.StringFlag{Name: InventoryBase64Key, Usage: "base64 encoded inventory data."}, + }, + }, + { + Name: "export", + Usage: "Export the bitrise configuration.", + Action: export, + Flags: []cli.Flag{ + flPath, + flConfig, + flConfigBase64, + flFormat, + flOutputPath, + flPretty, + }, + }, + { + Name: "normalize", + Usage: "Normalize the bitrise configuration.", + Action: normalize, + Flags: []cli.Flag{ + flPath, + flConfig, + flConfigBase64, + }, + }, + { + Name: "step-list", + Usage: "List of available steps.", + Action: stepList, + Flags: []cli.Flag{ + flCollection, + flFormat, + }, + }, + { + Name: "step-info", + Aliases: []string{"i"}, + Usage: "Provides information (step ID, last version, given version) about specified step.", + Action: stepInfo, + Flags: []cli.Flag{ + flCollection, + flVersion, + flFormat, + flShort, + flStepYML, + }, + }, + { + Name: "workflows", + Usage: "List of available workflows in config.", + Action: workflowList, + Flags: []cli.Flag{ + flPath, + flConfig, + flConfigBase64, + flFormat, + cli.BoolFlag{ + Name: MinimalModeKey, + Usage: "Print only workflow summary.", + }, + }, + }, + { + Name: "share", + Usage: "Publish your step.", + Action: share, + Subcommands: []cli.Command{ + { + Name: "start", + Usage: "Preparations for publishing.", + Action: start, + Flags: []cli.Flag{ + flCollection, + }, + }, + { + Name: "create", + Usage: "Create your change - add it to your own copy of the collection.", + Action: create, + Flags: []cli.Flag{ + flTag, + flGit, + flStepID, + }, + }, + { + Name: "audit", + Usage: "Validates the step collection.", + Action: shareAudit, + }, + { + Name: "finish", + Usage: "Finish up.", + Action: finish, + }, + }, + }, + pluginCommand, + stepmanCommand, + envmanCommand, + } +) diff --git a/vendor/github.com/bitrise-io/bitrise/cli/export.go b/vendor/github.com/bitrise-io/bitrise/cli/export.go new file mode 100644 index 00000000..f3a5a2e7 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/export.go @@ -0,0 +1,78 @@ +package cli + +import ( + "encoding/json" + + "gopkg.in/yaml.v2" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/bitrise/output" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/urfave/cli" +) + +func export(c *cli.Context) error { + // Expand cli.Context + bitriseConfigBase64Data := c.String(ConfigBase64Key) + + bitriseConfigPath := c.String(ConfigKey) + deprecatedBitriseConfigPath := c.String(PathKey) + if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { + log.Warn("'path' key is deprecated, use 'config' instead!") + bitriseConfigPath = deprecatedBitriseConfigPath + } + + outfilePth := c.String(OuputPathKey) + outFormat := c.String(OuputFormatKey) + prettyFormat := c.Bool(PrettyFormatKey) + // + + if outfilePth == "" { + showSubcommandHelp(c) + log.Fatal("No output file path specified!") + } + + if outFormat == "" { + showSubcommandHelp(c) + log.Fatal("No output format format specified!") + } + + // Config validation + bitriseConfig, warnings, err := CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath) + for _, warning := range warnings { + log.Warnf("warning: %s", warning) + } + if err != nil { + showSubcommandHelp(c) + log.Fatalf("Failed to create bitrise config, error: %s", err) + } + + // serialize + configBytes := []byte{} + if outFormat == output.FormatJSON { + if prettyFormat { + configBytes, err = json.MarshalIndent(bitriseConfig, "", "\t") + } else { + configBytes, err = json.Marshal(bitriseConfig) + } + if err != nil { + log.Fatalf("Failed to generate config JSON, error: %s", err) + } + } else if outFormat == output.FormatYML { + configBytes, err = yaml.Marshal(bitriseConfig) + if err != nil { + log.Fatalf("Failed to generate config YML, error: %s", err) + } + } else { + log.Fatalf("Invalid output format: %s", outFormat) + } + + // write to file + if err := fileutil.WriteBytesToFile(outfilePth, configBytes); err != nil { + log.Fatalf("Failed to write file (%s), error: %s", outfilePth, err) + } + + log.Infof("Done, saved to path: %s", outfilePth) + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/flags.go b/vendor/github.com/bitrise-io/bitrise/cli/flags.go new file mode 100644 index 00000000..3df8a580 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/flags.go @@ -0,0 +1,192 @@ +package cli + +import ( + "github.com/bitrise-io/bitrise/configs" + "github.com/urfave/cli" +) + +const ( + // CollectionPathEnvKey ... + CollectionPathEnvKey = "STEPMAN_COLLECTION" + // CIKey ... + CIKey = "ci" + // PRKey ... + PRKey = "pr" + // DebugModeKey ... + DebugModeKey = "debug" + + // LogLevelKey ... + LogLevelKey = "loglevel" + logLevelKeyShort = "l" + + // VersionKey ... + VersionKey = "version" + versionKeyShort = "v" + + // PathKey ... + PathKey = "path" + pathKeyShort = "p" + + // CollectionKey ... + CollectionKey = "collection" + collectionKeyShort = "c" + + inventoryShortKey = "i" + + // InventoryBase64Key ... + InventoryBase64Key = "inventory-base64" + + configShortKey = "c" + + // ConfigBase64Key ... + ConfigBase64Key = "config-base64" + + // HelpKey ... + HelpKey = "help" + helpKeyShort = "h" + + // MinimalModeKey ... + MinimalModeKey = "minimal" + // FullModeKey ... + FullModeKey = "full" + + ouputFormatKeyShort = "f" + // OuputPathKey ... + OuputPathKey = "outpath" + // PrettyFormatKey ... + PrettyFormatKey = "pretty" + + // IDKey ... + IDKey = "id" + idKeyShort = "i" + // ShortKey ... + ShortKey = "short" + + // StepYMLKey ... + StepYMLKey = "step-yml" + + // + // Stepman share + + // TagKey ... + TagKey = "tag" + // GitKey ... + GitKey = "git" + // StepIDKey ... + StepIDKey = "stepid" +) + +var ( + // App flags + flLogLevel = cli.StringFlag{ + Name: LogLevelKey + ", " + logLevelKeyShort, + Usage: "Log level (options: debug, info, warn, error, fatal, panic).", + EnvVar: configs.LogLevelEnvKey, + } + flDebugMode = cli.BoolFlag{ + Name: DebugModeKey, + Usage: "If true it enabled DEBUG mode. If no separate Log Level is specified this will also set the loglevel to debug.", + EnvVar: configs.DebugModeEnvKey, + } + flTool = cli.BoolFlag{ + Name: CIKey, + Usage: "If true it indicates that we're used by another tool so don't require any user input!", + EnvVar: configs.CIModeEnvKey, + } + flPRMode = cli.BoolFlag{ + Name: PRKey, + Usage: "If true bitrise runs in pull request mode.", + } + flags = []cli.Flag{ + flLogLevel, + flDebugMode, + flTool, + flPRMode, + } + // Command flags + flOutputFormat = cli.StringFlag{ + Name: OuputFormatKey + ", " + ouputFormatKeyShort, + Usage: "Output format. Accepted: raw (default), json, yml", + } + flPath = cli.StringFlag{ + Name: PathKey + ", " + pathKeyShort, + Usage: "[Deprecated!!! Use 'config'] Path where the workflow config file is located.", + } + flCollection = cli.StringFlag{ + Name: CollectionKey + ", " + collectionKeyShort, + Usage: "Collection of step.", + EnvVar: CollectionPathEnvKey, + } + flConfig = cli.StringFlag{ + Name: ConfigKey + ", " + configShortKey, + Usage: "Path where the workflow config file is located.", + } + flConfigBase64 = cli.StringFlag{ + Name: ConfigBase64Key, + Usage: "base64 decoded config data.", + } + flInventory = cli.StringFlag{ + Name: InventoryKey + ", " + inventoryShortKey, + Usage: "Path of the inventory file.", + } + flInventoryBase64 = cli.StringFlag{ + Name: InventoryBase64Key, + Usage: "base64 decoded inventory data.", + } + + // Export + flFormat = cli.StringFlag{ + Name: OuputFormatKey, + Usage: "Output format. Accepted: json, yml.", + } + flOutputPath = cli.StringFlag{ + Name: OuputPathKey, + Usage: "Output path, where the exported file will be saved.", + } + flPretty = cli.BoolFlag{ + Name: PrettyFormatKey, + Usage: "Pretty printed export?", + } + flID = cli.StringFlag{ + Name: IDKey + ", " + idKeyShort, + Usage: "Step id.", + } + flVersion = cli.StringFlag{ + Name: VersionKey + ", " + versionKeyShort, + Usage: "Step version.", + } + flShort = cli.BoolFlag{ + Name: ShortKey, + Usage: "Show short version of infos.", + } + flStepYML = cli.StringFlag{ + Name: StepYMLKey, + Usage: "Path of step.yml", + } + + // Stepman share + flTag = cli.StringFlag{ + Name: TagKey, + Usage: "Git (version) tag.", + } + flGit = cli.StringFlag{ + Name: GitKey, + Usage: "Git clone url of the step repository.", + } + flStepID = cli.StringFlag{ + Name: StepIDKey, + Usage: "ID of the step.", + } +) + +func initHelpAndVersionFlags() { + cli.HelpFlag = cli.BoolFlag{ + Name: HelpKey + ", " + helpKeyShort, + Usage: "Show help.", + } + + cli.VersionFlag = cli.BoolFlag{ + Name: VersionKey + ", " + versionKeyShort, + Usage: "Print the version.", + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/help.go b/vendor/github.com/bitrise-io/bitrise/cli/help.go new file mode 100644 index 00000000..e0d1dc1f --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/help.go @@ -0,0 +1,59 @@ +package cli + +import ( + "log" + "strings" + + "fmt" + + "github.com/bitrise-io/bitrise/plugins" + "github.com/urfave/cli" +) + +const ( + helpTemplate = ` +NAME: {{.Name}} - {{.Usage}} + +USAGE: {{.Name}} {{if .Flags}}[OPTIONS] {{end}}COMMAND/PLUGIN [arg...] + +VERSION: {{.Version}}{{if or .Author .Email}} + +AUTHOR:{{if .Author}} + {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} + {{.Email}}{{end}}{{end}} +{{if .Flags}} +GLOBAL OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{end}} +COMMANDS: + {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}} +%s +COMMAND HELP: {{.Name}} COMMAND --help/-h + +` +) + +func getPluginsList() string { + pluginListString := "PLUGINS:\n" + + pluginList, err := plugins.InstalledPluginList() + if err != nil { + log.Fatalf("Failed to list plugins, error: %s", err) + } + + if len(pluginList) > 0 { + plugins.SortByName(pluginList) + for _, plugin := range pluginList { + pluginListString += fmt.Sprintf(" :%s\t%s\n", plugin.Name, strings.Split(plugin.Description, "\n")[0]) + } + } else { + pluginListString += " No plugins installed\n" + } + + return pluginListString +} + +func initAppHelpTemplate() { + cli.AppHelpTemplate = fmt.Sprintf(helpTemplate, getPluginsList()) +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/init.go b/vendor/github.com/bitrise-io/bitrise/cli/init.go new file mode 100644 index 00000000..c2852307 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/init.go @@ -0,0 +1,50 @@ +package cli + +import ( + "fmt" + + "github.com/Sirupsen/logrus" + "github.com/bitrise-io/bitrise/plugins" + "github.com/urfave/cli" +) + +var initCmd = cli.Command{ + Name: "init", + Aliases: []string{"i"}, + Usage: "Init bitrise config.", + Action: func(c *cli.Context) error { + if err := initConfig(c); err != nil { + logrus.Fatal(err) + } + return nil + }, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "minimal", + Usage: "creates empty bitrise config and secrets", + }, + }, +} + +func initConfig(c *cli.Context) error { + minimal := c.Bool("minimal") + + pluginName := "init" + plugin, found, err := plugins.LoadPlugin(pluginName) + if err != nil { + return fmt.Errorf("Failed to get plugin (%s), error: %s", pluginName, err) + } + if !found { + return fmt.Errorf("Plugin (%s) not installed", pluginName) + } + + pluginArgs := []string{} + if minimal { + pluginArgs = []string{"--minimal"} + } + if err := plugins.RunPluginByCommand(plugin, pluginArgs); err != nil { + return fmt.Errorf("Failed to run plugin (%s), error: %s", pluginName, err) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/normalize.go b/vendor/github.com/bitrise-io/bitrise/cli/normalize.go new file mode 100644 index 00000000..8d13019f --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/normalize.go @@ -0,0 +1,50 @@ +package cli + +import ( + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/bitrise/bitrise" + "github.com/urfave/cli" +) + +func normalize(c *cli.Context) error { + // Expand cli.Context + bitriseConfigBase64Data := c.String(ConfigBase64Key) + + bitriseConfigPath := c.String(ConfigKey) + deprecatedBitriseConfigPath := c.String(PathKey) + if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { + log.Warn("'path' key is deprecated, use 'config' instead!") + bitriseConfigPath = deprecatedBitriseConfigPath + } + // + + // Input validation + bitriseConfigPath, err := GetBitriseConfigFilePath(bitriseConfigPath) + if err != nil { + log.Fatalf("Failed to get bitrise config path, error: %s", err) + } + if bitriseConfigPath == "" { + log.Fatal("No bitrise config path defined!") + } + + // Config validation + bitriseConfig, warnings, err := CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath) + for _, warning := range warnings { + log.Warnf("warning: %s", warning) + } + if err != nil { + log.Fatalf("Failed to create bitrise config, error: %s", err) + } + + // Normalize + if err := bitrise.RemoveConfigRedundantFieldsAndFillStepOutputs(&bitriseConfig); err != nil { + log.Fatalf("Failed to remove redundant fields, error: %s", err) + } + if err := bitrise.SaveConfigToFile(bitriseConfigPath, bitriseConfig); err != nil { + log.Fatalf("Failed to save config to file, error: %s", err) + } + + log.Info("Redundant fields removed") + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/plugin.go b/vendor/github.com/bitrise-io/bitrise/cli/plugin.go new file mode 100644 index 00000000..d09b2831 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/plugin.go @@ -0,0 +1,24 @@ +package cli + +import ( + "github.com/bitrise-io/go-utils/log" + "github.com/urfave/cli" +) + +var pluginCommand = cli.Command{ + Name: "plugin", + Usage: "Plugin handling.", + Subcommands: []cli.Command{ + pluginInstallCommand, + pluginUpdateCommand, + pluginDeleteCommand, + pluginInfoCommand, + pluginListCommand, + }, +} + +func showSubcommandHelp(c *cli.Context) { + if err := cli.ShowSubcommandHelp(c); err != nil { + log.Warnf("Failed to show help, error: %s", err) + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/plugin_delete.go b/vendor/github.com/bitrise-io/bitrise/cli/plugin_delete.go new file mode 100644 index 00000000..93f42e24 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/plugin_delete.go @@ -0,0 +1,58 @@ +package cli + +import ( + "errors" + "fmt" + "os" + + "github.com/bitrise-io/bitrise/plugins" + "github.com/bitrise-io/go-utils/log" + "github.com/urfave/cli" +) + +var pluginDeleteCommand = cli.Command{ + Name: "delete", + Usage: "Delete bitrise plugin.", + Action: func(c *cli.Context) error { + if err := pluginDelete(c); err != nil { + log.Errorf("Plugin delete failed, error: %s", err) + os.Exit(1) + } + return nil + }, + ArgsUsage: "", +} + +func pluginDelete(c *cli.Context) error { + // Input validation + args := c.Args() + if len(args) == 0 { + showSubcommandHelp(c) + return errors.New("plugin_name not defined") + } + + name := args[0] + if name == "" { + showSubcommandHelp(c) + return errors.New("plugin_name not defined") + } + // --- + + // Delete + if _, found, err := plugins.LoadPlugin(name); err != nil { + return fmt.Errorf("failed to check if plugin installed, error: %s", err) + } else if !found { + log.Warnf("Plugin not installed") + return nil + } + + log.Infof("Deleting plugin") + if err := plugins.DeletePlugin(name); err != nil { + return fmt.Errorf("failed to delete plugin, error: %s", err) + } + + log.Donef("Plugin deleted") + // --- + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/plugin_info.go b/vendor/github.com/bitrise-io/bitrise/cli/plugin_info.go new file mode 100644 index 00000000..2c94559a --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/plugin_info.go @@ -0,0 +1,106 @@ +package cli + +import ( + "errors" + "fmt" + "os" + + "github.com/bitrise-io/bitrise/output" + "github.com/bitrise-io/bitrise/plugins" + "github.com/bitrise-io/go-utils/log" + "github.com/urfave/cli" +) + +var pluginInfoCommand = cli.Command{ + Name: "info", + Usage: "Installed bitrise plugin's info", + Action: func(c *cli.Context) error { + if err := pluginInfo(c); err != nil { + log.Errorf("Plugin info failed, error: %s", err) + os.Exit(1) + } + return nil + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: output.FormatKey, + Usage: "Output format. Accepted: raw, json.", + }, + }, + ArgsUsage: "", +} + +func createPluginInfo(name string) (plugins.PluginInfoModel, error) { + plugin, found, err := plugins.LoadPlugin(name) + if err != nil { + return plugins.PluginInfoModel{}, fmt.Errorf("failed to check if plugin installed, error: %s", err) + } else if !found { + return plugins.PluginInfoModel{}, fmt.Errorf("plugin is not installed") + } + + route, found, err := plugins.ReadPluginRoute(plugin.Name) + if err != nil { + return plugins.PluginInfoModel{}, fmt.Errorf("failed to read plugin route, error: %s", err) + } else if !found { + return plugins.PluginInfoModel{}, errors.New("no route found for loaded plugin") + } + + pluginVersionPtr, err := plugins.GetPluginVersion(plugin.Name) + if err != nil { + return plugins.PluginInfoModel{}, fmt.Errorf("failed to read plugin version, error: %s", err) + } + + pluginDefinitionPth := plugins.GetPluginDefinitionPath(plugin.Name) + + pluginInfo := plugins.PluginInfoModel{ + Name: plugin.Name, + Version: pluginVersionPtr.String(), + Source: route.Source, + Plugin: plugin, + DefinitionPth: pluginDefinitionPth, + } + + return pluginInfo, nil +} + +func pluginInfo(c *cli.Context) error { + // Input validation + args := c.Args() + if len(args) == 0 { + showSubcommandHelp(c) + return errors.New("plugin_name not defined") + } + + name := args[0] + if name == "" { + showSubcommandHelp(c) + return errors.New("plugin_name not defined") + } + + format := c.String(output.FormatKey) + if format == "" { + format = output.FormatRaw + } + if format != output.FormatRaw && format != output.FormatJSON { + showSubcommandHelp(c) + return fmt.Errorf("invalid format: %s", format) + } + + var logger log.Logger + logger = log.NewDefaultRawLogger() + if format == output.FormatJSON { + logger = log.NewDefaultJSONLoger() + } + // --- + + // Info + pluginInfo, err := createPluginInfo(name) + if err != nil { + return err + } + + logger.Print(pluginInfo) + // --- + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/plugin_install.go b/vendor/github.com/bitrise-io/bitrise/cli/plugin_install.go new file mode 100644 index 00000000..e737b0c0 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/plugin_install.go @@ -0,0 +1,75 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/bitrise-io/bitrise/plugins" + "github.com/bitrise-io/go-utils/log" + "github.com/urfave/cli" +) + +var pluginInstallCommand = cli.Command{ + Name: "install", + Usage: "Intsall bitrise plugin.", + Action: func(c *cli.Context) error { + if err := pluginInstall(c); err != nil { + log.Errorf("Plugin install failed, error: %s", err) + os.Exit(1) + } + return nil + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "version", + Usage: "Plugin version tag.", + }, + cli.StringFlag{ + Name: "source", + Usage: "Deprecated!!! Specify as arg instead - Plugin source url (can be local path or remote url).", + }, + }, + ArgsUsage: "", +} + +func pluginInstall(c *cli.Context) error { + // Input validation + pluginSource := "" + if args := c.Args(); len(args) > 0 { + pluginSource = args[0] + } else { + pluginSource = c.String("source") + } + + pluginVersionTag := c.String("version") + + if pluginSource == "" { + showSubcommandHelp(c) + return fmt.Errorf("plugin source not defined") + } + // --- + + // Install + log.Infof("Installing plugin") + + plugin, version, err := plugins.InstallPlugin(pluginSource, pluginVersionTag) + if err != nil { + return err + } + + if len(plugin.Description) > 0 { + fmt.Println() + log.Infof("Description:") + fmt.Println(plugin.Description) + } + + fmt.Println() + if version == "" { + log.Donef("Local plugin (%s) installed ", plugin.Name) + } else { + log.Donef("Plugin (%s) with version (%s) installed ", plugin.Name, version) + } + // --- + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/plugin_list.go b/vendor/github.com/bitrise-io/bitrise/cli/plugin_list.go new file mode 100644 index 00000000..2450c662 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/plugin_list.go @@ -0,0 +1,77 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/bitrise-io/bitrise/output" + "github.com/bitrise-io/bitrise/plugins" + "github.com/bitrise-io/go-utils/log" + "github.com/urfave/cli" +) + +var pluginListCommand = cli.Command{ + Name: "list", + Usage: "List installed bitrise plugins.", + Action: func(c *cli.Context) error { + if err := pluginList(c); err != nil { + log.Errorf("Plugin list failed, error: %s", err) + os.Exit(1) + } + return nil + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: output.FormatKey, + Usage: "Output format. Accepted: raw, json.", + }, + }, + ArgsUsage: "", +} + +func pluginList(c *cli.Context) error { + // Input validation + format := c.String(output.FormatKey) + if format == "" { + format = output.FormatRaw + } + if format != output.FormatRaw && format != output.FormatJSON { + showSubcommandHelp(c) + return fmt.Errorf("invalid format: %s", format) + } + + var logger log.Logger + logger = log.NewDefaultRawLogger() + if format == output.FormatJSON { + logger = log.NewDefaultJSONLoger() + } + // --- + + // List + installedPlugins, err := plugins.InstalledPluginList() + if err != nil { + return fmt.Errorf("failed to list plugins, error: %s", err) + } + + if len(installedPlugins) == 0 { + log.Warnf("No installed plugin found") + return nil + } + + plugins.SortByName(installedPlugins) + + pluginInfos := plugins.PluginInfos{} + + for _, plugin := range installedPlugins { + pluginInfo, err := createPluginInfo(plugin.Name) + if err != nil { + return err + } + pluginInfos = append(pluginInfos, pluginInfo) + } + + logger.Print(pluginInfos) + // --- + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/plugin_update.go b/vendor/github.com/bitrise-io/bitrise/cli/plugin_update.go new file mode 100644 index 00000000..bd49e7fc --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/plugin_update.go @@ -0,0 +1,100 @@ +package cli + +import ( + "errors" + "fmt" + "os" + + "github.com/bitrise-io/bitrise/plugins" + "github.com/bitrise-io/go-utils/log" + "github.com/urfave/cli" +) + +var pluginUpdateCommand = cli.Command{ + Name: "update", + Usage: "Update bitrise plugin. If not specified, every plugin will be updated.", + Action: func(c *cli.Context) error { + if err := pluginUpdate(c); err != nil { + log.Errorf("Plugin update failed, error: %s", err) + os.Exit(1) + } + return nil + }, + ArgsUsage: "[]", +} + +func pluginUpdate(c *cli.Context) error { + // Input validation + pluginNameToUpdate := "" + + args := c.Args() + if len(args) != 0 { + pluginNameToUpdate = args[0] + } + // --- + + // Update + pluginsToUpdate := []plugins.Plugin{} + + if pluginNameToUpdate != "" { + plugin, found, err := plugins.LoadPlugin(pluginNameToUpdate) + if err != nil { + return fmt.Errorf("failed to check if plugin installed, error: %s", err) + } else if !found { + return fmt.Errorf("plugin is not installed") + } + + pluginsToUpdate = append(pluginsToUpdate, plugin) + } else { + installedPlugins, err := plugins.InstalledPluginList() + if err != nil { + return fmt.Errorf("failed to list plugins, error: %s", err) + } + + if len(installedPlugins) == 0 { + log.Warnf("No installed plugin found") + return nil + } + + plugins.SortByName(installedPlugins) + + pluginsToUpdate = append(pluginsToUpdate, installedPlugins...) + } + + for _, plugin := range pluginsToUpdate { + log.Infof("Updating plugin %s", plugin.Name) + + if newVersion, err := plugins.CheckForNewVersion(plugin); err != nil { + return fmt.Errorf("failed to check for plugin new version, error: %s", err) + } else if newVersion != "" { + log.Printf("Installing new version (%s)", newVersion) + + route, found, err := plugins.ReadPluginRoute(plugin.Name) + if err != nil { + return fmt.Errorf("failed to read plugin route, error: %s", err) + } + if !found { + return errors.New("no route found for already loaded plugin") + } + + plugin, version, err := plugins.InstallPlugin(route.Source, newVersion) + if err != nil { + return fmt.Errorf("failed to install plugin from (%s), error: %s", route.Source, err) + } + + if len(plugin.Description) > 0 { + fmt.Println() + log.Infof("Description:") + fmt.Println(plugin.Description) + } + + fmt.Println() + log.Donef("Plugin (%s) with version (%s) installed ", plugin.Name, version) + } else { + log.Donef("No new version available") + } + } + // --- + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/run.go b/vendor/github.com/bitrise-io/bitrise/cli/run.go new file mode 100644 index 00000000..e2620360 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/run.go @@ -0,0 +1,237 @@ +package cli + +import ( + "fmt" + "os" + "sort" + "strings" + "time" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/bitrise/bitrise" + "github.com/bitrise-io/bitrise/models" + "github.com/bitrise-io/bitrise/version" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/pointers" + "github.com/urfave/cli" +) + +const ( + // DefaultBitriseConfigFileName ... + DefaultBitriseConfigFileName = "bitrise.yml" + // DefaultSecretsFileName ... + DefaultSecretsFileName = ".bitrise.secrets.yml" + + depManagerBrew = "brew" + depManagerTryCheck = "_" +) + +// -------------------- +// Utility +// -------------------- + +func printAboutUtilityWorkflowsText() { + fmt.Println("Note about utility workflows:") + fmt.Println(" Utility workflow names start with '_' (example: _my_utility_workflow).") + fmt.Println(" These workflows can't be triggered directly, but can be used by other workflows") + fmt.Println(" in the before_run and after_run lists.") +} + +func printAvailableWorkflows(config models.BitriseDataModel) { + workflowNames := []string{} + utilityWorkflowNames := []string{} + + for wfName := range config.Workflows { + if strings.HasPrefix(wfName, "_") { + utilityWorkflowNames = append(utilityWorkflowNames, wfName) + } else { + workflowNames = append(workflowNames, wfName) + } + } + sort.Strings(workflowNames) + sort.Strings(utilityWorkflowNames) + + if len(workflowNames) > 0 { + fmt.Println("The following workflows are available:") + for _, wfName := range workflowNames { + fmt.Println(" * " + wfName) + } + + fmt.Println() + fmt.Println("You can run a selected workflow with:") + fmt.Println("$ bitrise run WORKFLOW-ID") + fmt.Println() + } else { + fmt.Println("No workflows are available!") + } + + if len(utilityWorkflowNames) > 0 { + fmt.Println() + fmt.Println("The following utility workflows are defined:") + for _, wfName := range utilityWorkflowNames { + fmt.Println(" * " + wfName) + } + + fmt.Println() + printAboutUtilityWorkflowsText() + fmt.Println() + } +} + +func runAndExit(bitriseConfig models.BitriseDataModel, inventoryEnvironments []envmanModels.EnvironmentItemModel, workflowToRunID string) { + if workflowToRunID == "" { + log.Fatal("No workflow id specified") + } + + if err := bitrise.RunSetupIfNeeded(version.VERSION, false); err != nil { + log.Fatalf("Setup failed, error: %s", err) + } + + startTime := time.Now() + + // Run selected configuration + if buildRunResults, err := runWorkflowWithConfiguration(startTime, workflowToRunID, bitriseConfig, inventoryEnvironments); err != nil { + log.Fatalf("Failed to run workflow, error: %s", err) + } else if buildRunResults.IsBuildFailed() { + os.Exit(1) + } + os.Exit(0) +} + +func printRunningWorkflow(bitriseConfig models.BitriseDataModel, targetWorkflowToRunID string) { + beforeWorkflowIDs := bitriseConfig.Workflows[targetWorkflowToRunID].BeforeRun + afterWorkflowIDs := bitriseConfig.Workflows[targetWorkflowToRunID].AfterRun + workflowsString := "" + if len(beforeWorkflowIDs) == 0 && len(afterWorkflowIDs) == 0 { + workflowsString = "Running workflow: " + } else { + workflowsString = "Running workflows: " + } + + if len(beforeWorkflowIDs) != 0 { + for _, workflowName := range beforeWorkflowIDs { + workflowsString = workflowsString + workflowName + " --> " + } + } + + workflowsString = workflowsString + colorstring.Green(targetWorkflowToRunID) + + if len(afterWorkflowIDs) != 0 { + for _, workflowName := range afterWorkflowIDs { + workflowsString = workflowsString + " --> " + workflowName + } + } + + log.Infof(workflowsString) +} + +// -------------------- +// CLI command +// -------------------- + +func run(c *cli.Context) error { + PrintBitriseHeaderASCIIArt(version.VERSION) + + // + // Expand cli.Context + var prGlobalFlagPtr *bool + if c.GlobalIsSet(PRKey) { + prGlobalFlagPtr = pointers.NewBoolPtr(c.GlobalBool(PRKey)) + } + + var ciGlobalFlagPtr *bool + if c.GlobalIsSet(CIKey) { + ciGlobalFlagPtr = pointers.NewBoolPtr(c.GlobalBool(CIKey)) + } + + workflowToRunID := c.String(WorkflowKey) + if workflowToRunID == "" && len(c.Args()) > 0 { + workflowToRunID = c.Args()[0] + } + + bitriseConfigBase64Data := c.String(ConfigBase64Key) + bitriseConfigPath := c.String(ConfigKey) + deprecatedBitriseConfigPath := c.String(PathKey) + if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { + log.Warn("'path' key is deprecated, use 'config' instead!") + bitriseConfigPath = deprecatedBitriseConfigPath + } + + inventoryBase64Data := c.String(InventoryBase64Key) + inventoryPath := c.String(InventoryKey) + + jsonParams := c.String(JSONParamsKey) + jsonParamsBase64 := c.String(JSONParamsBase64Key) + + runParams, err := parseRunParams( + workflowToRunID, + bitriseConfigPath, bitriseConfigBase64Data, + inventoryPath, inventoryBase64Data, + jsonParams, jsonParamsBase64) + if err != nil { + return fmt.Errorf("Failed to parse command params, error: %s", err) + } + // + + // Inventory validation + inventoryEnvironments, err := CreateInventoryFromCLIParams(runParams.InventoryBase64Data, runParams.InventoryPath) + if err != nil { + log.Fatalf("Failed to create inventory, error: %s", err) + } + + // Config validation + bitriseConfig, warnings, err := CreateBitriseConfigFromCLIParams(runParams.BitriseConfigBase64Data, runParams.BitriseConfigPath) + for _, warning := range warnings { + log.Warnf("warning: %s", warning) + } + if err != nil { + log.Fatalf("Failed to create bitrise config, error: %s", err) + } + + // Workflow id validation + if runParams.WorkflowToRunID == "" { + // no workflow specified + // list all the available ones and then exit + log.Error("No workfow specified!") + fmt.Println() + printAvailableWorkflows(bitriseConfig) + os.Exit(1) + } + if strings.HasPrefix(runParams.WorkflowToRunID, "_") { + // util workflow specified + // print about util workflows and then exit + log.Error("Utility workflows can't be triggered directly") + fmt.Println() + printAboutUtilityWorkflowsText() + os.Exit(1) + } + // + + // + // Main + isPRMode, err := isPRMode(prGlobalFlagPtr, inventoryEnvironments) + if err != nil { + log.Fatalf("Failed to check PR mode, error: %s", err) + } + + if err := registerPrMode(isPRMode); err != nil { + log.Fatalf("Failed to register PR mode, error: %s", err) + } + + isCIMode, err := isCIMode(ciGlobalFlagPtr, inventoryEnvironments) + if err != nil { + log.Fatalf("Failed to check CI mode, error: %s", err) + } + + if err := registerCIMode(isCIMode); err != nil { + log.Fatalf("Failed to register CI mode, error: %s", err) + } + + printRunningWorkflow(bitriseConfig, runParams.WorkflowToRunID) + + runAndExit(bitriseConfig, inventoryEnvironments, runParams.WorkflowToRunID) + // + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/run_test.go b/vendor/github.com/bitrise-io/bitrise/cli/run_test.go new file mode 100644 index 00000000..b5e4af78 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/run_test.go @@ -0,0 +1,1482 @@ +package cli + +import ( + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "github.com/bitrise-io/bitrise/bitrise" + "github.com/bitrise-io/bitrise/configs" + "github.com/bitrise-io/bitrise/models" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestSkipIfEmpty(t *testing.T) { + t.Log("skip_if_empty=true && value=empty => should not add") + { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + skip_if_empty: + envs: + - TEST: test + - TEST: + opts: + skip_if_empty: true + steps: + - script: + is_skippable: true + title: "Envman add DELETE_TEST" + inputs: + - content: | + #!/bin/bash + if [ -z $TEST ] ; then + echo "TEST shuld exist" + exit 1 + fi +` + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "skip_if_empty", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 1, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) + } + + t.Log("skip_if_empty=false && value=empty => should add") + { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + skip_if_empty: + envs: + - TEST: test + - TEST: + opts: + skip_if_empty: false + steps: + - script: + is_skippable: true + title: "Envman add DELETE_TEST" + inputs: + - content: | + #!/bin/bash + if [ ! -z $TEST ] ; then + echo "TEST env shuld not exist" + exit 1 + fi +` + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "skip_if_empty", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 1, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) + } +} + +func TestDeleteEnvironment(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + test: + steps: + - script: + is_skippable: true + title: "Envman add DELETE_TEST" + inputs: + - content: | + #!/bin/bash + envman add --key DELETE_TEST --value "delete test" + - script: + title: "Test env DELETE_TEST" + inputs: + - content: | + #!/bin/bash + set -v + echo "DELETE_TEST: $DELETE_TEST" + if [ -z "$DELETE_TEST" ] ; then + exit 1 + fi + - script: + title: "Delete env DELETE_TEST" + inputs: + - content: | + #!/bin/bash + envman add --key DELETE_TEST --value "" + - script: + title: "Test env DELETE_TEST" + inputs: + - content: | + #!/bin/bash + set -v + echo "DELETE_TEST: $DELETE_TEST" + if [ ! -z "$DELETE_TEST" ] ; then + exit 1 + fi +` + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "test", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 4, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) +} + +func TestStepOutputsInTemplate(t *testing.T) { + inventoryStr := ` +envs: +- TEMPLATE_TEST0: "true" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +app: + envs: + - TEMPLATE_TEST1: "true" + +workflows: + test: + envs: + - TEMPLATE_TEST2: "true" + steps: + - script: + title: "Envman add" + inputs: + - content: | + #!/bin/bash + set -v + envman add --key TEMPLATE_TEST3 --value "true" + - script: + title: "TEMPLATE_TEST0" + run_if: |- + {{enveq "TEMPLATE_TEST0" "true"}} + - script: + title: "TEMPLATE_TEST1" + run_if: |- + {{enveq "TEMPLATE_TEST1" "true"}} + - script: + title: "TEMPLATE_TEST2" + run_if: |- + {{enveq "TEMPLATE_TEST2" "true"}} + - script: + title: "TEMPLATE_TEST3" + run_if: |- + {{enveq "TEMPLATE_TEST3" "true"}} + - script: + title: "TEMPLATE_TEST_NO_VALUE" + run_if: |- + {{enveq "TEMPLATE_TEST_NO_VALUE" "true"}} +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) + require.NoError(t, err) + require.Equal(t, 5, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 1, len(buildRunResults.SkippedSteps)) +} + +func TestFailedStepOutputs(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + test: + steps: + - script: + is_skippable: true + title: "Envman add" + inputs: + - content: | + #!/bin/bash + set -v + envman add --key FAILED_OUTPUT_TEST --value "failed step output" + exit 1 + - script: + title: "Test failed output" + inputs: + - content: | + #!/bin/bash + set -v + echo "FAILED_OUTPUT_TEST: $FAILED_OUTPUT_TEST" + if [[ "$FAILED_OUTPUT_TEST" != "failed step output" ]] ; then + exit 1 + fi +` + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "test", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 1, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 1, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) +} + +func TestBitriseSourceDir(t *testing.T) { + currPth, err := pathutil.NormalizedOSTempDirPath("bitrise_source_dir_test") + require.NoError(t, err) + + testPths := []string{} + for i := 0; i < 4; i++ { + testPth := filepath.Join(currPth, fmt.Sprintf("_test%d", i)) + require.NoError(t, os.RemoveAll(testPth)) + require.NoError(t, os.Mkdir(testPth, 0777)) + + // eval symlinks: the Go generated temp folder on OS X is a symlink + // from /var/ to /private/var/ + testPth, err = filepath.EvalSymlinks(testPth) + require.NoError(t, err) + + defer func() { require.NoError(t, os.RemoveAll(testPth)) }() + + testPths = append(testPths, testPth) + } + + t.Log("BITRISE_SOURCE_DIR defined in Secret") + { + inventoryStr := ` +envs: +- BITRISE_SOURCE_DIR: "` + testPths[0] + `" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + test: + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + echo "BITRISE_SOURCE_DIR: $BITRISE_SOURCE_DIR" + if [[ "$BITRISE_SOURCE_DIR" != "` + testPths[0] + `" ]] ; then + exit 1 + fi +` + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) + require.NoError(t, err) + } + + t.Log("BITRISE_SOURCE_DIR defined in Secret, and in App") + { + inventoryStr := ` +envs: +- BITRISE_SOURCE_DIR: "` + testPths[0] + `" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +app: + envs: + - BITRISE_SOURCE_DIR: "` + testPths[1] + `" + +workflows: + test: + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + echo "BITRISE_SOURCE_DIR: $BITRISE_SOURCE_DIR" + if [[ "$BITRISE_SOURCE_DIR" != "` + testPths[1] + `" ]] ; then + exit 1 + fi +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) + require.NoError(t, err) + } + + t.Log("BITRISE_SOURCE_DIR defined in Secret, App and Workflow") + { + inventoryStr := ` +envs: +- BITRISE_SOURCE_DIR: "` + testPths[0] + `" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +app: + envs: + - BITRISE_SOURCE_DIR: "` + testPths[1] + `" + +workflows: + test: + envs: + - BITRISE_SOURCE_DIR: "` + testPths[2] + `" + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + echo "BITRISE_SOURCE_DIR: $BITRISE_SOURCE_DIR" + if [[ "$BITRISE_SOURCE_DIR" != "` + testPths[2] + `" ]] ; then + exit 1 + fi +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) + require.NoError(t, err) + } + + t.Log("BITRISE_SOURCE_DIR defined in secret, App, Workflow and Step") + { + inventoryStr := ` +envs: +- BITRISE_SOURCE_DIR: "` + testPths[0] + `" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +app: + envs: + - BITRISE_SOURCE_DIR: "` + testPths[1] + `" + +workflows: + test: + envs: + - BITRISE_SOURCE_DIR: "` + testPths[2] + `" + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + envman add --key BITRISE_SOURCE_DIR --value ` + testPths[3] + ` + - script: + inputs: + - content: | + #!/bin/bash + set -v + echo "BITRISE_SOURCE_DIR: $BITRISE_SOURCE_DIR" + if [[ "$BITRISE_SOURCE_DIR" != "` + testPths[3] + `" ]] ; then + exit 1 + fi +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) + require.NoError(t, err) + } +} + +func TestEnvOrders(t *testing.T) { + t.Log("Only secret env - secret env should be use") + { + inventoryStr := ` +envs: +- ENV_ORDER_TEST: "should be the 1." +` + + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + test: + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + echo "ENV_ORDER_TEST: $ENV_ORDER_TEST" + if [[ "$ENV_ORDER_TEST" != "should be the 1." ]] ; then + exit 1 + fi +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) + require.NoError(t, err) + } + + t.Log("Secret env & app env also defined - app env should be use") + { + inventoryStr := ` +envs: +- ENV_ORDER_TEST: "should be the 1." +` + + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +app: + envs: + - ENV_ORDER_TEST: "should be the 2." + +workflows: + test: + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + echo "ENV_ORDER_TEST: $ENV_ORDER_TEST" + if [[ "$ENV_ORDER_TEST" != "should be the 2." ]] ; then + exit 1 + fi + +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) + require.NoError(t, err) + } + + t.Log("Secret env & app env && workflow env also defined - workflow env should be use") + { + inventoryStr := ` +envs: +- ENV_ORDER_TEST: "should be the 1." +` + + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +app: + envs: + - ENV_ORDER_TEST: "should be the 2." + +workflows: + test: + envs: + - ENV_ORDER_TEST: "should be the 3." + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + echo "ENV_ORDER_TEST: $ENV_ORDER_TEST" + if [[ "$ENV_ORDER_TEST" != "should be the 3." ]] ; then + exit 1 + fi +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) + require.NoError(t, err) + } + + t.Log("Secret env & app env && workflow env && step output env also defined - step output env should be use") + { + inventoryStr := ` +envs: +- ENV_ORDER_TEST: "should be the 1." +` + + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +app: + envs: + - ENV_ORDER_TEST: "should be the 2." + +workflows: + test: + envs: + - ENV_ORDER_TEST: "should be the 3." + steps: + - script: + inputs: + - content: envman add --key ENV_ORDER_TEST --value "should be the 4." + - script: + inputs: + - content: | + #!/bin/bash + set -v + echo "ENV_ORDER_TEST: $ENV_ORDER_TEST" + if [[ "$ENV_ORDER_TEST" != "should be the 4." ]] ; then + exit 1 + fi +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) + require.NoError(t, err) + } +} + +// Test - Bitrise activateAndRunWorkflow +// If workflow contains no steps +func Test0Steps1Workflows(t *testing.T) { + workflow := models.WorkflowModel{} + + require.NoError(t, os.Setenv("BITRISE_BUILD_STATUS", "0")) + defer func() { require.NoError(t, os.Unsetenv("BITRISE_BUILD_STATUS")) }() + + require.NoError(t, os.Setenv("STEPLIB_BUILD_STATUS", "0")) + defer func() { require.NoError(t, os.Unsetenv("STEPLIB_BUILD_STATUS")) }() + + config := models.BitriseDataModel{ + FormatVersion: "1.0.0", + DefaultStepLibSource: "https://github.com/bitrise-io/bitrise-steplib.git", + Workflows: map[string]models.WorkflowModel{ + "zero_steps": workflow, + }, + } + + _, err := config.Validate() + require.NoError(t, err) + + buildRunResults := models.BuildRunResultsModel{ + StartTime: time.Now(), + StepmanUpdates: map[string]int{}, + } + + require.NoError(t, configs.InitPaths()) + + buildRunResults, err = runWorkflowWithConfiguration(time.Now(), "zero_steps", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 0, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) + + require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Test - Bitrise activateAndRunWorkflow +// Workflow contains before and after workflow, and no one contains steps +func Test0Steps3WorkflowsBeforeAfter(t *testing.T) { + require.NoError(t, os.Setenv("BITRISE_BUILD_STATUS", "0")) + defer func() { require.NoError(t, os.Unsetenv("BITRISE_BUILD_STATUS")) }() + + require.NoError(t, os.Setenv("STEPLIB_BUILD_STATUS", "0")) + defer func() { require.NoError(t, os.Unsetenv("STEPLIB_BUILD_STATUS")) }() + + beforeWorkflow := models.WorkflowModel{} + afterWorkflow := models.WorkflowModel{} + + workflow := models.WorkflowModel{ + BeforeRun: []string{"before"}, + AfterRun: []string{"after"}, + } + + config := models.BitriseDataModel{ + FormatVersion: "1.0.0", + DefaultStepLibSource: "https://github.com/bitrise-io/bitrise-steplib.git", + Workflows: map[string]models.WorkflowModel{ + "target": workflow, + "before": beforeWorkflow, + "after": afterWorkflow, + }, + } + + _, err := config.Validate() + require.NoError(t, err) + + buildRunResults := models.BuildRunResultsModel{ + StartTime: time.Now(), + StepmanUpdates: map[string]int{}, + } + + buildRunResults, err = activateAndRunWorkflow("target", workflow, config, buildRunResults, &[]envmanModels.EnvironmentItemModel{}, "") + require.NoError(t, err) + require.Equal(t, 0, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) + + require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Test - Bitrise Validate workflow +// Workflow contains before and after workflow, and no one contains steps, but circular wofklow dependecy exist, which should fail +func Test0Steps3WorkflowsCircularDependency(t *testing.T) { + require.NoError(t, os.Setenv("BITRISE_BUILD_STATUS", "0")) + defer func() { require.NoError(t, os.Unsetenv("BITRISE_BUILD_STATUS")) }() + + require.NoError(t, os.Setenv("STEPLIB_BUILD_STATUS", "0")) + defer func() { require.NoError(t, os.Unsetenv("STEPLIB_BUILD_STATUS")) }() + + beforeWorkflow := models.WorkflowModel{ + BeforeRun: []string{"target"}, + } + + afterWorkflow := models.WorkflowModel{} + + workflow := models.WorkflowModel{ + BeforeRun: []string{"before"}, + AfterRun: []string{"after"}, + } + + config := models.BitriseDataModel{ + FormatVersion: "1.0.0", + DefaultStepLibSource: "https://github.com/bitrise-io/bitrise-steplib.git", + Workflows: map[string]models.WorkflowModel{ + "target": workflow, + "before": beforeWorkflow, + "after": afterWorkflow, + }, + } + + _, err := config.Validate() + require.Error(t, err) + + require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Test - Bitrise activateAndRunWorkflow +// Trivial test with 1 workflow +func Test1Workflows(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + trivial_fail: + steps: + - script: + title: Should success + - script: + title: Should fail, but skippable + is_skippable: true + inputs: + - content: | + #!/bin/bash + set -v + exit 2 + - script: + title: Should success + - script: + title: Should fail + inputs: + - content: | + #!/bin/bash + set -v + exit 2 + - script: + title: Should success + is_always_run: true + - script: + title: Should skipped + ` + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + workflow, found := config.Workflows["trivial_fail"] + require.Equal(t, true, found) + + buildRunResults := models.BuildRunResultsModel{ + StartTime: time.Now(), + StepmanUpdates: map[string]int{}, + } + + buildRunResults, err = activateAndRunWorkflow("trivial_fail", workflow, config, buildRunResults, &[]envmanModels.EnvironmentItemModel{}, "") + require.NoError(t, err) + require.Equal(t, 3, len(buildRunResults.SuccessSteps)) + require.Equal(t, 1, len(buildRunResults.FailedSteps)) + require.Equal(t, 1, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 1, len(buildRunResults.SkippedSteps)) + + require.Equal(t, "1", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "1", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Test - Bitrise activateAndRunWorkflow +// Trivial test with before, after workflows +func Test3Workflows(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + before1: + steps: + - script: + title: Should success + - script: + title: Should fail, but skippable + is_skippable: true + inputs: + - content: | + #!/bin/bash + set -v + exit 2 + - script: + title: Should success + + before2: + steps: + - script: + title: Should success + + target: + before_run: + - before1 + - before2 + after_run: + - after1 + - after2 + steps: + - script: + title: Should fail + inputs: + - content: | + #!/bin/bash + set -v + exit 2 + + after1: + steps: + - script: + title: Should fail + is_always_run: true + inputs: + - content: | + #!/bin/bash + set -v + exit 2 + + after2: + steps: + - script: + title: Should be skipped + ` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, found := config.Workflows["target"] + require.Equal(t, true, found) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 3, len(buildRunResults.SuccessSteps)) + require.Equal(t, 2, len(buildRunResults.FailedSteps)) + require.Equal(t, 1, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 1, len(buildRunResults.SkippedSteps)) + + require.Equal(t, "1", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "1", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Test - Bitrise ConfigModelFromYAMLBytes +// Workflow contains before and after workflow, and no one contains steps, but circular wofklow dependecy exist, which should fail +func TestRefeneceCycle(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + before1: + before_run: + - before2 + + before2: + before_run: + - before1 + + target: + before_run: + - before1 + - before2 + ` + _, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.Error(t, err) + require.Equal(t, 0, len(warnings)) +} + +// Test - Bitrise BuildStatusEnv +// Checks if BuildStatusEnv is set correctly +func TestBuildStatusEnv(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + before1: + steps: + - script: + title: Should success + - script: + title: Should fail, but skippable + is_skippable: true + inputs: + - content: | + #!/bin/bash + set -v + exit 2 + - script: + title: Should success + + before2: + steps: + - script: + title: Should success + + target: + steps: + - script: + title: Should success + inputs: + - content: | + #!/bin/bash + set -v + if [[ "$BITRISE_BUILD_STATUS" != "0" ]] ; then + exit 1 + fi + if [[ "$STEPLIB_BUILD_STATUS" != "0" ]] ; then + exit 1 + fi + - script: + title: Should fail, but skippable + is_skippable: true + inputs: + - content: | + #!/bin/bash + set -v + echo 'This is a before workflow' + exit 2 + - script: + title: Should success + inputs: + - content: | + #!/bin/bash + set -v + if [[ "$BITRISE_BUILD_STATUS" != "0" ]] ; then + exit 1 + fi + if [[ "$STEPLIB_BUILD_STATUS" != "0" ]] ; then + exit 1 + fi + - script: + title: Should fail + inputs: + - content: | + #!/bin/bash + set -v + exit 1 + - script: + title: Should success + is_always_run: true + inputs: + - content: | + #!/bin/bash + set -v + if [[ "$BITRISE_BUILD_STATUS" != "1" ]] ; then + echo "should fail" + fi + if [[ "$STEPLIB_BUILD_STATUS" != "1" ]] ; then + echo "should fail" + fi + - script: + title: Should skipped + ` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, found := config.Workflows["target"] + require.Equal(t, true, found) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 3, len(buildRunResults.SuccessSteps)) + require.Equal(t, 1, len(buildRunResults.FailedSteps)) + require.Equal(t, 1, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 1, len(buildRunResults.SkippedSteps)) + + require.Equal(t, "1", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "1", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Test - Bitrise activateAndRunWorkflow +// Trivial fail test +func TestFail(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + target: + steps: + - script: + title: Should success + - script: + title: Should fail, but skippable + is_skippable: true + inputs: + - content: | + #!/bin/bash + set -v + exit 2 + - script: + title: Should success + - script: + title: Should fail + inputs: + - content: | + #!/bin/bash + set -v + exit 1 + - script: + title: Should skipped + - script: + title: Should success + is_always_run: true + ` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, found := config.Workflows["target"] + require.Equal(t, true, found) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 3, len(buildRunResults.SuccessSteps)) + require.Equal(t, 1, len(buildRunResults.FailedSteps)) + require.Equal(t, 1, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 1, len(buildRunResults.SkippedSteps)) + + require.Equal(t, "1", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "1", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Test - Bitrise activateAndRunWorkflow +// Trivial success test +func TestSuccess(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + target: + steps: + - script: + title: Should success + ` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, found := config.Workflows["target"] + require.Equal(t, true, found) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 1, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) + + require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Test - Bitrise BuildStatusEnv +// Checks if BuildStatusEnv is set correctly +func TestBuildFailedMode(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + before1: + title: before1 + steps: + - script: + title: Should success + - script: + title: Should fail + inputs: + - content: | + #!/bin/bash + set -v + exit 2 + + before2: + title: before2 + steps: + - script: + title: Should skipped + + target: + title: target + before_run: + - before1 + - before2 + steps: + - script: + title: Should skipped + ` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, found := config.Workflows["target"] + require.Equal(t, true, found) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 1, len(buildRunResults.SuccessSteps)) + require.Equal(t, 1, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 2, len(buildRunResults.SkippedSteps)) + + require.Equal(t, "1", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "1", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Test - Bitrise Environments +// Trivial test for workflow environment handling +// Before workflows env should be visible in target and after workflow +func TestWorkflowEnvironments(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + before: + envs: + - BEFORE_ENV: beforeenv + + target: + title: target + before_run: + - before + after_run: + - after + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + if [[ "$BEFORE_ENV" != "beforeenv" ]] ; then + exit 1 + fi + + after: + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + if [[ "$BEFORE_ENV" != "beforeenv" ]] ; then + exit 1 + fi + ` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, found := config.Workflows["target"] + require.Equal(t, true, found) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 2, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) + + require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Test - Bitrise Environments +// Test for same env in before and target workflow, actual workflow should overwrite environemnt and use own value +func TestWorkflowEnvironmentOverWrite(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + before: + envs: + - ENV: env1 + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + echo ${ENV} + if [[ "$ENV" != "env1" ]] ; then + exit 1 + fi + + target: + title: target + envs: + - ENV: env2 + before_run: + - before + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + echo ${ENV} + if [[ "$ENV" != "env2" ]] ; then + exit 1 + fi +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, found := config.Workflows["target"] + require.Equal(t, true, found) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 2, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) + + require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Test - Bitrise Environments +// Target workflows env should be visible in before and after workflow +func TestTargetDefinedWorkflowEnvironment(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + before: + steps: + - script: + inputs: + - content: | + #!/bin/bash + set -v + echo ${ENV} + if [[ "$ENV" != "targetenv" ]] ; then + exit 3 + fi + + target: + title: target + envs: + - ENV: targetenv + before_run: + - before +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, found := config.Workflows["target"] + require.Equal(t, true, found) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 1, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) + + require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Test - Bitrise Environments +// Step input should visible only for actual step and invisible for other steps +func TestStepInputEnvironment(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + before: + steps: + - script@1.1.3: + inputs: + - working_dir: $HOME + + target: + title: target + before_run: + - before + steps: + - script@1.1.3: + title: "${working_dir} should not exist" + inputs: + - content: | + #!/bin/bash + set -v + env + if [ ! -z "$working_dir" ] ; then + echo ${working_dir} + exit 3 + fi +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, found := config.Workflows["target"] + require.Equal(t, true, found) + + if os.Getenv("working_dir") != "" { + require.Equal(t, nil, os.Unsetenv("working_dir")) + } + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 2, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) + + require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +// Outputs exported with `envman add` should be accessible for subsequent Steps. +func TestStepOutputEnvironment(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + out-test: + title: Output Test + steps: + - script: + inputs: + - content: envman -l=debug add --key MY_TEST_1 --value 'Test value 1' + - script: + inputs: + - content: |- + if [[ "${MY_TEST_1}" != "Test value 1" ]] ; then + echo " [!] MY_TEST_1 invalid: ${MY_TEST_1}" + exit 1 + fi + - script: + title: Should fail + inputs: + - content: |- + envman add --key MY_TEST_2 --value 'Test value 2' + # exported output, but test fails + exit 22 + - script: + is_always_run: true + inputs: + - content: |- + if [[ "${MY_TEST_2}" != "Test value 2" ]] ; then + exit 1 + fi +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + _, found := config.Workflows["out-test"] + require.Equal(t, true, found) + + _, err = config.Validate() + require.NoError(t, err) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "out-test", config, []envmanModels.EnvironmentItemModel{}) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) + require.Equal(t, 3, len(buildRunResults.SuccessSteps)) + require.Equal(t, 1, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + + // the exported output envs should NOT be exposed here, should NOT be available! + require.Equal(t, "", os.Getenv("MY_TEST_1")) + require.Equal(t, "", os.Getenv("MY_TEST_2")) + + // standard, Build Status ENV test + require.Equal(t, "1", os.Getenv("BITRISE_BUILD_STATUS")) + require.Equal(t, "1", os.Getenv("STEPLIB_BUILD_STATUS")) +} + +func TestLastWorkflowIDInConfig(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + before: + + target: + title: target + before_run: + - before + after_run: + - after1 + + after1: + after_run: + - after2 + + after2: + ` + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + last, err := lastWorkflowIDInConfig("target", config) + require.NoError(t, err) + require.Equal(t, "after2", last) +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params.go b/vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params.go new file mode 100644 index 00000000..9d20a4e4 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params.go @@ -0,0 +1,138 @@ +package cli + +import ( + "encoding/base64" + "encoding/json" +) + +// -------------------- +// Models +// -------------------- + +// RunAndTriggerParamsModel ... +type RunAndTriggerParamsModel struct { + // Run Params + WorkflowToRunID string `json:"workflow"` + + // Trigger Params + TriggerPattern string `json:"pattern"` + + PushBranch string `json:"push-branch"` + PRSourceBranch string `json:"pr-source-branch"` + PRTargetBranch string `json:"pr-target-branch"` + Tag string `json:"tag"` + + // Trigger Check Params + Format string `json:"format"` + + // Bitrise Config Params + BitriseConfigPath string `json:"config"` + BitriseConfigBase64Data string `json:"config-base64"` + + InventoryPath string `json:"inventory"` + InventoryBase64Data string `json:"inventory-base64"` +} + +func parseRunAndTriggerJSONParams(jsonParams string) (RunAndTriggerParamsModel, error) { + params := RunAndTriggerParamsModel{} + if err := json.Unmarshal([]byte(jsonParams), ¶ms); err != nil { + return RunAndTriggerParamsModel{}, err + } + return params, nil +} + +func parseRunAndTriggerParams( + workflowToRunID, + triggerPattern, + pushBranch, prSourceBranch, prTargetBranch, tag, + format, + bitriseConfigPath, bitriseConfigBase64Data, + inventoryPath, inventoryBase64Data, + jsonParams, base64JSONParams string) (RunAndTriggerParamsModel, error) { + params := RunAndTriggerParamsModel{} + var err error + + // Parse json params if exist + if jsonParams == "" && base64JSONParams != "" { + jsonParamsBytes, err := base64.StdEncoding.DecodeString(base64JSONParams) + if err != nil { + return RunAndTriggerParamsModel{}, err + } + jsonParams = string(jsonParamsBytes) + } + + if jsonParams != "" { + params, err = parseRunAndTriggerJSONParams(jsonParams) + if err != nil { + return RunAndTriggerParamsModel{}, err + } + } + + // Owerride params + if workflowToRunID != "" { + params.WorkflowToRunID = workflowToRunID + } + + if triggerPattern != "" { + params.TriggerPattern = triggerPattern + } + + if pushBranch != "" { + params.PushBranch = pushBranch + } + if prSourceBranch != "" { + params.PRSourceBranch = prSourceBranch + } + if prTargetBranch != "" { + params.PRTargetBranch = prTargetBranch + } + if tag != "" { + params.Tag = tag + } + + if format != "" { + params.Format = format + } + + if bitriseConfigPath != "" { + params.BitriseConfigPath = bitriseConfigPath + } + if bitriseConfigBase64Data != "" { + params.BitriseConfigBase64Data = bitriseConfigBase64Data + } + if inventoryPath != "" { + params.InventoryPath = inventoryPath + } + if inventoryBase64Data != "" { + params.InventoryBase64Data = inventoryBase64Data + } + + return params, nil +} + +func parseRunParams( + workflowToRunID, + bitriseConfigPath, bitriseConfigBase64Data, + inventoryPath, inventoryBase64Data, + jsonParams, base64JSONParams string) (RunAndTriggerParamsModel, error) { + return parseRunAndTriggerParams(workflowToRunID, "", "", "", "", "", "", bitriseConfigPath, bitriseConfigBase64Data, inventoryPath, inventoryBase64Data, jsonParams, base64JSONParams) +} + +func parseTriggerParams( + triggerPattern, + pushBranch, prSourceBranch, prTargetBranch, tag, + bitriseConfigPath, bitriseConfigBase64Data, + inventoryPath, inventoryBase64Data, + jsonParams, base64JSONParams string) (RunAndTriggerParamsModel, error) { + return parseRunAndTriggerParams("", triggerPattern, pushBranch, prSourceBranch, prTargetBranch, tag, "", bitriseConfigPath, bitriseConfigBase64Data, inventoryPath, inventoryBase64Data, jsonParams, base64JSONParams) +} + +func parseTriggerCheckParams( + triggerPattern, + pushBranch, prSourceBranch, prTargetBranch, tag, + format, + bitriseConfigPath, bitriseConfigBase64Data, + inventoryPath, inventoryBase64Data, + jsonParams, base64JSONParams string) (RunAndTriggerParamsModel, error) { + return parseRunAndTriggerParams("", triggerPattern, pushBranch, prSourceBranch, prTargetBranch, tag, format, bitriseConfigPath, bitriseConfigBase64Data, inventoryPath, inventoryBase64Data, jsonParams, base64JSONParams) +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params_test.go b/vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params_test.go new file mode 100644 index 00000000..68eae2a4 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params_test.go @@ -0,0 +1,483 @@ +package cli + +import ( + "encoding/base64" + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func toBase64(t *testing.T, str string) string { + bytes := base64.StdEncoding.EncodeToString([]byte(str)) + return string(bytes) +} + +func toJSON(t *testing.T, stringStringMap map[string]string) string { + bytes, err := json.Marshal(stringStringMap) + require.NoError(t, err) + return string(bytes) +} + +func TestParseRunAndTriggerJSONParams(t *testing.T) { + t.Log("it parses cli params") + { + paramsMap := map[string]string{ + WorkflowKey: "primary", + + PatternKey: "master", + PushBranchKey: "deploy", + PRSourceBranchKey: "development", + PRTargetBranchKey: "release", + TagKey: "0.9.0", + + OuputFormatKey: "json", + + ConfigKey: "bitrise.yml", + ConfigBase64Key: toBase64(t, "bitrise.yml"), + + InventoryKey: ".secrets.bitrise.yml", + InventoryBase64Key: toBase64(t, ".secrets.bitrise.yml"), + } + params, err := parseRunAndTriggerJSONParams(toJSON(t, paramsMap)) + require.NoError(t, err) + + require.Equal(t, "primary", params.WorkflowToRunID) + + require.Equal(t, "master", params.TriggerPattern) + require.Equal(t, "deploy", params.PushBranch) + require.Equal(t, "development", params.PRSourceBranch) + require.Equal(t, "release", params.PRTargetBranch) + require.Equal(t, "0.9.0", params.Tag) + + require.Equal(t, "json", params.Format) + + require.Equal(t, "bitrise.yml", params.BitriseConfigPath) + require.Equal(t, toBase64(t, "bitrise.yml"), params.BitriseConfigBase64Data) + + require.Equal(t, ".secrets.bitrise.yml", params.InventoryPath) + require.Equal(t, toBase64(t, ".secrets.bitrise.yml"), params.InventoryBase64Data) + } + + t.Log("it fails for invalid json") + { + params, err := parseRunAndTriggerJSONParams("master") + require.Error(t, err) + + require.Equal(t, "", params.WorkflowToRunID) + + require.Equal(t, "", params.TriggerPattern) + require.Equal(t, "", params.PushBranch) + require.Equal(t, "", params.PRSourceBranch) + require.Equal(t, "", params.PRTargetBranch) + + require.Equal(t, "", params.Format) + + require.Equal(t, "", params.BitriseConfigPath) + require.Equal(t, "", params.BitriseConfigBase64Data) + + require.Equal(t, "", params.InventoryPath) + require.Equal(t, "", params.InventoryBase64Data) + } +} + +func TestParseRunAndTriggerParams(t *testing.T) { + t.Log("it parses cli params") + { + workflow := "primary" + + pattern := "*" + pushBranch := "master" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "0.9.0" + format := "json" + + bitriseConfigPath := "bitrise.yml" + bitriseConfigBase64Data := toBase64(t, "bitrise.yml") + + inventoryPath := ".secrets.bitrise.yml" + inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") + + jsonParams := "" + base64JSONParams := "" + + params, err := parseRunAndTriggerParams( + workflow, + pattern, + pushBranch, prSourceBranch, prTargetBranch, tag, + format, + bitriseConfigPath, bitriseConfigBase64Data, + inventoryPath, inventoryBase64Data, + jsonParams, base64JSONParams, + ) + require.NoError(t, err) + + require.Equal(t, workflow, params.WorkflowToRunID) + + require.Equal(t, pattern, params.TriggerPattern) + require.Equal(t, pushBranch, params.PushBranch) + require.Equal(t, prSourceBranch, params.PRSourceBranch) + require.Equal(t, prTargetBranch, params.PRTargetBranch) + require.Equal(t, tag, params.Tag) + + require.Equal(t, format, params.Format) + + require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) + require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) + + require.Equal(t, inventoryPath, params.InventoryPath) + require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) + } + + t.Log("it parses json params") + { + workflow := "primary" + + pattern := "*" + pushBranch := "master" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "0.9.0" + format := "json" + + bitriseConfigPath := "bitrise.yml" + bitriseConfigBase64Data := toBase64(t, "bitrise.yml") + + inventoryPath := ".secrets.bitrise.yml" + inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") + + paramsMap := map[string]string{ + WorkflowKey: workflow, + + PatternKey: pattern, + PushBranchKey: pushBranch, + PRSourceBranchKey: prSourceBranch, + PRTargetBranchKey: prTargetBranch, + TagKey: tag, + OuputFormatKey: format, + + ConfigKey: bitriseConfigPath, + ConfigBase64Key: bitriseConfigBase64Data, + + InventoryKey: inventoryPath, + InventoryBase64Key: inventoryBase64Data, + } + + jsonParams := toJSON(t, paramsMap) + base64JSONParams := "" + + params, err := parseRunAndTriggerParams("", "", "", "", "", "", "", "", "", "", "", jsonParams, base64JSONParams) + require.NoError(t, err) + + require.Equal(t, workflow, params.WorkflowToRunID) + + require.Equal(t, pattern, params.TriggerPattern) + require.Equal(t, pushBranch, params.PushBranch) + require.Equal(t, prSourceBranch, params.PRSourceBranch) + require.Equal(t, prTargetBranch, params.PRTargetBranch) + require.Equal(t, tag, params.Tag) + + require.Equal(t, format, params.Format) + + require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) + require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) + + require.Equal(t, inventoryPath, params.InventoryPath) + require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) + } + + t.Log("it parses json params decoded in base64") + { + workflow := "primary" + + pattern := "*" + pushBranch := "master" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "0.9.0" + format := "json" + + bitriseConfigPath := "bitrise.yml" + bitriseConfigBase64Data := toBase64(t, "bitrise.yml") + + inventoryPath := ".secrets.bitrise.yml" + inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") + + paramsMap := map[string]string{ + WorkflowKey: workflow, + + PatternKey: pattern, + PushBranchKey: pushBranch, + PRSourceBranchKey: prSourceBranch, + PRTargetBranchKey: prTargetBranch, + TagKey: tag, + OuputFormatKey: format, + + ConfigKey: bitriseConfigPath, + ConfigBase64Key: bitriseConfigBase64Data, + + InventoryKey: inventoryPath, + InventoryBase64Key: inventoryBase64Data, + } + + jsonParams := "" + base64JSONParams := toBase64(t, toJSON(t, paramsMap)) + + params, err := parseRunAndTriggerParams("", "", "", "", "", "", "", "", "", "", "", jsonParams, base64JSONParams) + require.NoError(t, err) + + require.Equal(t, workflow, params.WorkflowToRunID) + + require.Equal(t, pattern, params.TriggerPattern) + require.Equal(t, pushBranch, params.PushBranch) + require.Equal(t, prSourceBranch, params.PRSourceBranch) + require.Equal(t, prTargetBranch, params.PRTargetBranch) + require.Equal(t, tag, params.Tag) + + require.Equal(t, format, params.Format) + + require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) + require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) + + require.Equal(t, inventoryPath, params.InventoryPath) + require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) + } + + t.Log("json params has priority over json params encoded in base 64") + { + workflow := "primary" + + pattern := "*" + pushBranch := "master" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "0.9.0" + format := "json" + + bitriseConfigPath := "bitrise.yml" + bitriseConfigBase64Data := toBase64(t, "bitrise.yml") + + inventoryPath := ".secrets.bitrise.yml" + inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") + + paramsMap := map[string]string{ + WorkflowKey: workflow, + + PatternKey: pattern, + PushBranchKey: pushBranch, + PRSourceBranchKey: prSourceBranch, + PRTargetBranchKey: prTargetBranch, + TagKey: tag, + OuputFormatKey: format, + + ConfigKey: bitriseConfigPath, + ConfigBase64Key: bitriseConfigBase64Data, + + InventoryKey: inventoryPath, + InventoryBase64Key: inventoryBase64Data, + } + + jsonParams := `{"workflow":"test"}` + base64JSONParams := toBase64(t, toJSON(t, paramsMap)) + + params, err := parseRunAndTriggerParams("", "", "", "", "", "", "", "", "", "", "", jsonParams, base64JSONParams) + require.NoError(t, err) + + require.Equal(t, "test", params.WorkflowToRunID) + + require.Equal(t, "", params.TriggerPattern) + require.Equal(t, "", params.PushBranch) + require.Equal(t, "", params.PRSourceBranch) + require.Equal(t, "", params.PRTargetBranch) + require.Equal(t, "", params.Tag) + + require.Equal(t, "", params.Format) + + require.Equal(t, "", params.BitriseConfigPath) + require.Equal(t, "", params.BitriseConfigBase64Data) + + require.Equal(t, "", params.InventoryPath) + require.Equal(t, "", params.InventoryBase64Data) + } + + t.Log("cli params can override json params") + { + workflow := "primary" + + pattern := "*" + pushBranch := "master" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "0.9.0" + format := "json" + + bitriseConfigPath := "bitrise.yml" + bitriseConfigBase64Data := toBase64(t, "bitrise.yml") + + inventoryPath := ".secrets.bitrise.yml" + inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") + + jsonParams := `{"workflow":"test","pattern":"feature/","config":"test.bitrise.yml","inventory":".test.secrets.bitrise.yml"}` + base64JSONParams := "" + + params, err := parseRunAndTriggerParams( + workflow, + pattern, + pushBranch, prSourceBranch, prTargetBranch, tag, + format, + bitriseConfigPath, bitriseConfigBase64Data, + inventoryPath, inventoryBase64Data, + jsonParams, base64JSONParams, + ) + require.NoError(t, err) + + require.Equal(t, workflow, params.WorkflowToRunID) + + require.Equal(t, pattern, params.TriggerPattern) + require.Equal(t, pushBranch, params.PushBranch) + require.Equal(t, prSourceBranch, params.PRSourceBranch) + require.Equal(t, prTargetBranch, params.PRTargetBranch) + require.Equal(t, tag, params.Tag) + + require.Equal(t, format, params.Format) + + require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) + require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) + + require.Equal(t, inventoryPath, params.InventoryPath) + require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) + } +} + +func TestParseRunParams(t *testing.T) { + t.Log("it parses cli params") + { + workflow := "primary" + + bitriseConfigPath := "bitrise.yml" + bitriseConfigBase64Data := toBase64(t, "bitrise.yml") + + inventoryPath := ".secrets.bitrise.yml" + inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") + + jsonParams := "" + base64JSONParams := "" + + params, err := parseRunParams( + workflow, + bitriseConfigPath, bitriseConfigBase64Data, + inventoryPath, inventoryBase64Data, + jsonParams, base64JSONParams, + ) + require.NoError(t, err) + + require.Equal(t, workflow, params.WorkflowToRunID) + + require.Equal(t, "", params.TriggerPattern) + require.Equal(t, "", params.PushBranch) + require.Equal(t, "", params.PRSourceBranch) + require.Equal(t, "", params.PRTargetBranch) + require.Equal(t, "", params.Tag) + + require.Equal(t, "", params.Format) + + require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) + require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) + + require.Equal(t, inventoryPath, params.InventoryPath) + require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) + } +} + +func TestParseTriggerParams(t *testing.T) { + t.Log("it parses cli params") + { + pattern := "*" + pushBranch := "master" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "0.9.0" + + bitriseConfigPath := "bitrise.yml" + bitriseConfigBase64Data := toBase64(t, "bitrise.yml") + + inventoryPath := ".secrets.bitrise.yml" + inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") + + jsonParams := "" + base64JSONParams := "" + + params, err := parseTriggerParams( + pattern, + pushBranch, prSourceBranch, prTargetBranch, tag, + bitriseConfigPath, bitriseConfigBase64Data, + inventoryPath, inventoryBase64Data, + jsonParams, base64JSONParams, + ) + require.NoError(t, err) + + require.Equal(t, "", params.WorkflowToRunID) + + require.Equal(t, pattern, params.TriggerPattern) + require.Equal(t, pushBranch, params.PushBranch) + require.Equal(t, prSourceBranch, params.PRSourceBranch) + require.Equal(t, prTargetBranch, params.PRTargetBranch) + require.Equal(t, tag, params.Tag) + + require.Equal(t, "", params.Format) + + require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) + require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) + + require.Equal(t, inventoryPath, params.InventoryPath) + require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) + } +} + +func TestParseTriggerCheckParams(t *testing.T) { + t.Log("it parses cli params") + { + pattern := "*" + pushBranch := "master" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "0.9.0" + format := "json" + + bitriseConfigPath := "bitrise.yml" + bitriseConfigBase64Data := toBase64(t, "bitrise.yml") + + inventoryPath := ".secrets.bitrise.yml" + inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") + + jsonParams := "" + base64JSONParams := "" + + params, err := parseTriggerCheckParams( + pattern, + pushBranch, prSourceBranch, prTargetBranch, tag, + format, + bitriseConfigPath, bitriseConfigBase64Data, + inventoryPath, inventoryBase64Data, + jsonParams, base64JSONParams, + ) + require.NoError(t, err) + + require.Equal(t, "", params.WorkflowToRunID) + + require.Equal(t, pattern, params.TriggerPattern) + require.Equal(t, pushBranch, params.PushBranch) + require.Equal(t, prSourceBranch, params.PRSourceBranch) + require.Equal(t, prTargetBranch, params.PRTargetBranch) + require.Equal(t, tag, params.Tag) + + require.Equal(t, format, params.Format) + + require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) + require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) + + require.Equal(t, inventoryPath, params.InventoryPath) + require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/run_util.go b/vendor/github.com/bitrise-io/bitrise/cli/run_util.go new file mode 100644 index 00000000..37cc8bd3 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/run_util.go @@ -0,0 +1,981 @@ +package cli + +import ( + "encoding/base64" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "time" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/bitrise/bitrise" + "github.com/bitrise-io/bitrise/configs" + "github.com/bitrise-io/bitrise/models" + "github.com/bitrise-io/bitrise/plugins" + "github.com/bitrise-io/bitrise/toolkits" + "github.com/bitrise-io/bitrise/tools" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/command/git" + "github.com/bitrise-io/go-utils/errorutil" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/pointers" + "github.com/bitrise-io/go-utils/retry" + "github.com/bitrise-io/go-utils/versions" + stepmanModels "github.com/bitrise-io/stepman/models" +) + +func isPRMode(prGlobalFlagPtr *bool, inventoryEnvironments []envmanModels.EnvironmentItemModel) (bool, error) { + if prGlobalFlagPtr != nil { + return *prGlobalFlagPtr, nil + } + + prIDEnv := os.Getenv(configs.PullRequestIDEnvKey) + prModeEnv := os.Getenv(configs.PRModeEnvKey) + + if prIDEnv != "" || prModeEnv == "true" { + return true, nil + } + + for _, env := range inventoryEnvironments { + key, value, err := env.GetKeyValuePair() + if err != nil { + return false, err + } + + if key == configs.PullRequestIDEnvKey && value != "" { + return true, nil + } + if key == configs.PRModeEnvKey && value == "true" { + return true, nil + } + } + + return false, nil +} + +func registerPrMode(isPRMode bool) error { + configs.IsPullRequestMode = isPRMode + + if isPRMode { + log.Info(colorstring.Yellow("bitrise runs in PR mode")) + return os.Setenv(configs.PRModeEnvKey, "true") + } + return os.Setenv(configs.PRModeEnvKey, "false") +} + +func isCIMode(ciGlobalFlagPtr *bool, inventoryEnvironments []envmanModels.EnvironmentItemModel) (bool, error) { + if ciGlobalFlagPtr != nil { + return *ciGlobalFlagPtr, nil + } + + ciModeEnv := os.Getenv(configs.CIModeEnvKey) + + if ciModeEnv == "true" { + return true, nil + } + + for _, env := range inventoryEnvironments { + key, value, err := env.GetKeyValuePair() + if err != nil { + return false, err + } + + if key == configs.CIModeEnvKey && value == "true" { + return true, nil + } + } + + return false, nil +} + +func registerCIMode(isCIMode bool) error { + configs.IsCIMode = isCIMode + + if isCIMode { + log.Info(colorstring.Yellow("bitrise runs in CI mode")) + return os.Setenv(configs.CIModeEnvKey, "true") + } + return os.Setenv(configs.CIModeEnvKey, "false") +} + +// GetBitriseConfigFromBase64Data ... +func GetBitriseConfigFromBase64Data(configBase64Str string) (models.BitriseDataModel, []string, error) { + configBase64Bytes, err := base64.StdEncoding.DecodeString(configBase64Str) + if err != nil { + return models.BitriseDataModel{}, []string{}, fmt.Errorf("Failed to decode base 64 string, error: %s", err) + } + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes(configBase64Bytes) + if err != nil { + return models.BitriseDataModel{}, warnings, fmt.Errorf("Failed to parse bitrise config, error: %s", err) + } + + return config, warnings, nil +} + +// GetBitriseConfigFilePath ... +func GetBitriseConfigFilePath(bitriseConfigPath string) (string, error) { + if bitriseConfigPath == "" { + bitriseConfigPath = filepath.Join(configs.CurrentDir, DefaultBitriseConfigFileName) + + if exist, err := pathutil.IsPathExists(bitriseConfigPath); err != nil { + return "", err + } else if !exist { + return "", fmt.Errorf("bitrise.yml path not defined and not found on it's default path: %s", bitriseConfigPath) + } + } + + return bitriseConfigPath, nil +} + +// CreateBitriseConfigFromCLIParams ... +func CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath string) (models.BitriseDataModel, []string, error) { + bitriseConfig := models.BitriseDataModel{} + warnings := []string{} + + if bitriseConfigBase64Data != "" { + config, warns, err := GetBitriseConfigFromBase64Data(bitriseConfigBase64Data) + warnings = warns + if err != nil { + return models.BitriseDataModel{}, warnings, fmt.Errorf("Failed to get config (bitrise.yml) from base 64 data, err: %s", err) + } + bitriseConfig = config + } else { + bitriseConfigPath, err := GetBitriseConfigFilePath(bitriseConfigPath) + if err != nil { + return models.BitriseDataModel{}, []string{}, fmt.Errorf("Failed to get config (bitrise.yml) path: %s", err) + } + if bitriseConfigPath == "" { + return models.BitriseDataModel{}, []string{}, errors.New("Failed to get config (bitrise.yml) path: empty bitriseConfigPath") + } + + config, warns, err := bitrise.ReadBitriseConfig(bitriseConfigPath) + warnings = warns + if err != nil { + return models.BitriseDataModel{}, warnings, fmt.Errorf("Config (path:%s) is not valid: %s", bitriseConfigPath, err) + } + bitriseConfig = config + } + + isConfigVersionOK, err := versions.IsVersionGreaterOrEqual(models.Version, bitriseConfig.FormatVersion) + if err != nil { + log.Warn("bitrise CLI model version: ", models.Version) + log.Warn("bitrise.yml Format Version: ", bitriseConfig.FormatVersion) + return models.BitriseDataModel{}, warnings, fmt.Errorf("Failed to compare bitrise CLI models's version with the bitrise.yml FormatVersion: %s", err) + } + if !isConfigVersionOK { + log.Warnf("The bitrise.yml has a higher Format Version (%s) than the bitrise CLI model's version (%s).", bitriseConfig.FormatVersion, models.Version) + return models.BitriseDataModel{}, warnings, errors.New("This bitrise.yml was created with and for a newer version of bitrise CLI, please upgrade your bitrise CLI to use this bitrise.yml") + } + + return bitriseConfig, warnings, nil +} + +// GetInventoryFromBase64Data ... +func GetInventoryFromBase64Data(inventoryBase64Str string) ([]envmanModels.EnvironmentItemModel, error) { + inventoryBase64Bytes, err := base64.StdEncoding.DecodeString(inventoryBase64Str) + if err != nil { + return []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Failed to decode base 64 string, error: %s", err) + } + + inventory, err := bitrise.InventoryModelFromYAMLBytes(inventoryBase64Bytes) + if err != nil { + return []envmanModels.EnvironmentItemModel{}, err + } + + return inventory.Envs, nil +} + +// GetInventoryFilePath ... +func GetInventoryFilePath(inventoryPath string) (string, error) { + if inventoryPath == "" { + log.Debugln("[BITRISE_CLI] - Inventory path not defined, searching for " + DefaultSecretsFileName + " in current folder...") + inventoryPath = filepath.Join(configs.CurrentDir, DefaultSecretsFileName) + + if exist, err := pathutil.IsPathExists(inventoryPath); err != nil { + return "", err + } else if !exist { + inventoryPath = "" + } + } + + return inventoryPath, nil +} + +// CreateInventoryFromCLIParams ... +func CreateInventoryFromCLIParams(inventoryBase64Data, inventoryPath string) ([]envmanModels.EnvironmentItemModel, error) { + inventoryEnvironments := []envmanModels.EnvironmentItemModel{} + + if inventoryBase64Data != "" { + inventory, err := GetInventoryFromBase64Data(inventoryBase64Data) + if err != nil { + return []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Failed to get inventory from base 64 data, err: %s", err) + } + inventoryEnvironments = inventory + } else { + inventoryPath, err := GetInventoryFilePath(inventoryPath) + if err != nil { + return []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Failed to get inventory path: %s", err) + } + + if inventoryPath != "" { + bytes, err := fileutil.ReadBytesFromFile(inventoryPath) + if err != nil { + return []envmanModels.EnvironmentItemModel{}, err + } + + if len(bytes) == 0 { + return []envmanModels.EnvironmentItemModel{}, errors.New("empty config") + } + + inventory, err := bitrise.CollectEnvironmentsFromFile(inventoryPath) + if err != nil { + return []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Invalid invetory format: %s", err) + } + inventoryEnvironments = inventory + } + } + + return inventoryEnvironments, nil +} + +func getCurrentBitriseSourceDir(envlist []envmanModels.EnvironmentItemModel) (string, error) { + bitriseSourceDir := os.Getenv(configs.BitriseSourceDirEnvKey) + for i := len(envlist) - 1; i >= 0; i-- { + env := envlist[i] + + key, value, err := env.GetKeyValuePair() + if err != nil { + return bitriseSourceDir, err + } + + if key == configs.BitriseSourceDirEnvKey && value != "" { + return value, nil + } + } + return bitriseSourceDir, nil +} + +func checkAndInstallStepDependencies(step stepmanModels.StepModel) error { + if len(step.Dependencies) > 0 { + log.Warnf("step.dependencies is deprecated... Use step.deps instead.") + } + + if step.Deps != nil && (len(step.Deps.Brew) > 0 || len(step.Deps.AptGet) > 0 || len(step.Deps.CheckOnly) > 0) { + // + // New dependency handling + for _, checkOnlyDep := range step.Deps.CheckOnly { + if err := bitrise.DependencyTryCheckTool(checkOnlyDep.Name); err != nil { + return err + } + log.Infof(" * "+colorstring.Green("[OK]")+" Step dependency (%s) installed, available.", checkOnlyDep.Name) + } + + switch runtime.GOOS { + case "darwin": + for _, brewDep := range step.Deps.Brew { + if err := bitrise.InstallWithBrewIfNeeded(brewDep, configs.IsCIMode); err != nil { + log.Infof("Failed to install (%s) with brew", brewDep.Name) + return err + } + log.Infof(" * "+colorstring.Green("[OK]")+" Step dependency (%s) installed, available.", brewDep.GetBinaryName()) + } + case "linux": + for _, aptGetDep := range step.Deps.AptGet { + log.Infof("Start installing (%s) with apt-get", aptGetDep.Name) + if err := bitrise.InstallWithAptGetIfNeeded(aptGetDep, configs.IsCIMode); err != nil { + log.Infof("Failed to install (%s) with apt-get", aptGetDep.Name) + return err + } + log.Infof(" * "+colorstring.Green("[OK]")+" Step dependency (%s) installed, available.", aptGetDep.GetBinaryName()) + } + default: + return errors.New("Unsupported os") + } + } else if len(step.Dependencies) > 0 { + log.Info("Deprecated dependencies found") + // + // Deprecated dependency handling + for _, dep := range step.Dependencies { + isSkippedBecauseOfPlatform := false + switch dep.Manager { + case depManagerBrew: + if runtime.GOOS == "darwin" { + err := bitrise.InstallWithBrewIfNeeded(stepmanModels.BrewDepModel{Name: dep.Name}, configs.IsCIMode) + if err != nil { + return err + } + } else { + isSkippedBecauseOfPlatform = true + } + break + case depManagerTryCheck: + err := bitrise.DependencyTryCheckTool(dep.Name) + if err != nil { + return err + } + break + default: + return errors.New("Not supported dependency (" + dep.Manager + ") (" + dep.Name + ")") + } + + if isSkippedBecauseOfPlatform { + log.Debugf(" * Dependency (%s) skipped, manager (%s) not supported on this platform (%s)", dep.Name, dep.Manager, runtime.GOOS) + } else { + log.Infof(" * "+colorstring.Green("[OK]")+" Step dependency (%s) installed, available.", dep.Name) + } + } + } + + return nil +} + +func executeStep(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath, bitriseSourceDir string) (int, error) { + toolkitForStep := toolkits.ToolkitForStep(step) + toolkitName := toolkitForStep.ToolkitName() + + if err := toolkitForStep.PrepareForStepRun(step, sIDData, stepAbsDirPath); err != nil { + return 1, fmt.Errorf("Failed to prepare the step for execution through the required toolkit (%s), error: %s", + toolkitName, err) + } + + cmd, err := toolkitForStep.StepRunCommandArguments(step, sIDData, stepAbsDirPath) + if err != nil { + return 1, fmt.Errorf("Toolkit (%s) rejected the step, error: %s", + toolkitName, err) + } + + return tools.EnvmanRun(configs.InputEnvstorePath, bitriseSourceDir, cmd) +} + +func runStep(step stepmanModels.StepModel, stepIDData models.StepIDData, stepDir string, environments []envmanModels.EnvironmentItemModel, buildRunResults models.BuildRunResultsModel) (int, []envmanModels.EnvironmentItemModel, error) { + log.Debugf("[BITRISE_CLI] - Try running step: %s (%s)", stepIDData.IDorURI, stepIDData.Version) + + // Check & Install Step Dependencies + // [!] Make sure this happens BEFORE the Toolkit Bootstrap, + // so that if a Toolkit requires/allows the use of additional dependencies + // required for the step (e.g. a brew installed OpenSSH) it can be done + // with a Toolkit+Deps + if err := retry.Times(2).Try(func(attempt uint) error { + if attempt > 0 { + fmt.Println() + log.Warn("Installing Step dependency failed, retrying ...") + } + + return checkAndInstallStepDependencies(step) + }); err != nil { + return 1, []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Failed to install Step dependency, error: %s", err) + } + + // Collect step inputs + if err := tools.EnvmanInitAtPath(configs.InputEnvstorePath); err != nil { + return 1, []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Failed to init envman for the Step, error: %s", err) + } + + if err := bitrise.ExportEnvironmentsList(environments); err != nil { + return 1, []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Failed to export environment list for the Step, error: %s", err) + } + + evaluatedInputs := []envmanModels.EnvironmentItemModel{} + for _, input := range step.Inputs { + key, value, err := input.GetKeyValuePair() + if err != nil { + return 1, []envmanModels.EnvironmentItemModel{}, err + } + + options, err := input.GetOptions() + if err != nil { + return 1, []envmanModels.EnvironmentItemModel{}, err + } + + if options.IsTemplate != nil && *options.IsTemplate { + outStr, err := tools.EnvmanJSONPrint(configs.InputEnvstorePath) + if err != nil { + return 1, []envmanModels.EnvironmentItemModel{}, fmt.Errorf("EnvmanJSONPrint failed, err: %s", err) + } + + envList, err := envmanModels.NewEnvJSONList(outStr) + if err != nil { + return 1, []envmanModels.EnvironmentItemModel{}, fmt.Errorf("CreateFromJSON failed, err: %s", err) + } + + evaluatedValue, err := bitrise.EvaluateTemplateToString(value, configs.IsCIMode, configs.IsPullRequestMode, buildRunResults, envList) + if err != nil { + return 1, []envmanModels.EnvironmentItemModel{}, err + } + + input[key] = evaluatedValue + } + + evaluatedInputs = append(evaluatedInputs, input) + } + environments = append(environments, evaluatedInputs...) + + if err := tools.EnvmanInitAtPath(configs.InputEnvstorePath); err != nil { + return 1, []envmanModels.EnvironmentItemModel{}, err + } + + if err := bitrise.ExportEnvironmentsList(environments); err != nil { + return 1, []envmanModels.EnvironmentItemModel{}, err + } + + // Run step + bitriseSourceDir, err := getCurrentBitriseSourceDir(environments) + if err != nil { + return 1, []envmanModels.EnvironmentItemModel{}, err + } + if bitriseSourceDir == "" { + bitriseSourceDir = configs.CurrentDir + } + + if exit, err := executeStep(step, stepIDData, stepDir, bitriseSourceDir); err != nil { + stepOutputs, envErr := bitrise.CollectEnvironmentsFromFile(configs.OutputEnvstorePath) + if envErr != nil { + return 1, []envmanModels.EnvironmentItemModel{}, envErr + } + + updatedStepOutputs, updateErr := bitrise.ApplyOutputAliases(stepOutputs, step.Outputs) + if updateErr != nil { + return 1, []envmanModels.EnvironmentItemModel{}, updateErr + } + + return exit, updatedStepOutputs, err + } + + stepOutputs, err := bitrise.CollectEnvironmentsFromFile(configs.OutputEnvstorePath) + if err != nil { + return 1, []envmanModels.EnvironmentItemModel{}, err + } + + updatedStepOutputs, updateErr := bitrise.ApplyOutputAliases(stepOutputs, step.Outputs) + if updateErr != nil { + return 1, []envmanModels.EnvironmentItemModel{}, updateErr + } + + log.Debugf("[BITRISE_CLI] - Step executed: %s (%s)", stepIDData.IDorURI, stepIDData.Version) + + return 0, updatedStepOutputs, nil +} + +func activateAndRunSteps(workflow models.WorkflowModel, defaultStepLibSource string, buildRunResults models.BuildRunResultsModel, environments *[]envmanModels.EnvironmentItemModel, isLastWorkflow bool) models.BuildRunResultsModel { + log.Debugln("[BITRISE_CLI] - Activating and running steps") + + // ------------------------------------------ + // In function global variables - These are global for easy use in local register step run result methods. + var stepStartTime time.Time + + // ------------------------------------------ + // In function method - Registration methods, for register step run results. + registerStepRunResults := func(step stepmanModels.StepModel, stepInfoPtr stepmanModels.StepInfoModel, + stepIdxPtr int, runIf string, resultCode, exitCode int, err error, isLastStep, printStepHeader bool) { + + if printStepHeader { + bitrise.PrintRunningStepHeader(stepInfoPtr, step, stepIdxPtr) + } + + stepInfoCopy := stepmanModels.StepInfoModel{ + Library: stepInfoPtr.Library, + ID: stepInfoPtr.ID, + Version: stepInfoPtr.Version, + LatestVersion: stepInfoPtr.LatestVersion, + GroupInfo: stepInfoPtr.GroupInfo, + Step: stepInfoPtr.Step, + DefinitionPth: stepInfoPtr.DefinitionPth, + } + + errStr := "" + if err != nil { + errStr = err.Error() + } + + stepResults := models.StepRunResultsModel{ + StepInfo: stepInfoCopy, + Status: resultCode, + Idx: buildRunResults.ResultsCount(), + RunTime: time.Now().Sub(stepStartTime), + ErrorStr: errStr, + ExitCode: exitCode, + } + + isExitStatusError := true + if err != nil { + isExitStatusError = errorutil.IsExitStatusError(err) + } + + switch resultCode { + case models.StepRunStatusCodeSuccess: + buildRunResults.SuccessSteps = append(buildRunResults.SuccessSteps, stepResults) + break + case models.StepRunStatusCodeFailed: + if !isExitStatusError { + log.Errorf("Step (%s) failed, error: %s", pointers.StringWithDefault(stepInfoCopy.Step.Title, "missing title"), err) + } + + buildRunResults.FailedSteps = append(buildRunResults.FailedSteps, stepResults) + break + case models.StepRunStatusCodeFailedSkippable: + if !isExitStatusError { + log.Warnf("Step (%s) failed, but was marked as skippable, error: %s", pointers.StringWithDefault(stepInfoCopy.Step.Title, "missing title"), err) + } else { + log.Warnf("Step (%s) failed, but was marked as skippable", pointers.StringWithDefault(stepInfoCopy.Step.Title, "missing title")) + } + + buildRunResults.FailedSkippableSteps = append(buildRunResults.FailedSkippableSteps, stepResults) + break + case models.StepRunStatusCodeSkipped: + log.Warnf("A previous step failed, and this step (%s) was not marked as IsAlwaysRun, skipped", pointers.StringWithDefault(stepInfoCopy.Step.Title, "missing title")) + + buildRunResults.SkippedSteps = append(buildRunResults.SkippedSteps, stepResults) + break + case models.StepRunStatusCodeSkippedWithRunIf: + log.Warn("The step's (" + pointers.StringWithDefault(stepInfoCopy.Step.Title, "missing title") + ") Run-If expression evaluated to false - skipping") + if runIf != "" { + log.Info("The Run-If expression was: ", colorstring.Blue(runIf)) + } + + buildRunResults.SkippedSteps = append(buildRunResults.SkippedSteps, stepResults) + break + default: + log.Error("Unkown result code") + return + } + + bitrise.PrintRunningStepFooter(stepResults, isLastStep) + } + + // ------------------------------------------ + // Main - Preparing & running the steps + for idx, stepListItm := range workflow.Steps { + // Per step variables + stepStartTime = time.Now() + isLastStep := isLastWorkflow && (idx == len(workflow.Steps)-1) + stepInfoPtr := stepmanModels.StepInfoModel{} + stepIdxPtr := idx + + // Per step cleanup + if err := bitrise.SetBuildFailedEnv(buildRunResults.IsBuildFailed()); err != nil { + log.Error("Failed to set Build Status envs") + } + + if err := bitrise.CleanupStepWorkDir(); err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + + // + // Preparing the step + if err := tools.EnvmanInitAtPath(configs.InputEnvstorePath); err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + + if err := bitrise.ExportEnvironmentsList(*environments); err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + + // Get step id & version data + compositeStepIDStr, workflowStep, err := models.GetStepIDStepDataPair(stepListItm) + if err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + stepInfoPtr.ID = compositeStepIDStr + if workflowStep.Title != nil && *workflowStep.Title != "" { + stepInfoPtr.Step.Title = pointers.NewStringPtr(*workflowStep.Title) + } else { + stepInfoPtr.Step.Title = pointers.NewStringPtr(compositeStepIDStr) + } + + stepIDData, err := models.CreateStepIDDataFromString(compositeStepIDStr, defaultStepLibSource) + if err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + stepInfoPtr.ID = stepIDData.IDorURI + if stepInfoPtr.Step.Title == nil || *stepInfoPtr.Step.Title == "" { + stepInfoPtr.Step.Title = pointers.NewStringPtr(stepIDData.IDorURI) + } + stepInfoPtr.Version = stepIDData.Version + stepInfoPtr.Library = stepIDData.SteplibSource + + // + // Activating the step + stepDir := configs.BitriseWorkStepsDirPath + stepYMLPth := filepath.Join(configs.BitriseWorkDirPath, "current_step.yml") + + if stepIDData.SteplibSource == "path" { + log.Debugf("[BITRISE_CLI] - Local step found: (path:%s)", stepIDData.IDorURI) + stepAbsLocalPth, err := pathutil.AbsPath(stepIDData.IDorURI) + if err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + + log.Debugln("stepAbsLocalPth:", stepAbsLocalPth, "|stepDir:", stepDir) + + if err := command.CopyDir(stepAbsLocalPth, stepDir, true); err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + + if err := command.CopyFile(filepath.Join(stepAbsLocalPth, "step.yml"), stepYMLPth); err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + } else if stepIDData.SteplibSource == "git" { + log.Debugf("[BITRISE_CLI] - Remote step, with direct git uri: (uri:%s) (tag-or-branch:%s)", stepIDData.IDorURI, stepIDData.Version) + if err := git.CloneTagOrBranch(stepIDData.IDorURI, stepDir, stepIDData.Version); err != nil { + if strings.HasPrefix(stepIDData.IDorURI, "git@") { + fmt.Println(colorstring.Yellow(`Note: if the step's repository is an open source one,`)) + fmt.Println(colorstring.Yellow(`you should probably use a "https://..." git clone URL,`)) + fmt.Println(colorstring.Yellow(`instead of the "git@..." git clone URL which usually requires authentication`)) + fmt.Println(colorstring.Yellow(`even if the repository is open source!`)) + } + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + + if err := command.CopyFile(filepath.Join(stepDir, "step.yml"), stepYMLPth); err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + } else if stepIDData.SteplibSource == "_" { + log.Debugf("[BITRISE_CLI] - Steplib independent step, with direct git uri: (uri:%s) (tag-or-branch:%s)", stepIDData.IDorURI, stepIDData.Version) + + // Steplib independent steps are completly defined in workflow + stepYMLPth = "" + if err := workflowStep.FillMissingDefaults(); err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + + if err := git.CloneTagOrBranch(stepIDData.IDorURI, stepDir, stepIDData.Version); err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + } else if stepIDData.SteplibSource != "" { + log.Debugf("[BITRISE_CLI] - Steplib (%s) step (id:%s) (version:%s) found, activating step", stepIDData.SteplibSource, stepIDData.IDorURI, stepIDData.Version) + if err := tools.StepmanSetup(stepIDData.SteplibSource); err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + + isLatestVersionOfStep := (stepIDData.Version == "") + if isLatestVersionOfStep && !buildRunResults.IsStepLibUpdated(stepIDData.SteplibSource) { + log.Infof("Step uses latest version -- Updating StepLib ...") + if err := tools.StepmanUpdate(stepIDData.SteplibSource); err != nil { + log.Warnf("Step uses latest version, but failed to update StepLib, err: %s", err) + } else { + buildRunResults.StepmanUpdates[stepIDData.SteplibSource]++ + } + } + + outStr, err := tools.StepmanJSONStepLibStepInfo(stepIDData.SteplibSource, stepIDData.IDorURI, stepIDData.Version) + if err != nil { + if buildRunResults.IsStepLibUpdated(stepIDData.SteplibSource) { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, fmt.Errorf("StepmanJSONStepLibStepInfo failed, err: %s", err), isLastStep, true) + continue + } + // May StepLib should be updated + log.Infof("Step info not found in StepLib (%s) -- Updating ...", stepIDData.SteplibSource) + if err := tools.StepmanUpdate(stepIDData.SteplibSource); err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + buildRunResults.StepmanUpdates[stepIDData.SteplibSource]++ + + outStr, err = tools.StepmanJSONStepLibStepInfo(stepIDData.SteplibSource, stepIDData.IDorURI, stepIDData.Version) + if err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, fmt.Errorf("StepmanJSONStepLibStepInfo failed, err: %s", err), isLastStep, true) + continue + } + } + + stepInfo, err := stepmanModels.StepInfoModel{}.CreateFromJSON(outStr) + if err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, fmt.Errorf("CreateFromJSON failed, err: %s", err), isLastStep, true) + continue + } + + stepInfoPtr.ID = stepInfo.ID + if stepInfoPtr.Step.Title == nil || *stepInfoPtr.Step.Title == "" { + stepInfoPtr.Step.Title = pointers.NewStringPtr(stepInfo.ID) + } + stepInfoPtr.Version = stepInfo.Version + stepInfoPtr.LatestVersion = stepInfo.LatestVersion + stepInfoPtr.GroupInfo = stepInfo.GroupInfo + + if err := tools.StepmanActivate(stepIDData.SteplibSource, stepIDData.IDorURI, stepIDData.Version, stepDir, stepYMLPth); err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } else { + log.Debugf("[BITRISE_CLI] - Step activated: (ID:%s) (version:%s)", stepIDData.IDorURI, stepIDData.Version) + } + } else { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, fmt.Errorf("Invalid stepIDData: No SteplibSource or LocalPath defined (%v)", stepIDData), isLastStep, true) + continue + } + + // Fill step info with default step info, if exist + mergedStep := workflowStep + if stepYMLPth != "" { + specStep, err := bitrise.ReadSpecStep(stepYMLPth) + log.Debugf("Spec read from YML: %#v\n", specStep) + if err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + + mergedStep, err = models.MergeStepWith(specStep, workflowStep) + if err != nil { + registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, + "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) + continue + } + } + + if mergedStep.SupportURL != nil { + stepInfoPtr.Step.SupportURL = pointers.NewStringPtr(*mergedStep.SupportURL) + } + if mergedStep.SourceCodeURL != nil { + stepInfoPtr.Step.SourceCodeURL = pointers.NewStringPtr(*mergedStep.SourceCodeURL) + } + + // + // Run step + bitrise.PrintRunningStepHeader(stepInfoPtr, mergedStep, idx) + if mergedStep.RunIf != nil && *mergedStep.RunIf != "" { + outStr, err := tools.EnvmanJSONPrint(configs.InputEnvstorePath) + if err != nil { + registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, + *mergedStep.RunIf, models.StepRunStatusCodeFailed, 1, fmt.Errorf("EnvmanJSONPrint failed, err: %s", err), isLastStep, false) + continue + } + + envList, err := envmanModels.NewEnvJSONList(outStr) + if err != nil { + registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, + *mergedStep.RunIf, models.StepRunStatusCodeFailed, 1, fmt.Errorf("CreateFromJSON failed, err: %s", err), isLastStep, false) + continue + } + + isRun, err := bitrise.EvaluateTemplateToBool(*mergedStep.RunIf, configs.IsCIMode, configs.IsPullRequestMode, buildRunResults, envList) + if err != nil { + registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, + *mergedStep.RunIf, models.StepRunStatusCodeFailed, 1, err, isLastStep, false) + continue + } + if !isRun { + registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, + *mergedStep.RunIf, models.StepRunStatusCodeSkippedWithRunIf, 0, err, isLastStep, false) + continue + } + } + + isAlwaysRun := stepmanModels.DefaultIsAlwaysRun + if mergedStep.IsAlwaysRun != nil { + isAlwaysRun = *mergedStep.IsAlwaysRun + } else { + log.Warn("Step (%s) mergedStep.IsAlwaysRun is nil, should not!", stepIDData.IDorURI) + } + + if buildRunResults.IsBuildFailed() && !isAlwaysRun { + registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, + *mergedStep.RunIf, models.StepRunStatusCodeSkipped, 0, err, isLastStep, false) + } else { + exit, outEnvironments, err := runStep(mergedStep, stepIDData, stepDir, *environments, buildRunResults) + + if err := tools.EnvmanClear(configs.OutputEnvstorePath); err != nil { + log.Errorf("Failed to clear output envstore, error: %s", err) + } + + *environments = append(*environments, outEnvironments...) + if err != nil { + if *mergedStep.IsSkippable { + registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, + *mergedStep.RunIf, models.StepRunStatusCodeFailedSkippable, exit, err, isLastStep, false) + } else { + registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, + *mergedStep.RunIf, models.StepRunStatusCodeFailed, exit, err, isLastStep, false) + } + } else { + registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, + *mergedStep.RunIf, models.StepRunStatusCodeSuccess, 0, nil, isLastStep, false) + } + } + } + + return buildRunResults +} + +func runWorkflow(workflow models.WorkflowModel, steplibSource string, buildRunResults models.BuildRunResultsModel, environments *[]envmanModels.EnvironmentItemModel, isLastWorkflow bool) models.BuildRunResultsModel { + bitrise.PrintRunningWorkflow(workflow.Title) + + *environments = append(*environments, workflow.Environments...) + return activateAndRunSteps(workflow, steplibSource, buildRunResults, environments, isLastWorkflow) +} + +func activateAndRunWorkflow(workflowID string, workflow models.WorkflowModel, bitriseConfig models.BitriseDataModel, buildRunResults models.BuildRunResultsModel, environments *[]envmanModels.EnvironmentItemModel, lastWorkflowID string) (models.BuildRunResultsModel, error) { + var err error + // Run these workflows before running the target workflow + for _, beforeWorkflowID := range workflow.BeforeRun { + beforeWorkflow, exist := bitriseConfig.Workflows[beforeWorkflowID] + if !exist { + return buildRunResults, fmt.Errorf("Specified Workflow (%s) does not exist", beforeWorkflowID) + } + if beforeWorkflow.Title == "" { + beforeWorkflow.Title = beforeWorkflowID + } + buildRunResults, err = activateAndRunWorkflow(beforeWorkflowID, beforeWorkflow, bitriseConfig, buildRunResults, environments, lastWorkflowID) + if err != nil { + return buildRunResults, err + } + } + + // Run the target workflow + isLastWorkflow := (workflowID == lastWorkflowID) + buildRunResults = runWorkflow(workflow, bitriseConfig.DefaultStepLibSource, buildRunResults, environments, isLastWorkflow) + + // Run these workflows after running the target workflow + for _, afterWorkflowID := range workflow.AfterRun { + afterWorkflow, exist := bitriseConfig.Workflows[afterWorkflowID] + if !exist { + return buildRunResults, fmt.Errorf("Specified Workflow (%s) does not exist", afterWorkflowID) + } + if afterWorkflow.Title == "" { + afterWorkflow.Title = afterWorkflowID + } + buildRunResults, err = activateAndRunWorkflow(afterWorkflowID, afterWorkflow, bitriseConfig, buildRunResults, environments, lastWorkflowID) + if err != nil { + return buildRunResults, err + } + } + + return buildRunResults, nil +} + +func lastWorkflowIDInConfig(workflowToRunID string, bitriseConfig models.BitriseDataModel) (string, error) { + workflowToRun, exist := bitriseConfig.Workflows[workflowToRunID] + if !exist { + return "", errors.New("No worfklow exist with ID: " + workflowToRunID) + } + + if len(workflowToRun.AfterRun) > 0 { + lastAfterID := workflowToRun.AfterRun[len(workflowToRun.AfterRun)-1] + wfID, err := lastWorkflowIDInConfig(lastAfterID, bitriseConfig) + if err != nil { + return "", err + } + workflowToRunID = wfID + } + return workflowToRunID, nil +} + +// RunWorkflowWithConfiguration ... +func runWorkflowWithConfiguration( + startTime time.Time, + workflowToRunID string, + bitriseConfig models.BitriseDataModel, + secretEnvironments []envmanModels.EnvironmentItemModel) (models.BuildRunResultsModel, error) { + + workflowToRun, exist := bitriseConfig.Workflows[workflowToRunID] + if !exist { + return models.BuildRunResultsModel{}, fmt.Errorf("Specified Workflow (%s) does not exist", workflowToRunID) + } + + if workflowToRun.Title == "" { + workflowToRun.Title = workflowToRunID + } + + // Envman setup + if err := os.Setenv(configs.EnvstorePathEnvKey, configs.OutputEnvstorePath); err != nil { + return models.BuildRunResultsModel{}, fmt.Errorf("Failed to add env, err: %s", err) + } + + if err := os.Setenv(configs.FormattedOutputPathEnvKey, configs.FormattedOutputPath); err != nil { + return models.BuildRunResultsModel{}, fmt.Errorf("Failed to add env, err: %s", err) + } + + if err := tools.EnvmanInit(); err != nil { + return models.BuildRunResultsModel{}, errors.New("Failed to run envman init") + } + + // App level environment + environments := append(secretEnvironments, bitriseConfig.App.Environments...) + + if err := os.Setenv("BITRISE_TRIGGERED_WORKFLOW_ID", workflowToRunID); err != nil { + return models.BuildRunResultsModel{}, fmt.Errorf("Failed to set BITRISE_TRIGGERED_WORKFLOW_ID env: %s", err) + } + if err := os.Setenv("BITRISE_TRIGGERED_WORKFLOW_TITLE", workflowToRun.Title); err != nil { + return models.BuildRunResultsModel{}, fmt.Errorf("Failed to set BITRISE_TRIGGERED_WORKFLOW_TITLE env: %s", err) + } + + environments = append(environments, workflowToRun.Environments...) + + lastWorkflowID, err := lastWorkflowIDInConfig(workflowToRunID, bitriseConfig) + if err != nil { + return models.BuildRunResultsModel{}, fmt.Errorf("Failed to get last workflow id: %s", err) + } + + // Bootstrap Toolkits + for _, aToolkit := range toolkits.AllSupportedToolkits() { + toolkitName := aToolkit.ToolkitName() + if !aToolkit.IsToolAvailableInPATH() { + // don't bootstrap if any preinstalled version is available, + // the toolkit's `PrepareForStepRun` can bootstrap for itself later if required + // or if the system installed version is not sufficient + if err := aToolkit.Bootstrap(); err != nil { + return models.BuildRunResultsModel{}, fmt.Errorf("Failed to bootstrap the required toolkit for the step (%s), error: %s", + toolkitName, err) + } + } + } + + // + buildRunResults := models.BuildRunResultsModel{ + StartTime: startTime, + StepmanUpdates: map[string]int{}, + } + + buildRunResults, err = activateAndRunWorkflow(workflowToRunID, workflowToRun, bitriseConfig, buildRunResults, &environments, lastWorkflowID) + if err != nil { + return buildRunResults, errors.New("[BITRISE_CLI] - Failed to activate and run workflow " + workflowToRunID) + } + + // Build finished + bitrise.PrintSummary(buildRunResults) + + // Trigger WorkflowRunDidFinish + if err := plugins.TriggerEvent(plugins.DidFinishRun, buildRunResults); err != nil { + log.Warnf("Failed to trigger WorkflowRunDidFinish, error: %s", err) + } + + return buildRunResults, nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/run_util_test.go b/vendor/github.com/bitrise-io/bitrise/cli/run_util_test.go new file mode 100644 index 00000000..925a1222 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/run_util_test.go @@ -0,0 +1,463 @@ +package cli + +import ( + "encoding/base64" + "os" + "testing" + "time" + + "github.com/bitrise-io/bitrise/bitrise" + "github.com/bitrise-io/bitrise/configs" + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/pointers" + "github.com/stretchr/testify/require" +) + +func TestIsPRMode(t *testing.T) { + prModeEnv := os.Getenv(configs.PRModeEnvKey) + prIDEnv := os.Getenv(configs.PullRequestIDEnvKey) + + // cleanup Envs after these tests + defer func() { + require.NoError(t, os.Setenv(configs.PRModeEnvKey, prModeEnv)) + require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, prIDEnv)) + }() + + t.Log("Should be false for: prGlobalFlag: nil, prModeEnv: '', prIDEnv: ''") + { + require.NoError(t, os.Setenv(configs.PRModeEnvKey, "")) + require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) + + pr, err := isPRMode(nil, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, false, pr) + } + + t.Log("Should be false for: prGlobalFlag: nil, prModeEnv: '', prIDEnv: '', secrets: false") + { + inventoryStr := ` +envs: +- PR: "false" +- PULL_REQUEST_ID: "" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.PRModeEnvKey, "")) + require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) + + pr, err := isPRMode(nil, inventory.Envs) + require.NoError(t, err) + require.Equal(t, false, pr) + } + + t.Log("Should be false for: prGlobalFlag: nil, prModeEnv: 'false', prIDEnv: '', secrets: ''") + { + inventoryStr := ` +envs: +- PR: "" +- PULL_REQUEST_ID: "" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.PRModeEnvKey, "false")) + require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) + + pr, err := isPRMode(nil, inventory.Envs) + require.NoError(t, err) + require.Equal(t, false, pr) + } + + t.Log("Should be false for: prGlobalFlag: false, prModeEnv: 'true', prIDEnv: 'ID', secrets: 'true'") + { + inventoryStr := ` +envs: +- PR: "true" +- PULL_REQUEST_ID: "ID" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.PRModeEnvKey, "true")) + require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "ID")) + + pr, err := isPRMode(pointers.NewBoolPtr(false), inventory.Envs) + require.NoError(t, err) + require.Equal(t, false, pr) + } + + t.Log("Should be true for: prGlobalFlag: true, prModeEnv: '', prIDEnv: ''") + { + require.NoError(t, os.Setenv(configs.PRModeEnvKey, "")) + require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) + + pr, err := isPRMode(pointers.NewBoolPtr(true), []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, true, pr) + } + + t.Log("Should be true for: prGlobalFlag: true, prModeEnv: '', prIDEnv: '', secrets: false") + { + inventoryStr := ` +envs: +- PR: "false" +- PULL_REQUEST_ID: "" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.PRModeEnvKey, "")) + require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) + + pr, err := isPRMode(pointers.NewBoolPtr(true), inventory.Envs) + require.NoError(t, err) + require.Equal(t, true, pr) + } + + t.Log("Should be true for: prGlobalFlag: nil, prModeEnv: 'true', prIDEnv: '', secrets: false") + { + inventoryStr := ` +envs: +- PR: "false" +- PULL_REQUEST_ID: "" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.PRModeEnvKey, "true")) + require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) + + pr, err := isPRMode(nil, inventory.Envs) + require.NoError(t, err) + require.Equal(t, true, pr) + } + + t.Log("Should be true for: prGlobalFlag: nil, prModeEnv: 'false', prIDEnv: 'some', secrets: false") + { + inventoryStr := ` +envs: +- PR: "false" +- PULL_REQUEST_ID: "" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.PRModeEnvKey, "false")) + require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "some")) + + pr, err := isPRMode(nil, inventory.Envs) + require.NoError(t, err) + require.Equal(t, true, pr) + } + + t.Log("Should be true for: prGlobalFlag: nil, prModeEnv: '', prIDEnv: '', secrets: true") + { + inventoryStr := ` +envs: +- PR: "true" +- PULL_REQUEST_ID: "" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.PRModeEnvKey, "")) + require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) + + pr, err := isPRMode(nil, inventory.Envs) + require.NoError(t, err) + require.Equal(t, true, pr) + } + + t.Log("Should be true for: prGlobalFlag: nil, prModeEnv: 'false', prIDEnv: '', secrets: true") + { + inventoryStr := ` +envs: +- PR: "" +- PULL_REQUEST_ID: "some" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.PRModeEnvKey, "false")) + require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) + + pr, err := isPRMode(nil, inventory.Envs) + require.NoError(t, err) + require.Equal(t, true, pr) + } + + t.Log("Should be true for: prGlobalFlag: true, prModeEnv: 'false', prIDEnv: '', secrets: false") + { + inventoryStr := ` +envs: +- PR: "false" +- PULL_REQUEST_ID: "" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.PRModeEnvKey, "false")) + require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) + + pr, err := isPRMode(pointers.NewBoolPtr(true), inventory.Envs) + require.NoError(t, err) + require.Equal(t, true, pr) + } +} + +func TestIsCIMode(t *testing.T) { + ciModeEnv := os.Getenv(configs.CIModeEnvKey) + + defer func() { + require.NoError(t, os.Setenv(configs.CIModeEnvKey, ciModeEnv)) + }() + + t.Log("Should be false for: ciGlobalFlag: nil, ciModeEnv: 'false'") + { + require.NoError(t, os.Setenv(configs.CIModeEnvKey, "false")) + + ci, err := isCIMode(nil, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, false, ci) + } + + t.Log("Should be false for: ciGlobalFlag: false, ciModeEnv: 'false' secrets: false") + { + inventoryStr := ` +envs: +- CI: "false" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.CIModeEnvKey, "false")) + + ci, err := isCIMode(pointers.NewBoolPtr(false), inventory.Envs) + require.NoError(t, err) + require.Equal(t, false, ci) + } + + t.Log("Should be true for: ciGlobalFlag: true, ciModeEnv: 'false'") + { + require.NoError(t, os.Setenv(configs.CIModeEnvKey, "")) + + ci, err := isCIMode(pointers.NewBoolPtr(true), []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, true, ci) + } + + t.Log("Should be true for: ciGlobalFlag: true, ciModeEnv: '' secrets: false") + { + inventoryStr := ` +envs: +- CI: "false" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.CIModeEnvKey, "")) + + ci, err := isCIMode(pointers.NewBoolPtr(true), inventory.Envs) + require.NoError(t, err) + require.Equal(t, true, ci) + } + + t.Log("Should be true for: ciGlobalFlag: nil, ciModeEnv: 'true' secrets: false") + { + inventoryStr := ` +envs: +- CI: "" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.CIModeEnvKey, "true")) + + ci, err := isCIMode(nil, inventory.Envs) + require.NoError(t, err) + require.Equal(t, true, ci) + } + + t.Log("Should be true for: ciGlobalFlag: nil, ciModeEnv: '' secrets: true") + { + inventoryStr := ` +envs: +- CI: "true" +` + inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) + require.NoError(t, err) + + require.NoError(t, os.Setenv(configs.CIModeEnvKey, "")) + + ci, err := isCIMode(nil, inventory.Envs) + require.NoError(t, err) + require.Equal(t, true, ci) + } +} + +func TestExpandEnvs(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + test: + envs: + - ENV0: "Hello" + - ENV1: "$ENV0 world" + steps: + - script: + inputs: + - content: | + #!/bin/bash + envman add --key ENV2 --value "$ENV1 !" + - script: + inputs: + - content: | + #!/bin/bash + echo "ENV2: $ENV2" + if [ "$ENV2" != "Hello world !" ] ; then + echo "Actual ($ENV2), excpected (Hello world !)" + exit 1 + fi +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "test", config, []envmanModels.EnvironmentItemModel{}) + require.NoError(t, err) + require.Equal(t, 2, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) +} + +func TestEvaluateInputs(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + test: + envs: + - TEST_KEY: "test value" + steps: + - script: + title: "Template test" + inputs: + - content: | + #!/bin/bash + set -v + {{if .IsCI}} + exit 1 + {{else}} + exit 0 + {{end}} + opts: + is_template: true + - script: + title: "Template test" + inputs: + - content: | + #!/bin/bash + set -v + {{if enveq "TEST_KEY" "test value"}} + exit 0 + {{else}} + exit 1 + {{end}} + opts: + is_template: true +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "test", config, []envmanModels.EnvironmentItemModel{}) + require.Equal(t, nil, err) + require.Equal(t, 0, len(buildRunResults.SkippedSteps)) + require.Equal(t, 2, len(buildRunResults.SuccessSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSteps)) + require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) +} + +func TestGetBitriseConfigFromBase64Data(t *testing.T) { + configStr := ` +format_version: 0.9.10 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + target: + title: target +` + configBytes := []byte(configStr) + configBase64Str := base64.StdEncoding.EncodeToString(configBytes) + + config, warnings, err := GetBitriseConfigFromBase64Data(configBase64Str) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + require.Equal(t, "0.9.10", config.FormatVersion) + require.Equal(t, "https://github.com/bitrise-io/bitrise-steplib.git", config.DefaultStepLibSource) + + workflow, found := config.Workflows["target"] + require.Equal(t, true, found) + require.Equal(t, "target", workflow.Title) +} + +func TestGetInventoryFromBase64Data(t *testing.T) { + inventoryStr := ` +envs: + - MY_HOME: $HOME + opts: + is_expand: true +` + inventoryBytes := []byte(inventoryStr) + inventoryBase64Str := base64.StdEncoding.EncodeToString(inventoryBytes) + + inventory, err := GetInventoryFromBase64Data(inventoryBase64Str) + require.NoError(t, err) + + env := inventory[0] + + key, value, err := env.GetKeyValuePair() + require.NoError(t, err) + require.Equal(t, "MY_HOME", key) + require.Equal(t, "$HOME", value) + + opts, err := env.GetOptions() + require.NoError(t, err) + require.Equal(t, true, *opts.IsExpand) +} + +func TestInvalidStepID(t *testing.T) { + configStr := ` +format_version: 1.3.0 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + target: + title: Invalid step id + steps: + - invalid-step: + - invalid-step: + - invalid-step: +` + + require.NoError(t, configs.InitPaths()) + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + results, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) + require.Equal(t, 1, len(results.StepmanUpdates)) +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/setup.go b/vendor/github.com/bitrise-io/bitrise/cli/setup.go new file mode 100644 index 00000000..92c0e0ac --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/setup.go @@ -0,0 +1,68 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/bitrise-io/bitrise/bitrise" + "github.com/bitrise-io/go-utils/log" + "github.com/urfave/cli" +) + +var setupCommand = cli.Command{ + Name: "setup", + Usage: "Setup the current host. Install every required tool to run Workflows.", + Action: func(c *cli.Context) error { + if err := setup(c); err != nil { + log.Errorf("Setup failed, error: %s", err) + os.Exit(1) + } + return nil + }, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "full", + Usage: "Also calls 'brew doctor'.", + }, + cli.BoolFlag{ + Name: "clean", + Usage: "Removes bitrise's workdir before setup.", + }, + }, +} + +// PrintBitriseHeaderASCIIArt ... +func PrintBitriseHeaderASCIIArt(appVersion string) { + // generated here: http://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=Bitrise + fmt.Println(` + ██████╗ ██╗████████╗██████╗ ██╗███████╗███████╗ + ██╔══██╗██║╚══██╔══╝██╔══██╗██║██╔════╝██╔════╝ + ██████╔╝██║ ██║ ██████╔╝██║███████╗█████╗ + ██╔══██╗██║ ██║ ██╔══██╗██║╚════██║██╔══╝ + ██████╔╝██║ ██║ ██║ ██║██║███████║███████╗ + ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝╚══════╝`) + fmt.Println() + log.Donef(" version: %s", appVersion) + fmt.Println() +} + +func setup(c *cli.Context) error { + PrintBitriseHeaderASCIIArt(c.App.Version) + + fullMode := c.Bool("full") + cleanMode := c.Bool("clean") + + if err := bitrise.RunSetup(c.App.Version, fullMode, cleanMode); err != nil { + return err + } + + fmt.Println() + log.Infof("To start using bitrise:") + log.Printf("* cd into your project's directory (if you're not there already)") + log.Printf("* call: bitrise init") + log.Printf("* follow the guide") + fmt.Println() + log.Donef("That's all :)") + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/share.go b/vendor/github.com/bitrise-io/bitrise/cli/share.go new file mode 100644 index 00000000..c6db1428 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/share.go @@ -0,0 +1,16 @@ +package cli + +import ( + "log" + + "github.com/bitrise-io/bitrise/tools" + "github.com/urfave/cli" +) + +func share(c *cli.Context) error { + if err := tools.StepmanShare(); err != nil { + log.Fatalf("Bitrise share failed, error: %s", err) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/share_audit.go b/vendor/github.com/bitrise-io/bitrise/cli/share_audit.go new file mode 100644 index 00000000..bb9f9065 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/share_audit.go @@ -0,0 +1,16 @@ +package cli + +import ( + "log" + + "github.com/bitrise-io/bitrise/tools" + "github.com/urfave/cli" +) + +func shareAudit(c *cli.Context) error { + if err := tools.StepmanShareAudit(); err != nil { + log.Fatalf("Bitrise share audit failed, error: %s", err) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/share_create.go b/vendor/github.com/bitrise-io/bitrise/cli/share_create.go new file mode 100644 index 00000000..e101a5e8 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/share_create.go @@ -0,0 +1,28 @@ +package cli + +import ( + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/bitrise/tools" + "github.com/urfave/cli" +) + +func create(c *cli.Context) error { + // Input validation + tag := c.String(TagKey) + if tag == "" { + log.Fatal("No step tag specified") + } + + gitURI := c.String(GitKey) + if gitURI == "" { + log.Fatal("No step url specified") + } + + stepID := c.String(StepIDKey) + + if err := tools.StepmanShareCreate(tag, gitURI, stepID); err != nil { + log.Fatalf("Bitrise share create failed, error: %s", err) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/share_finish.go b/vendor/github.com/bitrise-io/bitrise/cli/share_finish.go new file mode 100644 index 00000000..c61a19c6 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/share_finish.go @@ -0,0 +1,16 @@ +package cli + +import ( + "log" + + "github.com/bitrise-io/bitrise/tools" + "github.com/urfave/cli" +) + +func finish(c *cli.Context) error { + if err := tools.StepmanShareFinish(); err != nil { + log.Fatalf("Bitrise share finish failed, error: %s", err) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/share_start.go b/vendor/github.com/bitrise-io/bitrise/cli/share_start.go new file mode 100644 index 00000000..77cefa8a --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/share_start.go @@ -0,0 +1,21 @@ +package cli + +import ( + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/bitrise/tools" + "github.com/urfave/cli" +) + +func start(c *cli.Context) error { + // Input validation + collectionURI := c.String(CollectionKey) + if collectionURI == "" { + log.Fatal("No step collection specified") + } + + if err := tools.StepmanShareStart(collectionURI); err != nil { + log.Fatalf("Bitrise share start failed, error: %s", err) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/step_info.go b/vendor/github.com/bitrise-io/bitrise/cli/step_info.go new file mode 100644 index 00000000..c951d0af --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/step_info.go @@ -0,0 +1,119 @@ +package cli + +import ( + "fmt" + + "github.com/bitrise-io/bitrise/output" + "github.com/bitrise-io/bitrise/tools" + "github.com/urfave/cli" +) + +func printStepLibStep(collectionURI, id, version, format string) error { + switch format { + case output.FormatRaw: + out, err := tools.StepmanRawStepLibStepInfo(collectionURI, id, version) + if out != "" { + fmt.Println("Step info:") + fmt.Printf("%s", out) + } + return err + case output.FormatJSON: + outStr, err := tools.StepmanJSONStepLibStepInfo(collectionURI, id, version) + if err != nil { + return fmt.Errorf("StepmanJSONStepLibStepInfo failed, err: %s", err) + } + fmt.Println(outStr) + break + default: + return fmt.Errorf("Invalid format: %s", format) + } + return nil +} + +func printLocalStepInfo(pth, format string) error { + switch format { + case output.FormatRaw: + out, err := tools.StepmanRawLocalStepInfo(pth) + if out != "" { + fmt.Println("Step info:") + fmt.Printf("%s", out) + } + return err + case output.FormatJSON: + outStr, err := tools.StepmanJSONLocalStepInfo(pth) + if err != nil { + return fmt.Errorf("StepmanJSONLocalStepInfo failed, err: %s", err) + } + fmt.Println(outStr) + break + default: + return fmt.Errorf("Invalid format: %s", format) + } + return nil +} + +func stepInfo(c *cli.Context) error { + warnings := []string{} + + // Expand cli.Context + bitriseConfigBase64Data := c.String(ConfigBase64Key) + + bitriseConfigPath := c.String(ConfigKey) + deprecatedBitriseConfigPath := c.String(PathKey) + if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { + warnings = append(warnings, "'path' key is deprecated, use 'config' instead!") + bitriseConfigPath = deprecatedBitriseConfigPath + } + + format := c.String(OuputFormatKey) + + YMLPath := c.String(StepYMLKey) + collectionURI := c.String(CollectionKey) + + id := "" + if len(c.Args()) < 1 { + registerFatal("No step specified!", warnings, format) + } else { + id = c.Args()[0] + } + + version := c.String(VersionKey) + // + + if format == "" { + format = output.FormatRaw + } else if !(format == output.FormatRaw || format == output.FormatJSON) { + registerFatal(fmt.Sprintf("Invalid format: %s", format), warnings, output.FormatJSON) + } + + if YMLPath != "" { + // + // Local step info + if err := printLocalStepInfo(YMLPath, format); err != nil { + registerFatal(fmt.Sprintf("Failed to print step info (yml path: %s), err: %s", YMLPath, err), warnings, format) + } + } else { + + // + // Steplib step info + if collectionURI == "" { + bitriseConfig, warns, err := CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath) + warnings = append(warnings, warns...) + if err != nil { + registerFatal(fmt.Sprintf("No collection defined and failed to read bitrise config, err: %s", err), warnings, format) + } + + if bitriseConfig.DefaultStepLibSource == "" { + registerFatal("No collection defined and no default collection found in bitrise config", warnings, format) + } + + collectionURI = bitriseConfig.DefaultStepLibSource + } + + if err := printStepLibStep(collectionURI, id, version, format); err != nil { + registerFatal(fmt.Sprintf("Failed to print step info, err: %s", err), warnings, format) + } + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/step_list.go b/vendor/github.com/bitrise-io/bitrise/cli/step_list.go new file mode 100644 index 00000000..5d5a50a9 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/step_list.go @@ -0,0 +1,73 @@ +package cli + +import ( + "fmt" + + "github.com/bitrise-io/bitrise/output" + "github.com/bitrise-io/bitrise/tools" + "github.com/urfave/cli" +) + +func stepList(c *cli.Context) error { + warnings := []string{} + + // Expand cli.Context + bitriseConfigBase64Data := c.String(ConfigBase64Key) + + bitriseConfigPath := c.String(ConfigKey) + deprecatedBitriseConfigPath := c.String(PathKey) + if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { + warnings = append(warnings, "'path' key is deprecated, use 'config' instead!") + bitriseConfigPath = deprecatedBitriseConfigPath + } + + format := c.String(OuputFormatKey) + + collectionURI := c.String(CollectionKey) + // + + // Input validation + if format == "" { + format = output.FormatRaw + } else if !(format == output.FormatRaw || format == output.FormatJSON) { + registerFatal(fmt.Sprintf("Invalid format: %s", format), warnings, output.FormatJSON) + } + + if collectionURI == "" { + bitriseConfig, warns, err := CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath) + warnings = append(warnings, warns...) + if err != nil { + registerFatal(fmt.Sprintf("No collection defined and failed to read bitrise config, err: %s", err), warnings, format) + } + + if bitriseConfig.DefaultStepLibSource == "" { + registerFatal("No collection defined and no default collection found in bitrise config", warnings, format) + } + + collectionURI = bitriseConfig.DefaultStepLibSource + } + + switch format { + case output.FormatRaw: + out, err := tools.StepmanRawStepList(collectionURI) + if out != "" { + fmt.Println("Step list:") + fmt.Printf("%s", out) + } + if err != nil { + registerFatal(fmt.Sprintf("Failed to print step info, err: %s", err), warnings, format) + } + break + case output.FormatJSON: + outStr, err := tools.StepmanJSONStepList(collectionURI) + if err != nil { + registerFatal(fmt.Sprintf("Failed to print step info, err: %s", err), warnings, format) + } + fmt.Println(outStr) + break + default: + registerFatal(fmt.Sprintf("Invalid format: %s", format), warnings, output.FormatJSON) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/tools.go b/vendor/github.com/bitrise-io/bitrise/cli/tools.go new file mode 100644 index 00000000..9bd5051f --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/tools.go @@ -0,0 +1,37 @@ +package cli + +import ( + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/go-utils/command" + "github.com/urfave/cli" +) + +var stepmanCommand = cli.Command{ + Name: "stepman", + Usage: "Runs a stepman command.", + SkipFlagParsing: true, + Action: func(c *cli.Context) error { + if err := runCommandWith("stepman", c); err != nil { + log.Fatalf("Command failed, error: %s", err) + } + return nil + }, +} + +var envmanCommand = cli.Command{ + Name: "envman", + Usage: "Runs an envman command.", + SkipFlagParsing: true, + Action: func(c *cli.Context) error { + if err := runCommandWith("envman", c); err != nil { + log.Fatalf("Command failed, error: %s", err) + } + return nil + }, +} + +func runCommandWith(toolName string, c *cli.Context) error { + args := c.Args() + cmd := command.NewWithStandardOuts(toolName, args...) + return cmd.Run() +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/trigger.go b/vendor/github.com/bitrise-io/bitrise/cli/trigger.go new file mode 100644 index 00000000..ebaa4205 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/trigger.go @@ -0,0 +1,163 @@ +package cli + +import ( + "fmt" + "os" + "strings" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/bitrise/models" + "github.com/bitrise-io/bitrise/version" + "github.com/bitrise-io/go-utils/pointers" + "github.com/urfave/cli" +) + +// -------------------- +// Utility +// -------------------- + +func printAvailableTriggerFilters(triggerMap []models.TriggerMapItemModel) { + log.Infoln("The following trigger filters are available:") + + for _, triggerItem := range triggerMap { + if triggerItem.Pattern != "" { + log.Infof(" * pattern: %s", triggerItem.Pattern) + log.Infof(" is_pull_request_allowed: %v", triggerItem.IsPullRequestAllowed) + log.Infof(" workflow: %s", triggerItem.WorkflowID) + } else { + if triggerItem.PushBranch != "" { + log.Infof(" * push_branch: %s", triggerItem.PushBranch) + log.Infof(" workflow: %s", triggerItem.WorkflowID) + } else if triggerItem.PullRequestSourceBranch != "" || triggerItem.PullRequestTargetBranch != "" { + log.Infof(" * pull_request_source_branch: %s", triggerItem.PullRequestSourceBranch) + log.Infof(" pull_request_target_branch: %s", triggerItem.PullRequestTargetBranch) + log.Infof(" workflow: %s", triggerItem.WorkflowID) + } else if triggerItem.Tag != "" { + log.Infof(" * tag: %s", triggerItem.Tag) + log.Infof(" workflow: %s", triggerItem.WorkflowID) + } + } + } +} + +// -------------------- +// CLI command +// -------------------- + +func trigger(c *cli.Context) error { + PrintBitriseHeaderASCIIArt(version.VERSION) + + // Expand cli.Context + var prGlobalFlagPtr *bool + if c.GlobalIsSet(PRKey) { + prGlobalFlagPtr = pointers.NewBoolPtr(c.GlobalBool(PRKey)) + } + + var ciGlobalFlagPtr *bool + if c.GlobalIsSet(CIKey) { + ciGlobalFlagPtr = pointers.NewBoolPtr(c.GlobalBool(CIKey)) + } + + triggerPattern := c.String(PatternKey) + if triggerPattern == "" && len(c.Args()) > 0 { + triggerPattern = c.Args()[0] + } + + pushBranch := c.String(PushBranchKey) + prSourceBranch := c.String(PRSourceBranchKey) + prTargetBranch := c.String(PRTargetBranchKey) + tag := c.String(TagKey) + + bitriseConfigBase64Data := c.String(ConfigBase64Key) + bitriseConfigPath := c.String(ConfigKey) + deprecatedBitriseConfigPath := c.String(PathKey) + if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { + log.Warn("'path' key is deprecated, use 'config' instead!") + bitriseConfigPath = deprecatedBitriseConfigPath + } + + inventoryBase64Data := c.String(InventoryBase64Key) + inventoryPath := c.String(InventoryKey) + + jsonParams := c.String(JSONParamsKey) + jsonParamsBase64 := c.String(JSONParamsBase64Key) + + triggerParams, err := parseTriggerParams( + triggerPattern, + pushBranch, prSourceBranch, prTargetBranch, tag, + bitriseConfigPath, bitriseConfigBase64Data, + inventoryPath, inventoryBase64Data, + jsonParams, jsonParamsBase64) + if err != nil { + return fmt.Errorf("Failed to parse trigger command params, error: %s", err) + } + + // Inventory validation + inventoryEnvironments, err := CreateInventoryFromCLIParams(triggerParams.InventoryBase64Data, triggerParams.InventoryPath) + if err != nil { + log.Fatalf("Failed to create inventory, error: %s", err) + } + + // Config validation + bitriseConfig, warnings, err := CreateBitriseConfigFromCLIParams(triggerParams.BitriseConfigBase64Data, triggerParams.BitriseConfigPath) + for _, warning := range warnings { + log.Warnf("warning: %s", warning) + } + if err != nil { + log.Fatalf("Failed to create bitrise config, error: %s", err) + } + + // Trigger filter validation + if triggerParams.TriggerPattern == "" && + triggerParams.PushBranch == "" && triggerParams.PRSourceBranch == "" && triggerParams.PRTargetBranch == "" && triggerParams.Tag == "" { + log.Error("No trigger pattern nor trigger params specified") + printAvailableTriggerFilters(bitriseConfig.TriggerMap) + os.Exit(1) + } + // + + // Main + isPRMode, err := isPRMode(prGlobalFlagPtr, inventoryEnvironments) + if err != nil { + log.Fatalf("Failed to check PR mode, error: %s", err) + } + + if err := registerPrMode(isPRMode); err != nil { + log.Fatalf("Failed to register PR mode, error: %s", err) + } + + isCIMode, err := isCIMode(ciGlobalFlagPtr, inventoryEnvironments) + if err != nil { + log.Fatalf("Failed to check CI mode, error: %s", err) + } + + if err := registerCIMode(isCIMode); err != nil { + log.Fatalf("Failed to register CI mode, error: %s", err) + } + + workflowToRunID, err := getWorkflowIDByParamsInCompatibleMode(bitriseConfig.TriggerMap, triggerParams, isPRMode) + if err != nil { + log.Errorf("Failed to get workflow id by pattern, error: %s", err) + if strings.Contains(err.Error(), "no matching workflow found with trigger params:") { + printAvailableTriggerFilters(bitriseConfig.TriggerMap) + } + os.Exit(1) + } + + if triggerParams.TriggerPattern != "" { + log.Infof("pattern (%s) triggered workflow (%s)", triggerParams.TriggerPattern, workflowToRunID) + } else { + if triggerParams.PushBranch != "" { + log.Infof("push-branch (%s) triggered workflow (%s)", triggerParams.PushBranch, workflowToRunID) + } else if triggerParams.PRSourceBranch != "" || triggerParams.PRTargetBranch != "" { + log.Infof("pr-source-branch (%s) and pr-target-branch (%s) triggered workflow (%s)", triggerParams.PRSourceBranch, triggerParams.PRTargetBranch, workflowToRunID) + } else if triggerParams.Tag != "" { + log.Infof("tag (%s) triggered workflow (%s)", triggerParams.Tag, workflowToRunID) + } + } + + runAndExit(bitriseConfig, inventoryEnvironments, workflowToRunID) + // + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/trigger_check.go b/vendor/github.com/bitrise-io/bitrise/cli/trigger_check.go new file mode 100644 index 00000000..692aa34e --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/trigger_check.go @@ -0,0 +1,221 @@ +package cli + +import ( + "encoding/json" + "fmt" + "os" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/bitrise/models" + "github.com/bitrise-io/bitrise/output" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/pointers" + "github.com/urfave/cli" +) + +// -------------------- +// Utility +// -------------------- + +func registerFatal(errorMsg string, warnings []string, format string) { + message := ValidationItemModel{ + IsValid: (len(errorMsg) > 0), + Error: errorMsg, + Warnings: warnings, + } + + if format == output.FormatRaw { + for _, warning := range message.Warnings { + log.Warnf("warning: %s", warning) + } + log.Fatal(message.Error) + } else { + bytes, err := json.Marshal(message) + if err != nil { + log.Fatalf("Failed to parse error model, error: %s", err) + } + + fmt.Println(string(bytes)) + os.Exit(1) + } +} + +func migratePatternToParams(params RunAndTriggerParamsModel, isPullRequestMode bool) RunAndTriggerParamsModel { + if isPullRequestMode { + params.PushBranch = "" + params.PRSourceBranch = params.TriggerPattern + params.PRTargetBranch = "" + params.Tag = "" + } else { + params.PushBranch = params.TriggerPattern + params.PRSourceBranch = "" + params.PRTargetBranch = "" + params.Tag = "" + } + + params.TriggerPattern = "" + + return params +} + +func getWorkflowIDByParams(triggerMap models.TriggerMapModel, params RunAndTriggerParamsModel) (string, error) { + for _, item := range triggerMap { + match, err := item.MatchWithParams(params.PushBranch, params.PRSourceBranch, params.PRTargetBranch, params.Tag) + if err != nil { + return "", err + } + if match { + return item.WorkflowID, nil + } + } + + return "", fmt.Errorf("no matching workflow found with trigger params: push-branch: %s, pr-source-branch: %s, pr-target-branch: %s, tag: %s", params.PushBranch, params.PRSourceBranch, params.PRTargetBranch, params.Tag) +} + +// migrates deprecated params.TriggerPattern to params.PushBranch or params.PRSourceBranch based on isPullRequestMode +// and returns the triggered workflow id +func getWorkflowIDByParamsInCompatibleMode(triggerMap models.TriggerMapModel, params RunAndTriggerParamsModel, isPullRequestMode bool) (string, error) { + if params.TriggerPattern != "" { + params = migratePatternToParams(params, isPullRequestMode) + } + + return getWorkflowIDByParams(triggerMap, params) +} + +// -------------------- +// CLI command +// -------------------- + +func triggerCheck(c *cli.Context) error { + warnings := []string{} + + // + // Expand cli.Context + var prGlobalFlagPtr *bool + if c.GlobalIsSet(PRKey) { + prGlobalFlagPtr = pointers.NewBoolPtr(c.GlobalBool(PRKey)) + } + + triggerPattern := c.String(PatternKey) + if triggerPattern == "" && len(c.Args()) > 0 { + triggerPattern = c.Args()[0] + } + + pushBranch := c.String(PushBranchKey) + prSourceBranch := c.String(PRSourceBranchKey) + prTargetBranch := c.String(PRTargetBranchKey) + tag := c.String(TagKey) + + bitriseConfigBase64Data := c.String(ConfigBase64Key) + bitriseConfigPath := c.String(ConfigKey) + deprecatedBitriseConfigPath := c.String(PathKey) + if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { + warnings = append(warnings, "'path' key is deprecated, use 'config' instead!") + bitriseConfigPath = deprecatedBitriseConfigPath + } + + inventoryBase64Data := c.String(InventoryBase64Key) + inventoryPath := c.String(InventoryKey) + + jsonParams := c.String(JSONParamsKey) + jsonParamsBase64 := c.String(JSONParamsBase64Key) + + format := c.String(OuputFormatKey) + + triggerParams, err := parseTriggerCheckParams( + triggerPattern, + pushBranch, prSourceBranch, prTargetBranch, tag, + format, + bitriseConfigPath, bitriseConfigBase64Data, + inventoryPath, inventoryBase64Data, + jsonParams, jsonParamsBase64) + if err != nil { + registerFatal(fmt.Sprintf("Failed to parse trigger check params, err: %s", err), warnings, triggerParams.Format) + } + // + + // Inventory validation + inventoryEnvironments, err := CreateInventoryFromCLIParams(triggerParams.InventoryBase64Data, triggerParams.InventoryPath) + if err != nil { + registerFatal(fmt.Sprintf("Failed to create inventory, err: %s", err), warnings, triggerParams.Format) + } + + // Config validation + bitriseConfig, warns, err := CreateBitriseConfigFromCLIParams(triggerParams.BitriseConfigBase64Data, triggerParams.BitriseConfigPath) + warnings = append(warnings, warns...) + if err != nil { + registerFatal(fmt.Sprintf("Failed to create config, err: %s", err), warnings, triggerParams.Format) + } + + // Format validation + if triggerParams.Format == "" { + triggerParams.Format = output.FormatRaw + } else if !(triggerParams.Format == output.FormatRaw || triggerParams.Format == output.FormatJSON) { + registerFatal(fmt.Sprintf("Invalid format: %s", triggerParams.Format), warnings, output.FormatJSON) + } + + // Trigger filter validation + if triggerParams.TriggerPattern == "" && + triggerParams.PushBranch == "" && triggerParams.PRSourceBranch == "" && triggerParams.PRTargetBranch == "" && triggerParams.Tag == "" { + registerFatal("No trigger pattern nor trigger params specified", warnings, triggerParams.Format) + } + // + + // + // Main + isPRMode, err := isPRMode(prGlobalFlagPtr, inventoryEnvironments) + if err != nil { + registerFatal(fmt.Sprintf("Failed to check PR mode, err: %s", err), warnings, triggerParams.Format) + } + + workflowToRunID, err := getWorkflowIDByParamsInCompatibleMode(bitriseConfig.TriggerMap, triggerParams, isPRMode) + if err != nil { + registerFatal(err.Error(), warnings, triggerParams.Format) + } + + triggerModel := map[string]string{"workflow": workflowToRunID} + + if triggerParams.TriggerPattern != "" { + triggerModel["pattern"] = triggerParams.TriggerPattern + } else { + if triggerParams.PushBranch != "" { + triggerModel["push-branch"] = triggerParams.PushBranch + } else if triggerParams.PRSourceBranch != "" || triggerParams.PRTargetBranch != "" { + if triggerParams.PRSourceBranch != "" { + triggerModel["pr-source-branch"] = triggerParams.PRSourceBranch + } + if triggerParams.PRTargetBranch != "" { + triggerModel["pr-target-branch"] = triggerParams.PRTargetBranch + } + } else if triggerParams.Tag != "" { + triggerModel["tag"] = triggerParams.Tag + } + } + + switch triggerParams.Format { + case output.FormatRaw: + msg := "" + for key, value := range triggerModel { + if key == "workflow" { + msg = msg + fmt.Sprintf("-> %s", colorstring.Blue(value)) + } else { + msg = fmt.Sprintf("%s: %s ", key, value) + msg + } + } + fmt.Println(msg) + break + case output.FormatJSON: + bytes, err := json.Marshal(triggerModel) + if err != nil { + registerFatal(fmt.Sprintf("Failed to parse trigger model, err: %s", err), warnings, triggerParams.Format) + } + + fmt.Println(string(bytes)) + break + default: + registerFatal(fmt.Sprintf("Invalid format: %s", triggerParams.Format), warnings, output.FormatJSON) + } + // + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/trigger_check_test.go b/vendor/github.com/bitrise-io/bitrise/cli/trigger_check_test.go new file mode 100644 index 00000000..d3fc0cdf --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/trigger_check_test.go @@ -0,0 +1,639 @@ +package cli + +import ( + "testing" + + "github.com/bitrise-io/bitrise/bitrise" + "github.com/stretchr/testify/require" +) + +func TestMigratePatternToParams(t *testing.T) { + t.Log("converts pattern in NON PR MODE to push-branch param") + { + isPullRequestMode := false + params := RunAndTriggerParamsModel{ + TriggerPattern: "master", + } + + convertedParams := migratePatternToParams(params, isPullRequestMode) + + require.Equal(t, "master", convertedParams.PushBranch) + require.Equal(t, "", convertedParams.PRSourceBranch) + require.Equal(t, "", convertedParams.PRTargetBranch) + require.Equal(t, "", convertedParams.TriggerPattern) + require.Equal(t, "", convertedParams.Tag) + + require.Equal(t, "", convertedParams.WorkflowToRunID) + require.Equal(t, "", convertedParams.Format) + require.Equal(t, "", convertedParams.BitriseConfigPath) + require.Equal(t, "", convertedParams.BitriseConfigBase64Data) + require.Equal(t, "", convertedParams.InventoryPath) + require.Equal(t, "", convertedParams.InventoryBase64Data) + } + + t.Log("converts pattern in PR MODE to pr-source-branch param") + { + isPullRequestMode := true + params := RunAndTriggerParamsModel{ + TriggerPattern: "master", + } + + convertedParams := migratePatternToParams(params, isPullRequestMode) + + require.Equal(t, "", convertedParams.PushBranch) + require.Equal(t, "master", convertedParams.PRSourceBranch) + require.Equal(t, "", convertedParams.PRTargetBranch) + require.Equal(t, "", convertedParams.TriggerPattern) + require.Equal(t, "", convertedParams.Tag) + + require.Equal(t, "", convertedParams.WorkflowToRunID) + require.Equal(t, "", convertedParams.Format) + require.Equal(t, "", convertedParams.BitriseConfigPath) + require.Equal(t, "", convertedParams.BitriseConfigBase64Data) + require.Equal(t, "", convertedParams.InventoryPath) + require.Equal(t, "", convertedParams.InventoryBase64Data) + } + + t.Log("only modifies PushBranch, PRSourceBranch, PRTargetBranch, TriggerPattern") + { + isPullRequestMode := true + params := RunAndTriggerParamsModel{ + PushBranch: "feature/login", + PRSourceBranch: "feature/landing", + PRTargetBranch: "develop", + Tag: "0.9.0", + TriggerPattern: "master", + + WorkflowToRunID: "primary", + Format: "json", + BitriseConfigPath: "bitrise.yml", + BitriseConfigBase64Data: "base64-bitrise.yml", + InventoryPath: "inventory.yml", + InventoryBase64Data: "base64-inventory.yml", + } + + convertedParams := migratePatternToParams(params, isPullRequestMode) + + require.Equal(t, "", convertedParams.PushBranch) + require.Equal(t, "master", convertedParams.PRSourceBranch) + require.Equal(t, "", convertedParams.PRTargetBranch) + require.Equal(t, "", convertedParams.TriggerPattern) + require.Equal(t, "", convertedParams.Tag) + + require.Equal(t, "primary", convertedParams.WorkflowToRunID) + require.Equal(t, "json", convertedParams.Format) + require.Equal(t, "bitrise.yml", convertedParams.BitriseConfigPath) + require.Equal(t, "base64-bitrise.yml", convertedParams.BitriseConfigBase64Data) + require.Equal(t, "inventory.yml", convertedParams.InventoryPath) + require.Equal(t, "base64-inventory.yml", convertedParams.InventoryBase64Data) + } +} + +func TestGetWorkflowIDByParamsInCompatibleMode_new_param_test(t *testing.T) { + t.Log("params - push_branch") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- push_branch: master + workflow: master + +workflows: + master: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{PushBranch: "master"}, false) + require.NoError(t, err) + require.Equal(t, "master", workflowID) + } + + t.Log("params - pull_request_source_branch") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- pull_request_source_branch: feature/* + workflow: test + +workflows: + test: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + params := RunAndTriggerParamsModel{ + PRSourceBranch: "feature/login", + PRTargetBranch: "develop", + } + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) + require.NoError(t, err) + require.Equal(t, "test", workflowID) + } + + t.Log("params - pull_request_target_branch") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- pull_request_target_branch: deploy_* + workflow: release + +workflows: + release: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + params := RunAndTriggerParamsModel{ + PRSourceBranch: "master", + PRTargetBranch: "deploy_1_0_0", + } + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) + require.NoError(t, err) + require.Equal(t, "release", workflowID) + } + + t.Log("params - pull_request_source_branch, pull_request_target_branch") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- pull_request_source_branch: feature/* + pull_request_target_branch: develop + workflow: test + +workflows: + test: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + params := RunAndTriggerParamsModel{ + PRSourceBranch: "feature/login", + PRTargetBranch: "develop", + } + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) + require.NoError(t, err) + require.Equal(t, "test", workflowID) + } + + t.Log("params - tag") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- tag: 1.* + workflow: deploy + +workflows: + deploy: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + params := RunAndTriggerParamsModel{ + Tag: "1.0.0", + } + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) + require.NoError(t, err) + require.Equal(t, "deploy", workflowID) + } + + t.Log("params - tag") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- tag: "*.*.*" + workflow: deploy + +workflows: + deploy: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + params := RunAndTriggerParamsModel{ + Tag: "1.0.0", + } + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) + require.NoError(t, err) + require.Equal(t, "deploy", workflowID) + } + + t.Log("params - tag") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- tag: "v*.*" + workflow: deploy + +workflows: + deploy: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + params := RunAndTriggerParamsModel{ + Tag: "v1.0", + } + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) + require.NoError(t, err) + require.Equal(t, "deploy", workflowID) + } + + t.Log("params - tag - no match") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- tag: "v*.*" + workflow: deploy + +workflows: + deploy: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + params := RunAndTriggerParamsModel{ + Tag: "1.0", + } + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) + require.EqualError(t, err, "no matching workflow found with trigger params: push-branch: , pr-source-branch: , pr-target-branch: , tag: 1.0") + require.Equal(t, "", workflowID) + } + + t.Log("params - tag") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- tag: "v*.*.*" + workflow: deploy + +workflows: + deploy: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + params := RunAndTriggerParamsModel{ + Tag: "v1.0.0", + } + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) + require.NoError(t, err) + require.Equal(t, "deploy", workflowID) + } + + t.Log("params - tag - no match") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- tag: "*.*.*" + workflow: deploy + +workflows: + deploy: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + params := RunAndTriggerParamsModel{ + Tag: "1.0", + } + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) + require.EqualError(t, err, "no matching workflow found with trigger params: push-branch: , pr-source-branch: , pr-target-branch: , tag: 1.0") + require.Equal(t, "", workflowID) + } + + t.Log("params - tag - no match") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- tag: "v*.*.*" + workflow: deploy + +workflows: + deploy: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + params := RunAndTriggerParamsModel{ + Tag: "v1.0", + } + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) + require.EqualError(t, err, "no matching workflow found with trigger params: push-branch: , pr-source-branch: , pr-target-branch: , tag: v1.0") + require.Equal(t, "", workflowID) + } + + t.Log("complex trigger map") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- pattern: feature/* + workflow: test +- push_branch: feature/* + workflow: test +- pull_request_source_branch: feature/* + pull_request_target_branch: develop + workflow: test +- tag: 1.* + workflow: deploy + +workflows: + test: + deploy: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + params := RunAndTriggerParamsModel{ + PRSourceBranch: "feature/login", + PRTargetBranch: "develop", + } + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) + require.NoError(t, err) + require.Equal(t, "test", workflowID) + } + + t.Log("complex trigger map") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- pattern: feature/* + workflow: test +- push_branch: feature/* + workflow: test +- pull_request_source_branch: feature/* + pull_request_target_branch: develop + workflow: test +- tag: 1.* + workflow: deploy + +workflows: + test: + deploy: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + params := RunAndTriggerParamsModel{ + Tag: "1.0.0", + } + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) + require.NoError(t, err) + require.Equal(t, "deploy", workflowID) + } +} + +func TestGetWorkflowIDByParamsInCompatibleMode_migration_test(t *testing.T) { + t.Log("deprecated code push trigger item") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- pattern: master + is_pull_request_allowed: false + workflow: master + +workflows: + master: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + t.Log("it works with deprecated pattern") + { + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "master"}, false) + require.NoError(t, err) + require.Equal(t, "master", workflowID) + } + + t.Log("it works with new params") + { + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{PushBranch: "master"}, false) + require.NoError(t, err) + require.Equal(t, "master", workflowID) + } + + t.Log("it works with new params") + { + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{PushBranch: "master"}, true) + require.NoError(t, err) + require.Equal(t, "master", workflowID) + } + } + + t.Log("deprecated pr trigger item") + { + configStr := `format_version: 1.4.0 + +trigger_map: +- pattern: master + is_pull_request_allowed: true + workflow: master + +workflows: + master: +` + + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + t.Log("it works with deprecated pattern") + { + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "master"}, false) + require.NoError(t, err) + require.Equal(t, "master", workflowID) + } + + t.Log("it works with new params") + { + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{PushBranch: "master"}, false) + require.NoError(t, err) + require.Equal(t, "master", workflowID) + } + + t.Log("it works with new params") + { + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{PushBranch: "master"}, true) + require.NoError(t, err) + require.Equal(t, "master", workflowID) + } + } +} + +func TestGetWorkflowIDByParamsInCompatibleMode_old_tests(t *testing.T) { + configStr := `format_version: 1.4.0 + +trigger_map: +- pattern: master + is_pull_request_allowed: false + workflow: master +- pattern: feature/* + is_pull_request_allowed: true + workflow: feature +- pattern: "*" + is_pull_request_allowed: true + workflow: primary + +workflows: + test: + master: + feature: + primary: +` + config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + t.Log("Default pattern defined & Non pull request mode") + { + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "master"}, false) + require.NoError(t, err) + require.Equal(t, "master", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/a"}, false) + require.NoError(t, err) + require.Equal(t, "feature", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/"}, false) + require.NoError(t, err) + require.Equal(t, "feature", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature"}, false) + require.NoError(t, err) + require.Equal(t, "primary", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "test"}, false) + require.NoError(t, err) + require.Equal(t, "primary", workflowID) + } + + t.Log("Default pattern defined & Pull request mode") + { + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "master"}, true) + require.NoError(t, err) + require.Equal(t, "primary", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/a"}, true) + require.NoError(t, err) + require.Equal(t, "feature", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/"}, true) + require.NoError(t, err) + require.Equal(t, "feature", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature"}, true) + require.NoError(t, err) + require.Equal(t, "primary", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "test"}, true) + require.NoError(t, err) + require.Equal(t, "primary", workflowID) + } + + configStr = `format_version: 1.4.0 + +trigger_map: +- pattern: master + is_pull_request_allowed: false + workflow: master +- pattern: feature/* + is_pull_request_allowed: true + workflow: feature + +workflows: + test: + master: + feature: + primary: + ` + config, warnings, err = bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + + t.Log("No default pattern defined & Non pull request mode") + { + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "master"}, false) + require.NoError(t, err) + require.Equal(t, "master", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/a"}, false) + require.NoError(t, err) + require.Equal(t, "feature", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/"}, false) + require.NoError(t, err) + require.Equal(t, "feature", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature"}, false) + require.NotEqual(t, nil, err) + require.Equal(t, "", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "test"}, false) + require.NotEqual(t, nil, err) + require.Equal(t, "", workflowID) + } + + t.Log("No default pattern defined & Pull request mode") + { + workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "master"}, true) + require.NotEqual(t, nil, err) + require.Equal(t, "", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/a"}, true) + require.NoError(t, err) + require.Equal(t, "feature", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/"}, true) + require.NoError(t, err) + require.Equal(t, "feature", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature"}, true) + require.NotEqual(t, nil, err) + require.Equal(t, "", workflowID) + + workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "test"}, true) + require.NotEqual(t, nil, err) + require.Equal(t, "", workflowID) + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/validate.go b/vendor/github.com/bitrise-io/bitrise/cli/validate.go new file mode 100644 index 00000000..597f6566 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/validate.go @@ -0,0 +1,239 @@ +package cli + +import ( + "encoding/json" + "fmt" + + "os" + + "strings" + + "github.com/bitrise-io/bitrise/output" + "github.com/bitrise-io/go-utils/colorstring" + flog "github.com/bitrise-io/go-utils/log" + "github.com/urfave/cli" +) + +// ValidationItemModel ... +type ValidationItemModel struct { + IsValid bool `json:"is_valid" yaml:"is_valid"` + Error string `json:"error,omitempty" yaml:"error,omitempty"` + Warnings []string `json:"warnings,omitempty" yaml:"warnings,omitempty"` +} + +// ValidationModel ... +type ValidationModel struct { + Config *ValidationItemModel `json:"config,omitempty" yaml:"config,omitempty"` + Secrets *ValidationItemModel `json:"secrets,omitempty" yaml:"secrets,omitempty"` +} + +// ValidateResponseModel ... +type ValidateResponseModel struct { + Data *ValidationModel `json:"data,omitempty" yaml:"data,omitempty"` + Error string `json:"error,omitempty" yaml:"error,omitempty"` + Warnings []string `json:"warnings,omitempty" yaml:"warnings,omitempty"` +} + +// NewValidationResponse ... +func NewValidationResponse(validation ValidationModel, warnings ...string) ValidateResponseModel { + return ValidateResponseModel{ + Data: &validation, + Warnings: warnings, + } +} + +// NewValidationError ... +func NewValidationError(err string, warnings ...string) ValidateResponseModel { + return ValidateResponseModel{ + Error: err, + Warnings: warnings, + } +} + +// IsValid ... +func (v ValidationModel) IsValid() bool { + if v.Config == nil && v.Secrets == nil { + return false + } + + if v.Config != nil && !v.Config.IsValid { + return false + } + + if v.Secrets != nil && !v.Secrets.IsValid { + return false + } + + return true +} + +// JSON ... +func (v ValidateResponseModel) JSON() string { + bytes, err := json.Marshal(v) + if err != nil { + return fmt.Sprintf(`"Failed to marshal validation result (%#v), err: %s"`, v, err) + } + return string(bytes) +} + +func (v ValidateResponseModel) String() string { + if v.Error != "" { + msg := fmt.Sprintf("%s: %s", colorstring.Red("Error"), v.Error) + if len(v.Warnings) > 0 { + msg += "\nWarning(s):\n" + for i, warning := range v.Warnings { + msg += fmt.Sprintf("- %s", warning) + if i != len(v.Warnings)-1 { + msg += "\n" + } + } + } + return msg + } + + if v.Data != nil { + msg := v.Data.String() + if len(v.Warnings) > 0 { + msg += "\nWarning(s):\n" + for i, warning := range v.Warnings { + msg += fmt.Sprintf("- %s", warning) + if i != len(v.Warnings)-1 { + msg += "\n" + } + } + } + return msg + } + + return "" +} + +// String ... +func (v ValidationModel) String() string { + msg := "" + + if v.Config != nil { + config := *v.Config + if config.IsValid { + msg += fmt.Sprintf("Config is valid: %s", colorstring.Greenf("%v", true)) + } else { + msg += fmt.Sprintf("Config is valid: %s", colorstring.Redf("%v", false)) + msg += fmt.Sprintf("\nError: %s", colorstring.Red(config.Error)) + } + + if len(config.Warnings) > 0 { + msg += "\nWarning(s):\n" + for i, warning := range config.Warnings { + msg += fmt.Sprintf("- %s", warning) + if i != len(config.Warnings)-1 { + msg += "\n" + } + } + } + } + + if v.Secrets != nil { + if v.Config != nil { + msg += "\n" + } + + secret := *v.Secrets + if secret.IsValid { + msg += fmt.Sprintf("Secret is valid: %s", colorstring.Greenf("%v", true)) + } else { + msg += fmt.Sprintf("Secret is valid: %s", colorstring.Redf("%v", false)) + msg += fmt.Sprintf("\nError: %s", colorstring.Red(secret.Error)) + } + } + + return msg +} + +func validate(c *cli.Context) error { + warnings := []string{} + + // Expand cli.Context + bitriseConfigBase64Data := c.String(ConfigBase64Key) + bitriseConfigPath := c.String(ConfigKey) + deprecatedBitriseConfigPath := c.String(PathKey) + if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { + warnings = append(warnings, "'path' key is deprecated, use 'config' instead!") + bitriseConfigPath = deprecatedBitriseConfigPath + } + + inventoryBase64Data := c.String(InventoryBase64Key) + inventoryPath := c.String(InventoryKey) + + format := c.String(OuputFormatKey) + if format == "" { + format = output.FormatRaw + } + // + + var log flog.Logger + log = flog.NewDefaultRawLogger() + if format == output.FormatRaw { + log = flog.NewDefaultRawLogger() + } else if format == output.FormatJSON { + log = flog.NewDefaultJSONLoger() + } else { + log.Print(NewValidationError(fmt.Sprintf("Invalid format: %s", format), warnings...)) + os.Exit(1) + } + + validation := ValidationModel{} + + pth, err := GetBitriseConfigFilePath(bitriseConfigPath) + if err != nil && !strings.Contains(err.Error(), "bitrise.yml path not defined and not found on it's default path:") { + log.Print(NewValidationError(fmt.Sprintf("Failed to get config path, err: %s", err), warnings...)) + os.Exit(1) + } + + if pth != "" || (pth == "" && bitriseConfigBase64Data != "") { + // Config validation + _, warns, err := CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath) + configValidation := ValidationItemModel{ + IsValid: true, + Warnings: warns, + } + if err != nil { + configValidation.IsValid = false + configValidation.Error = err.Error() + } + + validation.Config = &configValidation + } + + pth, err = GetInventoryFilePath(inventoryPath) + if err != nil { + log.Print(NewValidationError(fmt.Sprintf("Failed to get secrets path, err: %s", err), warnings...)) + os.Exit(1) + } + + if pth != "" || inventoryBase64Data != "" { + // Inventory validation + _, err := CreateInventoryFromCLIParams(inventoryBase64Data, inventoryPath) + secretValidation := ValidationItemModel{ + IsValid: true, + } + if err != nil { + secretValidation.IsValid = false + secretValidation.Error = err.Error() + } + + validation.Secrets = &secretValidation + } + + if validation.Config == nil && validation.Secrets == nil { + log.Print(NewValidationError("No config or secrets found for validation", warnings...)) + os.Exit(1) + } + + log.Print(NewValidationResponse(validation, warnings...)) + + if !validation.IsValid() { + os.Exit(1) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/version.go b/vendor/github.com/bitrise-io/bitrise/cli/version.go new file mode 100644 index 00000000..ccdc0e56 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/version.go @@ -0,0 +1,63 @@ +package cli + +import ( + "fmt" + "log" + + "runtime" + + "github.com/bitrise-io/bitrise/models" + "github.com/bitrise-io/bitrise/output" + "github.com/bitrise-io/bitrise/version" + "github.com/urfave/cli" +) + +// VersionOutputModel ... +type VersionOutputModel struct { + Version string `json:"version"` + FormatVersion string `json:"format_version"` + OS string `json:"os"` + GO string `json:"go"` + BuildNumber string `json:"build_number"` + Commit string `json:"commit"` +} + +func printVersionCmd(c *cli.Context) error { + fullVersion := c.Bool("full") + + if err := output.ConfigureOutputFormat(c); err != nil { + log.Fatalf("Failed to configure output format, error: %s", err) + } + + versionOutput := VersionOutputModel{ + Version: version.VERSION, + } + + if fullVersion { + versionOutput.FormatVersion = models.Version + versionOutput.BuildNumber = version.BuildNumber + versionOutput.Commit = version.Commit + versionOutput.OS = fmt.Sprintf("%s (%s)", runtime.GOOS, runtime.GOARCH) + versionOutput.GO = runtime.Version() + } + + if output.Format == output.FormatRaw { + if fullVersion { + versionStr := fmt.Sprintf(`version: %s +format version: %s +os: %s +go: %s +build number: %s +commit: %s +`, versionOutput.Version, versionOutput.FormatVersion, versionOutput.OS, versionOutput.GO, versionOutput.BuildNumber, versionOutput.Commit) + fmt.Println(versionStr) + } else { + versionStr := fmt.Sprintf("%s", versionOutput.Version) + fmt.Println(versionStr) + } + } else { + output.Print(versionOutput, output.Format) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/workflow_list.go b/vendor/github.com/bitrise-io/bitrise/cli/workflow_list.go new file mode 100644 index 00000000..559248b6 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/cli/workflow_list.go @@ -0,0 +1,129 @@ +package cli + +import ( + "encoding/json" + "fmt" + "sort" + "strings" + + "github.com/bitrise-io/bitrise/output" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/urfave/cli" +) + +func printWorkflList(workflowList map[string]map[string]string, format string, minimal bool) error { + printRawWorkflowMap := func(name string, workflow map[string]string) { + fmt.Printf("⚡️ %s\n", colorstring.Green(name)) + fmt.Printf(" %s: %s\n", colorstring.Yellow("Title"), workflow["title"]) + fmt.Printf(" %s: %s\n", colorstring.Yellow("Summary"), workflow["summary"]) + if !minimal { + fmt.Printf(" %s: %s\n", colorstring.Yellow("Description"), workflow["description"]) + } + fmt.Printf(" %s: bitrise run %s\n", colorstring.Yellow("Run with"), name) + fmt.Println() + } + + switch format { + case output.FormatRaw: + workflowNames := []string{} + utilityWorkflowNames := []string{} + + for wfName := range workflowList { + if strings.HasPrefix(wfName, "_") { + utilityWorkflowNames = append(utilityWorkflowNames, wfName) + } else { + workflowNames = append(workflowNames, wfName) + } + } + sort.Strings(workflowNames) + sort.Strings(utilityWorkflowNames) + + fmt.Println() + + if len(workflowNames) > 0 { + fmt.Printf("%s\n", "Workflows") + fmt.Printf("%s\n", "---------") + for _, name := range workflowNames { + workflow := workflowList[name] + printRawWorkflowMap(name, workflow) + } + fmt.Println() + } + + if len(utilityWorkflowNames) > 0 { + fmt.Printf("%s\n", "Util Workflows") + fmt.Printf("%s\n", "--------------") + for _, name := range utilityWorkflowNames { + workflow := workflowList[name] + printRawWorkflowMap(name, workflow) + } + fmt.Println() + } + + if len(workflowNames) == 0 && len(utilityWorkflowNames) == 0 { + fmt.Printf("Config doesn't contain any workflow") + } + + case output.FormatJSON: + bytes, err := json.Marshal(workflowList) + if err != nil { + return err + } + fmt.Println(string(bytes)) + default: + return fmt.Errorf("Invalid output format: %s", format) + } + return nil +} + +func workflowList(c *cli.Context) error { + warnings := []string{} + + // Expand cli.Context + bitriseConfigBase64Data := c.String(ConfigBase64Key) + + bitriseConfigPath := c.String(ConfigKey) + deprecatedBitriseConfigPath := c.String(PathKey) + if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { + warnings = append(warnings, "'path' key is deprecated, use 'config' instead!") + bitriseConfigPath = deprecatedBitriseConfigPath + } + + format := c.String(OuputFormatKey) + + minimal := c.Bool(MinimalModeKey) + // + + // Input validation + if format == "" { + format = output.FormatRaw + } else if !(format == output.FormatRaw || format == output.FormatJSON) { + registerFatal(fmt.Sprintf("Invalid format: %s", format), warnings, output.FormatJSON) + } + + // Config validation + bitriseConfig, warns, err := CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath) + warnings = append(warnings, warns...) + if err != nil { + registerFatal(fmt.Sprintf("Failed to create bitrise config, err: %s", err), warnings, output.FormatJSON) + } + + workflowList := map[string]map[string]string{} + if len(bitriseConfig.Workflows) > 0 { + for workflowID, workflow := range bitriseConfig.Workflows { + workflowMap := map[string]string{} + workflowMap["title"] = workflow.Title + workflowMap["summary"] = workflow.Summary + if !minimal { + workflowMap["description"] = workflow.Description + } + workflowList[workflowID] = workflowMap + } + } + + if err := printWorkflList(workflowList, format, minimal); err != nil { + registerFatal(fmt.Sprintf("Failed to print workflows, err: %s", err), warnings, output.FormatJSON) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/configs/configs.go b/vendor/github.com/bitrise-io/bitrise/configs/configs.go new file mode 100644 index 00000000..c55b23ff --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/configs/configs.go @@ -0,0 +1,159 @@ +package configs + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "time" + + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" +) + +// ConfigModel ... +type ConfigModel struct { + SetupVersion string `json:"setup_version"` + LastPluginUpdateCheck time.Time `json:"last_plugin_update_check"` +} + +// --------------------------- +// --- Project level vars / configs + +var ( + // IsCIMode ... + IsCIMode = false + // IsDebugMode ... + IsDebugMode = false + // IsPullRequestMode ... + IsPullRequestMode = false +) + +// --------------------------- +// --- Consts + +const ( + // CIModeEnvKey ... + CIModeEnvKey = "CI" + // PRModeEnvKey ... + PRModeEnvKey = "PR" + // PullRequestIDEnvKey ... + PullRequestIDEnvKey = "PULL_REQUEST_ID" + // DebugModeEnvKey ... + DebugModeEnvKey = "DEBUG" + // LogLevelEnvKey ... + LogLevelEnvKey = "LOGLEVEL" + + // --- Debug Options + + // DebugUseSystemTools ... + DebugUseSystemTools = "BITRISE_DEBUG_USE_SYSTEM_TOOLS" +) + +const ( + bitriseConfigFileName = "config.json" +) + +// IsDebugUseSystemTools ... +func IsDebugUseSystemTools() bool { + return os.Getenv(DebugUseSystemTools) == "true" +} + +func loadBitriseConfig() (ConfigModel, error) { + if err := EnsureBitriseConfigDirExists(); err != nil { + return ConfigModel{}, err + } + + configPth := getBitriseConfigFilePath() + if exist, err := pathutil.IsPathExists(configPth); err != nil { + return ConfigModel{}, err + } else if !exist { + return ConfigModel{}, nil + } + + bytes, err := fileutil.ReadBytesFromFile(configPth) + if err != nil { + return ConfigModel{}, err + } + + if len(bytes) == 0 { + return ConfigModel{}, errors.New("empty config file") + } + + config := ConfigModel{} + if err := json.Unmarshal(bytes, &config); err != nil { + return ConfigModel{}, fmt.Errorf("failed to marshal config (%s), error: %s", string(bytes), err) + } + + return config, nil +} + +func saveBitriseConfig(config ConfigModel) error { + bytes, err := json.Marshal(config) + if err != nil { + return err + } + + configPth := getBitriseConfigFilePath() + return fileutil.WriteBytesToFile(configPth, bytes) +} + +// DeleteBitriseConfigDir ... +func DeleteBitriseConfigDir() error { + confDirPth := GetBitriseHomeDirPath() + return os.RemoveAll(confDirPth) +} + +// EnsureBitriseConfigDirExists ... +func EnsureBitriseConfigDirExists() error { + confDirPth := GetBitriseHomeDirPath() + return pathutil.EnsureDirExist(confDirPth) +} + +// CheckIsPluginUpdateCheckRequired ... +func CheckIsPluginUpdateCheckRequired() bool { + config, err := loadBitriseConfig() + if err != nil { + return false + } + + duration := time.Now().Sub(config.LastPluginUpdateCheck) + if duration.Hours() >= 24 { + return true + } + + return false +} + +// SavePluginUpdateCheck ... +func SavePluginUpdateCheck() error { + config, err := loadBitriseConfig() + if err != nil { + return err + } + + config.LastPluginUpdateCheck = time.Now() + + return saveBitriseConfig(config) +} + +// CheckIsSetupWasDoneForVersion ... +func CheckIsSetupWasDoneForVersion(ver string) bool { + config, err := loadBitriseConfig() + if err != nil { + return false + } + return (config.SetupVersion == ver) +} + +// SaveSetupSuccessForVersion ... +func SaveSetupSuccessForVersion(ver string) error { + config, err := loadBitriseConfig() + if err != nil { + return err + } + + config.SetupVersion = ver + + return saveBitriseConfig(config) +} diff --git a/vendor/github.com/bitrise-io/bitrise/configs/configs_test.go b/vendor/github.com/bitrise-io/bitrise/configs/configs_test.go new file mode 100644 index 00000000..4aedae5b --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/configs/configs_test.go @@ -0,0 +1,30 @@ +package configs + +import ( + "os" + "testing" + + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestSetupForVersionChecks(t *testing.T) { + fakeHomePth, err := pathutil.NormalizedOSTempDirPath("_FAKE_HOME") + require.Equal(t, nil, err) + originalHome := os.Getenv("HOME") + + defer func() { + require.Equal(t, nil, os.Setenv("HOME", originalHome)) + require.Equal(t, nil, os.RemoveAll(fakeHomePth)) + }() + + require.Equal(t, nil, os.Setenv("HOME", fakeHomePth)) + + require.Equal(t, false, CheckIsSetupWasDoneForVersion("0.9.7")) + + require.Equal(t, nil, SaveSetupSuccessForVersion("0.9.7")) + + require.Equal(t, true, CheckIsSetupWasDoneForVersion("0.9.7")) + + require.Equal(t, false, CheckIsSetupWasDoneForVersion("0.9.8")) +} diff --git a/vendor/github.com/bitrise-io/bitrise/configs/paths.go b/vendor/github.com/bitrise-io/bitrise/configs/paths.go new file mode 100644 index 00000000..302818a2 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/configs/paths.go @@ -0,0 +1,203 @@ +package configs + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/go-utils/pathutil" +) + +var ( + // InputEnvstorePath ... + InputEnvstorePath string + // OutputEnvstorePath ... + OutputEnvstorePath string + // FormattedOutputPath ... + FormattedOutputPath string + // BitriseWorkDirPath ... + BitriseWorkDirPath string + // BitriseWorkStepsDirPath ... + BitriseWorkStepsDirPath string + // CurrentDir ... + CurrentDir string +) + +const ( + // EnvstorePathEnvKey ... + EnvstorePathEnvKey = "ENVMAN_ENVSTORE_PATH" + // FormattedOutputPathEnvKey ... + FormattedOutputPathEnvKey = "BITRISE_STEP_FORMATTED_OUTPUT_FILE_PATH" + // BitriseSourceDirEnvKey ... + BitriseSourceDirEnvKey = "BITRISE_SOURCE_DIR" + // BitriseDeployDirEnvKey ... + BitriseDeployDirEnvKey = "BITRISE_DEPLOY_DIR" + // BitriseCacheDirEnvKey ... + BitriseCacheDirEnvKey = "BITRISE_CACHE_DIR" + // BitriseTmpDirEnvKey ... + BitriseTmpDirEnvKey = "BITRISE_TMP_DIR" +) + +// GetBitriseHomeDirPath ... +func GetBitriseHomeDirPath() string { + return filepath.Join(pathutil.UserHomeDir(), ".bitrise") +} + +func getBitriseConfigFilePath() string { + return filepath.Join(GetBitriseHomeDirPath(), bitriseConfigFileName) +} + +// GetBitriseToolsDirPath ... +func GetBitriseToolsDirPath() string { + return filepath.Join(GetBitriseHomeDirPath(), "tools") +} + +// GetBitriseToolkitsDirPath ... +func GetBitriseToolkitsDirPath() string { + return filepath.Join(GetBitriseHomeDirPath(), "toolkits") +} + +func initBitriseWorkPaths() error { + bitriseWorkDirPath, err := pathutil.NormalizedOSTempDirPath("bitrise") + if err != nil { + return err + } + if exist, err := pathutil.IsPathExists(bitriseWorkDirPath); err != nil { + return err + } else if !exist { + if err := os.MkdirAll(bitriseWorkDirPath, 0777); err != nil { + return err + } + } + BitriseWorkDirPath = bitriseWorkDirPath + + bitriseWorkStepsDirPath, err := filepath.Abs(filepath.Join(BitriseWorkDirPath, "step_src")) + if err != nil { + return err + } + if exist, err := pathutil.IsPathExists(bitriseWorkStepsDirPath); err != nil { + return err + } else if !exist { + if err := os.MkdirAll(bitriseWorkStepsDirPath, 0777); err != nil { + return err + } + } + BitriseWorkStepsDirPath = bitriseWorkStepsDirPath + + return nil +} + +// GeneratePATHEnvString ... +func GeneratePATHEnvString(currentPATHEnv, pathToInclude string) string { + if currentPATHEnv == "" { + return pathToInclude + } + if pathToInclude == "" { + return currentPATHEnv + } + if pathToInclude == currentPATHEnv { + return currentPATHEnv + } + + pthWithPathIncluded := currentPATHEnv + if !strings.HasSuffix(pthWithPathIncluded, pathToInclude) && + !strings.Contains(pthWithPathIncluded, pathToInclude+":") { + pthWithPathIncluded = pathToInclude + ":" + pthWithPathIncluded + } + return pthWithPathIncluded +} + +// InitPaths ... +func InitPaths() error { + if err := initBitriseWorkPaths(); err != nil { + return fmt.Errorf("Failed to init bitrise paths, error: %s", err) + } + + // --- Bitrise TOOLS + { + bitriseToolsDirPth := GetBitriseToolsDirPath() + if err := pathutil.EnsureDirExist(bitriseToolsDirPth); err != nil { + return err + } + pthWithBitriseTools := GeneratePATHEnvString(os.Getenv("PATH"), bitriseToolsDirPth) + + if IsDebugUseSystemTools() { + log.Warn("[BitriseDebug] Using system tools, instead of the ones in BITRISE_HOME") + } else { + if err := os.Setenv("PATH", pthWithBitriseTools); err != nil { + return fmt.Errorf("Failed to set PATH to include BITRISE_HOME/tools! Error: %s", err) + } + } + } + + inputEnvstorePath, err := filepath.Abs(filepath.Join(BitriseWorkDirPath, "input_envstore.yml")) + if err != nil { + return fmt.Errorf("Failed to set input envstore path, error: %s", err) + } + InputEnvstorePath = inputEnvstorePath + + outputEnvstorePath, err := filepath.Abs(filepath.Join(BitriseWorkDirPath, "output_envstore.yml")) + if err != nil { + return fmt.Errorf("Failed to set output envstore path, error: %s", err) + } + OutputEnvstorePath = outputEnvstorePath + + formoutPath, err := filepath.Abs(filepath.Join(BitriseWorkDirPath, "formatted_output.md")) + if err != nil { + return fmt.Errorf("Failed to set formatted output path, error: %s", err) + } + FormattedOutputPath = formoutPath + + currentDir, err := filepath.Abs("./") + if err != nil { + return fmt.Errorf("Failed to set current dir, error: %s", err) + } + CurrentDir = currentDir + + // BITRISE_SOURCE_DIR + if os.Getenv(BitriseSourceDirEnvKey) == "" { + if err := os.Setenv(BitriseSourceDirEnvKey, currentDir); err != nil { + return fmt.Errorf("Failed to set BITRISE_SOURCE_DIR, error: %s", err) + } + } + + // BITRISE_DEPLOY_DIR + if os.Getenv(BitriseDeployDirEnvKey) == "" { + deployDir, err := pathutil.NormalizedOSTempDirPath("deploy") + if err != nil { + return fmt.Errorf("Failed to set deploy dir, error: %s", err) + } + + if err := os.Setenv(BitriseDeployDirEnvKey, deployDir); err != nil { + return fmt.Errorf("Failed to set BITRISE_DEPLOY_DIR, error: %s", err) + } + } + + // BITRISE_CACHE_DIR + if os.Getenv(BitriseCacheDirEnvKey) == "" { + cacheDir, err := pathutil.NormalizedOSTempDirPath("cache") + if err != nil { + return fmt.Errorf("Failed to set cache dir, error: %s", err) + } + + if err := os.Setenv(BitriseCacheDirEnvKey, cacheDir); err != nil { + return fmt.Errorf("Failed to set BITRISE_CACHE_DIR, error: %s", err) + } + } + + //BITRISE_TMP_DIR + if os.Getenv(BitriseTmpDirEnvKey) == "" { + tmpDir, err := pathutil.NormalizedOSTempDirPath("tmp") + if err != nil { + return fmt.Errorf("Failed to set tmp dir, error: %s", err) + } + + if err := os.Setenv(BitriseTmpDirEnvKey, tmpDir); err != nil { + return fmt.Errorf("Failed to set BITRISE_TMP_DIR, error: %s", err) + } + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/configs/paths_test.go b/vendor/github.com/bitrise-io/bitrise/configs/paths_test.go new file mode 100644 index 00000000..aacc1fbc --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/configs/paths_test.go @@ -0,0 +1,104 @@ +package configs + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGeneratePATHEnvString(t *testing.T) { + t.Log("Empty starting PATH") + require.Equal(t, "/MY/PATH", + GeneratePATHEnvString("", "/MY/PATH")) + + t.Log("Empty PathToInclude") + require.Equal(t, "/usr/bin:/usr/local/bin:/bin", + GeneratePATHEnvString("/usr/bin:/usr/local/bin:/bin", "")) + + t.Log("Both Empty") + require.Equal(t, "", + GeneratePATHEnvString("", "")) + + t.Log("PATH = the path to include") + require.Equal(t, "/MY/PATH", + GeneratePATHEnvString("/MY/PATH", "/MY/PATH")) + + t.Log("PathToInclude is not in the PATH yet") + require.Equal(t, "/MY/PATH:/usr/bin:/usr/local/bin:/bin", + GeneratePATHEnvString("/usr/bin:/usr/local/bin:/bin", "/MY/PATH")) + + t.Log("PathToInclude is at the START of the PATH") + require.Equal(t, "/MY/PATH:/usr/bin:/usr/local/bin:/bin", + GeneratePATHEnvString("/MY/PATH:/usr/bin:/usr/local/bin:/bin", "/MY/PATH")) + + t.Log("PathToInclude is at the END of the PATH") + require.Equal(t, "/usr/bin:/usr/local/bin:/bin:/MY/PATH", + GeneratePATHEnvString("/usr/bin:/usr/local/bin:/bin:/MY/PATH", "/MY/PATH")) + + t.Log("PathToInclude is in the MIDDLE of the PATH") + require.Equal(t, "/usr/bin:/MY/PATH:/usr/local/bin:/bin", + GeneratePATHEnvString("/usr/bin:/MY/PATH:/usr/local/bin:/bin", "/MY/PATH")) +} + +func TestInitPaths(t *testing.T) { + // + // BITRISE_SOURCE_DIR + + // Unset BITRISE_SOURCE_DIR -> after InitPaths BITRISE_SOURCE_DIR should be CurrentDir + if os.Getenv(BitriseSourceDirEnvKey) != "" { + require.Equal(t, nil, os.Unsetenv(BitriseSourceDirEnvKey)) + } + require.Equal(t, nil, InitPaths()) + require.Equal(t, CurrentDir, os.Getenv(BitriseSourceDirEnvKey)) + + // Set BITRISE_SOURCE_DIR -> after InitPaths BITRISE_SOURCE_DIR should keep content + require.Equal(t, nil, os.Setenv(BitriseSourceDirEnvKey, "$HOME/test")) + require.Equal(t, nil, InitPaths()) + require.Equal(t, "$HOME/test", os.Getenv(BitriseSourceDirEnvKey)) + + // + // BITRISE_DEPLOY_DIR + + // Unset BITRISE_DEPLOY_DIR -> after InitPaths BITRISE_DEPLOY_DIR should be temp dir + if os.Getenv(BitriseDeployDirEnvKey) != "" { + require.Equal(t, nil, os.Unsetenv(BitriseDeployDirEnvKey)) + } + require.Equal(t, nil, InitPaths()) + require.NotEqual(t, "", os.Getenv(BitriseDeployDirEnvKey)) + + // Set BITRISE_DEPLOY_DIR -> after InitPaths BITRISE_DEPLOY_DIR should keep content + require.Equal(t, nil, os.Setenv(BitriseDeployDirEnvKey, "$HOME/test")) + require.Equal(t, nil, InitPaths()) + require.Equal(t, "$HOME/test", os.Getenv(BitriseDeployDirEnvKey)) + + // + // BITRISE_CACHE_DIR + + // Unset BITRISE_CACHE_DIR -> after InitPaths BITRISE_CACHE_DIR should be temp dir + if os.Getenv(BitriseCacheDirEnvKey) != "" { + require.Equal(t, nil, os.Unsetenv(BitriseCacheDirEnvKey)) + } + require.Equal(t, nil, InitPaths()) + require.NotEqual(t, "", os.Getenv(BitriseCacheDirEnvKey)) + + // Set BITRISE_CACHE_DIR -> after InitPaths BITRISE_CACHE_DIR should keep content + require.Equal(t, nil, os.Setenv(BitriseCacheDirEnvKey, "$HOME/test")) + require.Equal(t, nil, InitPaths()) + require.Equal(t, "$HOME/test", os.Getenv(BitriseCacheDirEnvKey)) + + // + // BITRISE_TMP_DIR + + // Unset BITRISE_TMP_DIR -> after InitPaths BITRISE_TMP_DIR should be temp dir + if os.Getenv(BitriseTmpDirEnvKey) != "" { + require.Equal(t, nil, os.Unsetenv(BitriseTmpDirEnvKey)) + } + require.Equal(t, nil, InitPaths()) + require.NotEqual(t, "", os.Getenv(BitriseTmpDirEnvKey)) + + // Set BITRISE_TMP_DIR -> after InitPaths BITRISE_TMP_DIR should keep content + require.Equal(t, nil, os.Setenv(BitriseTmpDirEnvKey, "$HOME/test")) + require.Equal(t, nil, InitPaths()) + require.Equal(t, "$HOME/test", os.Getenv(BitriseTmpDirEnvKey)) +} diff --git a/vendor/github.com/bitrise-io/bitrise/deps.go b/vendor/github.com/bitrise-io/bitrise/deps.go new file mode 100644 index 00000000..756780b9 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/deps.go @@ -0,0 +1,9 @@ +// This file contains dependency imports +// which are only required for `go test`. +// Godeps recently introduced a breaking change, which now +// completely ignors every file ending with `_test.go`, +// and so the dependencies which are required only for `go test`. +// So, we'll declare those here. +package main + +import _ "github.com/stretchr/testify/require" diff --git a/vendor/github.com/bitrise-io/bitrise/docker-compose.yml b/vendor/github.com/bitrise-io/bitrise/docker-compose.yml new file mode 100644 index 00000000..1e0e93f8 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/docker-compose.yml @@ -0,0 +1,4 @@ +app: + build: . + volumes: + - .:/go/src/github.com/bitrise-io/bitrise diff --git a/vendor/github.com/bitrise-io/bitrise/gows.yml b/vendor/github.com/bitrise-io/bitrise/gows.yml new file mode 100644 index 00000000..0e298208 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/gows.yml @@ -0,0 +1 @@ +package_name: github.com/bitrise-io/bitrise diff --git a/vendor/github.com/bitrise-io/bitrise/main.go b/vendor/github.com/bitrise-io/bitrise/main.go new file mode 100644 index 00000000..e1f9a0f9 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/bitrise-io/bitrise/cli" + +func main() { + cli.Run() +} diff --git a/vendor/github.com/bitrise-io/bitrise/models/models.go b/vendor/github.com/bitrise-io/bitrise/models/models.go new file mode 100644 index 00000000..1cd413e5 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/models/models.go @@ -0,0 +1,123 @@ +package models + +import ( + "time" + + envmanModels "github.com/bitrise-io/envman/models" + stepmanModels "github.com/bitrise-io/stepman/models" +) + +const ( + // StepRunStatusCodeSuccess ... + StepRunStatusCodeSuccess = 0 + // StepRunStatusCodeFailed ... + StepRunStatusCodeFailed = 1 + // StepRunStatusCodeFailedSkippable ... + StepRunStatusCodeFailedSkippable = 2 + // StepRunStatusCodeSkipped ... + StepRunStatusCodeSkipped = 3 + // StepRunStatusCodeSkippedWithRunIf ... + StepRunStatusCodeSkippedWithRunIf = 4 + + // Version ... + Version = "4" +) + +// StepListItemModel ... +type StepListItemModel map[string]stepmanModels.StepModel + +// WorkflowModel ... +type WorkflowModel struct { + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + BeforeRun []string `json:"before_run,omitempty" yaml:"before_run,omitempty"` + AfterRun []string `json:"after_run,omitempty" yaml:"after_run,omitempty"` + Environments []envmanModels.EnvironmentItemModel `json:"envs,omitempty" yaml:"envs,omitempty"` + Steps []StepListItemModel `json:"steps,omitempty" yaml:"steps,omitempty"` +} + +// AppModel ... +type AppModel struct { + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Environments []envmanModels.EnvironmentItemModel `json:"envs,omitempty" yaml:"envs,omitempty"` +} + +// TriggerEventType ... +type TriggerEventType string + +const ( + // TriggerEventTypeCodePush ... + TriggerEventTypeCodePush TriggerEventType = "code-push" + // TriggerEventTypePullRequest ... + TriggerEventTypePullRequest TriggerEventType = "pull-request" + // TriggerEventTypeTag ... + TriggerEventTypeTag TriggerEventType = "tag" + // TriggerEventTypeUnknown ... + TriggerEventTypeUnknown TriggerEventType = "unkown" +) + +// TriggerMapItemModel ... +type TriggerMapItemModel struct { + PushBranch string `json:"push_branch,omitempty" yaml:"push_branch,omitempty"` + PullRequestSourceBranch string `json:"pull_request_source_branch,omitempty" yaml:"pull_request_source_branch,omitempty"` + PullRequestTargetBranch string `json:"pull_request_target_branch,omitempty" yaml:"pull_request_target_branch,omitempty"` + Tag string `json:"tag,omitempty" yaml:"tag,omitempty"` + WorkflowID string `json:"workflow,omitempty" yaml:"workflow,omitempty"` + + // deprecated + Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` + IsPullRequestAllowed bool `json:"is_pull_request_allowed,omitempty" yaml:"is_pull_request_allowed,omitempty"` +} + +// TriggerMapModel ... +type TriggerMapModel []TriggerMapItemModel + +// BitriseDataModel ... +type BitriseDataModel struct { + FormatVersion string `json:"format_version" yaml:"format_version"` + DefaultStepLibSource string `json:"default_step_lib_source,omitempty" yaml:"default_step_lib_source,omitempty"` + ProjectType string `json:"project_type" yaml:"project_type"` + // + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + // + App AppModel `json:"app,omitempty" yaml:"app,omitempty"` + TriggerMap TriggerMapModel `json:"trigger_map,omitempty" yaml:"trigger_map,omitempty"` + Workflows map[string]WorkflowModel `json:"workflows,omitempty" yaml:"workflows,omitempty"` +} + +// StepIDData ... +// structured representation of a composite-step-id +// a composite step id is: step-lib-source::step-id@1.0.0 +type StepIDData struct { + // SteplibSource : steplib source uri, or in case of local path just "path", and in case of direct git url just "git" + SteplibSource string + // IDOrURI : ID if steplib is provided, URI if local step or in case a direct git url provided + IDorURI string + // Version : version in the steplib, or in case of a direct git step the tag-or-branch to use + Version string +} + +// BuildRunResultsModel ... +type BuildRunResultsModel struct { + StartTime time.Time `json:"start_time" yaml:"start_time"` + StepmanUpdates map[string]int `json:"stepman_updates" yaml:"stepman_updates"` + SuccessSteps []StepRunResultsModel `json:"success_steps" yaml:"success_steps"` + FailedSteps []StepRunResultsModel `json:"failed_steps" yaml:"failed_steps"` + FailedSkippableSteps []StepRunResultsModel `json:"failed_skippable_steps" yaml:"failed_skippable_steps"` + SkippedSteps []StepRunResultsModel `json:"skipped_steps" yaml:"skipped_steps"` +} + +// StepRunResultsModel ... +type StepRunResultsModel struct { + StepInfo stepmanModels.StepInfoModel `json:"step_info" yaml:"step_info"` + Status int `json:"status" yaml:"status"` + Idx int `json:"idx" yaml:"idx"` + RunTime time.Duration `json:"run_time" yaml:"run_time"` + ErrorStr string `json:"error_str" yaml:"error_str"` + ExitCode int `json:"exit_code" yaml:"exit_code"` +} diff --git a/vendor/github.com/bitrise-io/bitrise/models/models_methods.go b/vendor/github.com/bitrise-io/bitrise/models/models_methods.go new file mode 100644 index 00000000..ad0cf700 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/models/models_methods.go @@ -0,0 +1,982 @@ +package models + +import ( + "errors" + "fmt" + "regexp" + "strings" + + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/pointers" + stepmanModels "github.com/bitrise-io/stepman/models" + "github.com/ryanuber/go-glob" +) + +func (triggerItem TriggerMapItemModel) String(printWorkflow bool) string { + str := "" + + if triggerItem.PushBranch != "" { + str = fmt.Sprintf("push_branch: %s", triggerItem.PushBranch) + } + + if triggerItem.PullRequestSourceBranch != "" || triggerItem.PullRequestTargetBranch != "" { + if str != "" { + str += " " + } + + if triggerItem.PullRequestSourceBranch != "" { + str += fmt.Sprintf("pull_request_source_branch: %s", triggerItem.PullRequestSourceBranch) + } + if triggerItem.PullRequestTargetBranch != "" { + if triggerItem.PullRequestSourceBranch != "" { + str += " && " + } + + str += fmt.Sprintf("pull_request_target_branch: %s", triggerItem.PullRequestTargetBranch) + } + } + + if triggerItem.Tag != "" { + if str != "" { + str += " " + } + + str += fmt.Sprintf("tag: %s", triggerItem.Tag) + } + + if triggerItem.Pattern != "" { + if str != "" { + str += " " + } + + str += fmt.Sprintf("pattern: %s && is_pull_request_allowed: %v", triggerItem.Pattern, triggerItem.IsPullRequestAllowed) + } + + if printWorkflow { + str += fmt.Sprintf(" -> workflow: %s", triggerItem.WorkflowID) + } + + return str +} + +func triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag string) (TriggerEventType, error) { + if pushBranch != "" { + // Ensure not mixed with code-push event + if prSourceBranch != "" { + return TriggerEventTypeUnknown, fmt.Errorf("push_branch (%s) selects code-push trigger event, but pull_request_source_branch (%s) also provided", pushBranch, prSourceBranch) + } + if prTargetBranch != "" { + return TriggerEventTypeUnknown, fmt.Errorf("push_branch (%s) selects code-push trigger event, but pull_request_target_branch (%s) also provided", pushBranch, prTargetBranch) + } + + // Ensure not mixed with tag event + if tag != "" { + return TriggerEventTypeUnknown, fmt.Errorf("push_branch (%s) selects code-push trigger event, but tag (%s) also provided", pushBranch, tag) + } + + return TriggerEventTypeCodePush, nil + } else if prSourceBranch != "" || prTargetBranch != "" { + // Ensure not mixed with tag event + if tag != "" { + return TriggerEventTypeUnknown, fmt.Errorf("pull_request_source_branch (%s) and pull_request_target_branch (%s) selects pull-request trigger event, but tag (%s) also provided", prSourceBranch, prTargetBranch, tag) + } + + return TriggerEventTypePullRequest, nil + } else if tag != "" { + return TriggerEventTypeTag, nil + } + + return TriggerEventTypeUnknown, fmt.Errorf("failed to determin trigger event from params: push-branch: %s, pr-source-branch: %s, pr-target-branch: %s, tag: %s", pushBranch, prSourceBranch, prTargetBranch, tag) +} + +func migrateDeprecatedTriggerItem(triggerItem TriggerMapItemModel) []TriggerMapItemModel { + migratedItems := []TriggerMapItemModel{ + TriggerMapItemModel{ + PushBranch: triggerItem.Pattern, + WorkflowID: triggerItem.WorkflowID, + }, + } + if triggerItem.IsPullRequestAllowed { + migratedItems = append(migratedItems, TriggerMapItemModel{ + PullRequestSourceBranch: triggerItem.Pattern, + WorkflowID: triggerItem.WorkflowID, + }) + } + return migratedItems +} + +// MatchWithParams ... +func (triggerItem TriggerMapItemModel) MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag string) (bool, error) { + paramsEventType, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) + if err != nil { + return false, err + } + + migratedTriggerItems := []TriggerMapItemModel{triggerItem} + if triggerItem.Pattern != "" { + migratedTriggerItems = migrateDeprecatedTriggerItem(triggerItem) + } + + for _, migratedTriggerItem := range migratedTriggerItems { + itemEventType, err := triggerEventType(migratedTriggerItem.PushBranch, migratedTriggerItem.PullRequestSourceBranch, migratedTriggerItem.PullRequestTargetBranch, migratedTriggerItem.Tag) + if err != nil { + return false, err + } + + if paramsEventType != itemEventType { + continue + } + + switch itemEventType { + case TriggerEventTypeCodePush: + match := glob.Glob(migratedTriggerItem.PushBranch, pushBranch) + return match, nil + case TriggerEventTypePullRequest: + sourceMatch := false + if migratedTriggerItem.PullRequestSourceBranch == "" { + sourceMatch = true + } else { + sourceMatch = glob.Glob(migratedTriggerItem.PullRequestSourceBranch, prSourceBranch) + } + + targetMatch := false + if migratedTriggerItem.PullRequestTargetBranch == "" { + targetMatch = true + } else { + targetMatch = glob.Glob(migratedTriggerItem.PullRequestTargetBranch, prTargetBranch) + } + + return (sourceMatch && targetMatch), nil + case TriggerEventTypeTag: + match := glob.Glob(migratedTriggerItem.Tag, tag) + return match, nil + } + } + + return false, nil +} + +func containsWorkflowName(title string, workflowStack []string) bool { + for _, t := range workflowStack { + if t == title { + return true + } + } + return false +} + +func removeWorkflowName(title string, workflowStack []string) []string { + newStack := []string{} + for _, t := range workflowStack { + if t != title { + newStack = append(newStack, t) + } + } + return newStack +} + +func checkWorkflowReferenceCycle(workflowID string, workflow WorkflowModel, bitriseConfig BitriseDataModel, workflowStack []string) error { + if containsWorkflowName(workflowID, workflowStack) { + stackStr := "" + for _, aWorkflowID := range workflowStack { + stackStr += aWorkflowID + " -> " + } + stackStr += workflowID + return fmt.Errorf("Workflow reference cycle found: %s", stackStr) + } + workflowStack = append(workflowStack, workflowID) + + for _, beforeWorkflowName := range workflow.BeforeRun { + beforeWorkflow, exist := bitriseConfig.Workflows[beforeWorkflowName] + if !exist { + return errors.New("Workflow does not exist with name " + beforeWorkflowName) + } + + err := checkWorkflowReferenceCycle(beforeWorkflowName, beforeWorkflow, bitriseConfig, workflowStack) + if err != nil { + return err + } + } + + for _, afterWorkflowName := range workflow.AfterRun { + afterWorkflow, exist := bitriseConfig.Workflows[afterWorkflowName] + if !exist { + return errors.New("Workflow does not exist with name " + afterWorkflowName) + } + + err := checkWorkflowReferenceCycle(afterWorkflowName, afterWorkflow, bitriseConfig, workflowStack) + if err != nil { + return err + } + } + + workflowStack = removeWorkflowName(workflowID, workflowStack) + + return nil +} + +// ---------------------------- +// --- Normalize + +// Normalize ... +func (workflow *WorkflowModel) Normalize() error { + for _, env := range workflow.Environments { + if err := env.Normalize(); err != nil { + return err + } + } + + for _, stepListItem := range workflow.Steps { + stepID, step, err := GetStepIDStepDataPair(stepListItem) + if err != nil { + return err + } + if err := step.Normalize(); err != nil { + return err + } + stepListItem[stepID] = step + } + + return nil +} + +// Normalize ... +func (app *AppModel) Normalize() error { + for _, env := range app.Environments { + if err := env.Normalize(); err != nil { + return err + } + } + return nil +} + +// Normalize ... +func (config *BitriseDataModel) Normalize() error { + if err := config.App.Normalize(); err != nil { + return err + } + + for _, workflow := range config.Workflows { + if err := workflow.Normalize(); err != nil { + return err + } + } + + return nil +} + +// ---------------------------- +// --- Validate + +// Validate ... +func (workflow *WorkflowModel) Validate() ([]string, error) { + for _, env := range workflow.Environments { + if err := env.Validate(); err != nil { + return []string{}, err + } + } + + warnings := []string{} + for _, stepListItem := range workflow.Steps { + stepID, step, err := GetStepIDStepDataPair(stepListItem) + if err != nil { + return warnings, err + } + + if err := step.ValidateInputAndOutputEnvs(false); err != nil { + return warnings, err + } + + stepInputMap := map[string]bool{} + for _, input := range step.Inputs { + key, _, err := input.GetKeyValuePair() + if err != nil { + return warnings, err + } + + _, found := stepInputMap[key] + if found { + warnings = append(warnings, fmt.Sprintf("invalid step: duplicated input found: (%s)", key)) + } + stepInputMap[key] = true + } + + stepListItem[stepID] = step + } + + return warnings, nil +} + +// Validate ... +func (app *AppModel) Validate() error { + for _, env := range app.Environments { + if err := env.Validate(); err != nil { + return err + } + } + return nil +} + +// Validate ... +func (triggerItem TriggerMapItemModel) Validate() error { + if triggerItem.WorkflowID == "" { + return fmt.Errorf("invalid trigger item: (%s) -> (%s), error: empty workflow id", triggerItem.Pattern, triggerItem.WorkflowID) + } + + if triggerItem.Pattern == "" { + _, err := triggerEventType(triggerItem.PushBranch, triggerItem.PullRequestSourceBranch, triggerItem.PullRequestTargetBranch, triggerItem.Tag) + if err != nil { + return fmt.Errorf("trigger map item (%s) validate failed, error: %s", triggerItem.String(true), err) + } + } else if triggerItem.PushBranch != "" || + triggerItem.PullRequestSourceBranch != "" || triggerItem.PullRequestTargetBranch != "" || triggerItem.Tag != "" { + return fmt.Errorf("deprecated trigger item (pattern defined), mixed with trigger params (push_branch: %s, pull_request_source_branch: %s, pull_request_target_branch: %s, tag: %s)", triggerItem.PushBranch, triggerItem.PullRequestSourceBranch, triggerItem.PullRequestTargetBranch, triggerItem.Tag) + } + + return nil +} + +// Validate ... +func (triggerMap TriggerMapModel) Validate() error { + for _, item := range triggerMap { + if err := item.Validate(); err != nil { + return err + } + } + + return nil +} + +func checkDuplicatedTriggerMapItems(triggerMap TriggerMapModel) error { + triggeTypeItemMap := map[string][]TriggerMapItemModel{} + + for _, triggerItem := range triggerMap { + if triggerItem.Pattern == "" { + triggerType, err := triggerEventType(triggerItem.PushBranch, triggerItem.PullRequestSourceBranch, triggerItem.PullRequestTargetBranch, triggerItem.Tag) + if err != nil { + return fmt.Errorf("trigger map item (%v) validate failed, error: %s", triggerItem, err) + } + + triggerItems := triggeTypeItemMap[string(triggerType)] + + for _, item := range triggerItems { + switch triggerType { + case TriggerEventTypeCodePush: + if triggerItem.PushBranch == item.PushBranch { + return fmt.Errorf("duplicated trigger item found (%s)", triggerItem.String(false)) + } + case TriggerEventTypePullRequest: + if triggerItem.PullRequestSourceBranch == item.PullRequestSourceBranch && + triggerItem.PullRequestTargetBranch == item.PullRequestTargetBranch { + return fmt.Errorf("duplicated trigger item found (%s)", triggerItem.String(false)) + } + case TriggerEventTypeTag: + if triggerItem.Tag == item.Tag { + return fmt.Errorf("duplicated trigger item found (%s)", triggerItem.String(false)) + } + } + } + + triggerItems = append(triggerItems, triggerItem) + triggeTypeItemMap[string(triggerType)] = triggerItems + } else if triggerItem.Pattern != "" { + triggerItems := triggeTypeItemMap["deprecated"] + + for _, item := range triggerItems { + if triggerItem.Pattern == item.Pattern && + triggerItem.IsPullRequestAllowed == item.IsPullRequestAllowed { + return fmt.Errorf("duplicated trigger item found (%s)", triggerItem.String(false)) + } + } + + triggerItems = append(triggerItems, triggerItem) + triggeTypeItemMap["deprecated"] = triggerItems + } + } + + return nil +} + +// Validate ... +func (config *BitriseDataModel) Validate() ([]string, error) { + warnings := []string{} + + if config.FormatVersion == "" { + return warnings, fmt.Errorf("missing format_version") + } + + // trigger map + if err := config.TriggerMap.Validate(); err != nil { + return warnings, err + } + + for _, triggerMapItem := range config.TriggerMap { + if strings.HasPrefix(triggerMapItem.WorkflowID, "_") { + warnings = append(warnings, fmt.Sprintf("workflow (%s) defined in trigger item (%s), but utility workflows can't be triggered directly", triggerMapItem.WorkflowID, triggerMapItem.String(true))) + } + + found := false + + for workflowID := range config.Workflows { + if workflowID == triggerMapItem.WorkflowID { + found = true + } + } + + if !found { + return warnings, fmt.Errorf("workflow (%s) defined in trigger item (%s), but does not exist", triggerMapItem.WorkflowID, triggerMapItem.String(true)) + } + } + + if err := checkDuplicatedTriggerMapItems(config.TriggerMap); err != nil { + return warnings, err + } + // --- + + // app + if err := config.App.Validate(); err != nil { + return warnings, err + } + // --- + + // workflows + for ID, workflow := range config.Workflows { + if ID == "" { + return warnings, fmt.Errorf("invalid workflow ID (%s): empty", ID) + } + + r := regexp.MustCompile(`[A-Za-z0-9-_.]+`) + if find := r.FindString(ID); find != ID { + warnings = append(warnings, fmt.Sprintf("invalid workflow ID (%s): doesn't conform to: [A-Za-z0-9-_.]", ID)) + } + + warns, err := workflow.Validate() + warnings = append(warnings, warns...) + if err != nil { + return warnings, err + } + + if err := checkWorkflowReferenceCycle(ID, workflow, *config, []string{}); err != nil { + return warnings, err + } + } + // --- + + return warnings, nil +} + +// ---------------------------- +// --- FillMissingDefaults + +// FillMissingDefaults ... +func (workflow *WorkflowModel) FillMissingDefaults(title string) error { + // Don't call step.FillMissingDefaults() + // StepLib versions of steps (which are the default versions), + // contains different env defaults then normal envs + // example: isExpand = true by default for normal envs, + // but script step content input env isExpand = false by default + + for _, env := range workflow.Environments { + if err := env.FillMissingDefaults(); err != nil { + return err + } + } + + if workflow.Title == "" { + workflow.Title = title + } + + return nil +} + +// FillMissingDefaults ... +func (app *AppModel) FillMissingDefaults() error { + for _, env := range app.Environments { + if err := env.FillMissingDefaults(); err != nil { + return err + } + } + return nil +} + +// FillMissingDefaults ... +func (config *BitriseDataModel) FillMissingDefaults() error { + if err := config.App.FillMissingDefaults(); err != nil { + return err + } + + for title, workflow := range config.Workflows { + if err := workflow.FillMissingDefaults(title); err != nil { + return err + } + } + + return nil +} + +// ---------------------------- +// --- RemoveRedundantFields + +func removeEnvironmentRedundantFields(env *envmanModels.EnvironmentItemModel) error { + options, err := env.GetOptions() + if err != nil { + return err + } + + hasOptions := false + + if options.Title != nil { + if *options.Title == "" { + options.Title = nil + } else { + hasOptions = true + } + } + if options.Description != nil { + if *options.Description == "" { + options.Description = nil + } else { + hasOptions = true + } + } + if options.Summary != nil { + if *options.Summary == "" { + options.Summary = nil + } else { + hasOptions = true + } + } + if options.IsRequired != nil { + if *options.IsRequired == envmanModels.DefaultIsRequired { + options.IsRequired = nil + } else { + hasOptions = true + } + } + if options.IsExpand != nil { + if *options.IsExpand == envmanModels.DefaultIsExpand { + options.IsExpand = nil + } else { + hasOptions = true + } + } + if options.IsDontChangeValue != nil { + if *options.IsDontChangeValue == envmanModels.DefaultIsDontChangeValue { + options.IsDontChangeValue = nil + } else { + hasOptions = true + } + } + if options.IsTemplate != nil { + if *options.IsTemplate == envmanModels.DefaultIsTemplate { + options.IsTemplate = nil + } else { + hasOptions = true + } + } + + if hasOptions { + (*env)[envmanModels.OptionsKey] = options + } else { + delete(*env, envmanModels.OptionsKey) + } + + return nil +} + +func (workflow *WorkflowModel) removeRedundantFields() error { + // Don't call step.RemoveRedundantFields() + // StepLib versions of steps (which are the default versions), + // contains different env defaults then normal envs + // example: isExpand = true by default for normal envs, + // but script step content input env isExpand = false by default + for _, env := range workflow.Environments { + if err := removeEnvironmentRedundantFields(&env); err != nil { + return err + } + } + return nil +} + +func (app *AppModel) removeRedundantFields() error { + for _, env := range app.Environments { + if err := removeEnvironmentRedundantFields(&env); err != nil { + return err + } + } + return nil +} + +// RemoveRedundantFields ... +func (config *BitriseDataModel) RemoveRedundantFields() error { + if err := config.App.removeRedundantFields(); err != nil { + return err + } + for _, workflow := range config.Workflows { + if err := workflow.removeRedundantFields(); err != nil { + return err + } + } + return nil +} + +// ---------------------------- +// --- Merge + +// MergeEnvironmentWith ... +func MergeEnvironmentWith(env *envmanModels.EnvironmentItemModel, otherEnv envmanModels.EnvironmentItemModel) error { + // merge key-value + key, _, err := env.GetKeyValuePair() + if err != nil { + return err + } + + otherKey, otherValue, err := otherEnv.GetKeyValuePair() + if err != nil { + return err + } + + if otherKey != key { + return errors.New("Env keys are diferent") + } + + (*env)[key] = otherValue + + //merge options + options, err := env.GetOptions() + if err != nil { + return err + } + + otherOptions, err := otherEnv.GetOptions() + if err != nil { + return err + } + + if otherOptions.IsExpand != nil { + options.IsExpand = pointers.NewBoolPtr(*otherOptions.IsExpand) + } + if otherOptions.SkipIfEmpty != nil { + options.SkipIfEmpty = pointers.NewBoolPtr(*otherOptions.SkipIfEmpty) + } + + if otherOptions.Title != nil { + options.Title = pointers.NewStringPtr(*otherOptions.Title) + } + if otherOptions.Description != nil { + options.Description = pointers.NewStringPtr(*otherOptions.Description) + } + if otherOptions.Summary != nil { + options.Summary = pointers.NewStringPtr(*otherOptions.Summary) + } + if otherOptions.Category != nil { + options.Category = pointers.NewStringPtr(*otherOptions.Category) + } + if len(otherOptions.ValueOptions) > 0 { + options.ValueOptions = otherOptions.ValueOptions + } + if otherOptions.IsRequired != nil { + options.IsRequired = pointers.NewBoolPtr(*otherOptions.IsRequired) + } + if otherOptions.IsDontChangeValue != nil { + options.IsDontChangeValue = pointers.NewBoolPtr(*otherOptions.IsDontChangeValue) + } + if otherOptions.IsTemplate != nil { + options.IsTemplate = pointers.NewBoolPtr(*otherOptions.IsTemplate) + } + (*env)[envmanModels.OptionsKey] = options + return nil +} + +func getInputByKey(step stepmanModels.StepModel, key string) (envmanModels.EnvironmentItemModel, bool) { + for _, input := range step.Inputs { + k, _, err := input.GetKeyValuePair() + if err != nil { + return envmanModels.EnvironmentItemModel{}, false + } + + if k == key { + return input, true + } + } + return envmanModels.EnvironmentItemModel{}, false +} + +func getOutputByKey(step stepmanModels.StepModel, key string) (envmanModels.EnvironmentItemModel, bool) { + for _, output := range step.Outputs { + k, _, err := output.GetKeyValuePair() + if err != nil { + return envmanModels.EnvironmentItemModel{}, false + } + + if k == key { + return output, true + } + } + return envmanModels.EnvironmentItemModel{}, false +} + +// MergeStepWith ... +func MergeStepWith(step, otherStep stepmanModels.StepModel) (stepmanModels.StepModel, error) { + if otherStep.Title != nil { + step.Title = pointers.NewStringPtr(*otherStep.Title) + } + if otherStep.Summary != nil { + step.Summary = pointers.NewStringPtr(*otherStep.Summary) + } + if otherStep.Description != nil { + step.Description = pointers.NewStringPtr(*otherStep.Description) + } + + if otherStep.Website != nil { + step.Website = pointers.NewStringPtr(*otherStep.Website) + } + if otherStep.SourceCodeURL != nil { + step.SourceCodeURL = pointers.NewStringPtr(*otherStep.SourceCodeURL) + } + if otherStep.SupportURL != nil { + step.SupportURL = pointers.NewStringPtr(*otherStep.SupportURL) + } + + if otherStep.PublishedAt != nil { + step.PublishedAt = pointers.NewTimePtr(*otherStep.PublishedAt) + } + if otherStep.Source != nil { + step.Source = new(stepmanModels.StepSourceModel) + + if otherStep.Source.Git != "" { + step.Source.Git = otherStep.Source.Git + } + if otherStep.Source.Commit != "" { + step.Source.Commit = otherStep.Source.Commit + } + } + if len(otherStep.AssetURLs) > 0 { + step.AssetURLs = otherStep.AssetURLs + } + + if len(otherStep.HostOsTags) > 0 { + step.HostOsTags = otherStep.HostOsTags + } + if len(otherStep.ProjectTypeTags) > 0 { + step.ProjectTypeTags = otherStep.ProjectTypeTags + } + if len(otherStep.TypeTags) > 0 { + step.TypeTags = otherStep.TypeTags + } + if len(otherStep.Dependencies) > 0 { + step.Dependencies = otherStep.Dependencies + } + if otherStep.Toolkit != nil { + step.Toolkit = new(stepmanModels.StepToolkitModel) + *step.Toolkit = *otherStep.Toolkit + } + if otherStep.Deps != nil && (len(otherStep.Deps.Brew) > 0 || len(otherStep.Deps.AptGet) > 0 || len(otherStep.Deps.CheckOnly) > 0) { + step.Deps = otherStep.Deps + } + if otherStep.IsRequiresAdminUser != nil { + step.IsRequiresAdminUser = pointers.NewBoolPtr(*otherStep.IsRequiresAdminUser) + } + + if otherStep.IsAlwaysRun != nil { + step.IsAlwaysRun = pointers.NewBoolPtr(*otherStep.IsAlwaysRun) + } + if otherStep.IsSkippable != nil { + step.IsSkippable = pointers.NewBoolPtr(*otherStep.IsSkippable) + } + if otherStep.RunIf != nil { + step.RunIf = pointers.NewStringPtr(*otherStep.RunIf) + } + if otherStep.Timeout != nil { + step.Timeout = pointers.NewIntPtr(*otherStep.Timeout) + } + + for _, input := range step.Inputs { + key, _, err := input.GetKeyValuePair() + if err != nil { + return stepmanModels.StepModel{}, err + } + otherInput, found := getInputByKey(otherStep, key) + if found { + err := MergeEnvironmentWith(&input, otherInput) + if err != nil { + return stepmanModels.StepModel{}, err + } + } + } + + for _, output := range step.Outputs { + key, _, err := output.GetKeyValuePair() + if err != nil { + return stepmanModels.StepModel{}, err + } + otherOutput, found := getOutputByKey(otherStep, key) + if found { + err := MergeEnvironmentWith(&output, otherOutput) + if err != nil { + return stepmanModels.StepModel{}, err + } + } + } + + return step, nil +} + +// ---------------------------- +// --- StepIDData + +// GetStepIDStepDataPair ... +func GetStepIDStepDataPair(stepListItem StepListItemModel) (string, stepmanModels.StepModel, error) { + if len(stepListItem) > 1 { + return "", stepmanModels.StepModel{}, errors.New("StepListItem contains more than 1 key-value pair") + } + for key, value := range stepListItem { + return key, value, nil + } + return "", stepmanModels.StepModel{}, errors.New("StepListItem does not contain a key-value pair") +} + +// CreateStepIDDataFromString ... +// compositeVersionStr examples: +// * local path: +// * path::~/path/to/step/dir +// * direct git url and branch or tag: +// * git::https://github.com/bitrise-io/steps-timestamp.git@master +// * Steplib independent step: +// * _::https://github.com/bitrise-io/steps-bash-script.git@2.0.0: +// * full ID with steplib, stepid and version: +// * https://github.com/bitrise-io/bitrise-steplib.git::script@2.0.0 +// * only stepid and version (requires a default steplib source to be provided): +// * script@2.0.0 +// * only stepid, latest version will be used (requires a default steplib source to be provided): +// * script +func CreateStepIDDataFromString(compositeVersionStr, defaultStepLibSource string) (StepIDData, error) { + // first, determine the steplib-source/type + stepSrc := "" + stepIDAndVersionOrURIStr := "" + libsourceStepSplits := strings.Split(compositeVersionStr, "::") + if len(libsourceStepSplits) == 2 { + // long/verbose ID mode, ex: step-lib-src::step-id@1.0.0 + stepSrc = libsourceStepSplits[0] + stepIDAndVersionOrURIStr = libsourceStepSplits[1] + } else if len(libsourceStepSplits) == 1 { + // missing steplib-src mode, ex: step-id@1.0.0 + // in this case if we have a default StepLibSource we'll use that + stepIDAndVersionOrURIStr = libsourceStepSplits[0] + } else { + return StepIDData{}, errors.New("No StepLib found, neither default provided (" + compositeVersionStr + ")") + } + + if stepSrc == "" { + if defaultStepLibSource == "" { + return StepIDData{}, errors.New("No default StepLib source, in this case the composite ID should contain the source, separated with a '::' separator from the step ID (" + compositeVersionStr + ")") + } + stepSrc = defaultStepLibSource + } + + // now determine the ID-or-URI and the version (if provided) + stepIDOrURI := "" + stepVersion := "" + stepidVersionOrURISplits := strings.Split(stepIDAndVersionOrURIStr, "@") + if len(stepidVersionOrURISplits) >= 2 { + splitsCnt := len(stepidVersionOrURISplits) + allButLastSplits := stepidVersionOrURISplits[:splitsCnt-1] + // the ID or URI is all components except the last @version component + // which will be the version itself + // for example in case it's a git direct URI like: + // git@github.com:bitrise-io/steps-timestamp.git@develop + // which contains 2 at (@) signs only the last should be the version, + // the first one is part of the URI + stepIDOrURI = strings.Join(allButLastSplits, "@") + // version is simply the last component + stepVersion = stepidVersionOrURISplits[splitsCnt-1] + } else if len(stepidVersionOrURISplits) == 1 { + stepIDOrURI = stepidVersionOrURISplits[0] + } else { + return StepIDData{}, errors.New("Step ID and version should be separated with a '@' separator (" + stepIDAndVersionOrURIStr + ")") + } + + if stepIDOrURI == "" { + return StepIDData{}, errors.New("No ID found at all (" + compositeVersionStr + ")") + } + + if stepSrc == "git" && stepVersion == "" { + stepVersion = "master" + } + + return StepIDData{ + SteplibSource: stepSrc, + IDorURI: stepIDOrURI, + Version: stepVersion, + }, nil +} + +// IsUniqueResourceID : true if this ID is a unique resource ID, which is true +// if the ID refers to the exact same step code/data every time. +// Practically, this is only true for steps from StepLibrary collections, +// a local path or direct git step ID is never guaranteed to identify the +// same resource every time, the step's behaviour can change at every execution! +// +// __If the ID is a Unique Resource ID then the step can be cached (locally)__, +// as it won't change between subsequent step execution. +func (sIDData StepIDData) IsUniqueResourceID() bool { + switch sIDData.SteplibSource { + case "path": + return false + case "git": + return false + case "_": + return false + case "": + return false + } + + // in any other case, it's a StepLib URL + // but it's only unique if StepID and Step Version are all defined! + if len(sIDData.IDorURI) > 0 && len(sIDData.Version) > 0 { + return true + } + + // in every other case, it's not unique, not even if it's from a StepLib + return false +} + +// ---------------------------- +// --- BuildRunResults + +// IsStepLibUpdated ... +func (buildRes BuildRunResultsModel) IsStepLibUpdated(stepLib string) bool { + return (buildRes.StepmanUpdates[stepLib] > 0) +} + +// IsBuildFailed ... +func (buildRes BuildRunResultsModel) IsBuildFailed() bool { + return len(buildRes.FailedSteps) > 0 +} + +// HasFailedSkippableSteps ... +func (buildRes BuildRunResultsModel) HasFailedSkippableSteps() bool { + return len(buildRes.FailedSkippableSteps) > 0 +} + +// ResultsCount ... +func (buildRes BuildRunResultsModel) ResultsCount() int { + return len(buildRes.SuccessSteps) + len(buildRes.FailedSteps) + len(buildRes.FailedSkippableSteps) + len(buildRes.SkippedSteps) +} + +func (buildRes BuildRunResultsModel) unorderedResults() []StepRunResultsModel { + results := append([]StepRunResultsModel{}, buildRes.SuccessSteps...) + results = append(results, buildRes.FailedSteps...) + results = append(results, buildRes.FailedSkippableSteps...) + return append(results, buildRes.SkippedSteps...) +} + +//OrderedResults ... +func (buildRes BuildRunResultsModel) OrderedResults() []StepRunResultsModel { + results := make([]StepRunResultsModel, buildRes.ResultsCount()) + unorderedResults := buildRes.unorderedResults() + for _, result := range unorderedResults { + results[result.Idx] = result + } + return results +} diff --git a/vendor/github.com/bitrise-io/bitrise/models/models_methods_test.go b/vendor/github.com/bitrise-io/bitrise/models/models_methods_test.go new file mode 100644 index 00000000..627dc24b --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/models/models_methods_test.go @@ -0,0 +1,1567 @@ +package models + +import ( + "strings" + "testing" + "time" + + "gopkg.in/yaml.v2" + + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/pointers" + stepmanModels "github.com/bitrise-io/stepman/models" + "github.com/stretchr/testify/require" +) + +func TestCheckDuplicatedTriggerMapItems(t *testing.T) { + t.Log("duplicated push - error") + { + err := checkDuplicatedTriggerMapItems(TriggerMapModel{ + TriggerMapItemModel{ + PushBranch: "master", + WorkflowID: "ci", + }, + TriggerMapItemModel{ + PushBranch: "master", + WorkflowID: "release", + }, + }) + + require.EqualError(t, err, "duplicated trigger item found (push_branch: master)") + } + + t.Log("duplicated pull request - error") + { + err := checkDuplicatedTriggerMapItems(TriggerMapModel{ + TriggerMapItemModel{ + PullRequestSourceBranch: "develop", + WorkflowID: "ci", + }, + TriggerMapItemModel{ + PullRequestSourceBranch: "develop", + WorkflowID: "release", + }, + }) + + require.EqualError(t, err, "duplicated trigger item found (pull_request_source_branch: develop)") + + err = checkDuplicatedTriggerMapItems(TriggerMapModel{ + TriggerMapItemModel{ + PullRequestTargetBranch: "master", + WorkflowID: "ci", + }, + TriggerMapItemModel{ + PullRequestTargetBranch: "master", + WorkflowID: "release", + }, + }) + + require.EqualError(t, err, "duplicated trigger item found (pull_request_target_branch: master)") + + err = checkDuplicatedTriggerMapItems(TriggerMapModel{ + TriggerMapItemModel{ + PullRequestSourceBranch: "develop", + PullRequestTargetBranch: "master", + WorkflowID: "ci", + }, + TriggerMapItemModel{ + PullRequestSourceBranch: "develop", + PullRequestTargetBranch: "master", + WorkflowID: "release", + }, + }) + + require.EqualError(t, err, "duplicated trigger item found (pull_request_source_branch: develop && pull_request_target_branch: master)") + } + + t.Log("duplicated tag - error") + { + err := checkDuplicatedTriggerMapItems(TriggerMapModel{ + TriggerMapItemModel{ + Tag: "0.9.0", + WorkflowID: "ci", + }, + TriggerMapItemModel{ + Tag: "0.9.0", + WorkflowID: "release", + }, + }) + + require.EqualError(t, err, "duplicated trigger item found (tag: 0.9.0)") + } + + t.Log("complex trigger map - no error") + { + err := checkDuplicatedTriggerMapItems(TriggerMapModel{ + TriggerMapItemModel{ + PushBranch: "master", + WorkflowID: "ci", + }, + TriggerMapItemModel{ + PullRequestSourceBranch: "develop", + PullRequestTargetBranch: "master", + WorkflowID: "ci", + }, + TriggerMapItemModel{ + Tag: "0.9.0", + WorkflowID: "release", + }, + }) + + require.NoError(t, err) + } +} + +func TestTriggerMapItemModelString(t *testing.T) { + t.Log("push event") + { + item := TriggerMapItemModel{ + PushBranch: "master", + WorkflowID: "ci", + } + require.Equal(t, "push_branch: master -> workflow: ci", item.String(true)) + require.Equal(t, "push_branch: master", item.String(false)) + } + + t.Log("pull request event") + { + prSourceItem := TriggerMapItemModel{ + PullRequestSourceBranch: "develop", + WorkflowID: "ci", + } + require.Equal(t, "pull_request_source_branch: develop -> workflow: ci", prSourceItem.String(true)) + require.Equal(t, "pull_request_source_branch: develop", prSourceItem.String(false)) + + prTargetItem := TriggerMapItemModel{ + PullRequestTargetBranch: "master", + WorkflowID: "ci", + } + require.Equal(t, "pull_request_target_branch: master -> workflow: ci", prTargetItem.String(true)) + require.Equal(t, "pull_request_target_branch: master", prTargetItem.String(false)) + + prItem := TriggerMapItemModel{ + PullRequestSourceBranch: "develop", + PullRequestTargetBranch: "master", + WorkflowID: "ci", + } + require.Equal(t, "pull_request_source_branch: develop && pull_request_target_branch: master -> workflow: ci", prItem.String(true)) + require.Equal(t, "pull_request_source_branch: develop && pull_request_target_branch: master", prItem.String(false)) + } + + t.Log("tag event") + { + item := TriggerMapItemModel{ + Tag: "0.9.0", + WorkflowID: "release", + } + require.Equal(t, "tag: 0.9.0 -> workflow: release", item.String(true)) + require.Equal(t, "tag: 0.9.0", item.String(false)) + } + + t.Log("deprecated type") + { + prNotAllowedItem := TriggerMapItemModel{ + Pattern: "master", + IsPullRequestAllowed: false, + WorkflowID: "ci", + } + require.Equal(t, "pattern: master && is_pull_request_allowed: false -> workflow: ci", prNotAllowedItem.String(true)) + require.Equal(t, "pattern: master && is_pull_request_allowed: false", prNotAllowedItem.String(false)) + + prAllowedItem := TriggerMapItemModel{ + Pattern: "master", + IsPullRequestAllowed: true, + WorkflowID: "ci", + } + require.Equal(t, "pattern: master && is_pull_request_allowed: true -> workflow: ci", prAllowedItem.String(true)) + require.Equal(t, "pattern: master && is_pull_request_allowed: true", prAllowedItem.String(false)) + } + + t.Log("mixed") + { + item := TriggerMapItemModel{ + PushBranch: "master", + PullRequestSourceBranch: "develop", + PullRequestTargetBranch: "master", + Tag: "0.9.0", + Pattern: "*", + IsPullRequestAllowed: true, + WorkflowID: "ci", + } + require.Equal(t, "push_branch: master pull_request_source_branch: develop && pull_request_target_branch: master tag: 0.9.0 pattern: * && is_pull_request_allowed: true -> workflow: ci", item.String(true)) + require.Equal(t, "push_branch: master pull_request_source_branch: develop && pull_request_target_branch: master tag: 0.9.0 pattern: * && is_pull_request_allowed: true", item.String(false)) + } +} + +func TestTriggerEventType(t *testing.T) { + t.Log("it determins trigger event type") + { + pushBranch := "master" + prSourceBranch := "" + prTargetBranch := "" + tag := "" + + event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, TriggerEventTypeCodePush, event) + } + + t.Log("it determins trigger event type") + { + pushBranch := "" + prSourceBranch := "develop" + prTargetBranch := "" + tag := "" + + event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, TriggerEventTypePullRequest, event) + } + + t.Log("it determins trigger event type") + { + pushBranch := "" + prSourceBranch := "" + prTargetBranch := "master" + tag := "" + + event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, TriggerEventTypePullRequest, event) + } + + t.Log("it determins trigger event type") + { + pushBranch := "" + prSourceBranch := "" + prTargetBranch := "" + tag := "0.9.0" + + event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, TriggerEventTypeTag, event) + } + + t.Log("it fails without inputs") + { + pushBranch := "" + prSourceBranch := "" + prTargetBranch := "" + tag := "" + + event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) + require.Error(t, err) + require.Equal(t, TriggerEventTypeUnknown, event) + } + + t.Log("it fails if event type not clear") + { + pushBranch := "master" + prSourceBranch := "develop" + prTargetBranch := "" + tag := "" + + event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) + require.Error(t, err) + require.Equal(t, TriggerEventTypeUnknown, event) + } + + t.Log("it fails if event type not clear") + { + pushBranch := "master" + prSourceBranch := "" + prTargetBranch := "master" + tag := "" + + event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) + require.Error(t, err) + require.Equal(t, TriggerEventTypeUnknown, event) + } + + t.Log("it fails if event type not clear") + { + pushBranch := "master" + prSourceBranch := "" + prTargetBranch := "" + tag := "0.9.0" + + event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) + require.Error(t, err) + require.Equal(t, TriggerEventTypeUnknown, event) + } +} + +func TestTriggerMapItemValidate(t *testing.T) { + t.Log("utility workflow triggered - Warning") + { + configStr := ` +format_version: 1.3.1 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +trigger_map: +- push_branch: "/release" + workflow: _deps-update + +workflows: + _deps-update: +` + + config, err := configModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + + warnings, err := config.Validate() + require.NoError(t, err) + require.Equal(t, []string{"workflow (_deps-update) defined in trigger item (push_branch: /release -> workflow: _deps-update), but utility workflows can't be triggered directly"}, warnings) + } + + t.Log("workflow not exists") + { + configStr := ` +format_version: 1.3.1 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +trigger_map: +- push_branch: "/release" + workflow: release + +workflows: + ci: +` + + config, err := configModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + + _, err = config.Validate() + require.EqualError(t, err, "workflow (release) defined in trigger item (push_branch: /release -> workflow: release), but does not exist") + } + + t.Log("it validates deprecated trigger item") + { + item := TriggerMapItemModel{ + Pattern: "*", + WorkflowID: "primary", + } + require.NoError(t, item.Validate()) + } + + t.Log("it fails for invalid deprecated trigger item - missing workflow") + { + item := TriggerMapItemModel{ + Pattern: "*", + WorkflowID: "", + } + require.Error(t, item.Validate()) + } + + t.Log("it fails for invalid deprecated trigger item - missing pattern") + { + item := TriggerMapItemModel{ + Pattern: "", + WorkflowID: "primary", + } + require.Error(t, item.Validate()) + } + + t.Log("it validates code-push trigger item") + { + item := TriggerMapItemModel{ + PushBranch: "*", + WorkflowID: "primary", + } + require.NoError(t, item.Validate()) + } + + t.Log("it fails for invalid code-push trigger item - missing push-branch") + { + item := TriggerMapItemModel{ + PushBranch: "", + WorkflowID: "primary", + } + require.Error(t, item.Validate()) + } + + t.Log("it fails for invalid code-push trigger item - missing workflow") + { + item := TriggerMapItemModel{ + PushBranch: "*", + WorkflowID: "", + } + require.Error(t, item.Validate()) + } + + t.Log("it validates pull-request trigger item") + { + item := TriggerMapItemModel{ + PullRequestSourceBranch: "feature/", + WorkflowID: "primary", + } + require.NoError(t, item.Validate()) + } + + t.Log("it validates pull-request trigger item") + { + item := TriggerMapItemModel{ + PullRequestTargetBranch: "master", + WorkflowID: "primary", + } + require.NoError(t, item.Validate()) + } + + t.Log("it fails for invalid pull-request trigger item - missing workflow") + { + item := TriggerMapItemModel{ + PullRequestTargetBranch: "*", + WorkflowID: "", + } + require.Error(t, item.Validate()) + } + + t.Log("it fails for invalid pull-request trigger item - missing workflow") + { + item := TriggerMapItemModel{ + PullRequestSourceBranch: "", + PullRequestTargetBranch: "", + WorkflowID: "primary", + } + require.Error(t, item.Validate()) + } + + t.Log("it fails for mixed trigger item") + { + item := TriggerMapItemModel{ + PushBranch: "master", + PullRequestSourceBranch: "feature/*", + PullRequestTargetBranch: "", + WorkflowID: "primary", + } + require.Error(t, item.Validate()) + } + + t.Log("it fails for mixed trigger item") + { + item := TriggerMapItemModel{ + PushBranch: "master", + Pattern: "*", + WorkflowID: "primary", + } + require.Error(t, item.Validate()) + } +} + +func TestMatchWithParamsCodePushItem(t *testing.T) { + t.Log("code-push against code-push type item - MATCH") + { + pushBranch := "master" + prSourceBranch := "" + prTargetBranch := "" + tag := "" + + item := TriggerMapItemModel{ + PushBranch: "master", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, true, match) + } + + t.Log("code-push against code-push type item - MATCH") + { + pushBranch := "master" + prSourceBranch := "" + prTargetBranch := "" + tag := "" + + item := TriggerMapItemModel{ + PushBranch: "*", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, true, match) + } + + t.Log("code-push against code-push type item - MATCH") + { + pushBranch := "feature/login" + prSourceBranch := "" + prTargetBranch := "" + tag := "" + + item := TriggerMapItemModel{ + PushBranch: "feature/*", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, true, match) + } + + t.Log("code-push against code-push type item - NOT MATCH") + { + pushBranch := "master" + prSourceBranch := "" + prTargetBranch := "" + tag := "" + + item := TriggerMapItemModel{ + PushBranch: "deploy", + WorkflowID: "deploy", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, false, match) + } + + t.Log("code-push against pr type item - NOT MATCH") + { + pushBranch := "master" + prSourceBranch := "" + prTargetBranch := "" + tag := "" + + item := TriggerMapItemModel{ + PullRequestSourceBranch: "develop", + WorkflowID: "test", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, false, match) + } + + t.Log("code-push against pr type item - NOT MATCH") + { + pushBranch := "master" + prSourceBranch := "" + prTargetBranch := "" + tag := "" + + item := TriggerMapItemModel{ + PullRequestTargetBranch: "master", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, false, match) + } + + t.Log("code-push against pr type item - NOT MATCH") + { + pushBranch := "master" + prSourceBranch := "" + prTargetBranch := "" + tag := "" + + item := TriggerMapItemModel{ + PullRequestSourceBranch: "develop", + PullRequestTargetBranch: "master", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, false, match) + } +} + +func TestMatchWithParamsPrTypeItem(t *testing.T) { + t.Log("pr against pr type item - MATCH") + { + pushBranch := "" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "" + + item := TriggerMapItemModel{ + PullRequestSourceBranch: "develop", + PullRequestTargetBranch: "master", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, true, match) + } + + t.Log("pr against pr type item - MATCH") + { + pushBranch := "" + prSourceBranch := "feature/login" + prTargetBranch := "develop" + tag := "" + + item := TriggerMapItemModel{ + PullRequestSourceBranch: "feature/*", + PullRequestTargetBranch: "develop", + WorkflowID: "test", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, true, match) + } + + t.Log("pr against pr type item - MATCH") + { + pushBranch := "" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "" + + item := TriggerMapItemModel{ + PullRequestSourceBranch: "*", + PullRequestTargetBranch: "master", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, true, match) + } + + t.Log("pr against pr type item - MATCH") + { + pushBranch := "" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "" + + item := TriggerMapItemModel{ + PullRequestTargetBranch: "master", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, true, match) + } + + t.Log("pr against pr type item - MATCH") + { + pushBranch := "" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "" + + item := TriggerMapItemModel{ + PullRequestSourceBranch: "develop", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, true, match) + } + + t.Log("pr against pr type item - MATCH") + { + pushBranch := "" + prSourceBranch := "" + prTargetBranch := "deploy_1_0_0" + tag := "" + + item := TriggerMapItemModel{ + PullRequestTargetBranch: "deploy_*", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, true, match) + } + + t.Log("pr against pr type item - NOT MATCH") + { + pushBranch := "" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "" + + item := TriggerMapItemModel{ + PullRequestSourceBranch: "develop", + PullRequestTargetBranch: "deploy", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, false, match) + } + + t.Log("pr against pr type item - NOT MATCH") + { + pushBranch := "" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "" + + item := TriggerMapItemModel{ + PullRequestSourceBranch: "feature/*", + PullRequestTargetBranch: "master", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, false, match) + } + + t.Log("pr against push type item - NOT MATCH") + { + pushBranch := "" + prSourceBranch := "develop" + prTargetBranch := "master" + tag := "" + + item := TriggerMapItemModel{ + PushBranch: "master", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, false, match) + } +} + +func TestMatchWithParamsTagTypeItem(t *testing.T) { + t.Log("tag against tag type item - MATCH") + { + pushBranch := "" + prSourceBranch := "" + prTargetBranch := "" + tag := "0.9.0" + + item := TriggerMapItemModel{ + Tag: "0.9.*", + WorkflowID: "deploy", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, true, match) + } + + t.Log("tag against tag type item - MATCH") + { + pushBranch := "" + prSourceBranch := "" + prTargetBranch := "" + tag := "0.9.0" + + item := TriggerMapItemModel{ + Tag: "0.9.0", + WorkflowID: "deploy", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, true, match) + } + + t.Log("tag against tag type item - MATCH") + { + pushBranch := "" + prSourceBranch := "" + prTargetBranch := "" + tag := "0.9.0-pre" + + item := TriggerMapItemModel{ + Tag: "0.9.*", + WorkflowID: "deploy", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, true, match) + } + + t.Log("tag against tag type item - NOT MATCH") + { + pushBranch := "" + prSourceBranch := "" + prTargetBranch := "" + tag := "0.9.0-pre" + + item := TriggerMapItemModel{ + Tag: "1.*", + WorkflowID: "deploy", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, false, match) + } + + t.Log("tag against push type item - NOT MATCH") + { + pushBranch := "" + prSourceBranch := "" + prTargetBranch := "" + tag := "0.9.0-pre" + + item := TriggerMapItemModel{ + PushBranch: "master", + WorkflowID: "primary", + } + match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) + require.NoError(t, err) + require.Equal(t, false, match) + } +} + +// ---------------------------- +// --- Validate + +// Config +func TestValidateConfig(t *testing.T) { + t.Log("Valid bitriseData ID") + { + bitriseData := BitriseDataModel{ + FormatVersion: "1.4.0", + Workflows: map[string]WorkflowModel{ + "A-Za-z0-9-_.": WorkflowModel{}, + }, + } + + warnings, err := bitriseData.Validate() + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + } + + t.Log("Invalid bitriseData ID - empty") + { + bitriseData := BitriseDataModel{ + FormatVersion: "1.4.0", + Workflows: map[string]WorkflowModel{ + "": WorkflowModel{}, + }, + } + warnings, err := bitriseData.Validate() + require.EqualError(t, err, "invalid workflow ID (): empty") + require.Equal(t, 0, len(warnings)) + } + + t.Log("Invalid bitriseData ID - contains: `/`") + { + bitriseData := BitriseDataModel{ + FormatVersion: "1.4.0", + Workflows: map[string]WorkflowModel{ + "wf/id": WorkflowModel{}, + }, + } + + warnings, err := bitriseData.Validate() + require.NoError(t, err) + require.Equal(t, 1, len(warnings)) + require.Equal(t, "invalid workflow ID (wf/id): doesn't conform to: [A-Za-z0-9-_.]", warnings[0]) + } + + t.Log("Invalid bitriseData ID - contains: `:`") + { + bitriseData := BitriseDataModel{ + FormatVersion: "1.4.0", + Workflows: map[string]WorkflowModel{ + "wf:id": WorkflowModel{}, + }, + } + + warnings, err := bitriseData.Validate() + require.NoError(t, err) + require.Equal(t, 1, len(warnings)) + require.Equal(t, "invalid workflow ID (wf:id): doesn't conform to: [A-Za-z0-9-_.]", warnings[0]) + } + + t.Log("Invalid bitriseData ID - contains: ` `") + { + bitriseData := BitriseDataModel{ + FormatVersion: "1.4.0", + Workflows: map[string]WorkflowModel{ + "wf id": WorkflowModel{}, + }, + } + + warnings, err := bitriseData.Validate() + require.NoError(t, err) + require.Equal(t, 1, len(warnings)) + require.Equal(t, "invalid workflow ID (wf id): doesn't conform to: [A-Za-z0-9-_.]", warnings[0]) + } + + t.Log("Invalid bitriseData ID - contains: ` `") + { + bitriseData := BitriseDataModel{ + FormatVersion: "1.4.0", + Workflows: map[string]WorkflowModel{ + " wfid": WorkflowModel{}, + }, + } + + warnings, err := bitriseData.Validate() + require.NoError(t, err) + require.Equal(t, 1, len(warnings)) + require.Equal(t, "invalid workflow ID ( wfid): doesn't conform to: [A-Za-z0-9-_.]", warnings[0]) + } + + t.Log("Invalid bitriseData ID - contains: ` `") + { + bitriseData := BitriseDataModel{ + FormatVersion: "1.4.0", + Workflows: map[string]WorkflowModel{ + "wfid ": WorkflowModel{}, + }, + } + + warnings, err := bitriseData.Validate() + require.NoError(t, err) + require.Equal(t, 1, len(warnings)) + } +} + +// Workflow +func TestValidateWorkflow(t *testing.T) { + t.Log("before-afetr test") + { + workflow := WorkflowModel{ + BeforeRun: []string{"befor1", "befor2", "befor3"}, + AfterRun: []string{"after1", "after2", "after3"}, + } + + warnings, err := workflow.Validate() + require.NoError(t, err) + require.Equal(t, 0, len(warnings)) + } + + t.Log("invalid workflow - Invalid env: more than 2 fields") + { + configStr := ` +format_version: 1.4.0 + +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + target: + envs: + - ENV_KEY: env_value + opts: + title: test_env + title: Output Test + steps: + - script: + title: Should fail + inputs: + - content: echo "Hello" + BAD_KEY: value +` + + config := BitriseDataModel{} + require.NoError(t, yaml.Unmarshal([]byte(configStr), &config)) + require.NoError(t, config.Normalize()) + + warnings, err := config.Validate() + require.Error(t, err) + require.Equal(t, true, strings.Contains(err.Error(), "more than 2 keys specified: [BAD_KEY content opts]")) + require.Equal(t, 0, len(warnings)) + } + + t.Log("vali workflow - Warning: duplicated inputs") + { + configStr := `format_version: 1.4.0 + +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" + +workflows: + target: + steps: + - script: + title: Should fail + inputs: + - content: echo "Hello" + - content: echo "Hello" +` + + config := BitriseDataModel{} + require.NoError(t, yaml.Unmarshal([]byte(configStr), &config)) + require.NoError(t, config.Normalize()) + + warnings, err := config.Validate() + require.NoError(t, err) + require.Equal(t, 1, len(warnings)) + } +} + +// ---------------------------- +// --- Merge + +func TestMergeEnvironmentWith(t *testing.T) { + diffEnv := envmanModels.EnvironmentItemModel{ + "test_key": "test_value", + envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + Description: pointers.NewStringPtr("test_description"), + Summary: pointers.NewStringPtr("test_summary"), + ValueOptions: []string{"test_valu_options1", "test_valu_options2"}, + IsRequired: pointers.NewBoolPtr(true), + IsExpand: pointers.NewBoolPtr(false), + IsDontChangeValue: pointers.NewBoolPtr(true), + IsTemplate: pointers.NewBoolPtr(true), + }, + } + + t.Log("Different keys") + { + env := envmanModels.EnvironmentItemModel{ + "test_key1": "test_value", + } + require.Error(t, MergeEnvironmentWith(&env, diffEnv)) + } + + t.Log("Normal merge") + { + env := envmanModels.EnvironmentItemModel{ + "test_key": "test_value", + envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ + SkipIfEmpty: pointers.NewBoolPtr(true), + Category: pointers.NewStringPtr("test"), + }, + } + require.NoError(t, MergeEnvironmentWith(&env, diffEnv)) + + options, err := env.GetOptions() + require.NoError(t, err) + + diffOptions, err := diffEnv.GetOptions() + require.NoError(t, err) + + require.Equal(t, *diffOptions.Title, *options.Title) + require.Equal(t, *diffOptions.Description, *options.Description) + require.Equal(t, *diffOptions.Summary, *options.Summary) + require.Equal(t, len(diffOptions.ValueOptions), len(options.ValueOptions)) + require.Equal(t, *diffOptions.IsRequired, *options.IsRequired) + require.Equal(t, *diffOptions.IsExpand, *options.IsExpand) + require.Equal(t, *diffOptions.IsDontChangeValue, *options.IsDontChangeValue) + require.Equal(t, *diffOptions.IsTemplate, *options.IsTemplate) + + require.Equal(t, true, *options.SkipIfEmpty) + require.Equal(t, "test", *options.Category) + } +} + +func TestMergeStepWith(t *testing.T) { + desc := "desc 1" + summ := "sum 1" + website := "web/1" + fork := "fork/1" + published := time.Date(2012, time.January, 1, 0, 0, 0, 0, time.UTC) + + stepData := stepmanModels.StepModel{ + Description: pointers.NewStringPtr(desc), + Summary: pointers.NewStringPtr(summ), + Website: pointers.NewStringPtr(website), + SourceCodeURL: pointers.NewStringPtr(fork), + PublishedAt: pointers.NewTimePtr(published), + HostOsTags: []string{"osx"}, + ProjectTypeTags: []string{"ios"}, + TypeTags: []string{"test"}, + IsRequiresAdminUser: pointers.NewBoolPtr(true), + Inputs: []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "KEY_1": "Value 1", + }, + envmanModels.EnvironmentItemModel{ + "KEY_2": "Value 2", + }, + }, + Outputs: []envmanModels.EnvironmentItemModel{}, + } + + diffTitle := "name 2" + newSuppURL := "supp" + runIfStr := "" + stepDiffToMerge := stepmanModels.StepModel{ + Title: pointers.NewStringPtr(diffTitle), + HostOsTags: []string{"linux"}, + Source: &stepmanModels.StepSourceModel{ + Git: "https://git.url", + }, + Dependencies: []stepmanModels.DependencyModel{ + stepmanModels.DependencyModel{ + Manager: "brew", + Name: "test", + }, + }, + SupportURL: pointers.NewStringPtr(newSuppURL), + RunIf: pointers.NewStringPtr(runIfStr), + Inputs: []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "KEY_2": "Value 2 CHANGED", + }, + }, + Timeout: pointers.NewIntPtr(1), + Toolkit: &stepmanModels.StepToolkitModel{ + Go: &stepmanModels.GoStepToolkitModel{ + PackageName: "test", + }, + }, + } + + mergedStepData, err := MergeStepWith(stepData, stepDiffToMerge) + require.NoError(t, err) + + require.Equal(t, "name 2", *mergedStepData.Title) + require.Equal(t, "desc 1", *mergedStepData.Description) + require.Equal(t, "sum 1", *mergedStepData.Summary) + require.Equal(t, "web/1", *mergedStepData.Website) + require.Equal(t, "fork/1", *mergedStepData.SourceCodeURL) + require.Equal(t, true, (*mergedStepData.PublishedAt).Equal(time.Date(2012, time.January, 1, 0, 0, 0, 0, time.UTC))) + require.Equal(t, "linux", mergedStepData.HostOsTags[0]) + require.Equal(t, "", *mergedStepData.RunIf) + require.Equal(t, 1, len(mergedStepData.Dependencies)) + require.Equal(t, "test", mergedStepData.Toolkit.Go.PackageName) + require.Equal(t, 1, *mergedStepData.Timeout) + + dep := mergedStepData.Dependencies[0] + require.Equal(t, "brew", dep.Manager) + require.Equal(t, "test", dep.Name) + + // inputs + input0 := mergedStepData.Inputs[0] + key0, value0, err := input0.GetKeyValuePair() + + require.NoError(t, err) + require.Equal(t, "KEY_1", key0) + require.Equal(t, "Value 1", value0) + + input1 := mergedStepData.Inputs[1] + key1, value1, err := input1.GetKeyValuePair() + + require.NoError(t, err) + require.Equal(t, "KEY_2", key1) + require.Equal(t, "Value 2 CHANGED", value1) +} + +func TestGetInputByKey(t *testing.T) { + stepData := stepmanModels.StepModel{ + Inputs: []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "KEY_1": "Value 1", + }, + envmanModels.EnvironmentItemModel{ + "KEY_2": "Value 2", + }, + }, + } + + _, found := getInputByKey(stepData, "KEY_1") + require.Equal(t, true, found) + + _, found = getInputByKey(stepData, "KEY_3") + require.Equal(t, false, found) +} + +// ---------------------------- +// --- StepIDData + +func Test_StepIDData_IsUniqueResourceID(t *testing.T) { + stepIDDataWithIDAndVersionSpecified := StepIDData{IDorURI: "stepid", Version: "version"} + stepIDDataWithOnlyVersionSpecified := StepIDData{Version: "version"} + stepIDDataWithOnlyIDSpecified := StepIDData{IDorURI: "stepid"} + stepIDDataEmpty := StepIDData{} + + // Not Unique + for _, aSourceID := range []string{"path", "git", "_", ""} { + stepIDDataWithIDAndVersionSpecified.SteplibSource = aSourceID + require.Equal(t, false, stepIDDataWithIDAndVersionSpecified.IsUniqueResourceID()) + + stepIDDataWithOnlyVersionSpecified.SteplibSource = aSourceID + require.Equal(t, false, stepIDDataWithOnlyVersionSpecified.IsUniqueResourceID()) + + stepIDDataWithOnlyIDSpecified.SteplibSource = aSourceID + require.Equal(t, false, stepIDDataWithOnlyIDSpecified.IsUniqueResourceID()) + + stepIDDataEmpty.SteplibSource = aSourceID + require.Equal(t, false, stepIDDataEmpty.IsUniqueResourceID()) + } + + for _, aSourceID := range []string{"a", "any-other-step-source", "https://github.com/bitrise-io/bitrise-steplib.git"} { + // Only if StepLib, AND both ID and Version are defined, only then + // this is a Unique Resource ID! + stepIDDataWithIDAndVersionSpecified.SteplibSource = aSourceID + require.Equal(t, true, stepIDDataWithIDAndVersionSpecified.IsUniqueResourceID()) + + // In any other case, it's not, + // even if it's from a StepLib + // but missing ID or version! + stepIDDataWithOnlyVersionSpecified.SteplibSource = aSourceID + require.Equal(t, false, stepIDDataWithOnlyVersionSpecified.IsUniqueResourceID()) + + stepIDDataWithOnlyIDSpecified.SteplibSource = aSourceID + require.Equal(t, false, stepIDDataWithOnlyIDSpecified.IsUniqueResourceID()) + + stepIDDataEmpty.SteplibSource = aSourceID + require.Equal(t, false, stepIDDataEmpty.IsUniqueResourceID()) + } +} + +func TestGetStepIDStepDataPair(t *testing.T) { + stepData := stepmanModels.StepModel{} + + t.Log("valid steplist item") + { + stepListItem := StepListItemModel{ + "step1": stepData, + } + + id, _, err := GetStepIDStepDataPair(stepListItem) + require.NoError(t, err) + require.Equal(t, "step1", id) + } + + t.Log("invalid steplist item - more than 1 step") + { + stepListItem := StepListItemModel{ + "step1": stepData, + "step2": stepData, + } + + id, _, err := GetStepIDStepDataPair(stepListItem) + require.Error(t, err) + require.Equal(t, "", id) + } +} + +func TestCreateStepIDDataFromString(t *testing.T) { + t.Log("default / long / verbose ID mode") + { + stepCompositeIDString := "steplib-src::step-id@0.0.1" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") + + require.NoError(t, err) + require.Equal(t, "steplib-src", stepIDData.SteplibSource) + require.Equal(t, "step-id", stepIDData.IDorURI) + require.Equal(t, "0.0.1", stepIDData.Version) + } + + t.Log("no steplib-source") + { + stepCompositeIDString := "step-id@0.0.1" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "default-steplib-src") + + require.NoError(t, err) + require.Equal(t, "default-steplib-src", stepIDData.SteplibSource) + require.Equal(t, "step-id", stepIDData.IDorURI) + require.Equal(t, "0.0.1", stepIDData.Version) + } + + t.Log("invalid/empty step lib source, but default provided") + { + stepCompositeIDString := "::step-id@0.0.1" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "default-steplib-src") + + require.NoError(t, err) + require.Equal(t, "default-steplib-src", stepIDData.SteplibSource) + require.Equal(t, "step-id", stepIDData.IDorURI) + require.Equal(t, "0.0.1", stepIDData.Version) + } + + t.Log("invalid/empty step lib source + no default") + { + stepCompositeIDString := "::step-id@0.0.1" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") + + require.Error(t, err) + require.Equal(t, "", stepIDData.SteplibSource) + require.Equal(t, "", stepIDData.IDorURI) + require.Equal(t, "", stepIDData.Version) + } + + t.Log("no steplib-source & no default -> fail") + { + stepCompositeIDString := "step-id@0.0.1" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") + + require.Error(t, err) + require.Equal(t, "", stepIDData.SteplibSource) + require.Equal(t, "", stepIDData.IDorURI) + require.Equal(t, "", stepIDData.Version) + } + + t.Log("no steplib & no version, only step-id") + { + stepCompositeIDString := "step-id" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "def-lib-src") + + require.NoError(t, err) + require.Equal(t, "def-lib-src", stepIDData.SteplibSource) + require.Equal(t, "step-id", stepIDData.IDorURI) + require.Equal(t, "", stepIDData.Version) + } + + t.Log("empty test") + { + stepCompositeIDString := "" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "def-step-src") + + require.Error(t, err) + require.Equal(t, "", stepIDData.SteplibSource) + require.Equal(t, "", stepIDData.IDorURI) + require.Equal(t, "", stepIDData.Version) + } + + t.Log("special empty test") + { + stepCompositeIDString := "@1.0.0" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "def-step-src") + + require.Error(t, err) + require.Equal(t, "", stepIDData.SteplibSource) + require.Equal(t, "", stepIDData.IDorURI) + require.Equal(t, "", stepIDData.Version) + } + + // + // ----- Local Path + t.Log("local Path") + { + stepCompositeIDString := "path::/some/path" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") + + require.NoError(t, err) + require.Equal(t, "path", stepIDData.SteplibSource) + require.Equal(t, "/some/path", stepIDData.IDorURI) + require.Equal(t, "", stepIDData.Version) + } + + t.Log("local Path") + { + stepCompositeIDString := "path::~/some/path/in/home" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") + + require.NoError(t, err) + require.Equal(t, "path", stepIDData.SteplibSource) + require.Equal(t, "~/some/path/in/home", stepIDData.IDorURI) + require.Equal(t, "", stepIDData.Version) + } + + t.Log("local Path") + { + stepCompositeIDString := "path::$HOME/some/path/in/home" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") + + require.NoError(t, err) + require.Equal(t, "path", stepIDData.SteplibSource) + require.Equal(t, "$HOME/some/path/in/home", stepIDData.IDorURI) + require.Equal(t, "", stepIDData.Version) + } + + // + // ----- Direct git uri + t.Log("direct git uri") + { + stepCompositeIDString := "git::https://github.com/bitrise-io/steps-timestamp.git@develop" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "some-def-coll") + + require.NoError(t, err) + require.Equal(t, "git", stepIDData.SteplibSource) + require.Equal(t, "https://github.com/bitrise-io/steps-timestamp.git", stepIDData.IDorURI) + require.Equal(t, "develop", stepIDData.Version) + } + + t.Log("direct git uri") + { + stepCompositeIDString := "git::git@github.com:bitrise-io/steps-timestamp.git@develop" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") + + require.NoError(t, err) + require.Equal(t, "git", stepIDData.SteplibSource) + require.Equal(t, "git@github.com:bitrise-io/steps-timestamp.git", stepIDData.IDorURI) + require.Equal(t, "develop", stepIDData.Version) + } + + t.Log("direct git uri") + { + stepCompositeIDString := "git::https://github.com/bitrise-io/steps-timestamp.git" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "some-def-coll") + + require.NoError(t, err) + require.Equal(t, "git", stepIDData.SteplibSource) + require.Equal(t, "https://github.com/bitrise-io/steps-timestamp.git", stepIDData.IDorURI) + require.Equal(t, "master", stepIDData.Version) + } + + // + // ----- Old step + t.Log("old step") + { + stepCompositeIDString := "_::https://github.com/bitrise-io/steps-timestamp.git@1.0.0" + stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") + + require.NoError(t, err) + require.Equal(t, "_", stepIDData.SteplibSource) + require.Equal(t, "https://github.com/bitrise-io/steps-timestamp.git", stepIDData.IDorURI) + require.Equal(t, "1.0.0", stepIDData.Version) + } +} + +// ---------------------------- +// --- RemoveRedundantFields + +func TestRemoveEnvironmentRedundantFields(t *testing.T) { + t.Log("Trivial remove - all fields should be default value") + { + env := envmanModels.EnvironmentItemModel{ + "TEST_KEY": "test_value", + envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr(""), + Description: pointers.NewStringPtr(""), + Summary: pointers.NewStringPtr(""), + ValueOptions: []string{}, + IsRequired: pointers.NewBoolPtr(envmanModels.DefaultIsRequired), + IsExpand: pointers.NewBoolPtr(envmanModels.DefaultIsExpand), + IsDontChangeValue: pointers.NewBoolPtr(envmanModels.DefaultIsDontChangeValue), + IsTemplate: pointers.NewBoolPtr(envmanModels.DefaultIsTemplate), + }, + } + require.NoError(t, removeEnvironmentRedundantFields(&env)) + + options, err := env.GetOptions() + require.NoError(t, err) + + require.Equal(t, (*string)(nil), options.Title) + require.Equal(t, (*string)(nil), options.Description) + require.Equal(t, (*string)(nil), options.Summary) + require.Equal(t, 0, len(options.ValueOptions)) + require.Equal(t, (*bool)(nil), options.IsRequired) + require.Equal(t, (*bool)(nil), options.IsExpand) + require.Equal(t, (*bool)(nil), options.IsDontChangeValue) + require.Equal(t, (*bool)(nil), options.IsTemplate) + } + + t.Log("Trivial don't remove - no fields should be default value") + { + env := envmanModels.EnvironmentItemModel{ + "TEST_KEY": "test_value", + envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("t"), + Description: pointers.NewStringPtr("d"), + Summary: pointers.NewStringPtr("s"), + ValueOptions: []string{"i"}, + IsRequired: pointers.NewBoolPtr(true), + IsExpand: pointers.NewBoolPtr(false), + IsDontChangeValue: pointers.NewBoolPtr(true), + IsTemplate: pointers.NewBoolPtr(true), + }, + } + require.NoError(t, removeEnvironmentRedundantFields(&env)) + + options, err := env.GetOptions() + require.NoError(t, err) + + require.Equal(t, "t", *options.Title) + require.Equal(t, "d", *options.Description) + require.Equal(t, "s", *options.Summary) + require.Equal(t, "i", options.ValueOptions[0]) + require.Equal(t, true, *options.IsRequired) + require.Equal(t, false, *options.IsExpand) + require.Equal(t, true, *options.IsDontChangeValue) + require.Equal(t, true, *options.IsTemplate) + } + + t.Log("No options - opts field shouldn't exist") + { + env := envmanModels.EnvironmentItemModel{ + "TEST_KEY": "test_value", + } + require.NoError(t, removeEnvironmentRedundantFields(&env)) + + _, ok := env[envmanModels.OptionsKey] + require.Equal(t, false, ok) + } +} + +func configModelFromYAMLBytes(configBytes []byte) (bitriseData BitriseDataModel, err error) { + if err = yaml.Unmarshal(configBytes, &bitriseData); err != nil { + return + } + return +} + +func TestRemoveWorkflowRedundantFields(t *testing.T) { + configStr := `format_version: 2 +default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" +project_type: ios + +app: + summary: "sum" + envs: + - ENV_KEY: env_value + opts: + is_required: true + +workflows: + target: + envs: + - ENV_KEY: env_value + opts: + title: test_env + title: Output Test + steps: + - script: + description: test +` + + config, err := configModelFromYAMLBytes([]byte(configStr)) + require.NoError(t, err) + + err = config.RemoveRedundantFields() + require.NoError(t, err) + + require.Equal(t, "2", config.FormatVersion) + require.Equal(t, "https://github.com/bitrise-io/bitrise-steplib.git", config.DefaultStepLibSource) + require.Equal(t, "ios", config.ProjectType) + + require.Equal(t, "", config.App.Title) + require.Equal(t, "", config.App.Description) + require.Equal(t, "sum", config.App.Summary) + + for _, env := range config.App.Environments { + options, err := env.GetOptions() + require.NoError(t, err) + + require.Nil(t, options.Title) + require.Nil(t, options.Description) + require.Nil(t, options.Summary) + require.Equal(t, 0, len(options.ValueOptions)) + require.Equal(t, true, *options.IsRequired) + require.Nil(t, options.IsExpand) + require.Nil(t, options.IsDontChangeValue) + } + + for _, workflow := range config.Workflows { + require.Equal(t, "Output Test", workflow.Title) + require.Equal(t, "", workflow.Description) + require.Equal(t, "", workflow.Summary) + + for _, env := range workflow.Environments { + options, err := env.GetOptions() + require.NoError(t, err) + + require.Equal(t, "test_env", *options.Title) + require.Nil(t, options.Description) + require.Nil(t, options.Summary) + require.Equal(t, 0, len(options.ValueOptions)) + require.Nil(t, options.IsRequired) + require.Nil(t, options.IsExpand) + require.Nil(t, options.IsDontChangeValue) + } + + for _, stepListItem := range workflow.Steps { + _, step, err := GetStepIDStepDataPair(stepListItem) + require.NoError(t, err) + + require.Nil(t, step.Title) + require.Equal(t, "test", *step.Description) + require.Nil(t, step.Summary) + require.Nil(t, step.Website) + require.Nil(t, step.SourceCodeURL) + require.Nil(t, step.SupportURL) + require.Nil(t, step.PublishedAt) + require.Nil(t, step.Source) + require.Nil(t, step.Deps) + require.Equal(t, 0, len(step.HostOsTags)) + require.Equal(t, 0, len(step.ProjectTypeTags)) + require.Equal(t, 0, len(step.TypeTags)) + require.Nil(t, step.IsRequiresAdminUser) + require.Nil(t, step.IsAlwaysRun) + require.Nil(t, step.IsSkippable) + require.Nil(t, step.RunIf) + require.Equal(t, 0, len(step.Inputs)) + require.Equal(t, 0, len(step.Outputs)) + } + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/output/output.go b/vendor/github.com/bitrise-io/bitrise/output/output.go new file mode 100644 index 00000000..a3e7d747 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/output/output.go @@ -0,0 +1,64 @@ +package output + +import ( + "encoding/json" + "fmt" + + "gopkg.in/yaml.v2" + + log "github.com/Sirupsen/logrus" + "github.com/urfave/cli" +) + +const ( + // FormatKey ... + FormatKey = "format" + // FormatRaw ... + FormatRaw = "raw" + // FormatJSON ... + FormatJSON = "json" + // FormatYML ... + FormatYML = "yml" +) + +// Format ... +var Format = FormatRaw + +// ConfigureOutputFormat ... +func ConfigureOutputFormat(c *cli.Context) error { + outFmt := c.String(FormatKey) + switch outFmt { + case FormatRaw, FormatJSON, FormatYML: + // valid + Format = outFmt + case "": + // default + Format = FormatRaw + default: + // invalid + return fmt.Errorf("Invalid Output Format: %s", outFmt) + } + return nil +} + +// Print ... +func Print(outModel interface{}, format string) { + switch format { + case FormatJSON: + serBytes, err := json.Marshal(outModel) + if err != nil { + log.Errorf("[.print] ERROR: %s", err) + return + } + fmt.Printf("%s\n", serBytes) + case FormatYML: + serBytes, err := yaml.Marshal(outModel) + if err != nil { + log.Errorf("[output.print] ERROR: %s", err) + return + } + fmt.Printf("%s\n", serBytes) + default: + log.Errorf("[output.print] Invalid output format: %s", format) + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/events.go b/vendor/github.com/bitrise-io/bitrise/plugins/events.go new file mode 100644 index 00000000..ab7075cc --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/events.go @@ -0,0 +1,72 @@ +package plugins + +import ( + "encoding/json" + "fmt" +) + +// TriggerEventName ... +type TriggerEventName string + +const ( + // DidFinishRun ... + DidFinishRun TriggerEventName = "DidFinishRun" +) + +// TriggerEvent ... +func TriggerEvent(name TriggerEventName, payload interface{}) error { + // Create plugin input + payloadBytes, err := json.Marshal(payload) + if err != nil { + return err + } + + pluginInput := PluginInput{ + PluginInputPayloadKey: string(payloadBytes), + PluginInputTriggerEventKey: string(name), + } + + // Load plugins + plugins, err := LoadPlugins(string(name)) + if err != nil { + return err + } + + // Run plugins + for _, plugin := range plugins { + if err := RunPluginByEvent(plugin, pluginInput); err != nil { + return err + } + } + + return nil +} + +// LoadPlugins ... +func LoadPlugins(eventName string) ([]Plugin, error) { + routing, err := readPluginRouting() + if err != nil { + return []Plugin{}, err + } + + pluginNames := []string{} + for name, route := range routing.RouteMap { + if route.TriggerEvent == eventName { + pluginNames = append(pluginNames, name) + } + } + + plugins := []Plugin{} + for _, name := range pluginNames { + plugin, found, err := LoadPlugin(name) + if err != nil { + return []Plugin{}, err + } + if !found { + return []Plugin{}, fmt.Errorf("Plugin (%s) exist in routing, but not found", name) + } + plugins = append(plugins, plugin) + } + + return plugins, nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/git.go b/vendor/github.com/bitrise-io/bitrise/plugins/git.go new file mode 100644 index 00000000..9eb608e7 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/git.go @@ -0,0 +1,221 @@ +package plugins + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "regexp" + "sort" + "strings" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/errorutil" + "github.com/bitrise-io/go-utils/pathutil" + ver "github.com/hashicorp/go-version" +) + +//======================================= +// Util +//======================================= + +func filterVersionTags(tagList []string) []*ver.Version { + versionTags := []*ver.Version{} + for _, tag := range tagList { + versionTag, err := ver.NewVersion(tag) + if err == nil && versionTag != nil { + versionTags = append(versionTags, versionTag) + } + } + return versionTags +} + +//======================================= +// Git +//======================================= + +func createError(prinatableCmd, cmdOut string, cmdErr error) error { + message := fmt.Sprintf("command (%s) failed", prinatableCmd) + if len(cmdOut) > 0 { + message += "\nout: " + cmdOut + } + if !errorutil.IsExitStatusError(cmdErr) { + message += "\nerr: " + cmdErr.Error() + } + return errors.New(message) +} + +func runAndHandle(cmd *command.Model) error { + if out, err := cmd.RunAndReturnTrimmedCombinedOutput(); err != nil { + return createError(cmd.PrintableCommandArgs(), out, err) + } + return nil +} + +func runForOutputAndHandle(cmd *command.Model) (string, error) { + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + if err != nil { + return "", createError(cmd.PrintableCommandArgs(), out, err) + } + return out, nil +} + +func commitHashOfTag(cloneIntoDir, tag string) (string, error) { + cmd := command.New("git", "show-ref", "--hash", tag) + cmd.SetDir(cloneIntoDir) + return runForOutputAndHandle(cmd) +} + +func gitRemoteTagList(cloneIntoDir string) ([]string, error) { + cmd := command.New("git", "ls-remote", "--tags") + cmd.SetDir(cloneIntoDir) + out, err := runForOutputAndHandle(cmd) + if err != nil { + return []string{}, err + } + if out == "" { + return []string{}, nil + } + + var exp = regexp.MustCompile(`(^[a-z0-9]+)+.*refs/tags/([0-9.]+)`) + versionMap := map[string]bool{} + outSplit := strings.Split(out, "\n") + + for _, line := range outSplit { + result := exp.FindAllStringSubmatch(line, -1) + if len(result) > 0 { + matches := result[0] + + if len(matches) == 3 { + version := matches[2] + versionMap[version] = true + } + } + } + + versions := []string{} + for key := range versionMap { + versions = append(versions, key) + } + + return versions, nil +} + +func gitInit(cloneIntoDir string) error { + cmd := command.New("git", "init") + cmd.SetDir(cloneIntoDir) + return runAndHandle(cmd) +} + +func gitAddRemote(cloneIntoDir, repositoryURL string) error { + cmd := command.New("git", "remote", "add", "origin", repositoryURL) + cmd.SetDir(cloneIntoDir) + return runAndHandle(cmd) +} + +func gitFetch(cloneIntoDir string) error { + cmd := command.New("git", "fetch") + cmd.SetDir(cloneIntoDir) + return runAndHandle(cmd) +} + +func gitCheckout(cloneIntoDir, gitCheckoutParam string) error { + cmd := command.New("git", "checkout", gitCheckoutParam) + cmd.SetDir(cloneIntoDir) + return runAndHandle(cmd) +} + +func gitLog(cloneIntoDir, formatParam string) (string, error) { + cmd := command.New("git", "log", "-1", "--format="+formatParam) + cmd.SetDir(cloneIntoDir) + return runForOutputAndHandle(cmd) +} + +func gitInitWithRemote(cloneIntoDir, repositoryURL string) error { + gitCheckPath := filepath.Join(cloneIntoDir, ".git") + if exist, err := pathutil.IsPathExists(gitCheckPath); err != nil { + return fmt.Errorf("Failed to file path (%s), err: %s", gitCheckPath, err) + } else if exist { + return fmt.Errorf(".git folder already exists in the destination dir (%s)", gitCheckPath) + } + + if err := os.MkdirAll(cloneIntoDir, 0777); err != nil { + return fmt.Errorf("Failed to create the clone_destination_dir at: %s", cloneIntoDir) + } + + if err := gitInit(cloneIntoDir); err != nil { + return fmt.Errorf("Could not init git repository, err: %s", cloneIntoDir) + } + + if err := gitAddRemote(cloneIntoDir, repositoryURL); err != nil { + return fmt.Errorf("Could not add remote, err: %s", err) + } + + if err := gitFetch(cloneIntoDir); err != nil { + return fmt.Errorf("Could not fetch from repository, err: %s", err) + } + + return nil +} + +//======================================= +// Main +//======================================= + +// ByVersion .. +type ByVersion []*ver.Version + +func (s ByVersion) Len() int { + return len(s) +} +func (s ByVersion) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} +func (s ByVersion) Less(i, j int) bool { + return s[i].LessThan(s[j]) +} + +// GitVersionTags ... +func GitVersionTags(gitRepoDir string) ([]*ver.Version, error) { + tagList, err := gitRemoteTagList(gitRepoDir) + if err != nil { + return []*ver.Version{}, fmt.Errorf("Could not get version tag list, error: %s", err) + } + + tags := filterVersionTags(tagList) + + sort.Sort(ByVersion(tags)) + + return tags, nil +} + +// GitCloneAndCheckoutVersionOrLatestVersion ... +func GitCloneAndCheckoutVersionOrLatestVersion(cloneIntoDir, repositoryURL, checkoutVersion string) (string, error) { + if err := gitInitWithRemote(cloneIntoDir, repositoryURL); err != nil { + return "", fmt.Errorf("git init failed, error: %s", err) + } + + if checkoutVersion == "" { + versionTagList, err := GitVersionTags(cloneIntoDir) + if err != nil { + return "", fmt.Errorf("could not get version tag list, error: %s", err) + } + + if len(versionTagList) == 0 { + return "", fmt.Errorf("no version tag found") + } + + versionPtr := versionTagList[len(versionTagList)-1] + if versionPtr == nil { + return "", fmt.Errorf("uninitialized version found") + } + + checkoutVersion = versionPtr.String() + } + + if err := gitCheckout(cloneIntoDir, checkoutVersion); err != nil { + return "", fmt.Errorf("could not checkout (%s), err :%s", checkoutVersion, err) + } + + return checkoutVersion, nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/git_test.go b/vendor/github.com/bitrise-io/bitrise/plugins/git_test.go new file mode 100644 index 00000000..14d2956c --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/git_test.go @@ -0,0 +1,89 @@ +package plugins + +import ( + "os" + "testing" + + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestFilterVersionTags(t *testing.T) { + t.Log("single version tag") + { + versionTags := filterVersionTags([]string{"1.0.0"}) + require.Equal(t, 1, len(versionTags)) + require.Equal(t, "1.0.0", versionTags[0].String()) + } + + t.Log("version tag list") + { + versionTags := filterVersionTags([]string{"1.0.0", "1.1.0", "1.1.1"}) + require.Equal(t, 3, len(versionTags)) + require.Equal(t, "1.0.0", versionTags[0].String()) + require.Equal(t, "1.1.0", versionTags[1].String()) + require.Equal(t, "1.1.1", versionTags[2].String()) + } + + t.Log("non version tag") + { + versionTags := filterVersionTags([]string{"release"}) + require.Equal(t, 0, len(versionTags)) + } + + t.Log("version tag + non version tag") + { + versionTags := filterVersionTags([]string{"1.0.0", "release"}) + require.Equal(t, 1, len(versionTags)) + require.Equal(t, "1.0.0", versionTags[0].String()) + } +} + +func TestClonePluginSrc(t *testing.T) { + t.Log("example plugin - latest version") + { + pluginSource := examplePluginGitURL + versionTag := "" + destinationDir, err := pathutil.NormalizedOSTempDirPath("TestClonePluginSrc") + require.NoError(t, err) + + exist, err := pathutil.IsPathExists(destinationDir) + require.NoError(t, err) + if exist { + err := os.RemoveAll(destinationDir) + require.NoError(t, err) + } + + version, err := GitCloneAndCheckoutVersionOrLatestVersion(destinationDir, pluginSource, versionTag) + require.NoError(t, err) + require.NotNil(t, version) + + exist, err = pathutil.IsPathExists(destinationDir) + require.NoError(t, err) + require.Equal(t, true, exist) + } + + t.Log("example plugin - 0.9.0 version") + { + pluginSource := examplePluginGitURL + versionTag := "0.9.0" + destinationDir, err := pathutil.NormalizedOSTempDirPath("TestClonePluginSrc") + require.NoError(t, err) + + exist, err := pathutil.IsPathExists(destinationDir) + require.NoError(t, err) + if exist { + err := os.RemoveAll(destinationDir) + require.NoError(t, err) + } + + version, err := GitCloneAndCheckoutVersionOrLatestVersion(destinationDir, pluginSource, versionTag) + require.NoError(t, err) + require.NotNil(t, version) + require.Equal(t, "0.9.0", version) + + exist, err = pathutil.IsPathExists(destinationDir) + require.NoError(t, err) + require.Equal(t, true, exist) + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/install.go b/vendor/github.com/bitrise-io/bitrise/plugins/install.go new file mode 100644 index 00000000..a893b66b --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/install.go @@ -0,0 +1,289 @@ +package plugins + +import ( + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/progress" + ver "github.com/hashicorp/go-version" +) + +//======================================= +// Util +//======================================= + +func validatePath(pth string) error { + if exist, err := pathutil.IsPathExists(pth); err != nil { + return fmt.Errorf("failed to check path (%s), error: %s", pth, err) + } else if !exist { + return fmt.Errorf("no file found at (%s)", pth) + } + return nil +} + +func validateVersion(current, requiredMin ver.Version, requiredMax *ver.Version) error { + if current.LessThan(&requiredMin) { + return fmt.Errorf("current version (%s) is less then min version (%s) ", current.String(), requiredMin.String()) + } else if requiredMax != nil && current.GreaterThan(requiredMax) { + return fmt.Errorf("current version (%s) is greater than max version (%s) ", current.String(), (*requiredMax).String()) + } + return nil +} + +func downloadPluginBin(sourceURL, destinationPth string) error { + url, err := url.Parse(sourceURL) + if err != nil { + return fmt.Errorf("failed to parse url (%s), error: %s", sourceURL, err) + } + + // Download local binary + if url.Scheme == "file" { + src := strings.Replace(sourceURL, url.Scheme+"://", "", -1) + + if err := command.CopyFile(src, destinationPth); err != nil { + return fmt.Errorf("failed to copy (%s) to (%s)", src, destinationPth) + } + return nil + } + + // Download remote binary + out, err := os.Create(destinationPth) + defer func() { + if err := out.Close(); err != nil { + log.Warnf("failed to close (%s)", destinationPth) + } + }() + if err != nil { + return fmt.Errorf("failed to create (%s), error: %s", destinationPth, err) + } + + resp, err := http.Get(sourceURL) + if err != nil { + return fmt.Errorf("failed to download from (%s), error: %s", sourceURL, err) + } + defer func() { + if err := resp.Body.Close(); err != nil { + log.Warnf("failed to close (%s) body", sourceURL) + } + }() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("non success status code (%d)", resp.StatusCode) + } + + _, err = io.Copy(out, resp.Body) + if err != nil { + return fmt.Errorf("failed to download from (%s), error: %s", sourceURL, err) + } + + return nil +} + +func cleanupPlugin(name string) error { + pluginDir := GetPluginDir(name) + + if err := os.RemoveAll(pluginDir); err != nil { + return err + } + + if err := DeletePluginRoute(name); err != nil { + return err + } + + return nil +} + +func installLocalPlugin(pluginSourceURI, pluginLocalPth string) (Plugin, error) { + // Parse & validate plugin + tmpPluginYMLPath := filepath.Join(pluginLocalPth, pluginDefinitionFileName) + + if err := validatePath(tmpPluginYMLPath); err != nil { + return Plugin{}, fmt.Errorf("bitrise-plugin.yml validation failed, error: %s", err) + } + + newPlugin, err := ParsePluginFromYML(tmpPluginYMLPath) + if err != nil { + return Plugin{}, fmt.Errorf("failed to parse bitrise-plugin.yml (%s), error: %s", tmpPluginYMLPath, err) + } + + if err := validatePlugin(newPlugin, pluginSourceURI); err != nil { + return Plugin{}, fmt.Errorf("plugin validation failed, error: %s", err) + } + // --- + + // Check if plugin already installed + if route, found, err := ReadPluginRoute(newPlugin.Name); err != nil { + return Plugin{}, fmt.Errorf("failed to check if plugin already installed, error: %s", err) + } else if found { + if route.Source != pluginSourceURI { + return Plugin{}, fmt.Errorf("plugin already installed with name (%s) from different source (%s)", route.Name, route.Source) + } + + installedPluginVersionPtr, err := GetPluginVersion(route.Name) + if err != nil { + return Plugin{}, fmt.Errorf("failed to check installed plugin (%s) version, error: %s", route.Name, err) + } + + if installedPluginVersionPtr != nil { + log.Warnf("installed plugin found with version (%s), overriding it...", (*installedPluginVersionPtr).String()) + } else { + log.Warnf("installed local plugin found, overriding it...") + } + } + // --- + + tmpPluginDir, err := pathutil.NormalizedOSTempDirPath("__plugin__") + if err != nil { + return Plugin{}, fmt.Errorf("failed to create tmp plugin dir, error: %s", err) + } + + // Install plugin executable + executableURL := newPlugin.ExecutableURL() + if executableURL != "" { + tmpPluginBinDir := filepath.Join(tmpPluginDir, "bin") + if err := os.MkdirAll(tmpPluginBinDir, 0777); err != nil { + return Plugin{}, fmt.Errorf("failed to create tmp plugin bin dir, error: %s", err) + } + + tmpPluginBinPth := filepath.Join(tmpPluginBinDir, newPlugin.Name) + + var err error + progress.NewDefaultWrapper("Downloading plugin binary").WrapAction(func() { + err = downloadPluginBin(executableURL, tmpPluginBinPth) + }) + if err != nil { + return Plugin{}, fmt.Errorf("failed to download plugin executable from (%s), error: %s", executableURL, err) + } + } + // --- + + // Install plugin source + tmpPluginSrcDir := filepath.Join(tmpPluginDir, "src") + if err := os.MkdirAll(tmpPluginSrcDir, 0777); err != nil { + return Plugin{}, fmt.Errorf("failed to create tmp plugin src dir, error: %s", err) + } + + if err := command.CopyDir(pluginLocalPth, tmpPluginSrcDir, true); err != nil { + return Plugin{}, fmt.Errorf("failed to copy plugin from (%s) to (%s), error: %s", pluginLocalPth, tmpPluginSrcDir, err) + } + // --- + + // Create plugin work dir + tmpPluginDataDir := filepath.Join(tmpPluginDir, "data") + if err := os.MkdirAll(tmpPluginDataDir, 0777); err != nil { + return Plugin{}, fmt.Errorf("failed to create tmp plugin data dir (%s), error: %s", tmpPluginDataDir, err) + } + // --- + + pluginDir := GetPluginDir(newPlugin.Name) + if err := command.CopyDir(tmpPluginDir, pluginDir, true); err != nil { + if err := cleanupPlugin(newPlugin.Name); err != nil { + log.Warnf("Failed to cleanup plugin (%s), error: %s", newPlugin.Name, err) + } + return Plugin{}, fmt.Errorf("failed to copy plugin, error: %s", err) + } + + if executableURL != "" { + pluginBinDir := GetPluginBinDir(newPlugin.Name) + pluginBinPth := filepath.Join(pluginBinDir, newPlugin.Name) + if err := os.Chmod(pluginBinPth, 0777); err != nil { + if err := cleanupPlugin(newPlugin.Name); err != nil { + log.Warnf("Failed to cleanup plugin (%s), error: %s", newPlugin.Name, err) + } + return Plugin{}, fmt.Errorf("failed to make plugin bin executable, error: %s", err) + } + } + + return newPlugin, nil +} + +func isLocalURL(urlStr string) bool { + parsed, err := url.Parse(urlStr) + if err != nil { + return false + } + if parsed == nil { + return false + } + return (parsed.Scheme == "file" || parsed.Scheme == "") +} + +//======================================= +// Main +//======================================= + +// InstallPlugin ... +func InstallPlugin(pluginSourceURI, versionTag string) (Plugin, string, error) { + newVersion := "" + pluginDir := "" + + if !isLocalURL(pluginSourceURI) { + pluginSrcTmpDir, err := pathutil.NormalizedOSTempDirPath("plugin-src-tmp") + if err != nil { + return Plugin{}, "", fmt.Errorf("failed to create plugin src temp directory, error: %s", err) + } + defer func() { + if err := os.RemoveAll(pluginSrcTmpDir); err != nil { + log.Warnf("Failed to remove path (%s)", pluginSrcTmpDir) + } + }() + + version := "" + err = nil + + progress.NewDefaultWrapper("git clone plugin source").WrapAction(func() { + version, err = GitCloneAndCheckoutVersionOrLatestVersion(pluginSrcTmpDir, pluginSourceURI, versionTag) + }) + + if err != nil { + return Plugin{}, "", fmt.Errorf("failed to download plugin, error: %s", err) + } + + pluginDir = pluginSrcTmpDir + newVersion = version + } else { + pluginSourceURI = strings.TrimPrefix(pluginSourceURI, "file://") + pluginDir = pluginSourceURI + } + + newPlugin, err := installLocalPlugin(pluginSourceURI, pluginDir) + if err != nil { + return Plugin{}, "", err + } + + // Register plugin + if err := CreateAndAddPluginRoute(newPlugin, pluginSourceURI, newVersion); err != nil { + if err := cleanupPlugin(newPlugin.Name); err != nil { + log.Warnf("Failed to cleanup plugin (%s), error: %s", newPlugin.Name, err) + } + return Plugin{}, "", fmt.Errorf("failed to add plugin route, error: %s", err) + } + // --- + + return newPlugin, newVersion, nil +} + +// DeletePlugin ... +func DeletePlugin(name string) error { + pluginDir := GetPluginDir(name) + + if exists, err := pathutil.IsDirExists(pluginDir); err != nil { + return err + } else if !exists { + return fmt.Errorf("Plugin (%s) not installed", name) + } + + if err := os.RemoveAll(pluginDir); err != nil { + return fmt.Errorf("failed to delete dir (%s)", pluginDir) + } + + return DeletePluginRoute(name) +} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/install_test.go b/vendor/github.com/bitrise-io/bitrise/plugins/install_test.go new file mode 100644 index 00000000..53833e9a --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/install_test.go @@ -0,0 +1,223 @@ +package plugins + +import ( + "os" + "path/filepath" + "testing" + + "github.com/bitrise-io/go-utils/pathutil" + ver "github.com/hashicorp/go-version" + "github.com/stretchr/testify/require" +) + +const examplePluginGitURL = "https://github.com/bitrise-core/bitrise-plugins-example.git" +const analyticsPluginBinURL = "https://github.com/bitrise-core/bitrise-plugins-analytics/releases/download/0.9.1/analytics-Darwin-x86_64" + +func TestIsLocalURL(t *testing.T) { + t.Log("local url - absolute") + { + require.Equal(t, true, isLocalURL("/usr/bin")) + } + + t.Log("local url - relative") + { + require.Equal(t, true, isLocalURL("../usr/bin")) + } + + t.Log("local url - with prefix: file://") + { + require.Equal(t, true, isLocalURL("file:///usr/bin")) + } + + t.Log("local url - relative with prefix: file://") + { + require.Equal(t, true, isLocalURL("file://./../usr/bin")) + } + + t.Log("remote url") + { + require.Equal(t, false, isLocalURL("https://bitrise.io")) + } + + t.Log("remote url- git ssh url") + { + require.Equal(t, false, isLocalURL("git@github.com:bitrise-io/bitrise.git")) + } +} + +func TestValidateVersion(t *testing.T) { + t.Log("required min - pass") + { + requiredMin, err := ver.NewVersion("1.0.0") + require.NoError(t, err) + + current, err := ver.NewVersion("1.0.1") + require.NoError(t, err) + + err = validateVersion(*current, *requiredMin, nil) + require.NoError(t, err) + } + + t.Log("required min - fail") + { + requiredMin, err := ver.NewVersion("1.0.2") + require.NoError(t, err) + + current, err := ver.NewVersion("1.0.1") + require.NoError(t, err) + + err = validateVersion(*current, *requiredMin, nil) + require.Error(t, err) + } + + t.Log("required min + required max - pass") + { + requiredMin, err := ver.NewVersion("1.0.0") + require.NoError(t, err) + + requiredMax, err := ver.NewVersion("1.0.2") + require.NoError(t, err) + + current, err := ver.NewVersion("1.0.1") + require.NoError(t, err) + + err = validateVersion(*current, *requiredMin, requiredMax) + require.NoError(t, err) + } + + t.Log("required min + required max - pass") + { + requiredMin, err := ver.NewVersion("1.0.0") + require.NoError(t, err) + + requiredMax, err := ver.NewVersion("1.0.0") + require.NoError(t, err) + + current, err := ver.NewVersion("1.0.0") + require.NoError(t, err) + + err = validateVersion(*current, *requiredMin, requiredMax) + require.NoError(t, err) + } + + t.Log("required min + required max - min fail") + { + requiredMin, err := ver.NewVersion("1.0.1") + require.NoError(t, err) + + requiredMax, err := ver.NewVersion("1.0.2") + require.NoError(t, err) + + current, err := ver.NewVersion("1.0.0") + require.NoError(t, err) + + err = validateVersion(*current, *requiredMin, requiredMax) + require.Error(t, err) + } + + t.Log("required min + required max - max fail") + { + requiredMin, err := ver.NewVersion("1.0.0") + require.NoError(t, err) + + requiredMax, err := ver.NewVersion("1.0.1") + require.NoError(t, err) + + current, err := ver.NewVersion("1.0.2") + require.NoError(t, err) + + err = validateVersion(*current, *requiredMin, requiredMax) + require.Error(t, err) + } +} + +func TestValidateRequirements(t *testing.T) { + bitriseVersion, err := ver.NewVersion("1.0.0") + require.NoError(t, err) + + envmanVersion, err := ver.NewVersion("1.0.0") + require.NoError(t, err) + + stepmanVersion, err := ver.NewVersion("1.0.0") + require.NoError(t, err) + + currentVersionMap := map[string]ver.Version{ + "bitrise": *bitriseVersion, + "envman": *envmanVersion, + "stepman": *stepmanVersion, + } + + t.Log("valid requirements") + { + requirements := []Requirement{ + Requirement{ + Tool: "bitrise", + MinVersion: "1.0.0", + MaxVersion: "1.0.0", + }, + Requirement{ + Tool: "envman", + MinVersion: "0.9.0", + MaxVersion: "1.1.0", + }, + Requirement{ + Tool: "stepman", + MinVersion: "1.0.0", + MaxVersion: "1.0.0", + }, + } + + err := validateRequirements(requirements, currentVersionMap) + require.NoError(t, err) + } + + t.Log("invalid requirements") + { + requirements := []Requirement{ + Requirement{ + Tool: "bitrise", + MinVersion: "1.0.0", + MaxVersion: "1.0.0", + }, + Requirement{ + Tool: "envman", + MinVersion: "1.1.0", + MaxVersion: "1.1.0", + }, + Requirement{ + Tool: "stepman", + MinVersion: "1.0.0", + MaxVersion: "1.0.0", + }, + } + + err := validateRequirements(requirements, currentVersionMap) + require.Error(t, err) + } +} + +func TestDownloadPluginBin(t *testing.T) { + t.Log("example plugin bin - ") + { + pluginBinURL := analyticsPluginBinURL + destinationDir, err := pathutil.NormalizedOSTempDirPath("TestDownloadPluginBin") + require.NoError(t, err) + + exist, err := pathutil.IsPathExists(destinationDir) + require.NoError(t, err) + if exist { + err := os.RemoveAll(destinationDir) + require.NoError(t, err) + } + + require.NoError(t, os.MkdirAll(destinationDir, 0777)) + + destinationPth := filepath.Join(destinationDir, "example") + + require.NoError(t, downloadPluginBin(pluginBinURL, destinationPth)) + + exist, err = pathutil.IsPathExists(destinationPth) + require.NoError(t, err) + require.Equal(t, true, exist) + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/model_methods_test.go b/vendor/github.com/bitrise-io/bitrise/plugins/model_methods_test.go new file mode 100644 index 00000000..86a2d0c8 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/model_methods_test.go @@ -0,0 +1,368 @@ +package plugins + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func write(t *testing.T, content, toPth string) { + toDir := filepath.Dir(toPth) + exist, err := pathutil.IsDirExists(toDir) + require.NoError(t, err) + if !exist { + require.NoError(t, os.MkdirAll(toDir, 0700)) + } + require.NoError(t, fileutil.WriteStringToFile(toPth, content)) +} + +func TestParseAndValidatePluginFromYML(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__plugin_test__") + require.NoError(t, err) + + t.Log("simple plugin - with executables") + { + pluginStr := `name: step +description: |- + Manage Bitrise CLI steps +trigger: +executable: + osx: bin_url + linux: bin_url +requirements: +- tool: bitrise + min_version: 1.3.0 + max_version: "" +` + + pth := filepath.Join(tmpDir, "bitrise-plugin.yml") + write(t, pluginStr, pth) + + plugin, err := ParsePluginFromYML(pth) + require.NoError(t, err) + + require.NoError(t, validatePlugin(plugin, pth)) + + require.Equal(t, "step", plugin.Name) + require.Equal(t, "Manage Bitrise CLI steps", plugin.Description) + require.Equal(t, 1, len(plugin.Requirements)) + + requirement := plugin.Requirements[0] + require.Equal(t, "bitrise", requirement.Tool) + require.Equal(t, "1.3.0", requirement.MinVersion) + require.Equal(t, "", requirement.MaxVersion) + } + + t.Log("invalid plugin - no name") + { + pluginStr := `name: +description: |- + Manage Bitrise CLI steps +trigger: +executable: + osx: bin_url + linux: bin_url +requirements: +- tool: bitrise + min_version: 1.3.0 + max_version: "" +` + + pth := filepath.Join(tmpDir, "bitrise-plugin.yml") + write(t, pluginStr, pth) + + plugin, err := ParsePluginFromYML(pth) + require.NoError(t, err) + require.EqualError(t, validatePlugin(plugin, pth), "missing name") + } + + t.Log("invalid plugin - no linux executable") + { + pluginStr := `name: step +description: |- + Manage Bitrise CLI steps +trigger: +executable: + osx: bin_url + linux: +requirements: +- tool: bitrise + min_version: 1.3.0 + max_version: "" +` + + pth := filepath.Join(tmpDir, "bitrise-plugin.yml") + write(t, pluginStr, pth) + + plugin, err := ParsePluginFromYML(pth) + require.NoError(t, err) + require.EqualError(t, validatePlugin(plugin, pth), "both osx and linux executable should be defined, or non of them") + } + + t.Log("invalid plugin - no osx executable") + { + pluginStr := `name: step +description: |- + Manage Bitrise CLI steps +trigger: +executable: + osx: + linux: bin_url +requirements: +- tool: bitrise + min_version: 1.3.0 + max_version: "" +` + + pth := filepath.Join(tmpDir, "bitrise-plugin.yml") + write(t, pluginStr, pth) + + plugin, err := ParsePluginFromYML(pth) + require.NoError(t, err) + require.EqualError(t, validatePlugin(plugin, pth), "both osx and linux executable should be defined, or non of them") + } + + t.Log("invalid plugin - no executables, no bitrise-plugin.sh") + { + pluginStr := `name: step +description: |- + Manage Bitrise CLI steps +trigger: +requirements: +- tool: bitrise + min_version: 1.3.0 + max_version: "" +` + + pth := filepath.Join(tmpDir, "bitrise-plugin.yml") + write(t, pluginStr, pth) + + plugin, err := ParsePluginFromYML(pth) + require.NoError(t, err) + + err = validatePlugin(plugin, pth) + require.Error(t, err) + require.Equal(t, true, strings.Contains(err.Error(), "no executable defined, nor bitrise-plugin.sh exist at:")) + } + + t.Log("simple plugin - with bitrise-plugin.sh") + { + pluginStr := `name: step +description: |- + Manage Bitrise CLI steps +trigger: +requirements: +- tool: bitrise + min_version: 1.3.0 + max_version: "" +` + + pth := filepath.Join(tmpDir, "bitrise-plugin.yml") + write(t, pluginStr, pth) + + write(t, "test", filepath.Join(tmpDir, "bitrise-plugin.sh")) + + plugin, err := ParsePluginFromYML(pth) + require.NoError(t, err) + + require.NoError(t, validatePlugin(plugin, pth)) + + require.Equal(t, "step", plugin.Name) + require.Equal(t, "Manage Bitrise CLI steps", plugin.Description) + require.Equal(t, 1, len(plugin.Requirements)) + + requirement := plugin.Requirements[0] + require.Equal(t, "bitrise", requirement.Tool) + require.Equal(t, "1.3.0", requirement.MinVersion) + require.Equal(t, "", requirement.MaxVersion) + } +} + +func TestSortByName(t *testing.T) { + t.Log("single plugin") + { + pluginA := Plugin{Name: "A"} + + plugins := []Plugin{pluginA} + + SortByName(plugins) + require.Equal(t, "A", plugins[0].Name) + } + + t.Log("simple sort") + { + pluginA := Plugin{Name: "A"} + pluginB := Plugin{Name: "B"} + pluginC := Plugin{Name: "C"} + + plugins := []Plugin{pluginC, pluginA, pluginB} + + SortByName(plugins) + require.Equal(t, "A", plugins[0].Name) + require.Equal(t, "B", plugins[1].Name) + require.Equal(t, "C", plugins[2].Name) + } +} + +func TestNewPluginRoutingFromBytes(t *testing.T) { + t.Log("simple routing") + { + routingStr := `route_map: + name: + name: name + source: source + version: "1.0.0" + commit_hash: hash + executable: "./test" +` + + routing, err := NewPluginRoutingFromBytes([]byte(routingStr)) + require.NoError(t, err) + + route, found := routing.RouteMap["name"] + require.Equal(t, true, found) + require.Equal(t, "name", route.Name) + require.Equal(t, "source", route.Source) + require.Equal(t, "1.0.0", route.Version) + require.Equal(t, "hash", route.CommitHash) + require.Equal(t, "./test", route.Executable) + } +} + +func TestValidateRouting(t *testing.T) { + t.Log("simple routing") + { + routing := PluginRouting{ + RouteMap: map[string]PluginRoute{ + "test": PluginRoute{ + Name: "test", + Source: "source", + Version: "1.0.0", + CommitHash: "hash", + Executable: "./executable", + }, + }, + } + + require.NoError(t, routing.Validate()) + } + + t.Log("invalid routing - missing required route's key") + { + routing := PluginRouting{ + RouteMap: map[string]PluginRoute{ + "": PluginRoute{ + Name: "test", + Source: "source", + Version: "1.0.0", + CommitHash: "hash", + Executable: "./executable", + }, + }, + } + + require.Error(t, routing.Validate()) + } + + t.Log("invalid routing - route's key, route's name missmatch") + { + routing := PluginRouting{ + RouteMap: map[string]PluginRoute{ + "test1": PluginRoute{ + Name: "test2", + Source: "source", + Version: "1.0.0", + CommitHash: "hash", + Executable: "./executable", + }, + }, + } + + require.Error(t, routing.Validate()) + } +} + +func TestAddRoute(t *testing.T) { + t.Log("simple add") + { + routing := PluginRouting{ + RouteMap: map[string]PluginRoute{ + "test1": PluginRoute{ + Name: "test1", + Source: "source1", + Version: "1.0.1", + CommitHash: "hash1", + Executable: "./executable1", + }, + }, + } + + route := PluginRoute{ + Name: "test2", + Source: "source2", + Version: "1.0.2", + CommitHash: "hash2", + Executable: "./executable2", + } + + routing.AddRoute(route) + + route, found := routing.RouteMap["test1"] + require.Equal(t, true, found) + require.Equal(t, "test1", route.Name) + require.Equal(t, "source1", route.Source) + require.Equal(t, "1.0.1", route.Version) + require.Equal(t, "hash1", route.CommitHash) + require.Equal(t, "./executable1", route.Executable) + + route, found = routing.RouteMap["test2"] + require.Equal(t, true, found) + require.Equal(t, "test2", route.Name) + require.Equal(t, "source2", route.Source) + require.Equal(t, "1.0.2", route.Version) + require.Equal(t, "hash2", route.CommitHash) + require.Equal(t, "./executable2", route.Executable) + } +} + +func DeleteRoute(t *testing.T) { + t.Log("simple delete") + { + routing := PluginRouting{ + RouteMap: map[string]PluginRoute{ + "test1": PluginRoute{ + Name: "test1", + Source: "source1", + Version: "1.0.1", + CommitHash: "hash1", + Executable: "./executable1", + }, + "test2": PluginRoute{ + Name: "test2", + Source: "source2", + Version: "1.0.2", + CommitHash: "hash2", + Executable: "./executable2", + }, + }, + } + + routing.DeleteRoute("test2") + + route, found := routing.RouteMap["test1"] + require.Equal(t, true, found) + require.Equal(t, "test1", route.Name) + require.Equal(t, "source1", route.Source) + require.Equal(t, "1.0.1", route.Version) + require.Equal(t, "hash1", route.CommitHash) + require.Equal(t, "./executable1", route.Executable) + + route, found = routing.RouteMap["test2"] + require.Equal(t, false, found) + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/models.go b/vendor/github.com/bitrise-io/bitrise/plugins/models.go new file mode 100644 index 00000000..b498c0a0 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/models.go @@ -0,0 +1,60 @@ +package plugins + +const ( + // TypeGeneric ... + TypeGeneric = "_" + // TypeInit ... + TypeInit = "init" + // TypeRun .... + TypeRun = "run" +) + +// PluginRoute ... +type PluginRoute struct { + Name string `yaml:"name"` + Source string `yaml:"source"` + Version string `yaml:"version"` + CommitHash string `yaml:"commit_hash"` + Executable string `yaml:"executable"` + TriggerEvent string `yaml:"trigger"` + LatestAvailableVersion string `yaml:"latest_available_version"` +} + +// PluginRouting ... +type PluginRouting struct { + RouteMap map[string]PluginRoute `yaml:"route_map"` +} + +// ExecutableModel ... +type ExecutableModel struct { + OSX string `yaml:"osx,omitempty"` + Linux string `yaml:"linux,omitempty"` +} + +// Requirement ... +type Requirement struct { + Tool string `yaml:"tool"` + MinVersion string `yaml:"min_version"` + MaxVersion string `yaml:"max_version"` +} + +// Plugin ... +type Plugin struct { + Name string `yaml:"name,omitempty"` + Description string `yaml:"description,omitempty"` + Executable ExecutableModel `yaml:"executable,omitempty"` + TriggerEvent string `yaml:"trigger,omitempty"` + Requirements []Requirement `yaml:"requirements,omitempty"` +} + +// PluginInfoModel ... +type PluginInfoModel struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + Source string `json:"source,omitempty"` + Plugin Plugin `json:"plugin,omitempty"` + DefinitionPth string `json:"definition_pth,omitempty"` +} + +// PluginInfos ... +type PluginInfos []PluginInfoModel diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/models_methods.go b/vendor/github.com/bitrise-io/bitrise/plugins/models_methods.go new file mode 100644 index 00000000..f6034c00 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/models_methods.go @@ -0,0 +1,344 @@ +package plugins + +import ( + "encoding/json" + "errors" + "fmt" + "path/filepath" + "sort" + + "gopkg.in/yaml.v2" + + "github.com/bitrise-io/bitrise/version" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + ver "github.com/hashicorp/go-version" +) + +//======================================= +// Plugin +//======================================= + +// String ... +func (info PluginInfoModel) String() string { + str := fmt.Sprintf("%s %s\n", colorstring.Blue("Name:"), info.Name) + str += fmt.Sprintf("%s %s\n", colorstring.Blue("Version:"), info.Version) + str += fmt.Sprintf("%s %s\n", colorstring.Blue("Source:"), info.Source) + str += fmt.Sprintf("%s\n", colorstring.Blue("Definition:")) + + definition, err := fileutil.ReadStringFromFile(info.DefinitionPth) + if err != nil { + str += colorstring.Redf("Failed to read plugin definition, error: %s", err) + return str + } + + str += definition + return str +} + +// JSON ... +func (info PluginInfoModel) JSON() string { + bytes, err := json.Marshal(info) + if err != nil { + return fmt.Sprintf(`"Failed to marshal plugin info (%#v), err: %s"`, info, err) + } + return string(bytes) + "\n" +} + +// String ... +func (infos PluginInfos) String() string { + str := "" + for _, info := range infos { + str += info.String() + str += "\n---\n\n" + } + return str +} + +// JSON ... +func (infos PluginInfos) JSON() string { + bytes, err := json.Marshal(infos) + if err != nil { + return fmt.Sprintf(`"Failed to marshal plugin infos (%#v), err: %s"`, infos, err) + } + return string(bytes) + "\n" +} + +func validateRequirements(requirements []Requirement, currentVersionMap map[string]ver.Version) error { + var err error + + for _, requirement := range requirements { + currentVersion := currentVersionMap[requirement.Tool] + + var minVersionPtr *ver.Version + if requirement.MinVersion == "" { + return fmt.Errorf("plugin requirement min version is required") + } + + minVersionPtr, err = ver.NewVersion(requirement.MinVersion) + if err != nil { + return fmt.Errorf("failed to parse plugin required min version (%s) for tool (%s), error: %s", requirement.MinVersion, requirement.Tool, err) + } + + var maxVersionPtr *ver.Version + if requirement.MaxVersion != "" { + maxVersionPtr, err = ver.NewVersion(requirement.MaxVersion) + if err != nil { + return fmt.Errorf("failed to parse plugin requirement version (%s) for tool (%s), error: %s", requirement.MaxVersion, requirement.Tool, err) + } + } + + if err := validateVersion(currentVersion, *minVersionPtr, maxVersionPtr); err != nil { + return fmt.Errorf("checking plugin tool (%s) requirements failed, error: %s", requirement.Tool, err) + } + } + + return nil +} + +func parsePluginFromBytes(bytes []byte) (plugin Plugin, err error) { + if err = yaml.Unmarshal(bytes, &plugin); err != nil { + return Plugin{}, err + } + return plugin, nil +} + +func validatePlugin(plugin Plugin, pluginDefinitionPth string) error { + // Validate plugin + if plugin.Name == "" { + return errors.New("missing name") + } + + osxRemoteExecutable := false + if plugin.Executable.OSX != "" { + osxRemoteExecutable = true + } + + linuxRemoteExecutable := false + if plugin.Executable.Linux != "" { + linuxRemoteExecutable = true + } + + if linuxRemoteExecutable != osxRemoteExecutable { + return errors.New("both osx and linux executable should be defined, or non of them") + } + + if !linuxRemoteExecutable && !osxRemoteExecutable { + pluginDir := filepath.Dir(pluginDefinitionPth) + pluginScriptPth := filepath.Join(pluginDir, pluginScriptFileName) + if exist, err := pathutil.IsPathExists(pluginScriptPth); err != nil { + return err + } else if !exist { + return fmt.Errorf("no executable defined, nor bitrise-plugin.sh exist at: %s", pluginScriptPth) + } + } + // --- + + // Ensure dependencies + currentVersionMap, err := version.ToolVersionMap() + if err != nil { + return fmt.Errorf("failed to get current version map, error: %s", err) + } + + if err := validateRequirements(plugin.Requirements, currentVersionMap); err != nil { + return fmt.Errorf("requirements validation failed, error: %s", err) + } + // --- + + return nil +} + +// ParsePluginFromYML ... +func ParsePluginFromYML(ymlPth string) (Plugin, error) { + // Parse plugin + if isExists, err := pathutil.IsPathExists(ymlPth); err != nil { + return Plugin{}, err + } else if !isExists { + return Plugin{}, fmt.Errorf("plugin definition does not exist at: %s", ymlPth) + } + + bytes, err := fileutil.ReadBytesFromFile(ymlPth) + if err != nil { + return Plugin{}, err + } + + plugin, err := parsePluginFromBytes(bytes) + if err != nil { + return Plugin{}, err + } + + return plugin, nil +} + +func (plugin Plugin) String() string { + pluginStr := colorstring.Green(plugin.Name) + pluginStr += fmt.Sprintf("\n Description: %s", plugin.Description) + return pluginStr +} + +func systemOsName() (string, error) { + osOut, err := command.RunCommandAndReturnCombinedStdoutAndStderr("uname", "-s") + if err != nil { + return "", err + } + return strip(osOut), nil +} + +// ExecutableURL ... +func (plugin Plugin) ExecutableURL() string { + systemOS, err := systemOsName() + if err != nil { + return "" + } + + switch systemOS { + case "Darwin": + return plugin.Executable.OSX + case "Linux": + return plugin.Executable.Linux + default: + return "" + } +} + +//======================================= +// Sorting + +// SortByName ... +func SortByName(plugins []Plugin) { + byName := func(p1, p2 *Plugin) bool { + return p1.Name < p2.Name + } + + sortBy(byName).sort(plugins) +} + +type sortBy func(p1, p2 *Plugin) bool + +func (by sortBy) sort(plugins []Plugin) { + ps := &pluginSorter{ + plugins: plugins, + sortBy: by, + } + sort.Sort(ps) +} + +type pluginSorter struct { + plugins []Plugin + sortBy sortBy +} + +//======================================= +// sort.Interface + +func (s *pluginSorter) Len() int { + return len(s.plugins) +} + +func (s *pluginSorter) Swap(i, j int) { + s.plugins[i], s.plugins[j] = s.plugins[j], s.plugins[i] +} + +func (s *pluginSorter) Less(i, j int) bool { + return s.sortBy(&s.plugins[i], &s.plugins[j]) +} + +//======================================= +// PluginRoute +//======================================= + +// NewPluginRoute ... +func NewPluginRoute(name, source, executable, version, triggerEvent string) (PluginRoute, error) { + route := PluginRoute{ + Name: name, + Source: source, + Executable: executable, + Version: version, + TriggerEvent: triggerEvent, + } + if err := route.Validate(); err != nil { + return PluginRoute{}, err + } + return route, nil +} + +// Validate ... +func (route PluginRoute) Validate() error { + if route.Name == "" { + return fmt.Errorf("invalid route: missing required name") + } + if route.Source == "" { + return fmt.Errorf("invalid route: missing required source") + } + if route.Version != "" { + if _, err := ver.NewVersion(route.Version); err != nil { + return fmt.Errorf("invalid route: invalid version (%s)", route.Version) + } + } + return nil +} + +//======================================= +// PluginRouting +//======================================= + +// NewPluginRouting ... +func NewPluginRouting() PluginRouting { + return PluginRouting{RouteMap: map[string]PluginRoute{}} +} + +// NewPluginRoutingFromBytes ... +func NewPluginRoutingFromBytes(bytes []byte) (PluginRouting, error) { + var routing PluginRouting + if err := yaml.Unmarshal(bytes, &routing); err != nil { + return PluginRouting{}, err + } + if err := routing.Validate(); err != nil { + return PluginRouting{}, err + } + return routing, nil +} + +// NewPluginRoutingFromYMLOrEmpty ... +func NewPluginRoutingFromYMLOrEmpty(ymlPth string) (PluginRouting, error) { + if exist, err := pathutil.IsPathExists(ymlPth); err != nil { + return PluginRouting{}, err + } else if exist { + bytes, err := fileutil.ReadBytesFromFile(ymlPth) + if err != nil { + return PluginRouting{}, err + } + + return NewPluginRoutingFromBytes(bytes) + } + + return NewPluginRouting(), nil +} + +// Validate ... +func (routing PluginRouting) Validate() error { + for name, route := range routing.RouteMap { + if name == "" { + return fmt.Errorf("invalid routing: missing required route's key") + } + if name != route.Name { + return fmt.Errorf("invalid routing: route's key (%s) should equal to route's name (%s)", name, route.Name) + } + if err := route.Validate(); err != nil { + return fmt.Errorf("invalid routing: invalid plugin: %s", err) + } + } + return nil +} + +// AddRoute ... +func (routing *PluginRouting) AddRoute(route PluginRoute) { + routing.RouteMap[route.Name] = route +} + +// DeleteRoute ... +func (routing *PluginRouting) DeleteRoute(routeName string) { + delete(routing.RouteMap, routeName) +} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/paths.go b/vendor/github.com/bitrise-io/bitrise/plugins/paths.go new file mode 100644 index 00000000..73345be8 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/paths.go @@ -0,0 +1,186 @@ +package plugins + +import ( + "fmt" + "path/filepath" + + "gopkg.in/yaml.v2" + + "github.com/bitrise-io/bitrise/configs" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + ver "github.com/hashicorp/go-version" +) + +const ( + pluginsDirName = "plugins" + pluginSpecFileName = "spec.yml" + + pluginScriptFileName = "bitrise-plugin.sh" + pluginDefinitionFileName = "bitrise-plugin.yml" +) + +var ( + pluginsDir = "" + pluginsRoutingPth = "" +) + +// ----------------------- +// --- Routing +// ----------------------- + +// CreateAndAddPluginRoute ... +func CreateAndAddPluginRoute(plugin Plugin, source, version string) error { + newRoute, err := NewPluginRoute(plugin.Name, source, plugin.ExecutableURL(), version, plugin.TriggerEvent) + if err != nil { + return err + } + + return AddPluginRoute(newRoute) +} + +// AddPluginRoute ... +func AddPluginRoute(route PluginRoute) error { + routing, err := readPluginRouting() + if err != nil { + return err + } + + routing.AddRoute(route) + + return writeRoutingToFile(routing) +} + +// DeletePluginRoute ... +func DeletePluginRoute(name string) error { + routing, err := readPluginRouting() + if err != nil { + return err + } + + routing.DeleteRoute(name) + + return writeRoutingToFile(routing) +} + +// GetPluginVersion ... +func GetPluginVersion(name string) (*ver.Version, error) { + route, found, err := ReadPluginRoute(name) + if err != nil { + return nil, err + } + + if !found { + return nil, fmt.Errorf("plugin not installed with name (%s)", name) + } + + if route.Version == "" { + return nil, nil + } + + pluginVersion, err := ver.NewVersion(route.Version) + if err != nil { + return nil, err + } + if pluginVersion == nil { + return nil, fmt.Errorf("failed to parse version (%s)", route.Version) + } + + return pluginVersion, nil +} + +// ReadPluginRoute ... +func ReadPluginRoute(name string) (PluginRoute, bool, error) { + routing, err := readPluginRouting() + if err != nil { + return PluginRoute{}, false, err + } + + route, found := routing.RouteMap[name] + return route, found, nil +} + +func writeRoutingToFile(routing PluginRouting) error { + bytes, err := yaml.Marshal(routing) + if err != nil { + return err + } + + return fileutil.WriteBytesToFile(pluginsRoutingPth, bytes) +} + +func readPluginRouting() (PluginRouting, error) { + return NewPluginRoutingFromYMLOrEmpty(pluginsRoutingPth) +} + +// ----------------------- +// --- Paths +// ----------------------- + +// GetPluginDir ... +func GetPluginDir(name string) string { + return filepath.Join(pluginsDir, name) +} + +// GetPluginSrcDir ... +func GetPluginSrcDir(name string) string { + return filepath.Join(GetPluginDir(name), "src") +} + +// GetPluginBinDir ... +func GetPluginBinDir(name string) string { + return filepath.Join(GetPluginDir(name), "bin") +} + +// GetPluginDataDir ... +func GetPluginDataDir(name string) string { + return filepath.Join(GetPluginDir(name), "data") +} + +// GetPluginDefinitionPath ... +func GetPluginDefinitionPath(name string) string { + return filepath.Join(GetPluginSrcDir(name), pluginDefinitionFileName) +} + +// GetPluginExecutablePath ... +func GetPluginExecutablePath(name string) (string, bool, error) { + route, found, err := ReadPluginRoute(name) + if err != nil { + return "", false, err + } + if !found { + return "", false, fmt.Errorf("plugin not installed with name (%s)", name) + } + + if route.Executable != "" { + return filepath.Join(GetPluginBinDir(name), name), true, nil + } + return filepath.Join(GetPluginSrcDir(name), pluginScriptFileName), false, nil +} + +// ----------------------- +// --- Init +// ----------------------- + +// InitPaths ... +func InitPaths() error { + // Plugins dir + if err := configs.EnsureBitriseConfigDirExists(); err != nil { + log.Errorf("Failed to ensure bitrise configs dir, err: %s", err) + } + + bitriseDir := configs.GetBitriseHomeDirPath() + tmpPluginsDir := filepath.Join(bitriseDir, pluginsDirName) + + if err := pathutil.EnsureDirExist(tmpPluginsDir); err != nil { + return err + } + + pluginsDir = tmpPluginsDir + + // Plugins routing + pluginsRoutingPth = filepath.Join(pluginsDir, pluginSpecFileName) + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/plugins.go b/vendor/github.com/bitrise-io/bitrise/plugins/plugins.go new file mode 100644 index 00000000..67b73efe --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/plugins.go @@ -0,0 +1,162 @@ +package plugins + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/bitrise-io/go-utils/pathutil" +) + +const ( + // PluginInputPayloadKey ... + PluginInputPayloadKey = "BITRISE_PLUGIN_INPUT_PAYLOAD" + // PluginInputBitriseVersionKey ... + PluginInputBitriseVersionKey = "BITRISE_PLUGIN_INPUT_BITRISE_VERSION" + // PluginInputTriggerEventKey ... + PluginInputTriggerEventKey = "BITRISE_PLUGIN_INPUT_TRIGGER" + // PluginInputPluginModeKey ... + PluginInputPluginModeKey = "BITRISE_PLUGIN_INPUT_PLUGIN_MODE" + // PluginInputDataDirKey ... + PluginInputDataDirKey = "BITRISE_PLUGIN_INPUT_DATA_DIR" + // PluginInputFormatVersionKey ... + PluginInputFormatVersionKey = "BITRISE_PLUGIN_INPUT_FORMAT_VERSION" + + // PluginOutputEnvKey ... + PluginOutputEnvKey = "BITRISE_PLUGIN_OUTPUT" +) + +const bitrisePluginPrefix = ":" + +const ( + // TriggerMode ... + TriggerMode PluginMode = "trigger" + // CommandMode ... + CommandMode PluginMode = "command" +) + +// PluginMode ... +type PluginMode string + +// PluginInput ... +type PluginInput map[string]string + +// ParseArgs ... +func ParseArgs(args []string) (string, []string, bool) { + + if len(args) == 0 { + return "", []string{}, false + } + + pluginName := "" + pluginArgs := []string{} + for idx, arg := range args { + + if strings.Contains(arg, bitrisePluginPrefix) { + pluginSplits := strings.Split(arg, ":") + + if len(pluginSplits) != 2 { + return "", []string{}, false + } + + pluginName = pluginSplits[1] + if len(args) > idx { + pluginArgs = args[idx+1 : len(args)] + } + return pluginName, pluginArgs, true + } + } + + return "", []string{}, false +} + +// CheckForNewVersion ... +func CheckForNewVersion(plugin Plugin) (string, error) { + route, found, err := ReadPluginRoute(plugin.Name) + if err != nil { + return "", err + } + if !found { + return "", fmt.Errorf("no route found for already loaded plugin (%s)", plugin.Name) + } + if route.Version == "" { + // local plugin, can not update + return "", nil + } + + pluginSrcDir := GetPluginSrcDir(plugin.Name) + + gitDirPath := filepath.Join(pluginSrcDir, ".git") + if exist, err := pathutil.IsPathExists(gitDirPath); err != nil { + return "", fmt.Errorf("failed to check if .git folder exist at (%s), error: %s", gitDirPath, err) + } else if !exist { + return "", fmt.Errorf(".git folder not exist at (%s), error: %s", gitDirPath, err) + } + + versions, err := GitVersionTags(pluginSrcDir) + if err != nil { + return "", err + } + + if len(versions) == 0 { + return "", nil + } + + latestVersion := versions[len(versions)-1] + + currentVersion, err := GetPluginVersion(plugin.Name) + if err != nil { + return "", fmt.Errorf("failed to check installed plugin (%s) version, error: %s", plugin.Name, err) + } + + if currentVersion == nil { + return "", nil + } + + if latestVersion.GreaterThan(currentVersion) { + return latestVersion.String(), nil + } + + return "", nil +} + +// LoadPlugin ... +func LoadPlugin(name string) (Plugin, bool, error) { + pluginDir := GetPluginDir(name) + + if exists, err := pathutil.IsDirExists(pluginDir); err != nil { + return Plugin{}, false, fmt.Errorf("Failed to check dir (%s), err: %s", pluginDir, err) + } else if !exists { + return Plugin{}, false, nil + } + + pluginYMLPath := GetPluginDefinitionPath(name) + plugin, err := ParsePluginFromYML(pluginYMLPath) + if err != nil { + return Plugin{}, true, err + } + + return plugin, true, nil +} + +// InstalledPluginList ... +func InstalledPluginList() ([]Plugin, error) { + routing, err := readPluginRouting() + if err != nil { + return []Plugin{}, err + } + + pluginList := []Plugin{} + + for name := range routing.RouteMap { + if plugin, found, err := LoadPlugin(name); err != nil { + return []Plugin{}, err + } else if !found { + return []Plugin{}, fmt.Errorf("Plugin (%s) found in route, but could not load it", name) + } else { + pluginList = append(pluginList, plugin) + } + } + + return pluginList, nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/plugins_test.go b/vendor/github.com/bitrise-io/bitrise/plugins/plugins_test.go new file mode 100644 index 00000000..7bf913aa --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/plugins_test.go @@ -0,0 +1,54 @@ +package plugins + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseArgs(t *testing.T) { + t.Log("simple plugin command") + { + args := []string{"bitrise", ":example"} + pluginName, pluginArgs, isPlugin := ParseArgs(args) + require.Equal(t, true, isPlugin) + require.Equal(t, "example", pluginName) + require.Equal(t, 0, len(pluginArgs)) + } + + t.Log("simple plugin command - with bitrise flags") + { + args := []string{"bitrise", "-l", "debug", ":example"} + pluginName, pluginArgs, isPlugin := ParseArgs(args) + require.Equal(t, true, isPlugin) + require.Equal(t, "example", pluginName) + require.Equal(t, 0, len(pluginArgs)) + } + + t.Log("plugin command - with args") + { + args := []string{"bitrise", ":example", "hello", "bitrise"} + pluginName, pluginArgs, isPlugin := ParseArgs(args) + require.Equal(t, true, isPlugin) + require.Equal(t, "example", pluginName) + require.EqualValues(t, []string{"hello", "bitrise"}, pluginArgs) + } + + t.Log("plugin command - with falg") + { + args := []string{"bitrise", ":example", "hello", "--name", "bitrise"} + pluginName, pluginArgs, isPlugin := ParseArgs(args) + require.Equal(t, true, isPlugin) + require.Equal(t, "example", pluginName) + require.EqualValues(t, []string{"hello", "--name", "bitrise"}, pluginArgs) + } + + t.Log("not plugin command") + { + args := []string{"bitrise", "hello", "bitrise"} + pluginName, pluginArgs, isPlugin := ParseArgs(args) + require.Equal(t, false, isPlugin) + require.Equal(t, "", pluginName) + require.Equal(t, 0, len(pluginArgs)) + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/run.go b/vendor/github.com/bitrise-io/bitrise/plugins/run.go new file mode 100644 index 00000000..530d3f00 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/run.go @@ -0,0 +1,188 @@ +package plugins + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/bitrise-io/bitrise/configs" + "github.com/bitrise-io/bitrise/models" + "github.com/bitrise-io/bitrise/tools" + "github.com/bitrise-io/bitrise/version" + "github.com/bitrise-io/go-utils/log" + flog "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" +) + +//======================================= +// Util +//======================================= + +func strip(str string) string { + dirty := true + strippedStr := str + for dirty { + hasWhiteSpacePrefix := false + if strings.HasPrefix(strippedStr, " ") { + hasWhiteSpacePrefix = true + strippedStr = strings.TrimPrefix(strippedStr, " ") + } + + hasWhiteSpaceSuffix := false + if strings.HasSuffix(strippedStr, " ") { + hasWhiteSpaceSuffix = true + strippedStr = strings.TrimSuffix(strippedStr, " ") + } + + hasNewlinePrefix := false + if strings.HasPrefix(strippedStr, "\n") { + hasNewlinePrefix = true + strippedStr = strings.TrimPrefix(strippedStr, "\n") + } + + hasNewlineSuffix := false + if strings.HasSuffix(strippedStr, "\n") { + hasNewlinePrefix = true + strippedStr = strings.TrimSuffix(strippedStr, "\n") + } + + if !hasWhiteSpacePrefix && !hasWhiteSpaceSuffix && !hasNewlinePrefix && !hasNewlineSuffix { + dirty = false + } + } + return strippedStr +} + +//======================================= +// Main +//======================================= + +// RunPluginByEvent ... +func RunPluginByEvent(plugin Plugin, pluginInput PluginInput) error { + pluginInput[PluginInputPluginModeKey] = string(TriggerMode) + + return runPlugin(plugin, []string{}, pluginInput) +} + +// RunPluginByCommand ... +func RunPluginByCommand(plugin Plugin, args []string) error { + pluginInput := PluginInput{ + PluginInputPluginModeKey: string(CommandMode), + } + + return runPlugin(plugin, args, pluginInput) +} + +func printPluginUpdateInfos(newVersion string, plugin Plugin) { + flog.Warnf("") + flog.Warnf("New version (%s) of plugin (%s) available", newVersion, plugin.Name) + flog.Printf("Run command to update plugin:") + fmt.Println() + flog.Donef("$ bitrise plugin update %s", plugin.Name) +} + +func runPlugin(plugin Plugin, args []string, pluginInput PluginInput) error { + if !configs.IsCIMode && configs.CheckIsPluginUpdateCheckRequired() { + // Check for new version + log.Infof("Checking for plugin (%s) new version...", plugin.Name) + + if newVersion, err := CheckForNewVersion(plugin); err != nil { + log.Warnf("") + log.Warnf("Failed to check for plugin (%s) new version, error: %s", plugin.Name, err) + } else if newVersion != "" { + printPluginUpdateInfos(newVersion, plugin) + + route, found, err := ReadPluginRoute(plugin.Name) + if err != nil { + return err + } + if !found { + return fmt.Errorf("no route found for already loaded plugin (%s)", plugin.Name) + } + + route.LatestAvailableVersion = newVersion + + if err := AddPluginRoute(route); err != nil { + return fmt.Errorf("failed to register available plugin (%s) update (%s), error: %s", plugin.Name, newVersion, err) + } + } else { + } + + if err := configs.SavePluginUpdateCheck(); err != nil { + return err + } + + fmt.Println() + } else { + route, found, err := ReadPluginRoute(plugin.Name) + if err != nil { + return err + } + if !found { + return fmt.Errorf("no route found for already loaded plugin (%s)", plugin.Name) + } + + if route.LatestAvailableVersion != "" { + printPluginUpdateInfos(route.LatestAvailableVersion, plugin) + fmt.Println() + } + } + + // Append common data to plugin iputs + bitriseVersion, err := version.BitriseCliVersion() + if err != nil { + return err + } + pluginInput[PluginInputBitriseVersionKey] = bitriseVersion.String() + pluginInput[PluginInputDataDirKey] = GetPluginDataDir(plugin.Name) + pluginInput[PluginInputFormatVersionKey] = models.Version + + // Prepare plugin envstore + pluginWorkDir, err := pathutil.NormalizedOSTempDirPath("plugin-work-dir") + if err != nil { + return err + } + defer func() { + if err := os.RemoveAll(pluginWorkDir); err != nil { + log.Warnf("Failed to remove path (%s)", pluginWorkDir) + } + }() + + pluginEnvstorePath := filepath.Join(pluginWorkDir, "envstore.yml") + + if err := tools.EnvmanInitAtPath(pluginEnvstorePath); err != nil { + return err + } + + if err := tools.EnvmanAdd(pluginEnvstorePath, configs.EnvstorePathEnvKey, pluginEnvstorePath, false, false); err != nil { + return err + } + + // Add plugin inputs + for key, value := range pluginInput { + if err := tools.EnvmanAdd(pluginEnvstorePath, key, value, false, false); err != nil { + return err + } + } + + // Run plugin executable + pluginExecutable, isBin, err := GetPluginExecutablePath(plugin.Name) + if err != nil { + return err + } + + cmd := []string{} + + if isBin { + cmd = append([]string{pluginExecutable}, args...) + } else { + cmd = append([]string{"bash", pluginExecutable}, args...) + } + + if _, err := tools.EnvmanRun(pluginEnvstorePath, "", cmd); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/run_test.go b/vendor/github.com/bitrise-io/bitrise/plugins/run_test.go new file mode 100644 index 00000000..77387d63 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/plugins/run_test.go @@ -0,0 +1,24 @@ +package plugins + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestStrip(t *testing.T) { + str := "test case" + require.Equal(t, "test case", strip(str)) + + str = " test case" + require.Equal(t, "test case", strip(str)) + + str = "test case " + require.Equal(t, "test case", strip(str)) + + str = " test case " + require.Equal(t, "test case", strip(str)) + + str = "" + require.Equal(t, "", strip(str)) +} diff --git a/vendor/github.com/bitrise-io/bitrise/release_config.yml b/vendor/github.com/bitrise-io/bitrise/release_config.yml new file mode 100644 index 00000000..14737840 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/release_config.yml @@ -0,0 +1,41 @@ +release: + development_branch: master + release_branch: master +changelog: + path: CHANGELOG.md + content_template: |- + {{range .ContentItems}}## {{.EndTaggedCommit.Tag}} ({{.EndTaggedCommit.Date.Format "2006 Jan 02"}}) + + ### Release Notes + + * __BREAKING__ : change 1 + * change 2 + + ### Install or upgrade + + To install this version, run the following commands (in a bash shell): + + ``` + curl -fL https://github.com/bitrise-io/bitrise/releases/download/{{.EndTaggedCommit.Tag}}/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise + ``` + + Then: + + ``` + chmod +x /usr/local/bin/bitrise + ``` + + That's all, you're ready to go! + + Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run + is installed and available, but if you forget to do this it'll be performed the first + time you call bitrise run. + + ### Release Commits - {{.StartTaggedCommit.Tag}} -> {{.EndTaggedCommit.Tag}} + + {{range .Commits}}* [{{firstChars .Hash 7}}] {{.Author}} - {{.Message}} ({{.Date.Format "2006 Jan 02"}}) + {{end}} + + {{end}} + header_template: '## Changelog (Current version: {{.Version}})' + footer_template: 'Updated: {{.CurrentDate.Format "2006 Jan 02"}}' diff --git a/vendor/github.com/bitrise-io/bitrise/toolkits/bash.go b/vendor/github.com/bitrise-io/bitrise/toolkits/bash.go new file mode 100644 index 00000000..38d9ad46 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/toolkits/bash.go @@ -0,0 +1,77 @@ +package toolkits + +import ( + "fmt" + "path/filepath" + + "github.com/bitrise-io/bitrise/models" + "github.com/bitrise-io/bitrise/utils" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/stringutil" + stepmanModels "github.com/bitrise-io/stepman/models" +) + +// BashToolkit ... +type BashToolkit struct { +} + +// Check ... +func (toolkit BashToolkit) Check() (bool, ToolkitCheckResult, error) { + binPath, err := utils.CheckProgramInstalledPath("bash") + if err != nil { + return false, ToolkitCheckResult{}, fmt.Errorf("Failed to get bash binary path, error: %s", err) + } + + verOut, err := command.RunCommandAndReturnStdout("bash", "--version") + if err != nil { + return false, ToolkitCheckResult{}, fmt.Errorf("Failed to check bash version, error: %s", err) + } + + verStr := stringutil.ReadFirstLine(verOut, true) + + return false, ToolkitCheckResult{ + Path: binPath, + Version: verStr, + }, nil +} + +// IsToolAvailableInPATH ... +func (toolkit BashToolkit) IsToolAvailableInPATH() bool { + binPath, err := utils.CheckProgramInstalledPath("bash") + if err != nil { + return false + } + return len(binPath) > 0 +} + +// Bootstrap ... +func (toolkit BashToolkit) Bootstrap() error { + return nil +} + +// Install ... +func (toolkit BashToolkit) Install() error { + return nil +} + +// ToolkitName ... +func (toolkit BashToolkit) ToolkitName() string { + return "bash" +} + +// PrepareForStepRun ... +func (toolkit BashToolkit) PrepareForStepRun(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) error { + return nil +} + +// StepRunCommandArguments ... +func (toolkit BashToolkit) StepRunCommandArguments(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) ([]string, error) { + entryFile := "step.sh" + if step.Toolkit != nil && step.Toolkit.Bash != nil && step.Toolkit.Bash.EntryFile != "" { + entryFile = step.Toolkit.Bash.EntryFile + } + + stepFilePath := filepath.Join(stepAbsDirPath, entryFile) + cmd := []string{"bash", stepFilePath} + return cmd, nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/toolkits/golang.go b/vendor/github.com/bitrise-io/bitrise/toolkits/golang.go new file mode 100644 index 00000000..d1512717 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/toolkits/golang.go @@ -0,0 +1,387 @@ +package toolkits + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "regexp" + "runtime" + "strings" + "time" + + "github.com/bitrise-io/bitrise/configs" + "github.com/bitrise-io/bitrise/models" + "github.com/bitrise-io/bitrise/tools" + "github.com/bitrise-io/bitrise/utils" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/progress" + "github.com/bitrise-io/go-utils/retry" + "github.com/bitrise-io/go-utils/versions" + stepmanModels "github.com/bitrise-io/stepman/models" + "github.com/bitrise-tools/gows/gows" +) + +const ( + minGoVersionForToolkit = "1.8.3" +) + +// === Base Toolkit struct === + +// GoToolkit ... +type GoToolkit struct { +} + +// ToolkitName ... +func (toolkit GoToolkit) ToolkitName() string { + return "go" +} + +// === Toolkit: Check === + +// GoConfigurationModel ... +type GoConfigurationModel struct { + // full path of the go binary to use + GoBinaryPath string + // GOROOT env var value to set (unless empty) + GOROOT string +} + +func checkGoConfiguration(goConfig GoConfigurationModel) (bool, ToolkitCheckResult, error) { + cmdEnvs := os.Environ() + if len(goConfig.GOROOT) > 0 { + cmdEnvs = append(cmdEnvs, "GOROOT="+goConfig.GOROOT) + } + verOut, err := command.New(goConfig.GoBinaryPath, "version").SetEnvs(cmdEnvs...).RunAndReturnTrimmedOutput() + if err != nil { + return false, ToolkitCheckResult{}, fmt.Errorf("Failed to check go version, error: %s", err) + } + + verStr, err := parseGoVersionFromGoVersionOutput(verOut) + if err != nil { + return false, ToolkitCheckResult{}, fmt.Errorf("Failed to parse go version, error: %s", err) + } + + checkRes := ToolkitCheckResult{ + Path: goConfig.GoBinaryPath, + Version: verStr, + } + + // version check + isVersionOk, err := versions.IsVersionGreaterOrEqual(verStr, minGoVersionForToolkit) + if err != nil { + return false, checkRes, fmt.Errorf("Failed to validate installed go version, error: %s", err) + } + if !isVersionOk { + return true, checkRes, nil + } + + return false, checkRes, nil +} + +func selectGoConfiguration() (bool, ToolkitCheckResult, GoConfigurationModel, error) { + potentialGoConfigurations := []GoConfigurationModel{} + // from PATH + { + binPath, err := utils.CheckProgramInstalledPath("go") + if err == nil { + potentialGoConfigurations = append(potentialGoConfigurations, GoConfigurationModel{GoBinaryPath: binPath}) + } + } + // from Bitrise Toolkits + { + binPath := goBinaryInToolkitFullPath() + if isExist, err := pathutil.IsPathExists(binPath); err != nil { + log.Warnf("Failed to check the status of the 'go' binary inside the Bitrise Toolkit dir, error: %s", err) + } else if isExist { + potentialGoConfigurations = append(potentialGoConfigurations, GoConfigurationModel{ + GoBinaryPath: binPath, + GOROOT: goToolkitInstallRootPath(), + }) + } + } + + isRequireInstall := true + checkResult := ToolkitCheckResult{} + goConfig := GoConfigurationModel{} + var checkError error + for _, aPotentialGoInfoToUse := range potentialGoConfigurations { + isInstReq, chkRes, err := checkGoConfiguration(aPotentialGoInfoToUse) + checkResult = chkRes + checkError = err + if !isInstReq { + // select this one + goConfig = aPotentialGoInfoToUse + isRequireInstall = false + break + } + } + + if len(potentialGoConfigurations) > 0 && isRequireInstall { + log.Warnf("Installed go found (path: %s), but not a supported version: %s", checkResult.Path, checkResult.Version) + } + + return isRequireInstall, checkResult, goConfig, checkError +} + +// Check ... +func (toolkit GoToolkit) Check() (bool, ToolkitCheckResult, error) { + isInstallRequired, checkResult, _, err := selectGoConfiguration() + return isInstallRequired, checkResult, err +} + +func parseGoVersionFromGoVersionOutput(goVersionCallOutput string) (string, error) { + origGoVersionCallOutput := goVersionCallOutput + goVersionCallOutput = strings.TrimSpace(goVersionCallOutput) + if goVersionCallOutput == "" { + return "", errors.New("Failed to parse Go version, error: version call output was empty") + } + + // example goVersionCallOutput: go version go1.7 darwin/amd64 + goVerExp := regexp.MustCompile(`go version go(?P[0-9.]+) (?P[a-zA-Z0-9]+/[a-zA-Z0-9]+)`) + expRes := goVerExp.FindStringSubmatch(goVersionCallOutput) + if expRes == nil { + return "", fmt.Errorf("Failed to parse Go version, error: failed to find version in input: %s", origGoVersionCallOutput) + } + verStr := expRes[1] + + return verStr, nil +} + +// IsToolAvailableInPATH ... +func (toolkit GoToolkit) IsToolAvailableInPATH() bool { + if configs.IsDebugUseSystemTools() { + log.Warnf("[BitriseDebug] Using system tools (system installed Go), instead of the ones in BITRISE_HOME") + return true + } + + if _, err := utils.CheckProgramInstalledPath("go"); err != nil { + return false + } + + if _, err := command.RunCommandAndReturnStdout("go", "version"); err != nil { + return false + } + + return true +} + +// === Toolkit: Bootstrap === + +// Bootstrap ... +func (toolkit GoToolkit) Bootstrap() error { + if toolkit.IsToolAvailableInPATH() { + return nil + } + + pthWithGoBins := configs.GeneratePATHEnvString(os.Getenv("PATH"), goToolkitBinsPath()) + if err := os.Setenv("PATH", pthWithGoBins); err != nil { + return fmt.Errorf("Failed to set PATH to include the Go toolkit bins, error: %s", err) + } + + if err := os.Setenv("GOROOT", goToolkitInstallRootPath()); err != nil { + return fmt.Errorf("Failed to set GOROOT to Go toolkit root, error: %s", err) + } + + return nil +} + +// === Toolkit: Install === + +func installGoTar(goTarGzPath string) error { + installToPath := goToolkitInstallToPath() + + if err := os.RemoveAll(installToPath); err != nil { + return fmt.Errorf("Failed to remove previous Go toolkit install (path: %s), error: %s", installToPath, err) + } + if err := pathutil.EnsureDirExist(installToPath); err != nil { + return fmt.Errorf("Failed create Go toolkit directory (path: %s), error: %s", installToPath, err) + } + + cmd := command.New("tar", "-C", installToPath, "-xzf", goTarGzPath) + if combinedOut, err := cmd.RunAndReturnTrimmedCombinedOutput(); err != nil { + log.Errorf(" [!] Failed to uncompress Go toolkit, output:") + log.Errorf(combinedOut) + return fmt.Errorf("Failed to uncompress Go toolkit, error: %s", err) + } + return nil +} + +// Install ... +func (toolkit GoToolkit) Install() error { + versionStr := minGoVersionForToolkit + osStr := runtime.GOOS + archStr := runtime.GOARCH + extentionStr := "tar.gz" + if osStr == "windows" { + extentionStr = "zip" + } + downloadURL := fmt.Sprintf("https://storage.googleapis.com/golang/go%s.%s-%s.%s", versionStr, osStr, archStr, extentionStr) + + goTmpDirPath := goToolkitTmpDirPath() + if err := pathutil.EnsureDirExist(goTmpDirPath); err != nil { + return fmt.Errorf("Failed to create Toolkits TMP directory, error: %s", err) + } + + localFileName := "go." + extentionStr + goArchiveDownloadPath := filepath.Join(goTmpDirPath, localFileName) + + var downloadErr error + progress.NewDefaultWrapper("Downloading").WrapAction(func() { + downloadErr = retry.Times(2).Wait(5 * time.Second).Try(func(attempt uint) error { + if attempt > 0 { + log.Warnf("==> Download failed, retrying ...") + } + return tools.DownloadFile(downloadURL, goArchiveDownloadPath) + }) + }) + if downloadErr != nil { + return fmt.Errorf("Failed to download toolkit (%s), error: %s", downloadURL, downloadErr) + } + + fmt.Println("=> Installing ...") + if err := installGoTar(goArchiveDownloadPath); err != nil { + return fmt.Errorf("Failed to install Go toolkit, error: %s", err) + } + if err := os.Remove(goArchiveDownloadPath); err != nil { + return fmt.Errorf("Failed to remove the downloaded Go archive (path: %s), error: %s", goArchiveDownloadPath, err) + } + fmt.Println("=> Installing [DONE]") + + return nil +} + +// === Toolkit: Prepare for Step Run === + +func goBuildInIsolation(packageName, srcPath, outputBinPath string) error { + workspaceRootPath, err := pathutil.NormalizedOSTempDirPath("bitrise-go-toolkit") + if err != nil { + return fmt.Errorf("Failed to create root directory of isolated workspace, error: %s", err) + } + + // origGOPATH := os.Getenv("GOPATH") + // if origGOPATH == "" { + // return fmt.Errorf("You don't have a GOPATH environment - please set it; GOPATH/bin will be symlinked") + // } + + // if err := gows.CreateGopathBinSymlink(origGOPATH, workspaceRootPath); err != nil { + // return fmt.Errorf("Failed to create GOPATH/bin symlink, error: %s", err) + // } + + fullPackageWorkspacePath := filepath.Join(workspaceRootPath, "src", packageName) + if err := gows.CreateOrUpdateSymlink(srcPath, fullPackageWorkspacePath); err != nil { + return fmt.Errorf("Failed to create Project->Workspace symlink, error: %s", err) + } + + { + isInstallRequired, _, goConfig, err := selectGoConfiguration() + if err != nil { + return fmt.Errorf("Failed to select an appropriate Go installation for compiling the step, error: %s", err) + } + if isInstallRequired { + return fmt.Errorf("Failed to select an appropriate Go installation for compiling the step, error: %s", + "Found Go version is older than required. Please run 'bitrise setup' to check and install the required version") + } + + cmd := gows.CreateCommand(workspaceRootPath, workspaceRootPath, + goConfig.GoBinaryPath, "build", "-o", outputBinPath, packageName) + cmd.Env = append(cmd.Env, "GOROOT="+goConfig.GOROOT) + if err := cmd.Run(); err != nil { + return fmt.Errorf("Failed to install package, error: %s", err) + } + } + + { + if err := os.RemoveAll(workspaceRootPath); err != nil { + return fmt.Errorf("Failed to delete temporary isolated workspace, error: %s", err) + } + } + + return nil +} + +// stepIDorURI : doesn't work for "path::./" yet!! +func stepBinaryFilename(sIDData models.StepIDData) string { + // + replaceRexp, err := regexp.Compile("[^A-Za-z0-9.-]") + if err != nil { + log.Warnf("Invalid regex, error: %s", err) + return "" + } + + compositeStepID := fmt.Sprintf("%s-%s-%s", + sIDData.SteplibSource, sIDData.IDorURI, sIDData.Version) + + safeStepID := replaceRexp.ReplaceAllString(compositeStepID, "_") + // + return safeStepID +} + +func stepBinaryCacheFullPath(sIDData models.StepIDData) string { + return filepath.Join(goToolkitCacheRootPath(), stepBinaryFilename(sIDData)) +} + +// PrepareForStepRun ... +func (toolkit GoToolkit) PrepareForStepRun(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) error { + fullStepBinPath := stepBinaryCacheFullPath(sIDData) + + // try to use cached binary, if possible + if sIDData.IsUniqueResourceID() { + if exists, err := pathutil.IsPathExists(fullStepBinPath); err != nil { + log.Warnf("Failed to check cached binary for step, error: %s", err) + } else if exists { + return nil + } + } + + // it's not cached, so compile it + + if step.Toolkit == nil { + return errors.New("No Toolkit information specified in step") + } + if step.Toolkit.Go == nil { + return errors.New("No Toolkit.Go information specified in step") + } + packageName := step.Toolkit.Go.PackageName + + return goBuildInIsolation(packageName, stepAbsDirPath, fullStepBinPath) +} + +// === Toolkit: Step Run === + +// StepRunCommandArguments ... +func (toolkit GoToolkit) StepRunCommandArguments(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) ([]string, error) { + fullStepBinPath := stepBinaryCacheFullPath(sIDData) + return []string{fullStepBinPath}, nil +} + +// === Toolkit path utility function === + +func goToolkitRootPath() string { + return filepath.Join(configs.GetBitriseToolkitsDirPath(), "go") +} + +func goToolkitTmpDirPath() string { + return filepath.Join(goToolkitRootPath(), "tmp") +} + +func goToolkitInstallToPath() string { + return filepath.Join(goToolkitRootPath(), "inst") +} +func goToolkitCacheRootPath() string { + return filepath.Join(goToolkitRootPath(), "cache") +} + +func goToolkitInstallRootPath() string { + return filepath.Join(goToolkitInstallToPath(), "go") +} + +func goToolkitBinsPath() string { + return filepath.Join(goToolkitInstallRootPath(), "bin") +} + +func goBinaryInToolkitFullPath() string { + return filepath.Join(goToolkitBinsPath(), "go") +} diff --git a/vendor/github.com/bitrise-io/bitrise/toolkits/golang_test.go b/vendor/github.com/bitrise-io/bitrise/toolkits/golang_test.go new file mode 100644 index 00000000..0b5a3c28 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/toolkits/golang_test.go @@ -0,0 +1,78 @@ +package toolkits + +import ( + "testing" + + "github.com/bitrise-io/bitrise/models" + "github.com/stretchr/testify/require" +) + +func Test_stepBinaryFilename(t *testing.T) { + { + sIDData := models.StepIDData{SteplibSource: "path", IDorURI: "./", Version: ""} + require.Equal(t, "path-._-", stepBinaryFilename(sIDData)) + } + + { + sIDData := models.StepIDData{SteplibSource: "git", IDorURI: "https://github.com/bitrise-steplib/steps-go-toolkit-hello-world.git", Version: "master"} + require.Equal(t, "git-https___github.com_bitrise-steplib_steps-go-toolkit-hello-world.git-master", stepBinaryFilename(sIDData)) + } + + { + sIDData := models.StepIDData{SteplibSource: "_", IDorURI: "https://github.com/bitrise-steplib/steps-go-toolkit-hello-world.git", Version: "master"} + require.Equal(t, "_-https___github.com_bitrise-steplib_steps-go-toolkit-hello-world.git-master", stepBinaryFilename(sIDData)) + } + + { + sIDData := models.StepIDData{SteplibSource: "https://github.com/bitrise-io/bitrise-steplib.git", IDorURI: "script", Version: "1.2.3"} + require.Equal(t, "https___github.com_bitrise-io_bitrise-steplib.git-script-1.2.3", stepBinaryFilename(sIDData)) + } +} + +func Test_parseGoVersionFromGoVersionOutput(t *testing.T) { + t.Log("Example OK") + { + verStr, err := parseGoVersionFromGoVersionOutput("go version go1.7 darwin/amd64") + require.NoError(t, err) + require.Equal(t, "1.7", verStr) + } + + t.Log("Example OK 2") + { + verStr, err := parseGoVersionFromGoVersionOutput(`go version go1.7 darwin/amd64 + +`) + require.NoError(t, err) + require.Equal(t, "1.7", verStr) + } + + t.Log("Example OK 3") + { + verStr, err := parseGoVersionFromGoVersionOutput("go version go1.7.1 darwin/amd64") + require.NoError(t, err) + require.Equal(t, "1.7.1", verStr) + } + + t.Log("Empty") + { + verStr, err := parseGoVersionFromGoVersionOutput("") + require.EqualError(t, err, "Failed to parse Go version, error: version call output was empty") + require.Equal(t, "", verStr) + } + + t.Log("Empty 2") + { + verStr, err := parseGoVersionFromGoVersionOutput(` + +`) + require.EqualError(t, err, "Failed to parse Go version, error: version call output was empty") + require.Equal(t, "", verStr) + } + + t.Log("Invalid") + { + verStr, err := parseGoVersionFromGoVersionOutput("go version REMOVED darwin/amd64") + require.EqualError(t, err, "Failed to parse Go version, error: failed to find version in input: go version REMOVED darwin/amd64") + require.Equal(t, "", verStr) + } +} diff --git a/vendor/github.com/bitrise-io/bitrise/toolkits/toolkit.go b/vendor/github.com/bitrise-io/bitrise/toolkits/toolkit.go new file mode 100644 index 00000000..26e165d3 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/toolkits/toolkit.go @@ -0,0 +1,82 @@ +package toolkits + +import ( + "github.com/bitrise-io/bitrise/models" + stepmanModels "github.com/bitrise-io/stepman/models" +) + +// ToolkitCheckResult ... +type ToolkitCheckResult struct { + Path string + Version string +} + +// Toolkit ... +type Toolkit interface { + // ToolkitName : a one liner name/id of the toolkit, for logging purposes + ToolkitName() string + + // Check the toolkit - first returned value (bool) indicates + // whether the toolkit have to be installed (true=install required | false=no install required). + // "Have to be installed" can be true if the toolkit is not installed, + // or if an older version is installed, and an update/newer version is required. + Check() (bool, ToolkitCheckResult, error) + + // Install the toolkit + Install() error + + // Check whether the toolkit's tool (e.g. Go, Ruby, Bash, ...) is available + // and "usable"" without any bootstrapping. + // Return true even if the version is older than the required version for this toolkit, + // you can pick / init the right version later. This function only checks + // whether the system has a pre-installed version of the toolkit's tool or not, + // no compability check is required! + IsToolAvailableInPATH() bool + + // Bootstrap : initialize the toolkit for use, ONLY IF THERE'S NO SYSTEM INSTALLED VERSION! + // If there's any version of the tool (e.g. Go) installed, Bootstrap should not overwrite it, + // so that the non toolkit steps will still use the system installed version! + // + // Will run only once, before the build would actually start, + // so that it can set e.g. a default Go or other language version, if there's no System Installed version! + // + // Bootstrap should only set a sensible default if there's no System Installed version of the tool, + // but should not enforce the toolkit's version of the tool! + // The `PrepareForStepRun` function will be called for every step, + // the toolkit should be "enforced" there, BUT ONLY FOR THAT FUNCTION (e.g. don't call os.Setenv there!) + Bootstrap() error + + // PrepareForStepRun can be used to pre-compile or otherwise prepare for the step's execution. + // + // Important: do NOT enforce the toolkit for subsequent / unrelated steps or functions, + // the toolkit should/can be "enforced" here (e.g. during the compilation), + // BUT ONLY for this function! E.g. don't call `os.Setenv` or something similar + // which would affect other functions, just pass the required envs to the compilation command! + PrepareForStepRun(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) error + + // StepRunCommandArguments ... + StepRunCommandArguments(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) ([]string, error) +} + +// +// === Utils === + +// ToolkitForStep ... +func ToolkitForStep(step stepmanModels.StepModel) Toolkit { + if step.Toolkit != nil { + stepToolkit := step.Toolkit + if stepToolkit.Go != nil { + return GoToolkit{} + } else if stepToolkit.Bash != nil { + return BashToolkit{} + } + } + + // default + return BashToolkit{} +} + +// AllSupportedToolkits ... +func AllSupportedToolkits() []Toolkit { + return []Toolkit{GoToolkit{}, BashToolkit{}} +} diff --git a/vendor/github.com/bitrise-io/bitrise/toolkits/toolkit_test.go b/vendor/github.com/bitrise-io/bitrise/toolkits/toolkit_test.go new file mode 100644 index 00000000..cdcc8596 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/toolkits/toolkit_test.go @@ -0,0 +1 @@ +package toolkits diff --git a/vendor/github.com/bitrise-io/bitrise/tools/tools.go b/vendor/github.com/bitrise-io/bitrise/tools/tools.go new file mode 100644 index 00000000..4d6bbf52 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/tools/tools.go @@ -0,0 +1,361 @@ +package tools + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "golang.org/x/sys/unix" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/bitrise/configs" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/errorutil" + "github.com/bitrise-io/go-utils/pathutil" +) + +// UnameGOOS ... +func UnameGOOS() (string, error) { + switch runtime.GOOS { + case "darwin": + return "Darwin", nil + case "linux": + return "Linux", nil + } + return "", fmt.Errorf("Unsupported platform (%s)", runtime.GOOS) +} + +// UnameGOARCH ... +func UnameGOARCH() (string, error) { + switch runtime.GOARCH { + case "amd64": + return "x86_64", nil + } + return "", fmt.Errorf("Unsupported architecture (%s)", runtime.GOARCH) +} + +// InstallToolFromGitHub ... +func InstallToolFromGitHub(toolname, githubUser, toolVersion string) error { + unameGOOS, err := UnameGOOS() + if err != nil { + return fmt.Errorf("Failed to determine OS: %s", err) + } + unameGOARCH, err := UnameGOARCH() + if err != nil { + return fmt.Errorf("Failed to determine ARCH: %s", err) + } + downloadURL := "https://github.com/" + githubUser + "/" + toolname + "/releases/download/" + toolVersion + "/" + toolname + "-" + unameGOOS + "-" + unameGOARCH + + return InstallFromURL(toolname, downloadURL) +} + +// DownloadFile ... +func DownloadFile(downloadURL, targetDirPath string) error { + outFile, err := os.Create(targetDirPath) + defer func() { + if err := outFile.Close(); err != nil { + log.Warnf("Failed to close (%s)", targetDirPath) + } + }() + if err != nil { + return fmt.Errorf("failed to create (%s), error: %s", targetDirPath, err) + } + + resp, err := http.Get(downloadURL) + if err != nil { + return fmt.Errorf("failed to download from (%s), error: %s", downloadURL, err) + } + defer func() { + if err := resp.Body.Close(); err != nil { + log.Warnf("failed to close (%s) body", downloadURL) + } + }() + + _, err = io.Copy(outFile, resp.Body) + if err != nil { + return fmt.Errorf("failed to download from (%s), error: %s", downloadURL, err) + } + + return nil +} + +// InstallFromURL ... +func InstallFromURL(toolBinName, downloadURL string) error { + if len(toolBinName) < 1 { + return fmt.Errorf("no Tool (bin) Name provided! URL was: %s", downloadURL) + } + + tmpDir, err := pathutil.NormalizedOSTempDirPath("__tmp_download_dest__") + if err != nil { + return fmt.Errorf("failed to create tmp dir for download destination") + } + tmpDestinationPth := filepath.Join(tmpDir, toolBinName) + + if err := DownloadFile(downloadURL, tmpDestinationPth); err != nil { + return fmt.Errorf("failed to download, error: %s", err) + } + + bitriseToolsDirPath := configs.GetBitriseToolsDirPath() + destinationPth := filepath.Join(bitriseToolsDirPath, toolBinName) + + if exist, err := pathutil.IsPathExists(destinationPth); err != nil { + return fmt.Errorf("failed to check if file exist (%s), error: %s", destinationPth, err) + } else if exist { + if err := os.Remove(destinationPth); err != nil { + return fmt.Errorf("failed to remove file (%s), error: %s", destinationPth, err) + } + } + + if err := MoveFile(tmpDestinationPth, destinationPth); err != nil { + return fmt.Errorf("failed to copy (%s) to (%s), error: %s", tmpDestinationPth, destinationPth, err) + } + + if err := os.Chmod(destinationPth, 0755); err != nil { + return fmt.Errorf("failed to make file (%s) executable, error: %s", destinationPth, err) + } + + return nil +} + +// ------------------ +// --- Stepman + +// StepmanSetup ... +func StepmanSetup(collection string) error { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "setup", "--collection", collection} + return command.RunCommand("stepman", args...) +} + +// StepmanActivate ... +func StepmanActivate(collection, stepID, stepVersion, dir, ymlPth string) error { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "activate", "--collection", collection, + "--id", stepID, "--version", stepVersion, "--path", dir, "--copyyml", ymlPth} + return command.RunCommand("stepman", args...) +} + +// StepmanUpdate ... +func StepmanUpdate(collection string) error { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "update", "--collection", collection} + return command.RunCommand("stepman", args...) +} + +// StepmanRawStepLibStepInfo ... +func StepmanRawStepLibStepInfo(collection, stepID, stepVersion string) (string, error) { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "step-info", "--collection", collection, + "--id", stepID, "--version", stepVersion, "--format", "raw"} + return command.RunCommandAndReturnCombinedStdoutAndStderr("stepman", args...) +} + +// StepmanRawLocalStepInfo ... +func StepmanRawLocalStepInfo(pth string) (string, error) { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "step-info", "--step-yml", pth, "--format", "raw"} + return command.RunCommandAndReturnCombinedStdoutAndStderr("stepman", args...) +} + +// StepmanJSONStepLibStepInfo ... +func StepmanJSONStepLibStepInfo(collection, stepID, stepVersion string) (string, error) { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "step-info", "--collection", collection, + "--id", stepID, "--version", stepVersion, "--format", "json"} + + var outBuffer bytes.Buffer + var errBuffer bytes.Buffer + + if err := command.RunCommandWithWriters(io.Writer(&outBuffer), io.Writer(&errBuffer), "stepman", args...); err != nil { + return outBuffer.String(), fmt.Errorf("Error: %s, details: %s", err, errBuffer.String()) + } + + return outBuffer.String(), nil +} + +// StepmanJSONLocalStepInfo ... +func StepmanJSONLocalStepInfo(pth string) (string, error) { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "step-info", "--step-yml", pth, "--format", "json"} + + var outBuffer bytes.Buffer + var errBuffer bytes.Buffer + + if err := command.RunCommandWithWriters(io.Writer(&outBuffer), io.Writer(&errBuffer), "stepman", args...); err != nil { + return outBuffer.String(), fmt.Errorf("Error: %s, details: %s", err, errBuffer.String()) + } + + return outBuffer.String(), nil +} + +// StepmanRawStepList ... +func StepmanRawStepList(collection string) (string, error) { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "step-list", "--collection", collection, "--format", "raw"} + return command.RunCommandAndReturnCombinedStdoutAndStderr("stepman", args...) +} + +// StepmanJSONStepList ... +func StepmanJSONStepList(collection string) (string, error) { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "step-list", "--collection", collection, "--format", "json"} + + var outBuffer bytes.Buffer + var errBuffer bytes.Buffer + + if err := command.RunCommandWithWriters(io.Writer(&outBuffer), io.Writer(&errBuffer), "stepman", args...); err != nil { + return outBuffer.String(), fmt.Errorf("Error: %s, details: %s", err, errBuffer.String()) + } + + return outBuffer.String(), nil +} + +// +// Share + +// StepmanShare ... +func StepmanShare() error { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "share", "--toolmode"} + return command.RunCommand("stepman", args...) +} + +// StepmanShareAudit ... +func StepmanShareAudit() error { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "share", "audit", "--toolmode"} + return command.RunCommand("stepman", args...) +} + +// StepmanShareCreate ... +func StepmanShareCreate(tag, git, stepID string) error { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "share", "create", "--tag", tag, "--git", git, "--stepid", stepID, "--toolmode"} + return command.RunCommand("stepman", args...) +} + +// StepmanShareFinish ... +func StepmanShareFinish() error { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "share", "finish", "--toolmode"} + return command.RunCommand("stepman", args...) +} + +// StepmanShareStart ... +func StepmanShareStart(collection string) error { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "share", "start", "--collection", collection, "--toolmode"} + return command.RunCommand("stepman", args...) +} + +// ------------------ +// --- Envman + +// EnvmanInit ... +func EnvmanInit() error { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "init"} + return command.RunCommand("envman", args...) +} + +// EnvmanInitAtPath ... +func EnvmanInitAtPath(envstorePth string) error { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "--path", envstorePth, "init", "--clear"} + return command.RunCommand("envman", args...) +} + +// EnvmanAdd ... +func EnvmanAdd(envstorePth, key, value string, expand, skipIfEmpty bool) error { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "--path", envstorePth, "add", "--key", key, "--append"} + if !expand { + args = append(args, "--no-expand") + } + if skipIfEmpty { + args = append(args, "--skip-if-empty") + } + + envman := exec.Command("envman", args...) + envman.Stdin = strings.NewReader(value) + envman.Stdout = os.Stdout + envman.Stderr = os.Stderr + return envman.Run() +} + +// EnvmanClear ... +func EnvmanClear(envstorePth string) error { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "--path", envstorePth, "clear"} + out, err := command.New("envman", args...).RunAndReturnTrimmedCombinedOutput() + if err != nil { + errorMsg := err.Error() + if errorutil.IsExitStatusError(err) && out != "" { + errorMsg = out + } + return fmt.Errorf("failed to clear envstore (%s), error: %s", envstorePth, errorMsg) + } + return nil +} + +// EnvmanRun ... +func EnvmanRun(envstorePth, workDirPth string, cmd []string) (int, error) { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "--path", envstorePth, "run"} + args = append(args, cmd...) + + return command.RunCommandInDirAndReturnExitCode(workDirPth, "envman", args...) +} + +// EnvmanJSONPrint ... +func EnvmanJSONPrint(envstorePth string) (string, error) { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "--path", envstorePth, "print", "--format", "json", "--expand"} + + var outBuffer bytes.Buffer + var errBuffer bytes.Buffer + + if err := command.RunCommandWithWriters(io.Writer(&outBuffer), io.Writer(&errBuffer), "envman", args...); err != nil { + return outBuffer.String(), fmt.Errorf("Error: %s, details: %s", err, errBuffer.String()) + } + + return outBuffer.String(), nil +} + +// MoveFile ... +func MoveFile(oldpath, newpath string) error { + err := os.Rename(oldpath, newpath) + if err == nil { + return nil + } + + if linkErr, ok := err.(*os.LinkError); ok { + if linkErr.Err == unix.EXDEV { + info, err := os.Stat(oldpath) + if err != nil { + return err + } + + data, err := ioutil.ReadFile(oldpath) + if err != nil { + return err + } + + err = ioutil.WriteFile(newpath, data, info.Mode()) + if err != nil { + return err + } + + return os.Remove(oldpath) + } + } + + return err +} diff --git a/vendor/github.com/bitrise-io/bitrise/tools/tools_test.go b/vendor/github.com/bitrise-io/bitrise/tools/tools_test.go new file mode 100644 index 00000000..7bbf1bcf --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/tools/tools_test.go @@ -0,0 +1,113 @@ +package tools + +import ( + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "testing" + + "github.com/bitrise-io/bitrise/configs" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestMoveFile(t *testing.T) { + srcPath := filepath.Join(os.TempDir(), "src.tmp") + _, err := os.Create(srcPath) + require.NoError(t, err) + + dstPath := filepath.Join(os.TempDir(), "dst.tmp") + require.NoError(t, MoveFile(srcPath, dstPath)) + + info, err := os.Stat(dstPath) + require.NoError(t, err) + require.False(t, info.IsDir()) + + require.NoError(t, os.Remove(dstPath)) +} + +func TestMoveFileDifferentDevices(t *testing.T) { + require.True(t, runtime.GOOS == "linux" || runtime.GOOS == "darwin") + + ramdiskPath := "" + ramdiskName := "RAMDISK" + volumeName := "" + if runtime.GOOS == "linux" { + tmpDir, err := ioutil.TempDir("", ramdiskName) + require.NoError(t, err) + + ramdiskPath = tmpDir + require.NoError(t, exec.Command("mount", "-t", "tmpfs", "-o", "size=12m", "tmpfs", ramdiskPath).Run()) + } else if runtime.GOOS == "darwin" { + out, err := command.New("hdiutil", "attach", "-nomount", "ram://64").RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err) + + volumeName = out + require.NoError(t, exec.Command("diskutil", "erasevolume", "MS-DOS", ramdiskName, volumeName).Run()) + + ramdiskPath = "/Volumes/" + ramdiskName + } + + filename := "test.tmp" + srcPath := filepath.Join(os.TempDir(), filename) + _, err := os.Create(srcPath) + require.NoError(t, err) + + dstPath := filepath.Join(ramdiskPath, filename) + require.NoError(t, MoveFile(srcPath, dstPath)) + + info, err := os.Stat(dstPath) + require.NoError(t, err) + require.False(t, info.IsDir()) + + if runtime.GOOS == "linux" { + require.NoError(t, exec.Command("umount", ramdiskPath).Run()) + require.NoError(t, os.RemoveAll(ramdiskPath)) + } else if runtime.GOOS == "darwin" { + require.NoError(t, exec.Command("hdiutil", "detach", volumeName).Run()) + } +} + +func TestStepmanJSONStepLibStepInfo(t *testing.T) { + // setup + require.NoError(t, configs.InitPaths()) + + // Valid params -- Err should empty, output filled + require.Equal(t, nil, StepmanSetup("https://github.com/bitrise-io/bitrise-steplib")) + + outStr, err := StepmanJSONStepLibStepInfo("https://github.com/bitrise-io/bitrise-steplib", "script", "0.9.0") + require.NoError(t, err) + require.NotEqual(t, "", outStr) + + // Invalid params -- Err should empty, output filled + outStr, err = StepmanJSONStepLibStepInfo("https://github.com/bitrise-io/bitrise-steplib", "script", "2") + require.NotEqual(t, nil, err) + require.Equal(t, "", outStr) +} + +func TestEnvmanJSONPrint(t *testing.T) { + // Initialized envstore -- Err should empty, output filled + testDirPth, err := pathutil.NormalizedOSTempDirPath("test_env_store") + require.NoError(t, err) + + envstorePth := filepath.Join(testDirPth, "envstore.yml") + + require.Equal(t, nil, EnvmanInitAtPath(envstorePth)) + + outStr, err := EnvmanJSONPrint(envstorePth) + require.NoError(t, err) + require.NotEqual(t, "", outStr) + + // Not initialized envstore -- Err should filled, output empty + testDirPth, err = pathutil.NormalizedOSTempDirPath("test_env_store") + require.NoError(t, err) + + envstorePth = filepath.Join("test_env_store", "envstore.yml") + + outStr, err = EnvmanJSONPrint(envstorePth) + require.NotEqual(t, nil, err) + require.Equal(t, "", outStr) +} diff --git a/vendor/github.com/bitrise-io/bitrise/utils/utils.go b/vendor/github.com/bitrise-io/bitrise/utils/utils.go new file mode 100644 index 00000000..31746729 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/utils/utils.go @@ -0,0 +1,16 @@ +package utils + +import ( + "os" + "os/exec" + "strings" +) + +// CheckProgramInstalledPath ... +func CheckProgramInstalledPath(clcommand string) (string, error) { + cmd := exec.Command("which", clcommand) + cmd.Stderr = os.Stderr + outBytes, err := cmd.Output() + outStr := string(outBytes) + return strings.TrimSpace(outStr), err +} diff --git a/vendor/github.com/bitrise-io/bitrise/version/build.go b/vendor/github.com/bitrise-io/bitrise/version/build.go new file mode 100644 index 00000000..06c70c10 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/version/build.go @@ -0,0 +1,7 @@ +package version + +// BuildNumber ... +var BuildNumber = "" + +// Commit ... +var Commit = "" diff --git a/vendor/github.com/bitrise-io/bitrise/version/tool_version.go b/vendor/github.com/bitrise-io/bitrise/version/tool_version.go new file mode 100644 index 00000000..ca2266da --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/version/tool_version.go @@ -0,0 +1,87 @@ +package version + +import ( + "fmt" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/go-utils/command" + "github.com/hashicorp/go-version" +) + +// StepmanVersion ... +func StepmanVersion() (version.Version, error) { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "--version"} + + versionOut, err := command.RunCommandAndReturnCombinedStdoutAndStderr("stepman", args...) + if err != nil { + return version.Version{}, err + } + + versionPtr, err := version.NewVersion(versionOut) + if err != nil { + return version.Version{}, err + } + if versionPtr == nil { + return version.Version{}, fmt.Errorf("Failed to parse version (%s)", versionOut) + } + + return *versionPtr, nil +} + +// EnvmanVersion ... +func EnvmanVersion() (version.Version, error) { + logLevel := log.GetLevel().String() + args := []string{"--loglevel", logLevel, "--version"} + versionOut, err := command.RunCommandAndReturnCombinedStdoutAndStderr("envman", args...) + if err != nil { + return version.Version{}, err + } + + versionPtr, err := version.NewVersion(versionOut) + if err != nil { + return version.Version{}, err + } + if versionPtr == nil { + return version.Version{}, fmt.Errorf("Failed to parse version (%s)", versionOut) + } + + return *versionPtr, nil +} + +// BitriseCliVersion ... +func BitriseCliVersion() (version.Version, error) { + versionPtr, err := version.NewVersion(VERSION) + if err != nil { + return version.Version{}, err + } + if versionPtr == nil { + return version.Version{}, fmt.Errorf("Failed to parse version (%s)", VERSION) + } + + return *versionPtr, nil +} + +// ToolVersionMap ... +func ToolVersionMap() (map[string]version.Version, error) { + envmanVersion, err := EnvmanVersion() + if err != nil { + return map[string]version.Version{}, err + } + + stepmanVersion, err := StepmanVersion() + if err != nil { + return map[string]version.Version{}, err + } + + bitriseVersion, err := BitriseCliVersion() + if err != nil { + return map[string]version.Version{}, err + } + + return map[string]version.Version{ + "bitrise": bitriseVersion, + "envman": envmanVersion, + "stepman": stepmanVersion, + }, nil +} diff --git a/vendor/github.com/bitrise-io/bitrise/version/version.go b/vendor/github.com/bitrise-io/bitrise/version/version.go new file mode 100644 index 00000000..6f4c87e0 --- /dev/null +++ b/vendor/github.com/bitrise-io/bitrise/version/version.go @@ -0,0 +1,4 @@ +package version + +// VERSION ... +const VERSION = "1.8.0" diff --git a/vendor/github.com/bitrise-io/envman/.gitignore b/vendor/github.com/bitrise-io/envman/.gitignore new file mode 100644 index 00000000..c8619c05 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/.gitignore @@ -0,0 +1,6 @@ +_tmp/ +.envstore.yml +.bitrise +.bitrise.secrets.yml +_bin/ +.gows.user.yml diff --git a/vendor/github.com/bitrise-io/envman/Dockerfile b/vendor/github.com/bitrise-io/envman/Dockerfile new file mode 100644 index 00000000..b0ad5ec4 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/Dockerfile @@ -0,0 +1,50 @@ +FROM ubuntu:16.04 + +ENV PROJ_NAME envman +ENV BITRISE_CLI_VERSION 1.21.0 +ENV GO_VERSION go1.10.3.linux-amd64.tar.gz + +RUN apt-get update + +RUN DEBIAN_FRONTEND=noninteractive apt-get -y install \ + # Requiered for Bitrise CLI + git \ + mercurial \ + curl \ + wget \ + rsync \ + sudo \ + expect \ + build-essential + +# +# Install Bitrise CLI +RUN curl -L https://github.com/bitrise-io/bitrise/releases/download/$BITRISE_CLI_VERSION/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +RUN chmod +x /usr/local/bin/bitrise +RUN bitrise setup + +# +# Install Go +# from official binary package +RUN wget -q https://storage.googleapis.com/golang/$GO_VERSION -O go-bins.tar.gz \ + && tar -C /usr/local -xvzf go-bins.tar.gz \ + && rm go-bins.tar.gz + +# ENV setup +ENV PATH $PATH:/usr/local/go/bin +# Go Workspace dirs & envs +# From the official Golang Dockerfile +# https://github.com/docker-library/golang +ENV GOPATH /go +ENV PATH $GOPATH/bin:$PATH +# 755 because Ruby complains if 777 (warning: Insecure world writable dir ... in PATH) +RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 755 "$GOPATH" + +RUN mkdir -p /go/src/github.com/bitrise-io/$PROJ_NAME +COPY . /go/src/github.com/bitrise-io/$PROJ_NAME + +WORKDIR /go/src/github.com/bitrise-io/$PROJ_NAME + +# install +RUN go install +CMD $PROJ_NAME --version diff --git a/vendor/github.com/bitrise-io/envman/Gopkg.lock b/vendor/github.com/bitrise-io/envman/Gopkg.lock new file mode 100644 index 00000000..ca0ab9b2 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/Gopkg.lock @@ -0,0 +1,121 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + digest = "1:d972dce278dd6023fccf060a44cc51c33f0ce25f0522970c000c502766826cb8" + name = "github.com/Sirupsen/logrus" + packages = ["."] + pruneopts = "" + revision = "eef6b768ab01a0598a0a6db97bad2a37d31df1d1" + +[[projects]] + branch = "master" + digest = "1:6083fc4e90fe661aad326c42fa16fb85774cb55711560d1208c82ff290534836" + name = "github.com/bitrise-io/go-utils" + packages = [ + "command", + "errorutil", + "fileutil", + "parseutil", + "pathutil", + "pointers", + ] + pruneopts = "" + revision = "2a09aab8380d7842750328aebd5671bcccea89c8" + +[[projects]] + branch = "master" + digest = "1:6825c56bedbe125a302e7516f731fa841ce9db05f873dbc5745ee807a8c3d3a2" + name = "github.com/bitrise-io/goinp" + packages = ["goinp"] + pruneopts = "" + revision = "f451b19ba4d087d8272d411ad91af5f4218fd517" + +[[projects]] + digest = "1:0deddd908b6b4b768cfc272c16ee61e7088a60f7fe2f06c547bd3d8e1f8b8e77" + name = "github.com/davecgh/go-spew" + packages = ["spew"] + pruneopts = "" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" + +[[projects]] + digest = "1:6a874e3ddfb9db2b42bd8c85b6875407c702fa868eed20634ff489bc896ccfd3" + name = "github.com/konsorten/go-windows-terminal-sequences" + packages = ["."] + pruneopts = "" + revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" + version = "v1.0.1" + +[[projects]] + digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + pruneopts = "" + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + branch = "master" + digest = "1:381bcbeb112a51493d9d998bbba207a529c73dbb49b3fd789e48c63fac1f192c" + name = "github.com/stretchr/testify" + packages = [ + "assert", + "require", + ] + pruneopts = "" + revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" + +[[projects]] + branch = "master" + digest = "1:438d5957cf1c26ca62975dc4b1b59242acc9e527f39ed3f6cda57ee6d3653daf" + name = "github.com/urfave/cli" + packages = ["."] + pruneopts = "" + revision = "b67dcf995b6a7b7f14fad5fcb7cc5441b05e814b" + +[[projects]] + branch = "master" + digest = "1:59b49c47c11a48f1054529207f65907c014ecf5f9a7c0d9c0f1616dec7b062ed" + name = "golang.org/x/crypto" + packages = ["ssh/terminal"] + pruneopts = "" + revision = "ff983b9c42bc9fbf91556e191cc8efb585c16908" + +[[projects]] + branch = "master" + digest = "1:30d295ca4b98f09df913be86c1ac48d1d321eb2a84543206a87704e5d25db19b" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows", + ] + pruneopts = "" + revision = "7fbe1cd0fcc20051e1fcb87fbabec4a1bacaaeba" + +[[projects]] + branch = "v2" + digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d" + name = "gopkg.in/yaml.v2" + packages = ["."] + pruneopts = "" + revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/Sirupsen/logrus", + "github.com/bitrise-io/go-utils/command", + "github.com/bitrise-io/go-utils/fileutil", + "github.com/bitrise-io/go-utils/parseutil", + "github.com/bitrise-io/go-utils/pathutil", + "github.com/bitrise-io/go-utils/pointers", + "github.com/bitrise-io/goinp/goinp", + "github.com/stretchr/testify/require", + "github.com/urfave/cli", + "gopkg.in/yaml.v2", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/bitrise-io/envman/Gopkg.toml b/vendor/github.com/bitrise-io/envman/Gopkg.toml new file mode 100644 index 00000000..cb817b33 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/Gopkg.toml @@ -0,0 +1,23 @@ +[[constraint]] + name = "github.com/Sirupsen/logrus" + branch = "master" + +[[constraint]] + name = "github.com/bitrise-io/go-utils" + branch = "master" + +[[constraint]] + name = "github.com/bitrise-io/goinp" + branch = "master" + +[[constraint]] + name = "github.com/stretchr/testify" + branch = "master" + +[[constraint]] + name = "github.com/urfave/cli" + branch = "master" + +[[constraint]] + name = "gopkg.in/yaml.v2" + branch = "v2" diff --git a/vendor/github.com/bitrise-io/envman/LICENSE b/vendor/github.com/bitrise-io/envman/LICENSE new file mode 100644 index 00000000..a6a5c39a --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Bitrise + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/bitrise-io/envman/README.md b/vendor/github.com/bitrise-io/envman/README.md new file mode 100644 index 00000000..64782e77 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/README.md @@ -0,0 +1,196 @@ +# envman + +Manage your Environment Variable collections. Switch between Environment Variable sets +quickly and easily, or run a single command with a pre-defined set of Environment Variables. + +`envman` can also be used as a bridge between separate tools, one tool can register its +outputs through `envman` and the next tool can access these as simple environment variables. + +**Public Beta:** this repository is still under active development, +frequent changes are expected, but we we don't plan to introduce breaking changes, +unless really necessary. **Feedback is greatly appreciated!** + +*Part of the Bitrise Continuous Integration, Delivery and Automations Stack, +with [bitrise](https://github.com/bitrise-io/bitrise) and [stepman](https://github.com/bitrise-io/stepman).* + +## Who and for what? + +- connect tools with each other : one tool saves an ENV, the other uses it (input & output) +- manage your environment-sets : use different ENVs for different projects +- complex environment values : if you want to store a complex input as ENV (for example a change log text/summary), `envman` makes this easy, you don't have to encode the value so that when you call bash's `export KEY=value` it will store your value as you intended. You can just give `envman` the value as it is through a `--valuefile` option or as an input stream (or just edit the related `.envstore` file manually), no encoding required. +- switch between environment sets : if you work on multiple projects where each one requires different environments you can manage this with `envman` + +## Install + +Check the latest release for instructions at: [https://github.com/bitrise-io/envman/releases](https://github.com/bitrise-io/envman/releases) + +## How? - Use cases + +- multi PATH handling: you have `packer` in your `$HOME` dir, in a bin subdir and terraform in another +- create an envman `.envset` to include these in your `$PATH` + +## Develop & Test in Docker + +Build: + +``` +docker build -t envman . +``` + +Run: + +``` +docker run --rm -it -v `pwd`:/go/src/github.com/bitrise-io/envman --name envman-dev envman /bin/bash +``` + +## How does it work? + +`envman` will run the command you specify +with the environments found in its environment store. + +When you add a new key-value pair with `envman add` it stores +the key and value in an `.envstore` file in the current +directory (this can be changed), and when you call `envman run` +the next time the environments in the `.envstore` file will +be loaded for the command, and the command will be executed +with an environment which includes the specified environment +variables. + +This is the same as you would manually set all the +environments, one by one with `export KEY=value` (in bash) +before calling the command. + +`envman` makes it easy to switch between environment sets +and to isolate these environment sets from each other - +you don't have to `unset` environments, the specified +environment set will only be available for that single +run of `envman` / the command and won't affect subsequent +calls of the command or `envman`. + +## Usage example: Simple Bash example + +Add/store an environment variable: + +``` +envman add --key MY_TEST_ENV_KEY --value 'test value for test key' +``` + +Echo it: + +``` +envman run bash -c 'echo "Environment test: $MY_TEST_ENV_KEY"' +``` + +*Why `bash -c` is required? Because `echo` in itself +does not do any environment expansion, it simply prints +its input. So if you want to see the value of an environment +you have to run it through a tool/shell which actually +performs the environment expansion (replaces the environment +key with the value associated with it).* + +## Usage example: Ruby + +### Add environment variable with `--value` flag + +``` +system( "envman add --key SOME_KEY --value 'some value' --expand false" ) +``` + +### Add environment variable from an input stream + +``` +IO.popen('envman add --key SOME_KEY', 'r+') {|f| + f.write('some value') + f.close_write + f.read +} +``` + +### Add environment variable with a value file + +``` +require 'tempfile' + +file = Tempfile.new('SOME_FILE_NAME') +file.write('some value') +file.close + +system( "envman add --key SOME_KEY --valuefile #{file.path}" ) + +file.unlink +``` + +## Usage example: Go + +### Add environment variable with `--value` flag + +``` +import "os/exec" + +c := exec.Command("envman", "add", "--key", "SOME_KEY", "--value", "some value") +err := c.Run() +if err != nil { + // Handle error +} +``` + +### Add environment variable from an input stream + +``` +import "os/exec" + +func RunPipedEnvmanAdd(keyStr, valueStr string) error { + envman := exec.Command("envman", "add", "--key", keyStr) + envman.Stdin = strings.NewReader(valueStr) + envman.Stdout = os.Stdout + envman.Stderr = os.Stderr + return envman.Run() +} + +err := RunPipedEnvmanAdd("SOME_KEY", "some value") +if err != nil { + // Handle error +} +``` + +### Add environment variable with a value file + +``` +import ( + "os/exec" + "fmt" +) + +c := exec.Command("envman", "add", "--key", "SOME_KEY", "--valuefile", "/path/to/file/which/contains/the/value") +err := c.Run() +if err != nil { + // Handle error +} +``` + +## Usage example: Bash + +### Add environment variable with `--value` flag + +``` +envman add --key SOME_KEY --value 'some value' +``` + +### Add environment variable from an input stream + +``` +echo "some value" | envman add --key SOME_KEY +``` + +### Add environment variable with a value file + +``` +envman add --key SOME_KEY --valuefile /path/to/file/which/contains/the/value --expand false +``` + +### Release a new version + +- merge every code changes to the master branch +- do not forget to merge every version related file changes: + - update the version number (in version.go file) +- push the new version tag to the master branch \ No newline at end of file diff --git a/vendor/github.com/bitrise-io/envman/_tests/integration/add_test.go b/vendor/github.com/bitrise-io/envman/_tests/integration/add_test.go new file mode 100644 index 00000000..59d416e6 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/_tests/integration/add_test.go @@ -0,0 +1,141 @@ +package integration + +import ( + "encoding/json" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/bitrise-io/envman/envman" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func addCommand(key, value, envstore string) *command.Model { + return command.New(binPath(), "-l", "debug", "-p", envstore, "add", "--key", key, "--value", value) +} + +func addFileCommand(key, pth, envstore string) *command.Model { + return command.New(binPath(), "-l", "debug", "-p", envstore, "add", "--key", key, "--valuefile", pth) +} + +func addPipeCommand(key string, reader io.Reader, envstore string) *command.Model { + return command.New(binPath(), "-l", "debug", "-p", envstore, "add", "--key", key).SetStdin(reader) +} + +func TestAdd(t *testing.T) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__envman__") + require.NoError(t, err) + + envstore := filepath.Join(tmpDir, ".envstore") + f, err := os.Create(envstore) + require.NoError(t, err) + require.NoError(t, f.Close()) + + t.Log("add flag value") + { + out, err := addCommand("KEY", "value", envstore).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + cont, err := fileutil.ReadStringFromFile(envstore) + require.NoError(t, err, out) + require.Equal(t, "envs:\n- KEY: value\n", cont) + } + + t.Log("add file flag value") + { + pth := filepath.Join(tmpDir, "file") + require.NoError(t, fileutil.WriteStringToFile(pth, "some content")) + + out, err := addFileCommand("KEY", pth, envstore).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + cont, err := fileutil.ReadStringFromFile(envstore) + require.NoError(t, err, out) + require.Equal(t, "envs:\n- KEY: some content\n", cont) + } + + t.Log("add piped value") + { + out, err := addPipeCommand("KEY", strings.NewReader("some piped value"), envstore).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + cont, err := fileutil.ReadStringFromFile(envstore) + require.NoError(t, err, out) + require.Equal(t, "envs:\n- KEY: some piped value\n", cont) + } + + t.Log("add piped value - zero EnvBytesLimitInKB") + { + configPath := filepath.Join(pathutil.UserHomeDir(), ".envman", "configs.json") + require.NoError(t, pathutil.EnsureDirExist(filepath.Dir(configPath))) + + exists, err := pathutil.IsPathExists(configPath) + require.NoError(t, err) + + var origData []byte + if exists { + origData, err = fileutil.ReadBytesFromFile(configPath) + require.NoError(t, err) + } + + cfgData, err := json.Marshal(envman.ConfigsModel{EnvBytesLimitInKB: 0, EnvListBytesLimitInKB: 3}) + require.NoError(t, err) + + require.NoError(t, fileutil.WriteBytesToFile(configPath, cfgData)) + + out, err := addPipeCommand("KEY", strings.NewReader(strings.Repeat("0", 2*1024)), envstore).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + if exists { + require.NoError(t, fileutil.WriteBytesToFile(configPath, origData)) + } else { + require.NoError(t, os.RemoveAll(configPath)) + } + } + + t.Log("add piped value - over limit") + { + configPath := filepath.Join(pathutil.UserHomeDir(), ".envman", "configs.json") + require.NoError(t, pathutil.EnsureDirExist(filepath.Dir(configPath))) + + exists, err := pathutil.IsPathExists(configPath) + require.NoError(t, err) + + var origData []byte + if exists { + origData, err = fileutil.ReadBytesFromFile(configPath) + require.NoError(t, err) + } + + cfgData, err := json.Marshal(envman.ConfigsModel{EnvBytesLimitInKB: 1, EnvListBytesLimitInKB: 2}) + require.NoError(t, err) + + require.NoError(t, fileutil.WriteBytesToFile(configPath, cfgData)) + + out, err := addPipeCommand("KEY", strings.NewReader(strings.Repeat("0", 2*1024)), envstore).RunAndReturnTrimmedCombinedOutput() + require.Error(t, err) + require.True(t, strings.Contains(out, "environment value size (2 KB) - max allowed size: 1 KB")) + + if exists { + require.NoError(t, fileutil.WriteBytesToFile(configPath, origData)) + } else { + require.NoError(t, os.RemoveAll(configPath)) + } + } + + t.Log("add piped value - empty value") + { + out, err := addPipeCommand("KEY", strings.NewReader(""), envstore).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + cont, err := fileutil.ReadStringFromFile(envstore) + require.NoError(t, err, out) + require.Equal(t, "envs:\n- KEY: \"\"\n", cont) + } +} diff --git a/vendor/github.com/bitrise-io/envman/_tests/integration/helper.go b/vendor/github.com/bitrise-io/envman/_tests/integration/helper.go new file mode 100644 index 00000000..fbd55914 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/_tests/integration/helper.go @@ -0,0 +1,7 @@ +package integration + +import "os" + +func binPath() string { + return os.Getenv("INTEGRATION_TEST_BINARY_PATH") +} diff --git a/vendor/github.com/bitrise-io/envman/bitrise.yml b/vendor/github.com/bitrise-io/envman/bitrise.yml new file mode 100644 index 00000000..e8ac35bb --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/bitrise.yml @@ -0,0 +1,97 @@ +format_version: "5" +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +project_type: other + +app: + envs: + - BIN_NAME: envman + +workflows: + test: + title: Runs tests + steps: + - go-list: + - golint: + - errcheck: + - go-test: + - codecov: + run_if: .IsCI + inputs: + - other_options: -f ${GO_CODE_COVERAGE_REPORT_PATH} + - CODECOV_TOKEN: "$CODECOV_UPLOAD_TOKEN" + - script: + title: Run integration tests + inputs: + - content: |- + #!/usr/bin/env bash + set -ex + + current_envman="$(pwd)/_tmp/test_envman" + go build -o "$current_envman" + + export PR="" PULL_REQUEST_ID="" + export INTEGRATION_TEST_BINARY_PATH="$current_envman" + go test -v ./_tests/integration/... + + create-binaries: + title: Create binaries + steps: + - script: + title: Create binaries + inputs: + - content: | + #!/bin/bash + set -ex + + echo + echo "Create final binaries" + echo " Build number: $BITRISE_BUILD_NUMBER" + + export ARCH=x86_64 + export GOARCH=amd64 + + # Create Darwin bin + export OS=Darwin + export GOOS=darwin + + DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" + echo " Create final Darwin binary at: $DEPLOY_PATH" + + version_package="github.com/bitrise-io/envman/version" + + go build \ + -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ + -o "$DEPLOY_PATH" + + envman add --key OSX_DEPLOY_PATH --value $DEPLOY_PATH + cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH + echo " Copy final Darwin binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" + + + # Create Linux binary + export OS=Linux + export GOOS=linux + + DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" + echo " Create final Linux binary at: $DEPLOY_PATH" + + go build \ + -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ + -o "$DEPLOY_PATH" + + envman add --key LINUX_DEPLOY_PATH --value $DEPLOY_PATH + cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH + echo " Copy final Linux binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" + + dep-update: + title: Dep update + steps: + - script: + title: Dependency update + inputs: + - content: |- + #!/bin/bash + set -ex + go get -u -v github.com/golang/dep/cmd/dep + dep ensure -v + dep ensure -v -update diff --git a/vendor/github.com/bitrise-io/envman/cli/add.go b/vendor/github.com/bitrise-io/envman/cli/add.go new file mode 100644 index 00000000..cfefb7bb --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/cli/add.go @@ -0,0 +1,225 @@ +package cli + +import ( + "errors" + "io/ioutil" + "os" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/envman/envman" + "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/pointers" + "github.com/urfave/cli" +) + +var errTimeout = errors.New("timeout") + +func envListSizeInBytes(envs []models.EnvironmentItemModel) (int, error) { + valueSizeInBytes := 0 + for _, env := range envs { + _, value, err := env.GetKeyValuePair() + if err != nil { + return 0, err + } + valueSizeInBytes += len([]byte(value)) + } + return valueSizeInBytes, nil +} + +func validateEnv(key, value string, envList []models.EnvironmentItemModel) (string, error) { + if key == "" { + return "", errors.New("Key is not specified, required") + } + + configs, err := envman.GetConfigs() + if err != nil { + return "", err + } + + valueSizeInBytes := len([]byte(value)) + if configs.EnvBytesLimitInKB > 0 { + if valueSizeInBytes > configs.EnvBytesLimitInKB*1024 { + valueSizeInKB := ((float64)(valueSizeInBytes)) / 1024.0 + log.Warnf("environment value (%s...) too large", value[0:100]) + log.Warnf("environment value size (%#v KB) - max allowed size: %#v KB", valueSizeInKB, (float64)(configs.EnvBytesLimitInKB)) + return "environment value too large - rejected", nil + } + } + + if configs.EnvListBytesLimitInKB > 0 { + envListSizeInBytes, err := envListSizeInBytes(envList) + if err != nil { + return "", err + } + if envListSizeInBytes+valueSizeInBytes > configs.EnvListBytesLimitInKB*1024 { + listSizeInKB := (float64)(envListSizeInBytes)/1024 + (float64)(valueSizeInBytes)/1024 + log.Warn("environment list too large") + log.Warnf("environment list size (%#v KB) - max allowed size: %#v KB", listSizeInKB, (float64)(configs.EnvListBytesLimitInKB)) + return "", errors.New("environment list too large") + } + } + return value, nil +} + +func addEnv(key string, value string, expand, replace, skipIfEmpty bool) error { + // Load envs, or create if not exist + environments, err := envman.ReadEnvsOrCreateEmptyList() + if err != nil { + return err + } + + // Validate input + validatedValue, err := validateEnv(key, value, environments) + if err != nil { + return err + } + value = validatedValue + + // Add or update envlist + newEnv := models.EnvironmentItemModel{ + key: value, + models.OptionsKey: models.EnvironmentItemOptionsModel{ + IsExpand: pointers.NewBoolPtr(expand), + SkipIfEmpty: pointers.NewBoolPtr(skipIfEmpty), + }, + } + if err := newEnv.NormalizeValidateFillDefaults(); err != nil { + return err + } + + newEnvSlice, err := envman.UpdateOrAddToEnvlist(environments, newEnv, replace) + if err != nil { + return err + } + + return envman.WriteEnvMapToFile(envman.CurrentEnvStoreFilePath, newEnvSlice) +} + +func loadValueFromFile(pth string) (string, error) { + buf, err := ioutil.ReadFile(pth) + if err != nil { + return "", err + } + + str := string(buf) + return str, nil +} + +func logEnvs() error { + environments, err := envman.ReadEnvs(envman.CurrentEnvStoreFilePath) + if err != nil { + return err + } + + if len(environments) == 0 { + log.Info("[ENVMAN] Empty envstore") + } else { + for _, env := range environments { + key, value, err := env.GetKeyValuePair() + if err != nil { + return err + } + + opts, err := env.GetOptions() + if err != nil { + return err + } + + envString := "- " + key + ": " + value + log.Debugln(envString) + if !*opts.IsExpand { + expandString := " " + "isExpand" + ": " + "false" + log.Debugln(expandString) + } + } + } + + return nil +} + +func add(c *cli.Context) error { + log.Debugln("[ENVMAN] Work path:", envman.CurrentEnvStoreFilePath) + + key := c.String(KeyKey) + expand := !c.Bool(NoExpandKey) + replace := !c.Bool(AppendKey) + skipIfEmpty := c.Bool(SkipIfEmptyKey) + + var value string + + // read flag value + if c.IsSet(ValueKey) { + value = c.String(ValueKey) + log.Debugf("adding flag value: (%s)", value) + } + + // read flag file + if value == "" && c.String(ValueFileKey) != "" { + var err error + if value, err = loadValueFromFile(c.String(ValueFileKey)); err != nil { + log.Fatal("[ENVMAN] Failed to read file value: ", err) + } + log.Debugf("adding file flag value: (%s)", value) + } + + // read piped stdin value + if value == "" { + info, err := os.Stdin.Stat() + if err != nil { + log.Fatalf("[ENVMAN] Failed to get standard input FileInfo: %s", err) + } + if info.Mode()&os.ModeNamedPipe == os.ModeNamedPipe { + log.Debugf("adding from piped stdin") + + configs, err := envman.GetConfigs() + if err != nil { + log.Fatalf("[ENVMAN] Failed to load envman config: %s", err) + } + + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + log.Fatalf("[ENVMAN] Failed to read standard input: %s", err) + } + + if configs.EnvBytesLimitInKB > 0 && len(data) > configs.EnvBytesLimitInKB*1024 { + valueSizeInKB := ((float64)(len(data))) / 1024.0 + log.Warnf("environment value too large") + log.Warnf("environment value size (%#v KB) - max allowed size: %#v KB", valueSizeInKB, (float64)(configs.EnvBytesLimitInKB)) + log.Fatalf("[ENVMAN] environment value too large - rejected") + } + + if configs.EnvListBytesLimitInKB > 0 { + envList, err := envman.ReadEnvsOrCreateEmptyList() + if err != nil { + log.Fatalf("[ENVMAN] failed to get env list, error: %s", err) + } + envListSizeInBytes, err := envListSizeInBytes(envList) + if err != nil { + log.Fatalf("[ENVMAN] failed to get env list size, error: %s", err) + } + if envListSizeInBytes+len(data) > configs.EnvListBytesLimitInKB*1024 { + listSizeInKB := (float64)(envListSizeInBytes)/1024 + (float64)(len(data))/1024 + log.Warn("environment list too large") + log.Warnf("environment list size (%#v KB) - max allowed size: %#v KB", listSizeInKB, (float64)(configs.EnvListBytesLimitInKB)) + log.Fatalf("[ENVMAN] environment list too large") + } + } + + value = string(data) + + log.Debugf("stdin value: (%s)", value) + } + } + + if err := addEnv(key, value, expand, replace, skipIfEmpty); err != nil { + log.Fatal("[ENVMAN] Failed to add env:", err) + } + + log.Debugln("[ENVMAN] Env added") + + if err := logEnvs(); err != nil { + log.Fatal("[ENVMAN] Failed to print:", err) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/envman/cli/add_test.go b/vendor/github.com/bitrise-io/envman/cli/add_test.go new file mode 100644 index 00000000..5a58b531 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/cli/add_test.go @@ -0,0 +1,76 @@ +package cli + +import ( + "errors" + "strings" + "testing" + + "github.com/bitrise-io/envman/models" + "github.com/stretchr/testify/require" +) + +func TestEnvListSizeInBytes(t *testing.T) { + str100Bytes := strings.Repeat("a", 100) + require.Equal(t, 100, len([]byte(str100Bytes))) + + env := models.EnvironmentItemModel{ + "key": str100Bytes, + } + + envList := []models.EnvironmentItemModel{env} + size, err := envListSizeInBytes(envList) + require.Equal(t, nil, err) + require.Equal(t, 100, size) + + envList = []models.EnvironmentItemModel{env, env} + size, err = envListSizeInBytes(envList) + require.Equal(t, nil, err) + require.Equal(t, 200, size) +} + +func TestValidateEnv(t *testing.T) { + // Valid - max allowed + str20KBytes := strings.Repeat("a", (20 * 1024)) + env1 := models.EnvironmentItemModel{ + "key": str20KBytes, + } + envs := []models.EnvironmentItemModel{env1} + + valValue, err := validateEnv("key", str20KBytes, envs) + require.NoError(t, err) + require.Equal(t, str20KBytes, valValue) + + // List oversize + // first create a large, but valid env set + for i := 0; i < 3; i++ { + envs = append(envs, env1) + } + + valValue, err = validateEnv("key", str20KBytes, envs) + require.NoError(t, err) + require.Equal(t, str20KBytes, valValue) + + // append one more -> too large + envs = append(envs, env1) + _, err = validateEnv("key", str20KBytes, envs) + require.Equal(t, errors.New("environment list too large"), err) + + // List oversize + too big value + str10Kbytes := strings.Repeat("a", (10 * 1024)) + env1 = models.EnvironmentItemModel{ + "key": str10Kbytes, + } + envs = []models.EnvironmentItemModel{} + for i := 0; i < 8; i++ { + env := models.EnvironmentItemModel{ + "key": str10Kbytes, + } + envs = append(envs, env) + } + + str21Kbytes := strings.Repeat("a", (21 * 1024)) + + valValue, err = validateEnv("key", str21Kbytes, envs) + require.NoError(t, err) + require.Equal(t, "environment value too large - rejected", valValue) +} diff --git a/vendor/github.com/bitrise-io/envman/cli/clear.go b/vendor/github.com/bitrise-io/envman/cli/clear.go new file mode 100644 index 00000000..d9ae53a5 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/cli/clear.go @@ -0,0 +1,34 @@ +package cli + +import ( + "errors" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/envman/envman" + "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/urfave/cli" +) + +func clearEnvs() error { + if isExists, err := pathutil.IsPathExists(envman.CurrentEnvStoreFilePath); err != nil { + return err + } else if !isExists { + errMsg := "EnvStore not found in path:" + envman.CurrentEnvStoreFilePath + return errors.New(errMsg) + } + + return envman.WriteEnvMapToFile(envman.CurrentEnvStoreFilePath, []models.EnvironmentItemModel{}) +} + +func clear(c *cli.Context) error { + log.Debugln("[ENVMAN] - Work path:", envman.CurrentEnvStoreFilePath) + + if err := clearEnvs(); err != nil { + log.Fatal("[ENVMAN] - Failed to clear EnvStore:", err) + } + + log.Info("[ENVMAN] - EnvStore cleared") + + return nil +} diff --git a/vendor/github.com/bitrise-io/envman/cli/cli.go b/vendor/github.com/bitrise-io/envman/cli/cli.go new file mode 100644 index 00000000..6db10352 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/cli/cli.go @@ -0,0 +1,96 @@ +package cli + +import ( + "fmt" + "os" + "path" + "path/filepath" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/envman/envman" + "github.com/bitrise-io/envman/version" + "github.com/urfave/cli" +) + +const ( + defaultEnvStoreName = ".envstore.yml" + helpTemplate = ` +NAME: {{.Name}} - {{.Usage}} + +USAGE: {{.Name}} {{if .Flags}}[OPTIONS] {{end}}COMMAND [arg...] + +VERSION: {{.Version}}{{if or .Author .Email}} + +AUTHOR:{{if .Author}} + {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} + {{.Email}}{{end}}{{end}} +{{if .Flags}} +GLOBAL OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{end}} +COMMANDS: + {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}} +COMMAND HELP: {{.Name}} COMMAND --help/-h + +` +) + +func before(c *cli.Context) error { + log.SetFormatter(&log.TextFormatter{ForceColors: true, FullTimestamp: true, TimestampFormat: "15:04:05"}) + + // Log level + if logLevel, err := log.ParseLevel(c.String(LogLevelKey)); err != nil { + log.Fatal("[BITRISE_CLI] - Failed to parse log level:", err) + } else { + log.SetLevel(logLevel) + } + + // Befor parsing cli, and running command + // we need to decide wich path will be used by envman + envman.CurrentEnvStoreFilePath = c.String(PathKey) + if envman.CurrentEnvStoreFilePath == "" { + if path, err := filepath.Abs(path.Join("./", defaultEnvStoreName)); err != nil { + log.Fatal("[ENVMAN] - Failed to set envman work path in current dir:", err) + } else { + envman.CurrentEnvStoreFilePath = path + } + } + + envman.ToolMode = c.Bool(ToolKey) + if envman.ToolMode { + log.Info("[ENVMAN] - Tool mode on") + } + + if _, err := envman.GetConfigs(); err != nil { + log.Fatal("[ENVMAN] - Failed to init configs:", err) + } + + return nil +} + +// Run the Envman CLI. +func Run() { + cli.HelpFlag = cli.BoolFlag{Name: HelpKey + ", " + helpKeyShort, Usage: "Show help."} + cli.AppHelpTemplate = helpTemplate + + cli.VersionFlag = cli.BoolFlag{Name: VersionKey + ", " + versionKeyShort, Usage: "Print the version."} + cli.VersionPrinter = func(c *cli.Context) { fmt.Println(c.App.Version) } + + app := cli.NewApp() + app.Name = path.Base(os.Args[0]) + app.Usage = "Environment variable manager" + app.Version = version.VERSION + + app.Author = "" + app.Email = "" + + app.Before = before + + app.Flags = flags + app.Commands = commands + + if err := app.Run(os.Args); err != nil { + log.Fatal("[ENVMAN] - Finished:", err) + } +} diff --git a/vendor/github.com/bitrise-io/envman/cli/commands.go b/vendor/github.com/bitrise-io/envman/cli/commands.go new file mode 100644 index 00000000..f122daaf --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/cli/commands.go @@ -0,0 +1,72 @@ +package cli + +import "github.com/urfave/cli" + +var ( + commands = []cli.Command{ + { + Name: "version", + Usage: "Prints the version", + Action: printVersionCmd, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "Output format. Accepted: json, yml", + }, + cli.BoolFlag{ + Name: "full", + Usage: "Prints the build number and commit as well.", + }, + }, + }, + { + Name: "init", + Aliases: []string{"i"}, + Usage: "Create an empty .envstore.yml into the current working directory, or to the path specified by the --path flag.", + Action: initEnvStore, + Flags: []cli.Flag{ + flClear, + }, + }, + { + Name: "add", + Aliases: []string{"a"}, + Usage: "Add new, or update an exist environment variable.", + Action: add, + Flags: []cli.Flag{ + flKey, + flValue, + flValueFile, + flNoExpand, + flAppend, + cli.BoolFlag{ + Name: SkipIfEmptyKey, + Usage: "If enabled the added environment variable will be skipped during envman run, if the value is empty. If not set then the empty value will be used.", + }, + }, + }, + { + Name: "clear", + Aliases: []string{"c"}, + Usage: "Clear the envstore.", + Action: clear, + }, + { + Name: "print", + Aliases: []string{"p"}, + Usage: "Print out the environment variables in envstore.", + Action: print, + Flags: []cli.Flag{ + flFormat, + flExpand, + }, + }, + { + Name: "run", + Aliases: []string{"r"}, + Usage: "Run the specified command with the environment variables stored in the envstore.", + SkipFlagParsing: true, + Action: run, + }, + } +) diff --git a/vendor/github.com/bitrise-io/envman/cli/flags.go b/vendor/github.com/bitrise-io/envman/cli/flags.go new file mode 100644 index 00000000..60025f91 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/cli/flags.go @@ -0,0 +1,127 @@ +package cli + +import "github.com/urfave/cli" + +const ( + // PathEnvKey ... + PathEnvKey = "ENVMAN_ENVSTORE_PATH" + // PathKey ... + PathKey = "path" + pathKeyShort = "p" + + // LogLevelEnvKey ... + LogLevelEnvKey = "LOGLEVEL" + // LogLevelKey ... + LogLevelKey = "loglevel" + logLevelKeyShort = "l" + + // KeyKey ... + KeyKey = "key" + keyKeyShortT = "k" + + // ValueKey ... + ValueKey = "value" + valueKeyShort = "v" + + // ValueFileKey ... + ValueFileKey = "valuefile" + valueFileKeyShort = "f" + + // NoExpandKey ... + NoExpandKey = "no-expand" + noExpandKeyShort = "n" + + // AppendKey ... + AppendKey = "append" + appendKeyShort = "a" + + // SkipIfEmptyKey ... + SkipIfEmptyKey = "skip-if-empty" + + // ToolEnvKey ... + ToolEnvKey = "ENVMAN_TOOLMODE" + // ToolKey ... + ToolKey = "tool" + toolKeyShort = "t" + + // ClearKey .... + ClearKey = "clear" + clearKeyShort = "c" + + // HelpKey ... + HelpKey = "help" + helpKeyShort = "h" + + // VersionKey ... + VersionKey = "version" + versionKeyShort = "v" + + // ExpandKey ... + ExpandKey = "expand" + // FormatKey ... + FormatKey = "format" + // OutputFormatRaw ... + OutputFormatRaw = "raw" + // OutputFormatJSON ... + OutputFormatJSON = "json" +) + +var ( + // App flags + flLogLevel = cli.StringFlag{ + Name: LogLevelKey + ", " + logLevelKeyShort, + Value: "info", + Usage: "Log level (options: debug, info, warn, error, fatal, panic).", + EnvVar: LogLevelEnvKey, + } + flPath = cli.StringFlag{ + Name: PathKey + ", " + pathKeyShort, + EnvVar: PathEnvKey, + Value: "", + Usage: "Path of the envstore.", + } + flTool = cli.BoolFlag{ + Name: ToolKey + ", " + toolKeyShort, + EnvVar: ToolEnvKey, + Usage: "If enabled, envman will NOT ask for user inputs.", + } + flags = []cli.Flag{ + flLogLevel, + flPath, + flTool, + } + + // Command flags + flKey = cli.StringFlag{ + Name: KeyKey + ", " + keyKeyShortT, + Usage: "Key of the environment variable. Empty string (\"\") is NOT accepted.", + } + flValue = cli.StringFlag{ + Name: ValueKey + ", " + valueKeyShort, + Usage: "Value of the environment variable. Empty string is accepted.", + } + flValueFile = cli.StringFlag{ + Name: ValueFileKey + ", " + valueFileKeyShort, + Usage: "Path of a file which contains the environment variable's value to be stored.", + } + flNoExpand = cli.BoolFlag{ + Name: NoExpandKey + ", " + noExpandKeyShort, + Usage: "If enabled, envman will NOT replaces ${var} or $var in the string according to the values of the current environment variables.", + } + flAppend = cli.BoolFlag{ + Name: AppendKey + ", " + appendKeyShort, + Usage: "If enabled, new env will append to envstore, otherwise if env exist with specified key, will replaced.", + } + flClear = cli.BoolFlag{ + Name: ClearKey + ", " + clearKeyShort, + Usage: "If enabled, 'envman init' removes envstore if exist.", + } + flFormat = cli.StringFlag{ + Name: FormatKey, + Usage: "Output format (options: raw, json).", + } + flExpand = cli.BoolFlag{ + Name: ExpandKey, + Usage: "If enabled, expanded envs will use.", + } +) diff --git a/vendor/github.com/bitrise-io/envman/cli/init.go b/vendor/github.com/bitrise-io/envman/cli/init.go new file mode 100644 index 00000000..049ba50b --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/cli/init.go @@ -0,0 +1,27 @@ +package cli + +import ( + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/envman/envman" + "github.com/bitrise-io/go-utils/command" + "github.com/urfave/cli" +) + +func initEnvStore(c *cli.Context) error { + log.Debugln("[ENVMAN] - Work path:", envman.CurrentEnvStoreFilePath) + + clear := c.Bool(ClearKey) + if clear { + if err := command.RemoveFile(envman.CurrentEnvStoreFilePath); err != nil { + log.Fatal("[ENVMAN] - Failed to clear path:", err) + } + } + + if err := envman.InitAtPath(envman.CurrentEnvStoreFilePath); err != nil { + log.Fatal("[ENVMAN] - Failed to init at path:", err) + } + + log.Debugln("[ENVMAN] - Initialized") + + return nil +} diff --git a/vendor/github.com/bitrise-io/envman/cli/print.go b/vendor/github.com/bitrise-io/envman/cli/print.go new file mode 100644 index 00000000..7aa9d984 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/cli/print.go @@ -0,0 +1,95 @@ +package cli + +import ( + "encoding/json" + "fmt" + "os" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/envman/envman" + "github.com/bitrise-io/envman/models" + "github.com/urfave/cli" +) + +func printJSONEnvs(envList models.EnvsJSONListModel) error { + bytes, err := json.Marshal(envList) + if err != nil { + return err + } + + fmt.Println(string(bytes)) + return nil +} + +func printRawEnvs(envList models.EnvsJSONListModel) { + fmt.Println() + for key, value := range envList { + fmt.Printf("%s: %s\n", key, value) + } + fmt.Println() +} + +func convertToEnsJSONModel(envs []models.EnvironmentItemModel, expand bool) (models.EnvsJSONListModel, error) { + JSONModels := models.EnvsJSONListModel{} + for _, env := range envs { + key, value, err := env.GetKeyValuePair() + if err != nil { + return models.EnvsJSONListModel{}, err + } + + opts, err := env.GetOptions() + if err != nil { + return models.EnvsJSONListModel{}, err + } + + if expand && (opts.IsExpand != nil && *opts.IsExpand) { + value = expandEnvsInString(value) + } + + JSONModels[key] = value + + if err := os.Setenv(key, value); err != nil { + return models.EnvsJSONListModel{}, err + } + } + return JSONModels, nil +} + +func print(c *cli.Context) error { + // Input validation + format := c.String(FormatKey) + if format == "" { + format = OutputFormatRaw + } else if !(format == OutputFormatRaw || format == OutputFormatJSON) { + log.Fatalf("Invalid format: %s", format) + } + + expand := c.Bool(ExpandKey) + + // Read envs + environments, err := envman.ReadEnvs(envman.CurrentEnvStoreFilePath) + if err != nil { + log.Fatalf("Failed to read envs, error: %s", err) + } + + envsJSONList, err := convertToEnsJSONModel(environments, expand) + if err != nil { + log.Fatalf("Failed to convert envs, error: %s", err) + } + + // Print envs + switch format { + case OutputFormatRaw: + printRawEnvs(envsJSONList) + break + case OutputFormatJSON: + if err := printJSONEnvs(envsJSONList); err != nil { + log.Fatalf("Failed to print env list, err: %s", err) + } + break + default: + log.Fatalf("[STEPMAN] - Invalid format: %s", format) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/envman/cli/print_test.go b/vendor/github.com/bitrise-io/envman/cli/print_test.go new file mode 100644 index 00000000..dfd3f801 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/cli/print_test.go @@ -0,0 +1,32 @@ +package cli + +import ( + "os" + "path" + "testing" + + "github.com/bitrise-io/envman/envman" + "github.com/stretchr/testify/require" +) + +func TestPrint(t *testing.T) { + envsStr := ` +envs: +- TEST_HOME1: $HOME +- TEST_HOME2: $TEST_HOME1/test +` + environments, err := envman.ParseEnvsYML([]byte(envsStr)) + require.Equal(t, nil, err) + + envsJSONList, err := convertToEnsJSONModel(environments, false) + require.Equal(t, nil, err) + require.Equal(t, "$HOME", envsJSONList["TEST_HOME1"]) + require.Equal(t, "$TEST_HOME1/test", envsJSONList["TEST_HOME2"]) + + testHome1 := os.Getenv("HOME") + testHome2 := path.Join(testHome1, "test") + envsJSONList, err = convertToEnsJSONModel(environments, true) + require.Equal(t, nil, err) + require.Equal(t, testHome1, envsJSONList["TEST_HOME1"]) + require.Equal(t, testHome2, envsJSONList["TEST_HOME2"]) +} diff --git a/vendor/github.com/bitrise-io/envman/cli/run.go b/vendor/github.com/bitrise-io/envman/cli/run.go new file mode 100644 index 00000000..fb137240 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/cli/run.go @@ -0,0 +1,102 @@ +package cli + +import ( + "os" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/envman/envman" + "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/command" + "github.com/urfave/cli" +) + +// CommandModel ... +type CommandModel struct { + Command string + Argumentums []string + Environments []models.EnvironmentItemModel +} + +func expandEnvsInString(inp string) string { + return os.ExpandEnv(inp) +} + +func commandEnvs(envs []models.EnvironmentItemModel) ([]string, error) { + for _, env := range envs { + key, value, err := env.GetKeyValuePair() + if err != nil { + return []string{}, err + } + + opts, err := env.GetOptions() + if err != nil { + return []string{}, err + } + + if *opts.SkipIfEmpty && value == "" { + continue + } + + var valueStr string + if *opts.IsExpand { + valueStr = expandEnvsInString(value) + } else { + valueStr = value + } + + if err := os.Setenv(key, valueStr); err != nil { + return []string{}, err + } + } + return os.Environ(), nil +} + +func runCommandModel(cmdModel CommandModel) (int, error) { + cmdEnvs, err := commandEnvs(cmdModel.Environments) + if err != nil { + return 1, err + } + + return command.RunCommandWithEnvsAndReturnExitCode(cmdEnvs, cmdModel.Command, cmdModel.Argumentums...) +} + +func run(c *cli.Context) error { + log.Debug("[ENVMAN] - Work path:", envman.CurrentEnvStoreFilePath) + + if len(c.Args()) > 0 { + doCmdEnvs, err := envman.ReadEnvs(envman.CurrentEnvStoreFilePath) + if err != nil { + log.Fatal("[ENVMAN] - Failed to load EnvStore:", err) + } + + doCommand := c.Args()[0] + + doArgs := []string{} + if len(c.Args()) > 1 { + doArgs = c.Args()[1:] + } + + cmdToExecute := CommandModel{ + Command: doCommand, + Environments: doCmdEnvs, + Argumentums: doArgs, + } + + log.Debug("[ENVMAN] - Executing command:", cmdToExecute) + + if exit, err := runCommandModel(cmdToExecute); err != nil { + log.Debug("[ENVMAN] - Failed to execute command:", err) + if exit == 0 { + log.Error("[ENVMAN] - Failed to execute command:", err) + exit = 1 + } + os.Exit(exit) + } + + log.Debug("[ENVMAN] - Command executed") + } else { + log.Fatal("[ENVMAN] - No command specified") + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/envman/cli/run_test.go b/vendor/github.com/bitrise-io/envman/cli/run_test.go new file mode 100644 index 00000000..48646dc4 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/cli/run_test.go @@ -0,0 +1,237 @@ +package cli + +import ( + "os" + "strings" + "testing" + + "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/pointers" + "github.com/stretchr/testify/require" +) + +func TestExpandEnvsInString(t *testing.T) { + t.Log("Expand env") + { + require.Equal(t, nil, os.Setenv("MY_ENV_KEY", "key")) + + inp := "${MY_ENV_KEY} of my home" + expanded := expandEnvsInString(inp) + + key := os.Getenv("MY_ENV_KEY") + require.NotEqual(t, "", expanded) + require.Equal(t, key+" of my home", expanded) + } +} + +func TestCommandEnvs(t *testing.T) { + t.Log("commandEnvs test") + { + env1 := models.EnvironmentItemModel{ + "test_key1": "test_value1", + } + require.Equal(t, nil, env1.FillMissingDefaults()) + + env2 := models.EnvironmentItemModel{ + "test_key2": "test_value2", + } + require.Equal(t, nil, env2.FillMissingDefaults()) + + envs := []models.EnvironmentItemModel{env1, env2} + + sessionEnvs, err := commandEnvs(envs) + require.Equal(t, nil, err) + + env1Found := false + env2Found := false + for _, envString := range sessionEnvs { + comp := strings.Split(envString, "=") + key := comp[0] + value := comp[1] + + envKey1, envValue1, err := env1.GetKeyValuePair() + require.Equal(t, nil, err) + + envKey2, envValue2, err := env2.GetKeyValuePair() + require.Equal(t, nil, err) + + if key == envKey1 && value == envValue1 { + env1Found = true + } + if key == envKey2 && value == envValue2 { + env2Found = true + } + } + require.Equal(t, true, env1Found) + require.Equal(t, true, env2Found) + } + + // Test skip_if_empty + t.Log("skip_if_empty=false && value=empty => should add") + { + env1 := models.EnvironmentItemModel{ + "test_key3": "", + } + require.Equal(t, nil, env1.FillMissingDefaults()) + + env2 := models.EnvironmentItemModel{ + "test_key4": "test_value4", + } + require.Equal(t, nil, env2.FillMissingDefaults()) + + envs := []models.EnvironmentItemModel{env1, env2} + + sessionEnvs, err := commandEnvs(envs) + require.Equal(t, nil, err) + + env1Found := false + env2Found := false + for _, envString := range sessionEnvs { + comp := strings.Split(envString, "=") + key := comp[0] + value := comp[1] + + envKey1, envValue1, err := env1.GetKeyValuePair() + require.Equal(t, nil, err) + + envKey2, envValue2, err := env2.GetKeyValuePair() + require.Equal(t, nil, err) + + if key == envKey1 && value == envValue1 { + env1Found = true + } + if key == envKey2 && value == envValue2 { + env2Found = true + } + } + require.Equal(t, true, env1Found) + require.Equal(t, true, env2Found) + } + + t.Log("skip_if_empty=true && value=empty => should NOT add") + { + env1 := models.EnvironmentItemModel{ + "test_key5": "", + "opts": models.EnvironmentItemOptionsModel{ + SkipIfEmpty: pointers.NewBoolPtr(true), + }, + } + require.Equal(t, nil, env1.FillMissingDefaults()) + + env2 := models.EnvironmentItemModel{ + "test_key6": "test_value6", + } + require.Equal(t, nil, env2.FillMissingDefaults()) + + envs := []models.EnvironmentItemModel{env1, env2} + + sessionEnvs, err := commandEnvs(envs) + require.Equal(t, nil, err) + + env1Found := false + env2Found := false + for _, envString := range sessionEnvs { + comp := strings.Split(envString, "=") + key := comp[0] + value := comp[1] + + envKey1, envValue1, err := env1.GetKeyValuePair() + require.Equal(t, nil, err) + + envKey2, envValue2, err := env2.GetKeyValuePair() + require.Equal(t, nil, err) + + if key == envKey1 && value == envValue1 { + env1Found = true + } + if key == envKey2 && value == envValue2 { + env2Found = true + } + } + require.Equal(t, false, env1Found) + require.Equal(t, true, env2Found) + } + + t.Log("skip_if_empty=true && value=NOT_empty => should add") + { + env1 := models.EnvironmentItemModel{ + "test_key7": "test_value7", + "opts": models.EnvironmentItemOptionsModel{ + SkipIfEmpty: pointers.NewBoolPtr(true), + }, + } + require.Equal(t, nil, env1.FillMissingDefaults()) + + env2 := models.EnvironmentItemModel{ + "test_key8": "test_value8", + } + require.Equal(t, nil, env2.FillMissingDefaults()) + + envs := []models.EnvironmentItemModel{env1, env2} + + sessionEnvs, err := commandEnvs(envs) + require.Equal(t, nil, err) + + env1Found := false + env2Found := false + for _, envString := range sessionEnvs { + comp := strings.Split(envString, "=") + key := comp[0] + value := comp[1] + + envKey1, envValue1, err := env1.GetKeyValuePair() + require.Equal(t, nil, err) + + envKey2, envValue2, err := env2.GetKeyValuePair() + require.Equal(t, nil, err) + + if key == envKey1 && value == envValue1 { + env1Found = true + } + if key == envKey2 && value == envValue2 { + env2Found = true + } + } + require.Equal(t, true, env1Found) + require.Equal(t, true, env2Found) + } + + t.Log("expand envs test") + { + env1 := models.EnvironmentItemModel{ + "env1": "Hello", + } + require.Equal(t, nil, env1.FillMissingDefaults()) + + env2 := models.EnvironmentItemModel{ + "env2": "${env1} world", + } + require.Equal(t, nil, env2.FillMissingDefaults()) + + env3 := models.EnvironmentItemModel{ + "env3": "${env2} !", + } + require.Equal(t, nil, env3.FillMissingDefaults()) + + envs := []models.EnvironmentItemModel{env1, env2, env3} + + sessionEnvs, err := commandEnvs(envs) + require.Equal(t, nil, err) + + env3Found := false + for _, envString := range sessionEnvs { + comp := strings.Split(envString, "=") + key := comp[0] + value := comp[1] + + envKey3, _, err := env3.GetKeyValuePair() + require.Equal(t, nil, err) + + if key == envKey3 { + require.Equal(t, "Hello world !", value) + env3Found = true + } + } + require.Equal(t, true, env3Found) + } +} diff --git a/vendor/github.com/bitrise-io/envman/cli/version.go b/vendor/github.com/bitrise-io/envman/cli/version.go new file mode 100644 index 00000000..e206d880 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/cli/version.go @@ -0,0 +1,46 @@ +package cli + +import ( + "fmt" + "log" + + "github.com/bitrise-io/envman/output" + "github.com/bitrise-io/envman/version" + "github.com/urfave/cli" +) + +// VersionOutputModel ... +type VersionOutputModel struct { + Version string `json:"version"` + BuildNumber string `json:"build_number"` + Commit string `json:"commit"` +} + +func printVersionCmd(c *cli.Context) error { + fullVersion := c.Bool("full") + + if err := output.ConfigureOutputFormat(c); err != nil { + log.Fatalf("Error: %s", err) + } + + versionOutput := VersionOutputModel{ + Version: version.VERSION, + } + + if fullVersion { + versionOutput.BuildNumber = version.BuildNumber + versionOutput.Commit = version.Commit + } + + if output.Format == output.FormatRaw { + if fullVersion { + fmt.Printf("version: %v\nbuild_number: %v\ncommit: %v\n", versionOutput.Version, versionOutput.BuildNumber, versionOutput.Commit) + } else { + fmt.Println(versionOutput.Version) + } + } else { + output.Print(versionOutput, output.Format) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/envman/docker-compose.yml b/vendor/github.com/bitrise-io/envman/docker-compose.yml new file mode 100644 index 00000000..be582a93 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/docker-compose.yml @@ -0,0 +1,4 @@ +app: + build: . + volumes: + - .:/go/src/github.com/bitrise-io/envman diff --git a/vendor/github.com/bitrise-io/envman/envman/configs.go b/vendor/github.com/bitrise-io/envman/envman/configs.go new file mode 100644 index 00000000..8c09c803 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/envman/configs.go @@ -0,0 +1,99 @@ +package envman + +import ( + "encoding/json" + "os" + "path" + + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" +) + +const ( + envmanConfigFileName = "configs.json" + defaultEnvBytesLimitInKB = 20 + defaultEnvListBytesLimitInKB = 100 +) + +// ConfigsModel ... +type ConfigsModel struct { + EnvBytesLimitInKB int `json:"env_bytes_limit_in_kb,omitempty"` + EnvListBytesLimitInKB int `json:"env_list_bytes_limit_in_kb,omitempty"` +} + +func getEnvmanConfigsDirPath() string { + return path.Join(pathutil.UserHomeDir(), ".envman") +} + +func getEnvmanConfigsFilePath() string { + return path.Join(getEnvmanConfigsDirPath(), envmanConfigFileName) +} + +func ensureEnvmanConfigDirExists() error { + confDirPth := getEnvmanConfigsDirPath() + isExists, err := pathutil.IsDirExists(confDirPth) + if !isExists || err != nil { + if err := os.MkdirAll(confDirPth, 0777); err != nil { + return err + } + } + return nil +} + +func createDefaultConfigsModel() ConfigsModel { + return ConfigsModel{ + EnvBytesLimitInKB: defaultEnvBytesLimitInKB, + EnvListBytesLimitInKB: defaultEnvListBytesLimitInKB, + } +} + +// GetConfigs ... +func GetConfigs() (ConfigsModel, error) { + configPth := getEnvmanConfigsFilePath() + defaultConfigs := createDefaultConfigsModel() + + if isExist, err := pathutil.IsPathExists(configPth); err != nil { + return ConfigsModel{}, err + } else if !isExist { + return defaultConfigs, nil + } + + bytes, err := fileutil.ReadBytesFromFile(configPth) + if err != nil { + return ConfigsModel{}, err + } + + type ConfigsFileMode struct { + EnvBytesLimitInKB *int `json:"env_bytes_limit_in_kb,omitempty"` + EnvListBytesLimitInKB *int `json:"env_list_bytes_limit_in_kb,omitempty"` + } + + var userConfigs ConfigsFileMode + if err := json.Unmarshal(bytes, &userConfigs); err != nil { + return ConfigsModel{}, err + } + + if userConfigs.EnvBytesLimitInKB != nil { + defaultConfigs.EnvBytesLimitInKB = *userConfigs.EnvBytesLimitInKB + } + if userConfigs.EnvListBytesLimitInKB != nil { + defaultConfigs.EnvListBytesLimitInKB = *userConfigs.EnvListBytesLimitInKB + } + + return defaultConfigs, nil +} + +// saveConfigs ... +// only used for unit testing at the moment +func saveConfigs(configModel ConfigsModel) error { + if err := ensureEnvmanConfigDirExists(); err != nil { + return err + } + + bytes, err := json.Marshal(configModel) + if err != nil { + return err + } + configsPth := getEnvmanConfigsFilePath() + return fileutil.WriteBytesToFile(configsPth, bytes) +} diff --git a/vendor/github.com/bitrise-io/envman/envman/configs_test.go b/vendor/github.com/bitrise-io/envman/envman/configs_test.go new file mode 100644 index 00000000..2a6ee4e8 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/envman/configs_test.go @@ -0,0 +1,51 @@ +package envman + +import ( + "os" + "testing" + + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestGetConfigs(t *testing.T) { + // fake home, to save the configs into + fakeHomePth, err := pathutil.NormalizedOSTempDirPath("_FAKE_HOME") + t.Logf("fakeHomePth: %s", fakeHomePth) + require.NoError(t, err) + originalHome := os.Getenv("HOME") + defer func() { + require.NoError(t, os.Setenv("HOME", originalHome)) + require.NoError(t, os.RemoveAll(fakeHomePth)) + }() + require.Equal(t, nil, os.Setenv("HOME", fakeHomePth)) + + configPth := getEnvmanConfigsFilePath() + t.Logf("configPth: %s", configPth) + + // --- TESTING + + baseConf, err := GetConfigs() + t.Logf("baseConf: %#v", baseConf) + require.NoError(t, err) + require.Equal(t, defaultEnvBytesLimitInKB, baseConf.EnvBytesLimitInKB) + require.Equal(t, defaultEnvListBytesLimitInKB, baseConf.EnvListBytesLimitInKB) + + // modify it + baseConf.EnvBytesLimitInKB = 123 + baseConf.EnvListBytesLimitInKB = 321 + + // save to file + require.NoError(t, saveConfigs(baseConf)) + + // read it back + configs, err := GetConfigs() + t.Logf("configs: %#v", configs) + require.NoError(t, err) + require.Equal(t, configs, baseConf) + require.Equal(t, 123, configs.EnvBytesLimitInKB) + require.Equal(t, 321, configs.EnvListBytesLimitInKB) + + // delete the tmp config file + require.NoError(t, os.Remove(configPth)) +} diff --git a/vendor/github.com/bitrise-io/envman/envman/util.go b/vendor/github.com/bitrise-io/envman/envman/util.go new file mode 100644 index 00000000..fd24fa1a --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/envman/util.go @@ -0,0 +1,254 @@ +package envman + +import ( + "errors" + + "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/goinp/goinp" + "gopkg.in/yaml.v2" +) + +var ( + // CurrentEnvStoreFilePath ... + CurrentEnvStoreFilePath string + + // ToolMode ... + ToolMode bool +) + +// ------------------- +// --- Environment handling methods + +// UpdateOrAddToEnvlist ... +func UpdateOrAddToEnvlist(oldEnvSlice []models.EnvironmentItemModel, newEnv models.EnvironmentItemModel, replace bool) ([]models.EnvironmentItemModel, error) { + newKey, _, err := newEnv.GetKeyValuePair() + if err != nil { + return []models.EnvironmentItemModel{}, err + } + + var newEnvs []models.EnvironmentItemModel + exist := false + + if replace { + match := 0 + for _, env := range oldEnvSlice { + key, _, err := env.GetKeyValuePair() + if err != nil { + return []models.EnvironmentItemModel{}, err + } + + if key == newKey { + match = match + 1 + } + } + if match > 1 { + if ToolMode { + return []models.EnvironmentItemModel{}, errors.New("More then one env exist with key '" + newKey + "'") + } + msg := " More then one env exist with key '" + newKey + "' replace all/append ['replace/append'] ?" + answer, err := goinp.AskForString(msg) + if err != nil { + return []models.EnvironmentItemModel{}, err + } + + switch answer { + case "replace": + break + case "append": + replace = false + break + default: + return []models.EnvironmentItemModel{}, errors.New("Failed to parse answer: '" + answer + "' use ['replace/append']!") + } + } + } + + for _, env := range oldEnvSlice { + key, _, err := env.GetKeyValuePair() + if err != nil { + return []models.EnvironmentItemModel{}, err + } + + if replace && key == newKey { + exist = true + newEnvs = append(newEnvs, newEnv) + } else { + newEnvs = append(newEnvs, env) + } + } + + if !exist { + newEnvs = append(newEnvs, newEnv) + } + + return newEnvs, nil +} + +func removeDefaults(env *models.EnvironmentItemModel) error { + opts, err := env.GetOptions() + if err != nil { + return err + } + + if opts.Title != nil && *opts.Title == "" { + opts.Title = nil + } + if opts.Description != nil && *opts.Description == "" { + opts.Description = nil + } + if opts.Summary != nil && *opts.Summary == "" { + opts.Summary = nil + } + if opts.IsRequired != nil && *opts.IsRequired == models.DefaultIsRequired { + opts.IsRequired = nil + } + if opts.IsDontChangeValue != nil && *opts.IsDontChangeValue == models.DefaultIsDontChangeValue { + opts.IsDontChangeValue = nil + } + if opts.IsTemplate != nil && *opts.IsTemplate == models.DefaultIsTemplate { + opts.IsTemplate = nil + } + if opts.IsExpand != nil && *opts.IsExpand == models.DefaultIsExpand { + opts.IsExpand = nil + } + if opts.IsSensitive != nil && *opts.IsSensitive == models.DefaultIsSensitive { + opts.IsSensitive = nil + } + if opts.SkipIfEmpty != nil && *opts.SkipIfEmpty == models.DefaultSkipIfEmpty { + opts.SkipIfEmpty = nil + } + + (*env)[models.OptionsKey] = opts + return nil +} + +func generateFormattedYMLForEnvModels(envs []models.EnvironmentItemModel) (models.EnvsSerializeModel, error) { + envMapSlice := []models.EnvironmentItemModel{} + for _, env := range envs { + err := removeDefaults(&env) + if err != nil { + return models.EnvsSerializeModel{}, err + } + + hasOptions := false + opts, err := env.GetOptions() + if err != nil { + return models.EnvsSerializeModel{}, err + } + + if opts.Title != nil { + hasOptions = true + } + if opts.Description != nil { + hasOptions = true + } + if opts.Summary != nil { + hasOptions = true + } + if len(opts.ValueOptions) > 0 { + hasOptions = true + } + if opts.IsRequired != nil { + hasOptions = true + } + if opts.IsDontChangeValue != nil { + hasOptions = true + } + if opts.IsTemplate != nil { + hasOptions = true + } + if opts.IsExpand != nil { + hasOptions = true + } + if opts.IsSensitive != nil { + hasOptions = true + } + if opts.SkipIfEmpty != nil { + hasOptions = true + } + + if !hasOptions { + delete(env, models.OptionsKey) + } + + envMapSlice = append(envMapSlice, env) + } + + return models.EnvsSerializeModel{ + Envs: envMapSlice, + }, nil +} + +// ------------------- +// --- File methods + +// WriteEnvMapToFile ... +func WriteEnvMapToFile(pth string, envs []models.EnvironmentItemModel) error { + if pth == "" { + return errors.New("No path provided") + } + + envYML, err := generateFormattedYMLForEnvModels(envs) + if err != nil { + return err + } + bytes, err := yaml.Marshal(envYML) + if err != nil { + return err + } + return fileutil.WriteBytesToFile(pth, bytes) +} + +// InitAtPath ... +func InitAtPath(pth string) error { + if exist, err := pathutil.IsPathExists(pth); err != nil { + return err + } else if !exist { + if err := WriteEnvMapToFile(pth, []models.EnvironmentItemModel{}); err != nil { + return err + } + } else { + errorMsg := "Path already exist: " + pth + return errors.New(errorMsg) + } + return nil +} + +// ParseEnvsYML ... +func ParseEnvsYML(bytes []byte) ([]models.EnvironmentItemModel, error) { + var envsYML models.EnvsSerializeModel + if err := yaml.Unmarshal(bytes, &envsYML); err != nil { + return []models.EnvironmentItemModel{}, err + } + for _, env := range envsYML.Envs { + if err := env.NormalizeValidateFillDefaults(); err != nil { + return []models.EnvironmentItemModel{}, err + } + } + return envsYML.Envs, nil +} + +// ReadEnvs ... +func ReadEnvs(pth string) ([]models.EnvironmentItemModel, error) { + bytes, err := fileutil.ReadBytesFromFile(pth) + if err != nil { + return []models.EnvironmentItemModel{}, err + } + + return ParseEnvsYML(bytes) +} + +// ReadEnvsOrCreateEmptyList ... +func ReadEnvsOrCreateEmptyList() ([]models.EnvironmentItemModel, error) { + envModels, err := ReadEnvs(CurrentEnvStoreFilePath) + if err != nil { + if err.Error() == "No environment variable list found" { + err = InitAtPath(CurrentEnvStoreFilePath) + return []models.EnvironmentItemModel{}, err + } + return []models.EnvironmentItemModel{}, err + } + return envModels, nil +} diff --git a/vendor/github.com/bitrise-io/envman/envman/util_test.go b/vendor/github.com/bitrise-io/envman/envman/util_test.go new file mode 100644 index 00000000..17fd9aa0 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/envman/util_test.go @@ -0,0 +1,125 @@ +package envman + +import ( + "testing" + + "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/pointers" + "github.com/stretchr/testify/require" +) + +func countOfEnvInEnvSlice(env models.EnvironmentItemModel, envSlice []models.EnvironmentItemModel) (cnt int, err error) { + for _, e := range envSlice { + key, value, err := env.GetKeyValuePair() + if err != nil { + return 0, err + } + + k, v, err := e.GetKeyValuePair() + if err != nil { + return 0, err + } + + if key == k && value == v { + cnt++ + } + } + return +} + +func countOfEnvKeyInEnvSlice(env models.EnvironmentItemModel, envSlice []models.EnvironmentItemModel) (cnt int, err error) { + for _, e := range envSlice { + key, _, err := env.GetKeyValuePair() + if err != nil { + return 0, err + } + + k, _, err := e.GetKeyValuePair() + if err != nil { + return 0, err + } + + if key == k { + cnt++ + } + } + return +} + +func TestUpdateOrAddToEnvlist(t *testing.T) { + env1 := models.EnvironmentItemModel{ + "test_key1": "test_value1", + } + require.Equal(t, nil, env1.FillMissingDefaults()) + + env2 := models.EnvironmentItemModel{ + "test_key2": "test_value2", + } + require.Equal(t, nil, env2.FillMissingDefaults()) + + // Should add to list, but not override + oldEnvSlice := []models.EnvironmentItemModel{env1, env2} + newEnvSlice, err := UpdateOrAddToEnvlist(oldEnvSlice, env1, false) + require.Equal(t, nil, err) + + env1Cnt, err := countOfEnvKeyInEnvSlice(env1, newEnvSlice) + require.Equal(t, nil, err) + require.Equal(t, 2, env1Cnt) + + env2Cnt, err := countOfEnvKeyInEnvSlice(env2, newEnvSlice) + require.Equal(t, nil, err) + require.Equal(t, 1, env2Cnt) + + // Should update list + oldEnvSlice = []models.EnvironmentItemModel{env1, env2} + newEnvSlice, err = UpdateOrAddToEnvlist(oldEnvSlice, env1, true) + require.Equal(t, nil, err) + + env1Cnt, err = countOfEnvKeyInEnvSlice(env1, newEnvSlice) + require.Equal(t, nil, err) + require.Equal(t, 1, env1Cnt) + + env2Cnt, err = countOfEnvKeyInEnvSlice(env2, newEnvSlice) + require.Equal(t, nil, err) + require.Equal(t, 1, env2Cnt) +} + +func TestRemoveDefaults(t *testing.T) { + // Filled env + env := models.EnvironmentItemModel{ + "test_key": "test_value", + models.OptionsKey: models.EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + IsTemplate: pointers.NewBoolPtr(!models.DefaultIsTemplate), + + Description: pointers.NewStringPtr(""), + Summary: pointers.NewStringPtr(""), + ValueOptions: []string{}, + IsRequired: pointers.NewBoolPtr(models.DefaultIsRequired), + IsDontChangeValue: pointers.NewBoolPtr(models.DefaultIsDontChangeValue), + IsExpand: pointers.NewBoolPtr(models.DefaultIsExpand), + IsSensitive: pointers.NewBoolPtr(models.DefaultIsSensitive), + SkipIfEmpty: pointers.NewBoolPtr(models.DefaultSkipIfEmpty), + }, + } + + require.Equal(t, nil, removeDefaults(&env)) + + opts, err := env.GetOptions() + require.Equal(t, nil, err) + + require.NotEqual(t, (*string)(nil), opts.Title) + require.Equal(t, "test_title", *opts.Title) + require.NotEqual(t, (*bool)(nil), opts.IsTemplate) + require.Equal(t, !models.DefaultIsTemplate, *opts.IsTemplate) + + require.Equal(t, (*string)(nil), opts.Description) + require.Equal(t, (*string)(nil), opts.Summary) + require.Equal(t, 0, len(opts.ValueOptions)) + require.Equal(t, (*bool)(nil), opts.IsRequired) + require.Equal(t, (*bool)(nil), opts.IsDontChangeValue) + require.Equal(t, (*bool)(nil), opts.IsExpand) + require.Equal(t, (*bool)(nil), opts.IsSensitive) + require.Equal(t, (*bool)(nil), opts.SkipIfEmpty) + +} diff --git a/vendor/github.com/bitrise-io/envman/gows.yml b/vendor/github.com/bitrise-io/envman/gows.yml new file mode 100644 index 00000000..7fd86324 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/gows.yml @@ -0,0 +1 @@ +package_name: github.com/bitrise-io/envman diff --git a/vendor/github.com/bitrise-io/envman/main.go b/vendor/github.com/bitrise-io/envman/main.go new file mode 100644 index 00000000..02d0f39e --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/bitrise-io/envman/cli" + +func main() { + cli.Run() +} diff --git a/vendor/github.com/bitrise-io/envman/models/models.go b/vendor/github.com/bitrise-io/envman/models/models.go new file mode 100644 index 00000000..aaec00a7 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/models/models.go @@ -0,0 +1,31 @@ +package models + +// EnvironmentItemOptionsModel ... +type EnvironmentItemOptionsModel struct { + // These fields are processed by envman at envman run + IsExpand *bool `json:"is_expand,omitempty" yaml:"is_expand,omitempty"` + SkipIfEmpty *bool `json:"skip_if_empty,omitempty" yaml:"skip_if_empty,omitempty"` + // These fields used only by bitrise + Title *string `json:"title,omitempty" yaml:"title,omitempty"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Category *string `json:"category,omitempty" yaml:"category,omitempty"` + ValueOptions []string `json:"value_options,omitempty" yaml:"value_options,omitempty"` + IsRequired *bool `json:"is_required,omitempty" yaml:"is_required,omitempty"` + IsDontChangeValue *bool `json:"is_dont_change_value,omitempty" yaml:"is_dont_change_value,omitempty"` + IsTemplate *bool `json:"is_template,omitempty" yaml:"is_template,omitempty"` + IsSensitive *bool `json:"is_sensitive,omitempty" yaml:"is_sensitive,omitempty"` + // + Meta map[string]interface{} `json:"meta,omitempty" yaml:"meta,omitempty"` +} + +// EnvironmentItemModel ... +type EnvironmentItemModel map[string]interface{} + +// EnvsSerializeModel ... +type EnvsSerializeModel struct { + Envs []EnvironmentItemModel `json:"envs" yaml:"envs"` +} + +// EnvsJSONListModel ... +type EnvsJSONListModel map[string]string diff --git a/vendor/github.com/bitrise-io/envman/models/models_methods.go b/vendor/github.com/bitrise-io/envman/models/models_methods.go new file mode 100644 index 00000000..9f93de66 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/models/models_methods.go @@ -0,0 +1,328 @@ +package models + +import ( + "encoding/json" + "errors" + "fmt" + "sort" + + "github.com/bitrise-io/go-utils/parseutil" + "github.com/bitrise-io/go-utils/pointers" +) + +const ( + // OptionsKey ... + OptionsKey = "opts" +) + +const ( + // DefaultIsExpand ... + DefaultIsExpand = true + // DefaultIsSensitive ... + DefaultIsSensitive = false + // DefaultSkipIfEmpty ... + DefaultSkipIfEmpty = false + + //DefaultIsRequired ... + DefaultIsRequired = false + // DefaultIsDontChangeValue ... + DefaultIsDontChangeValue = false + // DefaultIsTemplate ... + DefaultIsTemplate = false +) + +// NewEnvJSONList ... +func NewEnvJSONList(jsonStr string) (EnvsJSONListModel, error) { + list := EnvsJSONListModel{} + if err := json.Unmarshal([]byte(jsonStr), &list); err != nil { + return EnvsJSONListModel{}, err + } + return list, nil +} + +// GetKeyValuePair ... +func (env EnvironmentItemModel) GetKeyValuePair() (string, string, error) { + // Collect keys and values + keys := []string{} + values := []interface{}{} + + for key, value := range env { + keys = append(keys, key) + values = append(values, value) + } + + if len(keys) == 0 { + return "", "", errors.New("no environment key specified") + } else if len(keys) > 2 { + sort.Strings(keys) + return "", "", fmt.Errorf("more than 2 keys specified: %v", keys) + } + + // Collect env key and value + key := "" + var value interface{} + optionsFound := false + + for i := 0; i < len(keys); i++ { + k := keys[i] + if k != OptionsKey { + key = k + value = values[i] + } else { + optionsFound = true + } + } + + if key == "" { + sort.Strings(keys) + return "", "", fmt.Errorf("no environment key found, keys: %v", keys) + } + if len(keys) > 1 && !optionsFound { + sort.Strings(keys) + return "", "", fmt.Errorf("more than 1 environment key specified: %v", keys) + } + + // Cast env value to string + valueStr := "" + + if value != nil { + if str, ok := value.(string); ok { + valueStr = str + } else if str := parseutil.CastToString(value); str != "" { + valueStr = str + } else { + return "", "", fmt.Errorf("value (%#v) is not a string for key (%s)", value, key) + } + } + + return key, valueStr, nil +} + +// ParseFromInterfaceMap ... +func (envSerModel *EnvironmentItemOptionsModel) ParseFromInterfaceMap(input map[string]interface{}) error { + for keyStr, value := range input { + + switch keyStr { + case "title": + envSerModel.Title = parseutil.CastToStringPtr(value) + case "description": + envSerModel.Description = parseutil.CastToStringPtr(value) + case "summary": + envSerModel.Summary = parseutil.CastToStringPtr(value) + case "category": + envSerModel.Category = parseutil.CastToStringPtr(value) + case "value_options": + castedValue, ok := value.([]string) + if !ok { + // try with []interface{} instead and cast the + // items to string + castedValue = []string{} + interfArr, ok := value.([]interface{}) + if !ok { + return fmt.Errorf("invalid value type (%#v) for key: %s", value, keyStr) + } + for _, interfItm := range interfArr { + castedItm, ok := interfItm.(string) + if !ok { + castedItm = parseutil.CastToString(interfItm) + if castedItm == "" { + return fmt.Errorf("not a string value (%#v) in value_options", interfItm) + } + } + castedValue = append(castedValue, castedItm) + } + } + envSerModel.ValueOptions = castedValue + case "is_required": + castedBoolPtr, ok := parseutil.CastToBoolPtr(value) + if !ok { + return fmt.Errorf("failed to parse bool value (%#v) for key (%s)", value, keyStr) + } + envSerModel.IsRequired = castedBoolPtr + case "is_expand": + castedBoolPtr, ok := parseutil.CastToBoolPtr(value) + if !ok { + return fmt.Errorf("failed to parse bool value (%#v) for key (%s)", value, keyStr) + } + envSerModel.IsExpand = castedBoolPtr + case "is_sensitive": + castedBoolPtr, ok := parseutil.CastToBoolPtr(value) + if !ok { + return fmt.Errorf("failed to parse bool value (%#v) for key (%s)", value, keyStr) + } + envSerModel.IsSensitive = castedBoolPtr + case "is_dont_change_value": + castedBoolPtr, ok := parseutil.CastToBoolPtr(value) + if !ok { + return fmt.Errorf("failed to parse bool value (%#v) for key (%s)", value, keyStr) + } + envSerModel.IsDontChangeValue = castedBoolPtr + case "is_template": + castedBoolPtr, ok := parseutil.CastToBoolPtr(value) + if !ok { + return fmt.Errorf("failed to parse bool value (%#v) for key (%s)", value, keyStr) + } + envSerModel.IsTemplate = castedBoolPtr + case "skip_if_empty": + castedBoolPtr, ok := parseutil.CastToBoolPtr(value) + if !ok { + return fmt.Errorf("failed to parse bool value (%#v) for key (%s)", value, keyStr) + } + envSerModel.SkipIfEmpty = castedBoolPtr + case "meta": + castedMapStringInterface, ok := parseutil.CastToMapStringInterface(value) + if !ok { + return fmt.Errorf("failed to parse map[string]interface{} value (%#v) for key (%s)", value, keyStr) + } + envSerModel.Meta = castedMapStringInterface + default: + // intentional no-op case -- we just ignore unrecognized fields + } + } + return nil +} + +// GetOptions ... +func (env EnvironmentItemModel) GetOptions() (EnvironmentItemOptionsModel, error) { + value, found := env[OptionsKey] + if !found { + return EnvironmentItemOptionsModel{}, nil + } + + envItmCasted, ok := value.(EnvironmentItemOptionsModel) + if ok { + return envItmCasted, nil + } + + // if it's read from a file (YAML/JSON) then it's most likely not the proper type + // so cast it from the generic interface-interface map + normalizedOptsInterfaceMap := make(map[string]interface{}) + isNormalizeOK := false + if optionsInterfaceMap, ok := value.(map[interface{}]interface{}); ok { + // Try to normalize every key to String + for key, value := range optionsInterfaceMap { + keyStr, ok := key.(string) + if !ok { + return EnvironmentItemOptionsModel{}, fmt.Errorf("failed to cask options key (%#v) to string", key) + } + normalizedOptsInterfaceMap[keyStr] = value + } + isNormalizeOK = true + } else { + if castedTmp, ok := value.(map[string]interface{}); ok { + normalizedOptsInterfaceMap = castedTmp + isNormalizeOK = true + } + } + + if isNormalizeOK { + options := EnvironmentItemOptionsModel{} + err := options.ParseFromInterfaceMap(normalizedOptsInterfaceMap) + if err != nil { + return EnvironmentItemOptionsModel{}, err + } + + return options, nil + } + + return EnvironmentItemOptionsModel{}, fmt.Errorf("failed to cast options value: (%#v)", value) +} + +// Normalize ... +func (env *EnvironmentItemModel) Normalize() error { + opts, err := env.GetOptions() + if err != nil { + return err + } + (*env)[OptionsKey] = opts + return nil +} + +// Normalize - if successful this makes the model JSON serializable. +// Without this, if the object was created with e.g. a YAML parser, +// the type of `opts` might be map[interface]interface, which is not JSON serializable. +// After this call it's ensured that the type of objects is map[string]interface, +// which is JSON serializable. +func (envsSerializeObj *EnvsSerializeModel) Normalize() error { + for _, envObj := range envsSerializeObj.Envs { + if err := envObj.Normalize(); err != nil { + return err + } + } + return nil +} + +// FillMissingDefaults ... +func (env *EnvironmentItemModel) FillMissingDefaults() error { + options, err := env.GetOptions() + if err != nil { + return err + } + if options.Title == nil { + options.Title = pointers.NewStringPtr("") + } + if options.Description == nil { + options.Description = pointers.NewStringPtr("") + } + if options.Summary == nil { + options.Summary = pointers.NewStringPtr("") + } + if options.Category == nil { + options.Category = pointers.NewStringPtr("") + } + if options.ValueOptions == nil { + options.ValueOptions = []string{} + } + if options.IsRequired == nil { + options.IsRequired = pointers.NewBoolPtr(DefaultIsRequired) + } + if options.IsExpand == nil { + options.IsExpand = pointers.NewBoolPtr(DefaultIsExpand) + } + if options.IsSensitive == nil { + options.IsSensitive = pointers.NewBoolPtr(DefaultIsSensitive) + } + if options.IsDontChangeValue == nil { + options.IsDontChangeValue = pointers.NewBoolPtr(DefaultIsDontChangeValue) + } + if options.IsTemplate == nil { + options.IsTemplate = pointers.NewBoolPtr(DefaultIsTemplate) + } + if options.SkipIfEmpty == nil { + options.SkipIfEmpty = pointers.NewBoolPtr(DefaultSkipIfEmpty) + } + if options.Meta == nil { + options.Meta = map[string]interface{}{} + } + (*env)[OptionsKey] = options + return nil +} + +// Validate ... +func (env EnvironmentItemModel) Validate() error { + key, _, err := env.GetKeyValuePair() + if err != nil { + return err + } + if key == "" { + return errors.New("no environment key found") + } + _, err = env.GetOptions() + if err != nil { + return err + } + return nil +} + +// NormalizeValidateFillDefaults ... +func (env EnvironmentItemModel) NormalizeValidateFillDefaults() error { + if err := env.Normalize(); err != nil { + return err + } + + if err := env.Validate(); err != nil { + return err + } + + return env.FillMissingDefaults() +} diff --git a/vendor/github.com/bitrise-io/envman/models/models_methods_test.go b/vendor/github.com/bitrise-io/envman/models/models_methods_test.go new file mode 100644 index 00000000..0c5e1b47 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/models/models_methods_test.go @@ -0,0 +1,492 @@ +package models + +import ( + "testing" + + yaml "gopkg.in/yaml.v2" + + "encoding/json" + + "github.com/bitrise-io/go-utils/pointers" + "github.com/stretchr/testify/require" +) + +func TestGetKeyValuePair(t *testing.T) { + // Filled env + env := EnvironmentItemModel{ + "test_key": "test_value", + OptionsKey: EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + Description: pointers.NewStringPtr("test_description"), + Summary: pointers.NewStringPtr("test_summary"), + Category: pointers.NewStringPtr("category"), + ValueOptions: []string{"test_key2", "test_value2"}, + IsRequired: pointers.NewBoolPtr(true), + IsExpand: pointers.NewBoolPtr(false), + IsSensitive: pointers.NewBoolPtr(false), + IsDontChangeValue: pointers.NewBoolPtr(true), + IsTemplate: pointers.NewBoolPtr(false), + SkipIfEmpty: pointers.NewBoolPtr(false), + }, + } + + key, value, err := env.GetKeyValuePair() + require.NoError(t, err) + + require.Equal(t, "test_key", key) + require.Equal(t, "test_value", value) + + // More then 2 fields + env = EnvironmentItemModel{ + "test_key": "test_value", + "test_key1": "test_value1", + OptionsKey: EnvironmentItemOptionsModel{Title: pointers.NewStringPtr("test_title")}, + } + + key, value, err = env.GetKeyValuePair() + require.EqualError(t, err, `more than 2 keys specified: [opts test_key test_key1]`) + + // 2 key-value fields + env = EnvironmentItemModel{ + "test_key": "test_value", + "test_key1": "test_value1", + } + + key, value, err = env.GetKeyValuePair() + require.EqualError(t, err, `more than 1 environment key specified: [test_key test_key1]`) + + // Not string value + env = EnvironmentItemModel{"test_key": true} + + key, value, err = env.GetKeyValuePair() + require.NoError(t, err) + + require.Equal(t, "test_key", key) + require.Equal(t, "true", value) + + // Empty key + env = EnvironmentItemModel{"": "test_value"} + + key, value, err = env.GetKeyValuePair() + require.EqualError(t, err, "no environment key found, keys: []") + + // Missing key-value + env = EnvironmentItemModel{OptionsKey: EnvironmentItemOptionsModel{Title: pointers.NewStringPtr("test_title")}} + + key, value, err = env.GetKeyValuePair() + require.EqualError(t, err, "no environment key found, keys: [opts]") +} + +func TestParseFromInterfaceMap(t *testing.T) { + envOptions := EnvironmentItemOptionsModel{} + model := map[string]interface{}{} + + // Normal + model["title"] = "test_title" + model["value_options"] = []string{"test_key2", "test_value2"} + model["is_expand"] = true + model["is_sensitive"] = false + require.NoError(t, envOptions.ParseFromInterfaceMap(model)) + require.Equal(t, "test_title", *envOptions.Title) + require.Equal(t, "test_key2", envOptions.ValueOptions[0]) + require.Equal(t, "test_value2", envOptions.ValueOptions[1]) + require.Equal(t, true, *envOptions.IsExpand) + require.Equal(t, false, *envOptions.IsSensitive) + + // title is not a string + model = map[string]interface{}{} + model["title"] = true + require.NoError(t, envOptions.ParseFromInterfaceMap(model)) + require.Equal(t, "true", *envOptions.Title) + + // value_options is not a string slice + model = map[string]interface{}{} + model["value_options"] = []interface{}{true, false} + require.NoError(t, envOptions.ParseFromInterfaceMap(model)) + require.Equal(t, 2, len(envOptions.ValueOptions)) + require.Equal(t, "true", envOptions.ValueOptions[0]) + require.Equal(t, "false", envOptions.ValueOptions[1]) + + // is_required is not a bool + model = map[string]interface{}{} + model["is_required"] = pointers.NewBoolPtr(true) + require.Error(t, envOptions.ParseFromInterfaceMap(model)) + require.Nil(t, envOptions.IsRequired) + + model = map[string]interface{}{} + model["is_required"] = "YeS" + require.NoError(t, envOptions.ParseFromInterfaceMap(model)) + require.Equal(t, true, *envOptions.IsRequired) + + model = map[string]interface{}{} + model["is_required"] = "NO" + require.NoError(t, envOptions.ParseFromInterfaceMap(model)) + require.Equal(t, false, *envOptions.IsRequired) + + model = map[string]interface{}{} + model["is_required"] = "y" + require.NoError(t, envOptions.ParseFromInterfaceMap(model)) + require.Equal(t, true, *envOptions.IsRequired) + + model = map[string]interface{}{} + model["skip_if_empty"] = "true" + require.NoError(t, envOptions.ParseFromInterfaceMap(model)) + require.Equal(t, true, *envOptions.SkipIfEmpty) + + t.Log("parse meta field - Fail: string is not castable to map[string]interface{}") + { + model := map[string]interface{}{} + model["meta"] = "value" + require.Error(t, envOptions.ParseFromInterfaceMap(model)) + require.Nil(t, envOptions.Meta) + } + + t.Log("parse meta field") + { + serializedObj := `key: "value"` + var obj interface{} + require.NoError(t, yaml.Unmarshal([]byte(serializedObj), &obj)) + + model := map[string]interface{}{} + model["meta"] = obj + require.NoError(t, envOptions.ParseFromInterfaceMap(model)) + require.Equal(t, map[string]interface{}{"key": "value"}, envOptions.Meta) + } + + // other_key is not supported key + model = map[string]interface{}{} + model["other_key"] = true + require.NoError(t, envOptions.ParseFromInterfaceMap(model)) +} + +func TestGetOptions(t *testing.T) { + // Filled env + env := EnvironmentItemModel{ + "test_key": "test_value", + OptionsKey: EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + IsExpand: pointers.NewBoolPtr(false), + }, + } + opts, err := env.GetOptions() + require.NoError(t, err) + + require.NotNil(t, opts.Title) + require.Equal(t, "test_title", *opts.Title) + + require.NotNil(t, opts.IsExpand) + require.Equal(t, false, *opts.IsExpand) + + // Missing opts + env = EnvironmentItemModel{ + "test_key": "test_value", + } + _, err = env.GetOptions() + require.NoError(t, err) + + // Wrong opts + env = EnvironmentItemModel{ + "test_key": "test_value", + OptionsKey: map[interface{}]interface{}{ + "title": "test_title", + "test": "test_description", + }, + } + _, err = env.GetOptions() + require.NoError(t, err) +} + +func TestNormalize(t *testing.T) { + // Filled with map[string]interface{} options + env := EnvironmentItemModel{ + "test_key": "test_value", + OptionsKey: map[interface{}]interface{}{ + "title": "test_title", + "description": "test_description", + "summary": "test_summary", + "value_options": []string{"test_key2", "test_value2"}, + "is_required": true, + "skip_if_empty": false, + }, + } + + require.NoError(t, env.Normalize()) + + opts, err := env.GetOptions() + require.NoError(t, err) + + require.NotNil(t, opts.Title) + require.Equal(t, "test_title", *opts.Title) + + require.NotNil(t, opts.Description) + require.Equal(t, "test_description", *opts.Description) + + require.NotNil(t, opts.Summary) + require.Equal(t, "test_summary", *opts.Summary) + + require.Equal(t, 2, len(opts.ValueOptions)) + + require.NotNil(t, opts.IsRequired) + require.Equal(t, true, *opts.IsRequired) + + require.NotNil(t, opts.SkipIfEmpty) + require.Equal(t, false, *opts.SkipIfEmpty) + + // Filled with EnvironmentItemOptionsModel options + env = EnvironmentItemModel{ + "test_key": "test_value", + OptionsKey: EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + Description: pointers.NewStringPtr("test_description"), + Summary: pointers.NewStringPtr("test_summary"), + ValueOptions: []string{"test_key2", "test_value2"}, + IsRequired: pointers.NewBoolPtr(true), + }, + } + + require.NoError(t, env.Normalize()) + + opts, err = env.GetOptions() + require.NoError(t, err) + + require.NotNil(t, opts.Title) + require.Equal(t, "test_title", *opts.Title) + + require.NotNil(t, opts.Description) + require.Equal(t, "test_description", *opts.Description) + + require.NotNil(t, opts.Summary) + require.Equal(t, "test_summary", *opts.Summary) + + require.Equal(t, 2, len(opts.ValueOptions)) + + require.NotNil(t, opts.IsRequired) + require.Equal(t, true, *opts.IsRequired) + + // Empty options + env = EnvironmentItemModel{ + "test_key": "test_value", + } + + require.NoError(t, env.Normalize()) + + opts, err = env.GetOptions() + require.NoError(t, err) + + require.Equal(t, (*string)(nil), opts.Title) + require.Equal(t, (*string)(nil), opts.Description) + require.Equal(t, (*string)(nil), opts.Summary) + require.Equal(t, 0, len(opts.ValueOptions)) + require.Equal(t, (*bool)(nil), opts.IsRequired) + require.Equal(t, (*bool)(nil), opts.IsDontChangeValue) + require.Equal(t, (*bool)(nil), opts.IsExpand) + require.Equal(t, (*bool)(nil), opts.IsTemplate) + require.Equal(t, (*bool)(nil), opts.SkipIfEmpty) +} + +func TestFillMissingDefaults(t *testing.T) { + // Empty env + env := EnvironmentItemModel{ + "test_key": "test_value", + } + + require.NoError(t, env.FillMissingDefaults()) + + opts, err := env.GetOptions() + require.NoError(t, err) + + require.NotNil(t, opts.Description) + require.Equal(t, "", *opts.Description) + + require.NotNil(t, opts.Summary) + require.Equal(t, "", *opts.Summary) + + require.NotNil(t, opts.Category) + require.Equal(t, "", *opts.Category) + + require.NotNil(t, opts.IsRequired) + require.Equal(t, DefaultIsRequired, *opts.IsRequired) + + require.NotNil(t, opts.IsExpand) + require.Equal(t, DefaultIsExpand, *opts.IsExpand) + + require.NotNil(t, opts.IsDontChangeValue) + require.Equal(t, DefaultIsDontChangeValue, *opts.IsDontChangeValue) + + require.NotNil(t, opts.IsTemplate) + require.Equal(t, DefaultIsDontChangeValue, *opts.IsTemplate) + + require.NotNil(t, opts.SkipIfEmpty) + require.Equal(t, DefaultSkipIfEmpty, *opts.SkipIfEmpty) + + // Filled env + env = EnvironmentItemModel{ + "test_key": "test_value", + OptionsKey: EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + Description: pointers.NewStringPtr("test_description"), + Summary: pointers.NewStringPtr("test_summary"), + Category: pointers.NewStringPtr("required"), + ValueOptions: []string{"test_key2", "test_value2"}, + IsRequired: pointers.NewBoolPtr(true), + IsExpand: pointers.NewBoolPtr(true), + IsDontChangeValue: pointers.NewBoolPtr(false), + IsTemplate: pointers.NewBoolPtr(false), + SkipIfEmpty: pointers.NewBoolPtr(false), + }, + } + + require.NoError(t, env.FillMissingDefaults()) + + opts, err = env.GetOptions() + require.NoError(t, err) + + require.NotNil(t, opts.Title) + require.Equal(t, "test_title", *opts.Title) + + require.NotNil(t, opts.Description) + require.Equal(t, "test_description", *opts.Description) + + require.NotNil(t, opts.Summary) + require.Equal(t, "test_summary", *opts.Summary) + + require.NotNil(t, opts.Category) + require.Equal(t, "required", *opts.Category) + + require.Equal(t, 2, len(opts.ValueOptions)) + + require.NotNil(t, opts.IsRequired) + require.Equal(t, true, *opts.IsRequired) + + require.NotNil(t, opts.IsExpand) + require.Equal(t, true, *opts.IsExpand) + + require.NotNil(t, opts.IsDontChangeValue) + require.Equal(t, false, *opts.IsDontChangeValue) + + require.NotNil(t, opts.IsTemplate) + require.Equal(t, false, *opts.IsTemplate) + + require.NotNil(t, opts.SkipIfEmpty) + require.Equal(t, false, *opts.SkipIfEmpty) +} + +func TestValidate(t *testing.T) { + // No key-value + env := EnvironmentItemModel{ + OptionsKey: EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + Description: pointers.NewStringPtr("test_description"), + Summary: pointers.NewStringPtr("test_summary"), + Category: pointers.NewStringPtr("required"), + ValueOptions: []string{"test_key2", "test_value2"}, + IsRequired: pointers.NewBoolPtr(true), + IsExpand: pointers.NewBoolPtr(true), + IsDontChangeValue: pointers.NewBoolPtr(false), + }, + } + require.EqualError(t, env.Validate(), "no environment key found, keys: [opts]") + + // Empty key + env = EnvironmentItemModel{ + "": "test_value", + } + require.EqualError(t, env.Validate(), "no environment key found, keys: []") + + // Valid env + env = EnvironmentItemModel{ + "test_key": "test_value", + } + require.NoError(t, env.Validate()) +} + +func Test_EnvsSerializeModel_Normalize(t *testing.T) { + yamlContent := `envs: +- KEY_ONE: first value +- KEY_TWO: second value, with options + opts: + is_expand: true +` + var objFromYAML EnvsSerializeModel + require.NoError(t, yaml.Unmarshal([]byte(yamlContent), &objFromYAML)) + + // the objFromYAML object in this state can't be serialized to JSON directly, + // as the YAML parser parses the `opts` into map[interface]interface, + // which is not supported by JSON + { + _, err := json.Marshal(objFromYAML) + require.EqualError(t, err, `json: unsupported type: map[interface {}]interface {}`) + } + + // now, if we call Normalize on this object, that will convert the map[interface]interface + // into map[string]interface, which is JSON serializable + require.NoError(t, objFromYAML.Normalize()) + + // let's try the serialization again - this time it will work! + { + jsonContBytes, err := json.Marshal(objFromYAML) + require.NoError(t, err) + require.Equal(t, `{"envs":[{"KEY_ONE":"first value","opts":{}},{"KEY_TWO":"second value, with options","opts":{"is_expand":true}}]}`, string(jsonContBytes)) + } + + t.Log("test meta field") + { + yamlContent := `envs: +- KEY_ONE: first value +- KEY_TWO: second value, with options + opts: + meta: + is_expose: true +` + var objFromYAML EnvsSerializeModel + require.NoError(t, yaml.Unmarshal([]byte(yamlContent), &objFromYAML)) + + // the objFromYAML object in this state can't be serialized to JSON directly, + // as the YAML parser parses the `opts` into map[interface]interface, + // which is not supported by JSON + { + _, err := json.Marshal(objFromYAML) + require.EqualError(t, err, `json: unsupported type: map[interface {}]interface {}`) + } + + // now, if we call Normalize on this object, that will convert the map[interface]interface + // into map[string]interface, which is JSON serializable + require.NoError(t, objFromYAML.Normalize()) + + // let's try the serialization again - this time it will work! + { + jsonContBytes, err := json.Marshal(objFromYAML) + require.NoError(t, err) + require.Equal(t, `{"envs":[{"KEY_ONE":"first value","opts":{}},{"KEY_TWO":"second value, with options","opts":{"meta":{"is_expose":true}}}]}`, string(jsonContBytes)) + + var serializeModel EnvsSerializeModel + require.NoError(t, yaml.Unmarshal([]byte(yamlContent), &serializeModel)) + require.Equal(t, 2, len(serializeModel.Envs)) + for _, env := range serializeModel.Envs { + key, value, err := env.GetKeyValuePair() + require.NoError(t, err) + + if key == "KEY_ONE" { + require.Equal(t, "first value", value) + + options, err := env.GetOptions() + require.NoError(t, err) + require.Equal(t, EnvironmentItemOptionsModel{}, options) + } else if key == "KEY_TWO" { + require.Equal(t, "second value, with options", value) + + options, err := env.GetOptions() + require.NoError(t, err) + require.NotNil(t, options.Meta) + + isExposeValue := options.Meta["is_expose"] + isExpose, ok := isExposeValue.(bool) + require.Equal(t, true, ok) + require.Equal(t, true, isExpose) + } else { + t.Fatalf("unexpected key found: %s", key) + } + } + } + } +} diff --git a/vendor/github.com/bitrise-io/envman/output/output.go b/vendor/github.com/bitrise-io/envman/output/output.go new file mode 100644 index 00000000..a3e7d747 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/output/output.go @@ -0,0 +1,64 @@ +package output + +import ( + "encoding/json" + "fmt" + + "gopkg.in/yaml.v2" + + log "github.com/Sirupsen/logrus" + "github.com/urfave/cli" +) + +const ( + // FormatKey ... + FormatKey = "format" + // FormatRaw ... + FormatRaw = "raw" + // FormatJSON ... + FormatJSON = "json" + // FormatYML ... + FormatYML = "yml" +) + +// Format ... +var Format = FormatRaw + +// ConfigureOutputFormat ... +func ConfigureOutputFormat(c *cli.Context) error { + outFmt := c.String(FormatKey) + switch outFmt { + case FormatRaw, FormatJSON, FormatYML: + // valid + Format = outFmt + case "": + // default + Format = FormatRaw + default: + // invalid + return fmt.Errorf("Invalid Output Format: %s", outFmt) + } + return nil +} + +// Print ... +func Print(outModel interface{}, format string) { + switch format { + case FormatJSON: + serBytes, err := json.Marshal(outModel) + if err != nil { + log.Errorf("[.print] ERROR: %s", err) + return + } + fmt.Printf("%s\n", serBytes) + case FormatYML: + serBytes, err := yaml.Marshal(outModel) + if err != nil { + log.Errorf("[output.print] ERROR: %s", err) + return + } + fmt.Printf("%s\n", serBytes) + default: + log.Errorf("[output.print] Invalid output format: %s", format) + } +} diff --git a/vendor/github.com/bitrise-io/envman/version/build.go b/vendor/github.com/bitrise-io/envman/version/build.go new file mode 100644 index 00000000..06c70c10 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/version/build.go @@ -0,0 +1,7 @@ +package version + +// BuildNumber ... +var BuildNumber = "" + +// Commit ... +var Commit = "" diff --git a/vendor/github.com/bitrise-io/envman/version/version.go b/vendor/github.com/bitrise-io/envman/version/version.go new file mode 100644 index 00000000..ae3330e3 --- /dev/null +++ b/vendor/github.com/bitrise-io/envman/version/version.go @@ -0,0 +1,4 @@ +package version + +// VERSION ... +const VERSION = "2.1.3" diff --git a/vendor/github.com/bitrise-io/stepman/.gitignore b/vendor/github.com/bitrise-io/stepman/.gitignore new file mode 100644 index 00000000..4cc072b1 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/.gitignore @@ -0,0 +1,6 @@ +_temp/ +.bitrise +.bitrise.secrets.yml +_bin +_tmp +.gows.user.yml diff --git a/vendor/github.com/bitrise-io/stepman/Dockerfile b/vendor/github.com/bitrise-io/stepman/Dockerfile new file mode 100644 index 00000000..c1669c88 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/Dockerfile @@ -0,0 +1,31 @@ +FROM golang:1.5.2-wheezy + +ENV PROJ_NAME stepman + +RUN apt-get update + +RUN DEBIAN_FRONTEND=noninteractive apt-get -y install git mercurial curl rsync ruby + +# +# Install Bitrise CLI +RUN curl -L https://github.com/bitrise-io/bitrise/releases/download/1.2.4/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise +RUN chmod +x /usr/local/bin/bitrise +RUN bitrise setup --minimal + +# From the official Golang Dockerfile +# https://github.com/docker-library/golang/blob/master/1.4/Dockerfile +RUN mkdir -p /go/src /go/bin && chmod -R 777 /go +ENV GOPATH /go +ENV PATH /go/bin:$PATH + +RUN mkdir -p /go/src/github.com/bitrise-io/$PROJ_NAME +COPY . /go/src/github.com/bitrise-io/$PROJ_NAME + +WORKDIR /go/src/github.com/bitrise-io/$PROJ_NAME +# godep +RUN go get -u github.com/tools/godep +RUN godep restore +# install +RUN go install + +CMD $PROJ_NAME --version diff --git a/vendor/github.com/bitrise-io/stepman/Gopkg.lock b/vendor/github.com/bitrise-io/stepman/Gopkg.lock new file mode 100644 index 00000000..fb904174 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/Gopkg.lock @@ -0,0 +1,152 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + digest = "1:d972dce278dd6023fccf060a44cc51c33f0ce25f0522970c000c502766826cb8" + name = "github.com/Sirupsen/logrus" + packages = ["."] + pruneopts = "" + revision = "eef6b768ab01a0598a0a6db97bad2a37d31df1d1" + +[[projects]] + branch = "master" + digest = "1:aad9e0eeac37ffe5409673ca6d1160fca6786408cf904fba456b888bee6dba42" + name = "github.com/bitrise-io/envman" + packages = ["models"] + pruneopts = "" + revision = "41ea1b6f422eabd86189caf7da0c633208ba4760" + +[[projects]] + branch = "master" + digest = "1:6083fc4e90fe661aad326c42fa16fb85774cb55711560d1208c82ff290534836" + name = "github.com/bitrise-io/go-utils" + packages = [ + "colorstring", + "command", + "command/git", + "errorutil", + "fileutil", + "log", + "parseutil", + "pathutil", + "pointers", + "retry", + "stringutil", + "urlutil", + "versions", + ] + pruneopts = "" + revision = "2a09aab8380d7842750328aebd5671bcccea89c8" + +[[projects]] + branch = "master" + digest = "1:6825c56bedbe125a302e7516f731fa841ce9db05f873dbc5745ee807a8c3d3a2" + name = "github.com/bitrise-io/goinp" + packages = ["goinp"] + pruneopts = "" + revision = "f451b19ba4d087d8272d411ad91af5f4218fd517" + +[[projects]] + branch = "master" + digest = "1:006a3e4b6551ef1656f6917f6907f30b8b5a6ece203852f40bec891a7492e27c" + name = "github.com/bitrise-tools/colorstring" + packages = ["."] + pruneopts = "" + revision = "a8cd701151924e6beffb74a34a556b320e0cfcf7" + +[[projects]] + digest = "1:0deddd908b6b4b768cfc272c16ee61e7088a60f7fe2f06c547bd3d8e1f8b8e77" + name = "github.com/davecgh/go-spew" + packages = ["spew"] + pruneopts = "" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" + +[[projects]] + digest = "1:6a874e3ddfb9db2b42bd8c85b6875407c702fa868eed20634ff489bc896ccfd3" + name = "github.com/konsorten/go-windows-terminal-sequences" + packages = ["."] + pruneopts = "" + revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" + version = "v1.0.1" + +[[projects]] + digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + pruneopts = "" + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + branch = "master" + digest = "1:381bcbeb112a51493d9d998bbba207a529c73dbb49b3fd789e48c63fac1f192c" + name = "github.com/stretchr/testify" + packages = [ + "assert", + "require", + ] + pruneopts = "" + revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" + +[[projects]] + branch = "master" + digest = "1:438d5957cf1c26ca62975dc4b1b59242acc9e527f39ed3f6cda57ee6d3653daf" + name = "github.com/urfave/cli" + packages = ["."] + pruneopts = "" + revision = "b67dcf995b6a7b7f14fad5fcb7cc5441b05e814b" + +[[projects]] + branch = "master" + digest = "1:59b49c47c11a48f1054529207f65907c014ecf5f9a7c0d9c0f1616dec7b062ed" + name = "golang.org/x/crypto" + packages = ["ssh/terminal"] + pruneopts = "" + revision = "ff983b9c42bc9fbf91556e191cc8efb585c16908" + +[[projects]] + branch = "master" + digest = "1:30d295ca4b98f09df913be86c1ac48d1d321eb2a84543206a87704e5d25db19b" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows", + ] + pruneopts = "" + revision = "7fbe1cd0fcc20051e1fcb87fbabec4a1bacaaeba" + +[[projects]] + branch = "v2" + digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d" + name = "gopkg.in/yaml.v2" + packages = ["."] + pruneopts = "" + revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/Sirupsen/logrus", + "github.com/bitrise-io/envman/models", + "github.com/bitrise-io/go-utils/colorstring", + "github.com/bitrise-io/go-utils/command", + "github.com/bitrise-io/go-utils/command/git", + "github.com/bitrise-io/go-utils/fileutil", + "github.com/bitrise-io/go-utils/log", + "github.com/bitrise-io/go-utils/pathutil", + "github.com/bitrise-io/go-utils/pointers", + "github.com/bitrise-io/go-utils/retry", + "github.com/bitrise-io/go-utils/stringutil", + "github.com/bitrise-io/go-utils/urlutil", + "github.com/bitrise-io/go-utils/versions", + "github.com/bitrise-io/goinp/goinp", + "github.com/bitrise-tools/colorstring", + "github.com/stretchr/testify/require", + "github.com/urfave/cli", + "gopkg.in/yaml.v2", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/bitrise-io/stepman/Gopkg.toml b/vendor/github.com/bitrise-io/stepman/Gopkg.toml new file mode 100644 index 00000000..847f5be4 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/Gopkg.toml @@ -0,0 +1,27 @@ +[[constraint]] + name = "github.com/Sirupsen/logrus" + branch = "master" + +[[constraint]] + name = "github.com/bitrise-io/envman" + branch = "master" + +[[constraint]] + name = "github.com/bitrise-io/go-utils" + branch = "master" + +[[constraint]] + name = "github.com/bitrise-io/goinp" + branch = "master" + +[[constraint]] + name = "github.com/stretchr/testify" + branch = "master" + +[[constraint]] + name = "github.com/urfave/cli" + branch = "master" + +[[constraint]] + name = "gopkg.in/yaml.v2" + branch = "v2" diff --git a/vendor/github.com/bitrise-io/stepman/LICENSE b/vendor/github.com/bitrise-io/stepman/LICENSE new file mode 100644 index 00000000..a6a5c39a --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Bitrise + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/bitrise-io/stepman/README.md b/vendor/github.com/bitrise-io/stepman/README.md new file mode 100644 index 00000000..4e61ae57 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/README.md @@ -0,0 +1,35 @@ +# stepman + +You can manage decentralized StepLib Step (script) Collections with `stepman`. + +You can register multiple collections, audit them, manage the caching of individual Steps locally, +define and handle fallback URLs for downloading the Step codes (archives), +and share new Steps into public StepLib collections with `stepman`. + +**Public Beta:** this repository is still under active development, +frequent changes are expected, but we we don't plan to introduce breaking changes, +unless really necessary. **Feedback is greatly appreciated!** + +*Part of the Bitrise Continuous Integration, Delivery and Automations Stack, +with [bitrise](https://github.com/bitrise-io/bitrise) and [envman](https://github.com/bitrise-io/envman).* + + +## Install + +Check the latest release for instructions at: [https://github.com/bitrise-io/stepman/releases](https://github.com/bitrise-io/stepman/releases) + + +## Share your own Step + +Call `stepman share` and follow the guide it prints. + +### Release a new version + +- merge every code changes to the master branch + +- do not forget to merge every version related file changes: + + - update the version number (in version.go file) + - update version tests (in _tests/integration/version_test.go file) + +- push the new version tag to the master branch \ No newline at end of file diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/helper.go b/vendor/github.com/bitrise-io/stepman/_tests/integration/helper.go new file mode 100644 index 00000000..f11abd67 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/_tests/integration/helper.go @@ -0,0 +1,23 @@ +package integration + +import ( + "os" + + "github.com/bitrise-io/go-utils/command" +) + +const defaultLibraryURI = "https://github.com/bitrise-io/bitrise-steplib.git" + +func binPath() string { + return os.Getenv("INTEGRATION_TEST_BINARY_PATH") +} + +func cleanupLibrary(libraryURI string) error { + cmd := command.New(binPath(), "delete", "--collection", libraryURI) + return cmd.Run() +} + +func setupLibrary(libraryURI string) error { + cmd := command.New(binPath(), "setup", "--collection", libraryURI) + return cmd.Run() +} diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/setup_test.go b/vendor/github.com/bitrise-io/stepman/_tests/integration/setup_test.go new file mode 100644 index 00000000..7961f70b --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/_tests/integration/setup_test.go @@ -0,0 +1,60 @@ +package integration + +import ( + "testing" + + "os" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/command/git" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestSetup(t *testing.T) { + t.Log("remote library") + { + out, err := command.New(binPath(), "delete", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "setup", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "delete", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + } + + t.Log("local library") + { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__library__") + require.NoError(t, err) + defer func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }() + + repo, err := git.New(tmpDir) + require.NoError(t, err) + require.NoError(t, repo.Clone(defaultLibraryURI).Run()) + + out, err := command.New(binPath(), "delete", "-c", tmpDir).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "setup", "--local", "-c", tmpDir).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "delete", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "setup", "--local", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "delete", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "setup", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "delete", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + } +} diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/step_info_test.go b/vendor/github.com/bitrise-io/stepman/_tests/integration/step_info_test.go new file mode 100644 index 00000000..15fcd595 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/_tests/integration/step_info_test.go @@ -0,0 +1,286 @@ +package integration + +import ( + "testing" + + "strings" + + "github.com/bitrise-io/go-utils/command" + "github.com/stretchr/testify/require" +) + +func TestStepInfo(t *testing.T) { + out, err := command.New(binPath(), "setup", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + t.Log("library step") + { + out, err = command.New(binPath(), "step-info", "--collection", defaultLibraryURI, "--id", "apk-info", "--version", "1.0.4").RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + require.Equal(t, apkInfo104Defintiion, out) + } + + t.Log("library step --format json") + { + out, err = command.New(binPath(), "step-info", "--collection", defaultLibraryURI, "--id", "apk-info", "--version", "1.0.4", "--format", "json").RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + require.Equal(t, true, strings.Contains(out, apkInfo104DefintiionJSON), out) + } + + t.Log("local step") + { + out, err := command.New(binPath(), "step-info", "--collection", "path", "--id", "./test-step").RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + require.Equal(t, localTestStepDefintion, out) + } + + t.Log("local step - deprecated --step-yml flag") + { + out, err := command.New(binPath(), "step-info", "--step-yml", "./test-step").RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + require.Equal(t, localTestStepDefintion, out) + } + + t.Log("local step --format json") + { + out, err := command.New(binPath(), "step-info", "--collection", "path", "--id", "./test-step", "--format", "json").RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + require.Equal(t, localTestStepDefintionJSON, out) + } + + t.Log("git step") + { + out, err := command.New(binPath(), "step-info", "--collection", "git", "--id", "https://github.com/bitrise-steplib/steps-xamarin-user-management.git", "--version", "1.0.3").RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + require.Equal(t, gitTestStepDefinition, out) + } + + t.Log("git step --format json") + { + out, err := command.New(binPath(), "step-info", "--collection", "git", "--id", "https://github.com/bitrise-steplib/steps-xamarin-user-management.git", "--version", "1.0.3", "--format", "json").RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + require.Equal(t, true, strings.Contains(out, gitTestStepDefintionJSON), out) + } +} + +func TestStepInfoExitCode(t *testing.T) { + t.Log("default setup - desired exit code: 0") + { + out, err := command.New(binPath(), "setup", "--collection", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + } + + t.Log("latest version - desired exit code: 0") + { + out, err := command.New(binPath(), "step-info", "--collection", defaultLibraryURI, "--id", "script").RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + } + + t.Log("latest version, json format - desired exit code: 0") + { + out, err := command.New(binPath(), "step-info", "--collection", defaultLibraryURI, "--id", "script", "--format", "json").RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + } + + t.Log("invalid version -1 - desired exit code: NOT 0") + { + out, err := command.New(binPath(), "step-info", "--collection", defaultLibraryURI, "--id", "script", "--version", "-1").RunAndReturnTrimmedCombinedOutput() + require.Error(t, err, out) + } + + t.Log("invalid version -1 - desired exit code: NOT 0") + { + out, err := command.New(binPath(), "step-info", "--collection", defaultLibraryURI, "--id", "script", "--version", "-1", "--format", "json").RunAndReturnTrimmedCombinedOutput() + require.Error(t, err, out) + } +} + +const gitTestStepDefintionJSON = `{"library":"git","id":"https://github.com/bitrise-steplib/steps-xamarin-user-management.git","version":"1.0.3","info":{},"step":{"title":"Xamarin User Management","summary":"This step helps you authenticate your user with Xamarin and to download your Xamarin liceses.","description":"This step helps you authenticate your user with Xamarin and to download your Xamarin licenses.","website":"https://github.com/bitrise-steplib/steps-xamarin-user-management","source_code_url":"https://github.com/bitrise-steplib/steps-xamarin-user-management","support_url":"https://github.com/bitrise-steplib/steps-xamarin-user-management/issues","host_os_tags":["osx-10.10"],"project_type_tags":["xamarin"],"is_requires_admin_user":false,"is_always_run":true,"is_skippable":false,"run_if":".IsCI","timeout":0,"inputs":[{"build_slug":"$BITRISE_BUILD_SLUG","opts":{"is_expand":true,"skip_if_empty":false,"title":"Bitrise build slug","description":"Bitrise build slug\n","summary":"","category":"","is_required":true,"is_dont_change_value":false,"is_template":false,"is_sensitive":false}},{"opts":{"is_expand":true,"skip_if_empty":false,"title":"Xamarin.iOS License","description":"Set to yes if you want to download the Xamarin.iOS license file\n","summary":"","category":"","value_options":["yes","no"],"is_required":true,"is_dont_change_value":false,"is_template":false,"is_sensitive":false},"xamarin_ios_license":"yes"},{"opts":{"is_expand":true,"skip_if_empty":false,"title":"Xamarin.Android License","description":"Set to yes if you want to download the Xamarin.Android license file\n","summary":"","category":"","value_options":["yes","no"],"is_required":true,"is_dont_change_value":false,"is_template":false,"is_sensitive":false},"xamarin_android_license":"yes"},{"opts":{"is_expand":true,"skip_if_empty":false,"title":"Xamarin.Mac License","description":"Set to yes if you want to download the Xamarin.Mac license file\n","summary":"","category":"","value_options":["yes","no"],"is_required":true,"is_dont_change_value":false,"is_template":false,"is_sensitive":false},"xamarin_mac_license":"no"}]}` + +const gitTestStepDefinition = "\x1b[34;1m" + `Library:` + "\x1b[0m" + ` git +` + "\x1b[34;1m" + `ID:` + "\x1b[0m" + ` https://github.com/bitrise-steplib/steps-xamarin-user-management.git +` + "\x1b[34;1m" + `Version:` + "\x1b[0m" + ` 1.0.3 +` + "\x1b[34;1m" + `LatestVersion:` + "\x1b[0m" + ` +` + "\x1b[34;1m" + `Definition:` + "\x1b[0m" + ` + +title: "Xamarin User Management" +summary: This step helps you authenticate your user with Xamarin and to download your Xamarin liceses. +description: |- + This step helps you authenticate your user with Xamarin and to download your Xamarin licenses. +website: https://github.com/bitrise-steplib/steps-xamarin-user-management +source_code_url: https://github.com/bitrise-steplib/steps-xamarin-user-management +support_url: https://github.com/bitrise-steplib/steps-xamarin-user-management/issues +host_os_tags: + - osx-10.10 +project_type_tags: + - xamarin +type_tags: +is_requires_admin_user: false +is_always_run: true +is_skippable: false +run_if: .IsCI +inputs: + - build_slug: $BITRISE_BUILD_SLUG + opts: + title: Bitrise build slug + description: | + Bitrise build slug + is_required: true + is_expand: true + - xamarin_ios_license: "yes" + opts: + title: Xamarin.iOS License + description: | + Set to yes if you want to download the Xamarin.iOS license file + value_options: + - "yes" + - "no" + is_required: true + is_expand: true + - xamarin_android_license: "yes" + opts: + title: Xamarin.Android License + description: | + Set to yes if you want to download the Xamarin.Android license file + value_options: + - "yes" + - "no" + is_required: true + is_expand: true + - xamarin_mac_license: "no" + opts: + title: Xamarin.Mac License + description: | + Set to yes if you want to download the Xamarin.Mac license file + value_options: + - "yes" + - "no" + is_required: true + is_expand: true` + +const localTestStepDefintionJSON = "{\"library\":\"path\",\"id\":\"./test-step\",\"info\":{},\"step\":{\"title\":\"STEP TEMPLATE\",\"summary\":\"A short summary of the step. Don't make it too long ;)\",\"description\":\"This is a Step template.\\nContains everything what's required for a valid Stepman managed step.\\n\\nA Step's description (and generally any description property)\\ncan be a [Markdown](https://en.wikipedia.org/wiki/Markdown) formatted text.\\n\\nTo create your own Step:\\n\\n1. Create a new repository on GitHub\\n2. Copy the files from this folder into your repository\\n3. That's all, you can use it on your own machine\\n4. Once you're happy with it you can share it with others.\",\"website\":\"https://github.com/...\",\"source_code_url\":\"https://github.com/...\",\"support_url\":\"https://github.com/.../issues\",\"host_os_tags\":[\"osx-10.10\"],\"project_type_tags\":[\"ios\",\"android\",\"xamarin\"],\"type_tags\":[\"script\"],\"deps\":{\"brew\":[{\"name\":\"git\"},{\"name\":\"wget\"}],\"apt_get\":[{\"name\":\"git\"},{\"name\":\"wget\"}]},\"is_requires_admin_user\":true,\"is_always_run\":false,\"is_skippable\":false,\"run_if\":\"\",\"timeout\":0,\"inputs\":[{\"example_step_input\":\"Default Value - you can leave this empty if you want to\",\"opts\":{\"is_expand\":true,\"skip_if_empty\":false,\"title\":\"Example Step Input\",\"description\":\"Description of this input.\\n\\nCan be Markdown formatted text.\\n\",\"summary\":\"Summary. No more than 2-3 sentences.\",\"category\":\"\",\"is_required\":true,\"is_dont_change_value\":false,\"is_template\":false,\"is_sensitive\":false}}],\"outputs\":[{\"EXAMPLE_STEP_OUTPUT\":null,\"opts\":{\"is_expand\":true,\"skip_if_empty\":false,\"title\":\"Example Step Output\",\"description\":\"Description of this output.\\n\\nCan be Markdown formatted text.\\n\",\"summary\":\"Summary. No more than 2-3 sentences.\",\"category\":\"\",\"is_required\":false,\"is_dont_change_value\":false,\"is_template\":false,\"is_sensitive\":false}}]},\"definition_pth\":\"test-step/step.yml\"}" + +const localTestStepDefintion = "\x1b[34;1m" + `Library:` + "\x1b[0m" + ` path +` + "\x1b[34;1m" + `ID:` + "\x1b[0m" + ` ./test-step +` + "\x1b[34;1m" + `Version:` + "\x1b[0m" + ` +` + "\x1b[34;1m" + `LatestVersion:` + "\x1b[0m" + ` +` + "\x1b[34;1m" + `Definition:` + "\x1b[0m" + ` + +title: "STEP TEMPLATE" +summary: A short summary of the step. Don't make it too long ;) +description: |- + This is a Step template. + Contains everything what's required for a valid Stepman managed step. + + A Step's description (and generally any description property) + can be a [Markdown](https://en.wikipedia.org/wiki/Markdown) formatted text. + + To create your own Step: + + 1. Create a new repository on GitHub + 2. Copy the files from this folder into your repository + 3. That's all, you can use it on your own machine + 4. Once you're happy with it you can share it with others. +website: https://github.com/... +source_code_url: https://github.com/... +support_url: https://github.com/.../issues +host_os_tags: + - osx-10.10 +project_type_tags: + - ios + - android + - xamarin +type_tags: + - script +is_requires_admin_user: true +is_always_run: false +is_skippable: false +deps: + brew: + - name: git + - name: wget + apt_get: + - name: git + - name: wget +run_if: "" +inputs: + - example_step_input: Default Value - you can leave this empty if you want to + opts: + title: "Example Step Input" + summary: Summary. No more than 2-3 sentences. + description: | + Description of this input. + + Can be Markdown formatted text. + is_expand: true + is_required: true + value_options: [] +outputs: + - EXAMPLE_STEP_OUTPUT: + opts: + title: "Example Step Output" + summary: Summary. No more than 2-3 sentences. + description: | + Description of this output. + + Can be Markdown formatted text.` + +const apkInfo104DefintiionJSON = `{"library":"https://github.com/bitrise-io/bitrise-steplib.git","id":"apk-info","version":"1.0.4","latest_version":"1.4.2","info":{},"step":{"title":"APK info","summary":"APK Android info provider","description":"Provides all possible Android APK information as package name, version name or version code.","website":"https://github.com/thefuntasty/bitrise-step-apk-info","source_code_url":"https://github.com/thefuntasty/bitrise-step-apk-info","support_url":"https://github.com/thefuntasty/bitrise-step-apk-info/issues","published_at":"2016-10-19T15:35:00.882498804+02:00","source":{"git":"https://github.com/thefuntasty/bitrise-step-apk-info.git","commit":"104e26a8800fc9363658b5837cf4747e5f26b032"},"asset_urls":{"icon.svg":"https://bitrise-steplib-collection.s3.amazonaws.com/steps/apk-info/assets/icon.svg"},"project_type_tags":["android"],"type_tags":["android","apk"],"is_requires_admin_user":false,"is_always_run":false,"is_skippable":false,"run_if":"","timeout":0,"inputs":[{"apk_path":"$BITRISE_APK_PATH","opts":{"category":"","description":"File path to APK file to get info from.\n","is_dont_change_value":false,"is_expand":true,"is_required":true,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"APK file path"}}],"outputs":[{"ANDROID_APP_PACKAGE_NAME":null,"opts":{"category":"","description":"Android application package name, ex. com.package.my","is_dont_change_value":false,"is_expand":true,"is_required":false,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"Android application package name"}},{"ANDROID_APK_FILE_SIZE":null,"opts":{"category":"","description":"Android APK file size, in bytes","is_dont_change_value":false,"is_expand":true,"is_required":false,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"Android APK file size"}},{"ANDROID_APP_NAME":null,"opts":{"category":"","description":"Android application name from APK","is_dont_change_value":false,"is_expand":true,"is_required":false,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"Android application name"}},{"ANDROID_APP_VERSION_NAME":null,"opts":{"category":"","description":"Android application version name from APK, ex. 1.0.0","is_dont_change_value":false,"is_expand":true,"is_required":false,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"Android application version name"}},{"ANDROID_APP_VERSION_CODE":null,"opts":{"category":"","description":"Android application version code from APK, ex. 10","is_dont_change_value":false,"is_expand":true,"is_required":false,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"Android application version code"}},{"ANDROID_ICON_PATH":null,"opts":{"category":"","description":"File path to android application icon","is_dont_change_value":false,"is_expand":true,"is_required":false,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"File path to icon"}}]}` + +const apkInfo104Defintiion = "\x1b[34;1m" + `Library:` + "\x1b[0m" + ` https://github.com/bitrise-io/bitrise-steplib.git +` + "\x1b[34;1m" + `ID:` + "\x1b[0m" + ` apk-info +` + "\x1b[34;1m" + `Version:` + "\x1b[0m" + ` 1.0.4 +` + "\x1b[34;1m" + `LatestVersion:` + "\x1b[0m" + ` 1.4.2 +` + "\x1b[34;1m" + `Definition:` + "\x1b[0m" + ` + +title: APK info +summary: APK Android info provider +description: Provides all possible Android APK information as package name, version + name or version code. +website: https://github.com/thefuntasty/bitrise-step-apk-info +source_code_url: https://github.com/thefuntasty/bitrise-step-apk-info +support_url: https://github.com/thefuntasty/bitrise-step-apk-info/issues +published_at: 2016-10-19T15:35:00.882498804+02:00 +source: + git: https://github.com/thefuntasty/bitrise-step-apk-info.git + commit: 104e26a8800fc9363658b5837cf4747e5f26b032 +project_type_tags: +- android +type_tags: +- android +- apk +is_requires_admin_user: false +is_always_run: false +is_skippable: false +inputs: +- apk_path: $BITRISE_APK_PATH + opts: + description: | + File path to APK file to get info from. + is_required: true + title: APK file path +outputs: +- ANDROID_APP_PACKAGE_NAME: null + opts: + description: Android application package name, ex. com.package.my + title: Android application package name +- ANDROID_APK_FILE_SIZE: null + opts: + description: Android APK file size, in bytes + title: Android APK file size +- ANDROID_APP_NAME: null + opts: + description: Android application name from APK + title: Android application name +- ANDROID_APP_VERSION_NAME: null + opts: + description: Android application version name from APK, ex. 1.0.0 + title: Android application version name +- ANDROID_APP_VERSION_CODE: null + opts: + description: Android application version code from APK, ex. 10 + title: Android application version code +- ANDROID_ICON_PATH: null + opts: + description: File path to android application icon + title: File path to icon` diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/.gitignore b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/.gitignore new file mode 100755 index 00000000..6397b469 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/.gitignore @@ -0,0 +1 @@ +.bitrise* diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/LICENSE b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/LICENSE new file mode 100755 index 00000000..121e544d --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 bitrise-steplib + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/README.md b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/README.md new file mode 100755 index 00000000..6ba2a372 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/README.md @@ -0,0 +1,93 @@ +# My Awesome Step + +My Awesome Step is a solid starting code base for +a new Step. + + +## How to use this Step + +Can be run directly with the [bitrise CLI](https://github.com/bitrise-io/bitrise), +just `git clone` this repository, `cd` into it's folder in your Terminal/Command Line +and call `bitrise run test`. + +*Check the `bitrise.yml` file for required inputs which have to be +added to your `.bitrise.secrets.yml` file!* + +Step by step: + +1. Open up your Terminal / Command Line +2. `git clone` the repository +3. `cd` into the directory of the step (the one you just `git clone`d) +5. Create a `.bitrise.secrets.yml` file in the same directory of `bitrise.yml` - the `.bitrise.secrets.yml` is a git ignored file, you can store your secrets in +6. Check the `bitrise.yml` file for any secret you should set in `.bitrise.secrets.yml` + * Best practice is to mark these options with something like `# define these in your .bitrise.secrets.yml`, in the `app:envs` section. +7. Once you have all the required secret parameters in your `.bitrise.secrets.yml` you can just run this step with the [bitrise CLI](https://github.com/bitrise-io/bitrise): `bitrise run test` + +An example `.bitrise.secrets.yml` file: + +``` +envs: +- A_SECRET_PARAM_ONE: the value for secret one +- A_SECRET_PARAM_TWO: the value for secret two +``` + +## How to create your own step + +1. Create a new git repository for your step (**don't fork** the *step template*, create a *new* repository) +2. Copy the [step template](https://github.com/bitrise-steplib/step-template) files into your repository +3. Fill the `step.sh` with your functionality +4. Wire out your inputs to `step.yml` (`inputs` section) +5. Fill out the other parts of the `step.yml` too +6. Provide test values for the inputs in the `bitrise.yml` +7. Run your step with `bitrise run test` - if it works, you're ready + +__For Step development guidelines & best practices__ check this documentation: [https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md](https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md). + +**NOTE:** + +If you want to use your step in your project's `bitrise.yml`: + +1. git push the step into it's repository +2. reference it in your `bitrise.yml` with the `git::PUBLIC-GIT-CLONE-URL@BRANCH` step reference style: + +``` +- git::https://github.com/user/my-step.git@branch: + title: My step + inputs: + - my_input_1: "my value 1" + - my_input_2: "my value 2" +``` + +You can find more examples of step reference styles +in the [bitrise CLI repository](https://github.com/bitrise-io/bitrise/blob/master/_examples/tutorials/steps-and-workflows/bitrise.yml#L65). + +## How to contribute to this Step + +1. Fork this repository +2. `git clone` it +3. Create a branch you'll work on +4. To use/test the step just follow the **How to use this Step** section +5. Do the changes you want to +6. Run/test the step before sending your contribution + * You can also test the step in your `bitrise` project, either on your Mac or on [bitrise.io](https://www.bitrise.io) + * You just have to replace the step ID in your project's `bitrise.yml` with either a relative path, or with a git URL format + * (relative) path format: instead of `- original-step-id:` use `- path::./relative/path/of/script/on/your/Mac:` + * direct git URL format: instead of `- original-step-id:` use `- git::https://github.com/user/step.git@branch:` + * You can find more example of alternative step referencing at: https://github.com/bitrise-io/bitrise/blob/master/_examples/tutorials/steps-and-workflows/bitrise.yml +7. Once you're done just commit your changes & create a Pull Request + + +## Share your own Step + +You can share your Step or step version with the [bitrise CLI](https://github.com/bitrise-io/bitrise). If you use the `bitrise.yml` included in this repository, all you have to do is: + +1. In your Terminal / Command Line `cd` into this directory (where the `bitrise.yml` of the step is located) +1. Run: `bitrise run test` to test the step +1. Run: `bitrise run audit-this-step` to audit the `step.yml` +1. Check the `share-this-step` workflow in the `bitrise.yml`, and fill out the + `envs` if you haven't done so already (don't forget to bump the version number if this is an update + of your step!) +1. Then run: `bitrise run share-this-step` to share the step (version) you specified in the `envs` +1. Send the Pull Request, as described in the logs of `bitrise run share-this-step` + +That's all ;) diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/bitrise.yml b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/bitrise.yml new file mode 100755 index 00000000..da3e64f4 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/bitrise.yml @@ -0,0 +1,84 @@ +format_version: 1.1.0 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + +app: + envs: + # define these in your .bitrise.secrets.yml + - A_SECRET_PARAM: $A_SECRET_PARAM + +workflows: + test: + steps: + - change-workdir: + title: Switch working dir to test / _tmp dir + description: |- + To prevent step testing issues, like referencing relative + files with just './some-file' in the step's code, which would + work for testing the step from this directory directly + but would break if the step is included in another `bitrise.yml`. + run_if: true + inputs: + - path: ./_tmp + - is_create_path: true + - path::./: + title: Step Test + description: |- + The example input has a default value, + you can overwrite it if you want to, just like we did below, + but the step would use the default value specified in the `step.yml` + file if you would not specify another value. + run_if: true + inputs: + - example_step_input: Example Step Input's value + + + # ---------------------------------------------------------------- + # --- workflows to Share this step into a Step Library + audit-this-step: + steps: + - script: + inputs: + - content: |- + #!/bin/bash + set -ex + stepman audit --step-yml ./step.yml + + share-this-step: + envs: + # if you want to share this step into a StepLib + - MY_STEPLIB_REPO_FORK_GIT_URL: + - STEP_ID_IN_STEPLIB: + - STEP_GIT_VERION_TAG_TO_SHARE: + - STEP_GIT_CLONE_URL: + description: |- + If this is the first time you try to share a Step you should + first call: $ bitrise share + + This will print you a guide, and information about how Step sharing + works. Please read it at least once! + + As noted in the Step sharing guide you'll have to fork the + StepLib you want to share this step into. Once you're done with forking + the repository you should set your own fork's git clone URL + in the `.bitrise.secrets.yml` file, or here in the `envs` section, + as the value of the `MY_STEPLIB_REPO_FORK_GIT_URL` environment. + + You're now ready to share this Step, just make sure that + the `STEP_ID_IN_STEPLIB` and `STEP_GIT_VERION_TAG_TO_SHARE` + environments are set to the desired values! + + To share this Step into a StepLib you can just run: $ bitrise run share-this-step + + Once it finishes the only thing left is to actually create a Pull Request, + the way described in the guide printed at the end of the process. + before_run: + - audit-this-step + steps: + - script: + inputs: + - content: |- + #!/bin/bash + set -ex + bitrise share start -c ${MY_STEPLIB_REPO_FORK_GIT_URL} + bitrise share create --stepid ${STEP_ID_IN_STEPLIB} --tag ${STEP_GIT_VERION_TAG_TO_SHARE} --git ${STEP_GIT_CLONE_URL} + bitrise share finish diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.sh b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.sh new file mode 100755 index 00000000..2cf7beee --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +echo "This is the value specified for the input 'example_step_input': ${example_step_input}" + +# +# --- Export Environment Variables for other Steps: +# You can export Environment Variables for other Steps with +# envman, which is automatically installed by `bitrise setup`. +# A very simple example: +# envman add --key EXAMPLE_STEP_OUTPUT --value 'the value you want to share' +# Envman can handle piped inputs, which is useful if the text you want to +# share is complex and you don't want to deal with proper bash escaping: +# cat file_with_complex_input | envman add --KEY EXAMPLE_STEP_OUTPUT +# You can find more usage examples on envman's GitHub page +# at: https://github.com/bitrise-io/envman + +# +# --- Exit codes: +# The exit code of your Step is very important. If you return +# with a 0 exit code `bitrise` will register your Step as "successful". +# Any non zero exit code will be registered as "failed" by `bitrise`. diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.yml b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.yml new file mode 100755 index 00000000..f188ee2d --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.yml @@ -0,0 +1,58 @@ +title: "STEP TEMPLATE" +summary: A short summary of the step. Don't make it too long ;) +description: |- + This is a Step template. + Contains everything what's required for a valid Stepman managed step. + + A Step's description (and generally any description property) + can be a [Markdown](https://en.wikipedia.org/wiki/Markdown) formatted text. + + To create your own Step: + + 1. Create a new repository on GitHub + 2. Copy the files from this folder into your repository + 3. That's all, you can use it on your own machine + 4. Once you're happy with it you can share it with others. +website: https://github.com/... +source_code_url: https://github.com/... +support_url: https://github.com/.../issues +host_os_tags: + - osx-10.10 +project_type_tags: + - ios + - android + - xamarin +type_tags: + - script +is_requires_admin_user: true +is_always_run: false +is_skippable: false +deps: + brew: + - name: git + - name: wget + apt_get: + - name: git + - name: wget +run_if: "" +inputs: + - example_step_input: Default Value - you can leave this empty if you want to + opts: + title: "Example Step Input" + summary: Summary. No more than 2-3 sentences. + description: | + Description of this input. + + Can be Markdown formatted text. + is_expand: true + is_required: true + value_options: [] +outputs: + - EXAMPLE_STEP_OUTPUT: + opts: + title: "Example Step Output" + summary: Summary. No more than 2-3 sentences. + description: | + Description of this output. + + Can be Markdown formatted text. diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/update_test.go b/vendor/github.com/bitrise-io/stepman/_tests/integration/update_test.go new file mode 100644 index 00000000..5dd3d187 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/_tests/integration/update_test.go @@ -0,0 +1,52 @@ +package integration + +import ( + "os" + "testing" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/command/git" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/stretchr/testify/require" +) + +func TestUpdate(t *testing.T) { + t.Log("remote library") + { + out, err := command.New(binPath(), "delete", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "setup", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "update", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + } + + t.Log("local library") + { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__library__") + require.NoError(t, err) + defer func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }() + repo, err := git.New(tmpDir) + require.NoError(t, err) + require.NoError(t, repo.Clone(defaultLibraryURI).Run()) + + out, err := command.New(binPath(), "delete", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "setup", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "update", "-c", tmpDir).RunAndReturnTrimmedCombinedOutput() + require.Error(t, err, out) + + out, err = command.New(binPath(), "update", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + + out, err = command.New(binPath(), "delete", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() + require.NoError(t, err, out) + } +} diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/version_test.go b/vendor/github.com/bitrise-io/stepman/_tests/integration/version_test.go new file mode 100644 index 00000000..2dc8fcf3 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/_tests/integration/version_test.go @@ -0,0 +1,34 @@ +package integration + +import ( + "fmt" + "runtime" + "testing" + + "github.com/bitrise-io/go-utils/command" + "github.com/stretchr/testify/require" +) + +func Test_VersionOutput(t *testing.T) { + t.Log("Version") + { + out, err := command.RunCommandAndReturnCombinedStdoutAndStderr(binPath(), "version") + require.NoError(t, err, out) + require.Equal(t, "0.11.0", out) + } + + t.Log("Version --full") + { + out, err := command.RunCommandAndReturnCombinedStdoutAndStderr(binPath(), "version", "--full") + require.NoError(t, err, out) + + expectedOSVersion := fmt.Sprintf("%s (%s)", runtime.GOOS, runtime.GOARCH) + expectedVersionOut := fmt.Sprintf(`version: 0.11.0 +os: %s +go: %s +build_number: +commit:`, expectedOSVersion, runtime.Version()) + + require.Equal(t, expectedVersionOut, out) + } +} diff --git a/vendor/github.com/bitrise-io/stepman/bitrise.yml b/vendor/github.com/bitrise-io/stepman/bitrise.yml new file mode 100644 index 00000000..e0c6e412 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/bitrise.yml @@ -0,0 +1,99 @@ +format_version: "5" +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +project_type: other + +app: + envs: + - BIN_NAME: stepman + +workflows: + # ---------------------------------------------------------------- + # --- workflows for CI and testing + test: + title: Runs tests + steps: + - go-list: + - golint: + - errcheck: + - go-test: + - codecov: + run_if: .IsCI + inputs: + - other_options: -f ${GO_CODE_COVERAGE_REPORT_PATH} + - CODECOV_TOKEN: "$CODECOV_UPLOAD_TOKEN" + - script: + title: Run integration tests + inputs: + - content: |- + #!/usr/bin/env bash + set -ex + + current_stepman="$(pwd)/_tmp/test_stepman" + go build -o "$current_stepman" + + export PR="" PULL_REQUEST_ID="" + export INTEGRATION_TEST_BINARY_PATH="$current_stepman" + go test -v ./_tests/integration/... + + create-binaries: + title: Create binaries + steps: + - script: + title: Create binaries + inputs: + - content: | + #!/bin/bash + set -ex + + echo + echo "Create final binaries" + echo " Build number: $BITRISE_BUILD_NUMBER" + + export ARCH=x86_64 + export GOARCH=amd64 + + # Create Darwin bin + export OS=Darwin + export GOOS=darwin + + DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" + echo " Create final Darwin binary at: $DEPLOY_PATH" + + version_package="github.com/bitrise-io/stepman/version" + + go build \ + -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ + -o "$DEPLOY_PATH" + + envman add --key OSX_DEPLOY_PATH --value $DEPLOY_PATH + cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH + echo " Copy final Darwin binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" + + + # Create Linux binary + export OS=Linux + export GOOS=linux + + DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" + echo " Create final Linux binary at: $DEPLOY_PATH" + + go build \ + -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ + -o "$DEPLOY_PATH" + + envman add --key LINUX_DEPLOY_PATH --value $DEPLOY_PATH + cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH + echo " Copy final Linux binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" + + dep-update: + title: Dep update + steps: + - script: + title: Dependency update + inputs: + - content: |- + #!/bin/bash + set -ex + go get -u -v github.com/golang/dep/cmd/dep + dep ensure -v + dep ensure -v -update diff --git a/vendor/github.com/bitrise-io/stepman/cli/activate.go b/vendor/github.com/bitrise-io/stepman/cli/activate.go new file mode 100644 index 00000000..53364ea1 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/activate.go @@ -0,0 +1,138 @@ +package cli + +import ( + "os" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/stepman/stepman" + "github.com/urfave/cli" +) + +func activate(c *cli.Context) error { + // Input validation + collectionURI := c.String(CollectionKey) + if collectionURI == "" { + log.Fatalf("No step collection specified") + } + + id := c.String(IDKey) + if id == "" { + log.Fatalf("Missing step id") + } + + path := c.String(PathKey) + if path == "" { + log.Fatalf("Missing destination path") + } + + version := c.String(VersionKey) + copyYML := c.String(CopyYMLKey) + update := c.Bool(UpdateKey) + + // Check if step exist in collection + collection, err := stepman.ReadStepSpec(collectionURI) + if err != nil { + log.Fatalf("Failed to read steps spec (spec.json), error: %s", err) + } + + _, stepFound, versionFound := collection.GetStep(id, version) + if !stepFound || !versionFound { + if !update { + if !stepFound { + log.Fatalf("Collection doesn't contain step with id: %s", id) + } else if !versionFound { + log.Fatalf("Collection doesn't contain step (%s) with version: %s", id, version) + } + } + + if !stepFound { + log.Infof("Collection doesn't contain step with id: %s -- Updating StepLib", id) + } else if !versionFound { + log.Infof("Collection doesn't contain step (%s) with version: %s -- Updating StepLib", id, version) + } + + collection, err = stepman.UpdateLibrary(collectionURI) + if err != nil { + log.Fatalf("Failed to update collection (%s), err: %s", collectionURI, err) + } + + _, stepFound, versionFound := collection.GetStep(id, version) + if !stepFound { + if !stepFound { + log.Fatalf("Collection doesn't contain step with id: %s", id) + } else if !versionFound { + log.Fatalf("Collection doesn't contain step (%s) with version: %s", id, version) + } + } + } + + // If version doesn't provided use latest + if version == "" { + latest, err := collection.GetLatestStepVersion(id) + if err != nil { + log.Fatalf("Failed to get step latest version, error: %s", err) + } + version = latest + } + + // Check step exist in local cache + step, stepFound, versionFound := collection.GetStep(id, version) + if !stepFound { + log.Fatalf("Collection doesn't contain step with id: %s", id) + } else if !versionFound { + log.Fatalf("Collection doesn't contain step (%s) with version: %s", id, version) + } + + if step.Source == nil { + log.Fatalf("Invalid step, missing Source property") + } + + route, found := stepman.ReadRoute(collectionURI) + if !found { + log.Fatalf("No route found for lib: %s", collectionURI) + } + + stepCacheDir := stepman.GetStepCacheDirPath(route, id, version) + if exist, err := pathutil.IsPathExists(stepCacheDir); err != nil { + log.Fatalf("Failed to check path, error: %s", err) + } else if !exist { + if err := stepman.DownloadStep(collectionURI, collection, id, version, step.Source.Commit); err != nil { + log.Fatalf("Failed to download step, error: %s", err) + } + } + + // Copy to specified path + srcFolder := stepCacheDir + destFolder := path + + if exist, err := pathutil.IsPathExists(destFolder); err != nil { + log.Fatalf("Failed to check path, error: %s", err) + } else if !exist { + if err := os.MkdirAll(destFolder, 0777); err != nil { + log.Fatalf("Failed to create path, error: %s", err) + } + } + + if err = command.CopyDir(srcFolder+"/", destFolder, true); err != nil { + log.Fatalf("Failed to copy step, error: %s", err) + } + + // Copy step.yml to specified path + if copyYML != "" { + if exist, err := pathutil.IsPathExists(copyYML); err != nil { + log.Fatalf("Failed to check path, error: %s", err) + } else if exist { + log.Fatalf("Failed to copy step.yml, error: destination path exists") + } + + stepCollectionDir := stepman.GetStepCollectionDirPath(route, id, version) + stepYMLSrc := stepCollectionDir + "/step.yml" + if err = command.CopyFile(stepYMLSrc, copyYML); err != nil { + log.Fatalf("Failed to copy step.yml, error: %s", err) + } + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/audit.go b/vendor/github.com/bitrise-io/stepman/cli/audit.go new file mode 100644 index 00000000..91a96195 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/audit.go @@ -0,0 +1,164 @@ +package cli + +import ( + "fmt" + "strings" + "time" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/command/git" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/retry" + "github.com/bitrise-io/stepman/models" + "github.com/bitrise-io/stepman/stepman" + "github.com/urfave/cli" +) + +func auditStepBeforeShare(pth string) error { + stepModel, err := stepman.ParseStepDefinition(pth, false) + if err != nil { + return err + } + return stepModel.AuditBeforeShare() +} + +func detectStepIDAndVersionFromPath(pth string) (stepID, stepVersion string, err error) { + pathComps := strings.Split(pth, "/") + if len(pathComps) < 4 { + err = fmt.Errorf("Path should contain at least 4 components: steps, step-id, step-version, step.yml: %s", pth) + return + } + // we only care about the last 4 component of the path + pathComps = pathComps[len(pathComps)-4:] + if pathComps[0] != "steps" { + err = fmt.Errorf("Invalid step.yml path, 'steps' should be included right before the step-id: %s", pth) + return + } + if pathComps[3] != "step.yml" { + err = fmt.Errorf("Invalid step.yml path, should end with 'step.yml': %s", pth) + return + } + stepID = pathComps[1] + stepVersion = pathComps[2] + return +} + +func auditStepBeforeSharePullRequest(pth string) error { + stepID, version, err := detectStepIDAndVersionFromPath(pth) + if err != nil { + return err + } + + stepModel, err := stepman.ParseStepDefinition(pth, false) + if err != nil { + return err + } + + return auditStepModelBeforeSharePullRequest(stepModel, stepID, version) +} + +func auditStepModelBeforeSharePullRequest(step models.StepModel, stepID, version string) error { + if err := step.Audit(); err != nil { + return fmt.Errorf("Failed to audit step infos, error: %s", err) + } + + pth, err := pathutil.NormalizedOSTempDirPath(stepID + version) + if err != nil { + return fmt.Errorf("Failed to create a temporary directory for the step's audit, error: %s", err) + } + + if step.Source == nil { + return fmt.Errorf("Missing Source porperty") + } + + repo, err := git.New(pth) + if err != nil { + return err + } + + err = retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { + return repo.CloneTagOrBranch(step.Source.Git, version).Run() + }) + if err != nil { + return fmt.Errorf("Failed to git-clone the step (url: %s) version (%s), error: %s", + step.Source.Git, version, err) + } + + latestCommit, err := repo.RevParse("HEAD").RunAndReturnTrimmedCombinedOutput() + if err != nil { + return fmt.Errorf("Failed to get commit, error: %s", err) + } + if latestCommit != step.Source.Commit { + return fmt.Errorf("Step commit hash (%s) should be the latest commit hash (%s) on git tag", step.Source.Commit, latestCommit) + } + + return nil +} + +func auditStepLibBeforeSharePullRequest(gitURI string) error { + if exist, err := stepman.RootExistForLibrary(gitURI); err != nil { + return err + } else if !exist { + return fmt.Errorf("Missing routing for collection, call 'stepman setup -c %s' before audit", gitURI) + } + + collection, err := stepman.ReadStepSpec(gitURI) + if err != nil { + return err + } + + for stepID, stepGroup := range collection.Steps { + log.Debugf("Start audit StepGrup, with ID: (%s)", stepID) + for version, step := range stepGroup.Versions { + log.Debugf("Start audit Step (%s) (%s)", stepID, version) + if err := auditStepModelBeforeSharePullRequest(step, stepID, version); err != nil { + log.Errorf(" * "+colorstring.Redf("[FAILED] ")+"Failed audit (%s) (%s)", stepID, version) + return fmt.Errorf(" Error: %s", err.Error()) + } + log.Infof(" * "+colorstring.Greenf("[OK] ")+"Success audit (%s) (%s)", stepID, version) + } + } + return nil +} + +func audit(c *cli.Context) error { + // Input validation + beforePR := c.Bool("before-pr") + + collectionURI := c.String("collection") + if collectionURI != "" { + if beforePR { + log.Warnln("before-pr flag is used only for Step audit") + } + + if err := auditStepLibBeforeSharePullRequest(collectionURI); err != nil { + log.Fatalf("Audit Step Collection failed, err: %s", err) + } + } else { + stepYMLPath := c.String("step-yml") + if stepYMLPath != "" { + if exist, err := pathutil.IsPathExists(stepYMLPath); err != nil { + log.Fatalf("Failed to check path (%s), err: %s", stepYMLPath, err) + } else if !exist { + log.Fatalf("step.yml doesn't exist at: %s", stepYMLPath) + } + + if beforePR { + if err := auditStepBeforeSharePullRequest(stepYMLPath); err != nil { + log.Fatalf("Step audit failed, err: %s", err) + } + } else { + if err := auditStepBeforeShare(stepYMLPath); err != nil { + log.Fatalf("Step audit failed, err: %s", err) + } + } + + log.Infof(" * "+colorstring.Greenf("[OK] ")+"Success audit (%s)", stepYMLPath) + } else { + log.Fatalln("'stepman audit' command needs --collection or --step-yml flag") + } + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/audit_test.go b/vendor/github.com/bitrise-io/stepman/cli/audit_test.go new file mode 100644 index 00000000..34d0d1a2 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/audit_test.go @@ -0,0 +1,41 @@ +package cli + +import ( + "testing" + "time" + + "github.com/bitrise-io/go-utils/pointers" + "github.com/bitrise-io/stepman/models" +) + +// Test - Stepman audit step +// Checks if step Source.Commit meets the git commit hash of realese version +// 'auditStep(...)' calls 'cmdex.GitCloneTagOrBranchAndValidateCommitHash(...)', which method validates the commit hash +func TestValidateStepCommitHash(t *testing.T) { + // Slack step - valid hash + stepSlack := models.StepModel{ + Title: pointers.NewStringPtr("hash_test"), + Summary: pointers.NewStringPtr("summary"), + Website: pointers.NewStringPtr("website"), + PublishedAt: pointers.NewTimePtr(time.Date(2012, time.January, 1, 0, 0, 0, 0, time.UTC)), + Source: &models.StepSourceModel{ + Git: "https://github.com/bitrise-io/steps-slack-message.git", + Commit: "756f39f76f94d525aaea2fc2d0c5a23799f8ec97", + }, + } + if err := auditStepModelBeforeSharePullRequest(stepSlack, "slack", "2.1.0"); err != nil { + t.Fatal("Step audit failed:", err) + } + + // Slack step - invalid hash + stepSlack.Source.Commit = "should fail commit" + if err := auditStepModelBeforeSharePullRequest(stepSlack, "slack", "2.1.0"); err == nil { + t.Fatal("Step audit should fail") + } + + // Slack step - empty hash + stepSlack.Source.Commit = "" + if err := auditStepModelBeforeSharePullRequest(stepSlack, "slack", "2.1.0"); err == nil { + t.Fatal("Step audit should fail") + } +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/cli.go b/vendor/github.com/bitrise-io/stepman/cli/cli.go new file mode 100644 index 00000000..c599987f --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/cli.go @@ -0,0 +1,67 @@ +package cli + +import ( + "fmt" + "os" + "path" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/stepman/stepman" + "github.com/bitrise-io/stepman/version" + "github.com/urfave/cli" +) + +func initLogFormatter() { + log.SetFormatter(&log.TextFormatter{ + ForceColors: true, + FullTimestamp: true, + TimestampFormat: "15:04:05", + }) +} + +func before(c *cli.Context) error { + initLogFormatter() + initHelpAndVersionFlags() + initAppHelpTemplate() + + // Log level + logLevel, err := log.ParseLevel(c.String(LogLevelKey)) + if err != nil { + return fmt.Errorf("Failed to parse log level, error: %s", err) + } + log.SetLevel(logLevel) + + // Setup + err = stepman.CreateStepManDirIfNeeded() + if err != nil { + return err + } + + return nil +} + +func printVersion(c *cli.Context) { + fmt.Println(c.App.Version) +} + +// Run ... +func Run() { + cli.VersionPrinter = printVersion + + app := cli.NewApp() + app.Name = path.Base(os.Args[0]) + app.Usage = "Step manager" + app.Version = version.VERSION + + app.Author = "" + app.Email = "" + + app.Before = before + + app.Flags = flags + app.Commands = commands + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/collections.go b/vendor/github.com/bitrise-io/stepman/cli/collections.go new file mode 100644 index 00000000..d7a7c92d --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/collections.go @@ -0,0 +1,107 @@ +package cli + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/bitrise-io/go-utils/colorstring" + flog "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/stepman/models" + "github.com/bitrise-io/stepman/stepman" + "github.com/urfave/cli" +) + +// ------------- +// Output Models + +// OutputModel ... +type OutputModel struct { + Data *([]models.SteplibInfoModel) `json:"data,omitempty" yaml:"data,omitempty"` + Error string `json:"error,omitempty" yaml:"error,omitempty"` +} + +// String ... +func (output OutputModel) String() string { + if output.Error != "" { + return fmt.Sprintf("%s: %s", colorstring.Red("Error"), output.Error) + } + + if output.Data == nil { + return "" + } + + str := "" + steplibInfos := *output.Data + for idx, steplibInfo := range steplibInfos { + str += colorstring.Bluef("%s\n", steplibInfo.URI) + str += fmt.Sprintf(" spec_path: %s\n", steplibInfo.SpecPath) + if idx != len(steplibInfos)-1 { + str += "\n" + } + } + return str +} + +// JSON ... +func (output OutputModel) JSON() string { + bytes, err := json.Marshal(output) + if err != nil { + return fmt.Sprintf(`"Failed to marshal output (%#v), err: %s"`, output, err) + } + return string(bytes) +} + +// NewOutput ... +func NewOutput(steplibInfos []models.SteplibInfoModel) OutputModel { + return OutputModel{ + Data: &steplibInfos, + } +} + +// NewErrorOutput ... +func NewErrorOutput(format string, v ...interface{}) OutputModel { + return OutputModel{ + Error: fmt.Sprintf(format, v...), + } +} + +// ------------- + +func collections(c *cli.Context) error { + format := c.String(FormatKey) + if format == "" { + format = OutputFormatRaw + } + + var log flog.Logger + if format == OutputFormatRaw { + log = flog.NewDefaultRawLogger() + } else if format == OutputFormatJSON { + log = flog.NewDefaultJSONLoger() + } else { + fmt.Printf("%s: invalid format: %s\n", colorstring.Red("Error"), format) + os.Exit(1) + } + + steplibInfos := []models.SteplibInfoModel{} + stepLibURIs := stepman.GetAllStepCollectionPath() + for _, steplibURI := range stepLibURIs { + route, found := stepman.ReadRoute(steplibURI) + if !found { + log.Print(NewErrorOutput("No routing found for steplib: %s", steplibURI)) + os.Exit(1) + } + + specPth := stepman.GetStepSpecPath(route) + + steplibInfos = append(steplibInfos, models.SteplibInfoModel{ + URI: steplibURI, + SpecPath: specPth, + }) + } + + log.Print(NewOutput(steplibInfos)) + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/commands.go b/vendor/github.com/bitrise-io/stepman/cli/commands.go new file mode 100644 index 00000000..0b713371 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/commands.go @@ -0,0 +1,177 @@ +package cli + +import "github.com/urfave/cli" + +var ( + commands = []cli.Command{ + { + Name: "version", + Usage: "Prints the version", + Action: printVersionCmd, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "Output format. Accepted: json, yml", + }, + cli.BoolFlag{ + Name: "full", + Usage: "Prints the build number and commit as well.", + }, + }, + }, + { + Name: "setup", + Usage: "Initialize the specified collection, it's required before using a collection.", + Action: setup, + Flags: []cli.Flag{ + flCollection, + flLocalCollection, + flCopySpecJSON, + }, + }, + { + Name: "update", + Usage: "Update the collection, if no --collection flag provided, all collections will updated.", + Action: update, + Flags: []cli.Flag{ + flCollection, + }, + }, + { + Name: "collections", + Usage: "List of localy available collections.", + Action: collections, + Flags: []cli.Flag{ + flFormat, + }, + }, + { + Name: "step-list", + Usage: "List of available steps.", + Action: stepList, + Flags: []cli.Flag{ + flCollection, + flFormat, + }, + }, + stepInfoCommand, + { + Name: "download", + Usage: "Download the step with provided --id and --version, from specified --collection, into local step downloads cache. If no --version defined, the latest version of the step (latest found in the collection) will be downloaded into the cache.", + Action: download, + Flags: []cli.Flag{ + flCollection, + flID, + flVersion, + flUpdate, + }, + }, + { + Name: "activate", + Usage: "Copy the step with specified --id, and --version, into provided path. If --version flag is not set, the latest version of the step will be used. If --copyyml flag is set, step.yml will be copied to the given path.", + Action: activate, + Flags: []cli.Flag{ + flCollection, + flID, + flVersion, + flPath, + flCopyYML, + flUpdate, + }, + }, + { + Name: "audit", + Usage: "Validates Step or Step Collection.", + Action: audit, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: CollectionKey + ", " + collectionKeyShort, + Usage: "For validating Step Collection before share.", + EnvVar: CollectionPathEnvKey, + }, + cli.StringFlag{ + Name: "step-yml", + Usage: "For validating Step before share or before share Pull Request.", + }, + cli.BoolFlag{ + Name: "before-pr", + Usage: "If flag is set, Step Pull Request required fields will be checked to. Note: only for Step audit.", + }, + }, + }, + { + Name: "share", + Usage: "Publish your step.", + Action: share, + Flags: []cli.Flag{ + flToolMode, + }, + Subcommands: []cli.Command{ + { + Name: "start", + Usage: "Preparations for publishing.", + Action: start, + Flags: []cli.Flag{ + flCollection, + flToolMode, + }, + }, + { + Name: "create", + Usage: "Create your change - add it to your own copy of the collection.", + Action: create, + Flags: []cli.Flag{ + flTag, + flGit, + flStepID, + flToolMode, + }, + }, + { + Name: "audit", + Usage: "Validates the step collection.", + Action: shareAudit, + Flags: []cli.Flag{ + flToolMode, + }, + }, + { + Name: "finish", + Usage: "Finish up.", + Action: finish, + Flags: []cli.Flag{ + flToolMode, + }, + }, + }, + }, + { + Name: "delete", + Usage: "Delete the specified collection from local caches.", + Action: deleteStepLib, + Flags: []cli.Flag{ + flCollection, + }, + }, + { + Name: "export-spec", + Usage: "Export the generated StepLib spec.", + Action: export, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "steplib", + Usage: "StepLib URI", + }, + cli.StringFlag{ + Name: "output", + Usage: "Output path", + }, + cli.StringFlag{ + Name: "export-type", + Value: "full", + Usage: "Export type, options: [full, latest, minimal]", + }, + }, + }, + } +) diff --git a/vendor/github.com/bitrise-io/stepman/cli/delete_steplib.go b/vendor/github.com/bitrise-io/stepman/cli/delete_steplib.go new file mode 100644 index 00000000..cc7b6bf2 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/delete_steplib.go @@ -0,0 +1,35 @@ +package cli + +import ( + "fmt" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/stepman/stepman" + "github.com/urfave/cli" +) + +func deleteStepLib(c *cli.Context) error { + // Input validation + collectionURI := c.String(CollectionKey) + if collectionURI == "" { + return fmt.Errorf("Missing required input: collection") + } + + log.Infof("Delete StepLib: %s", collectionURI) + + route, found := stepman.ReadRoute(collectionURI) + if !found { + log.Warnf("No route found for collection: %s, cleaning up routing..", collectionURI) + if err := stepman.CleanupDanglingLibrary(collectionURI); err != nil { + log.Errorf("Error cleaning up lib: %s", collectionURI) + } + log.Infof("Call 'stepman setup -c %s' for a clean setup", collectionURI) + return nil + } + + if err := stepman.CleanupRoute(route); err != nil { + return fmt.Errorf("Failed to cleanup route for StepLib: %s", collectionURI) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/download.go b/vendor/github.com/bitrise-io/stepman/cli/download.go new file mode 100644 index 00000000..25f04e10 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/download.go @@ -0,0 +1,80 @@ +package cli + +import ( + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/stepman/stepman" + "github.com/urfave/cli" +) + +func download(c *cli.Context) error { + // Input validation + collectionURI := c.String(CollectionKey) + if collectionURI == "" { + log.Fatalf("No step collection specified") + } + route, found := stepman.ReadRoute(collectionURI) + if !found { + log.Fatalf("No route found for lib: %s", collectionURI) + } + + id := c.String(IDKey) + if id == "" { + log.Fatalf("Missing step id") + } + + collection, err := stepman.ReadStepSpec(collectionURI) + if err != nil { + log.Fatalf("Failed to read step spec, error: %s", err) + } + + version := c.String(VersionKey) + if version == "" { + latest, err := collection.GetLatestStepVersion(id) + if err != nil { + log.Fatalf("Failed to get step latest version, error: %s", err) + } + version = latest + } + + update := c.Bool(UpdateKey) + + // Check step exist in collection + step, stepFound, versionFound := collection.GetStep(id, version) + if !stepFound || !versionFound { + if update { + if !stepFound { + log.Infof("Collection doesn't contain step with id: %s -- Updating StepLib", id) + } else if !versionFound { + log.Infof("Collection doesn't contain step (%s) with version: %s -- Updating StepLib", id, version) + } + + if err := stepman.ReGenerateLibrarySpec(route); err != nil { + log.Fatalf("Failed to update collection:%s error:%v", collectionURI, err) + } + + if _, stepFound, versionFound := collection.GetStep(id, version); !stepFound || !versionFound { + if !stepFound { + log.Fatalf("Even the updated collection doesn't contain step with id: %s", id) + } else if !versionFound { + log.Fatalf("Even the updated collection doesn't contain step (%s) with version: %s", id, version) + } + } + } else { + if !stepFound { + log.Fatalf("Collection doesn't contain step with id: %s -- Updating StepLib", id) + } else if !versionFound { + log.Fatalf("Collection doesn't contain step (%s) with version: %s -- Updating StepLib", id, version) + } + } + } + + if step.Source == nil { + log.Fatalf("Missing step's (%s) Source property", id) + } + + if err := stepman.DownloadStep(collectionURI, collection, id, version, step.Source.Commit); err != nil { + log.Fatalf("Failed to download step, error: %s", err) + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/export.go b/vendor/github.com/bitrise-io/stepman/cli/export.go new file mode 100644 index 00000000..204d2117 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/export.go @@ -0,0 +1,147 @@ +package cli + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/stepman/models" + "github.com/bitrise-io/stepman/stepman" + "github.com/urfave/cli" +) + +// ExportType ... +type ExportType int8 + +const ( + exportTypeFull ExportType = iota + exportTypeLatest + exportTypeMinimal +) + +func parseExportType(exportTypeStr string) (ExportType, error) { + switch exportTypeStr { + case "full": + return exportTypeFull, nil + case "latest": + return exportTypeLatest, nil + case "minimal": + return exportTypeMinimal, nil + } + + var exportType ExportType + return exportType, fmt.Errorf("Invalid export type (%s), available: [full, latest, minimal]", exportTypeStr) +} + +func convertToMinimalSpec(stepLib models.StepCollectionModel) models.StepCollectionModel { + steps := stepLib.Steps + + minimalSteps := models.StepHash{} + for stepID := range steps { + minimalSteps[stepID] = models.StepGroupModel{} + } + + stepLib.Steps = minimalSteps + return stepLib +} + +func convertToLatestSpec(stepLib models.StepCollectionModel) models.StepCollectionModel { + steps := stepLib.Steps + + latestSteps := models.StepHash{} + for stepID, stepGroup := range steps { + groupInfo := stepGroup.Info + versions := stepGroup.Versions + latestVersionStr := stepGroup.LatestVersionNumber + latestStep := versions[latestVersionStr] + + latestSteps[stepID] = models.StepGroupModel{ + Versions: map[string]models.StepModel{ + latestVersionStr: latestStep, + }, + Info: groupInfo, + } + } + + stepLib.Steps = latestSteps + return stepLib +} + +func export(c *cli.Context) error { + // Input validation + steplibURI := c.String("steplib") + outputPth := c.String("output") + exportTypeStr := c.String("export-type") + + if steplibURI == "" { + return fmt.Errorf("Missing required input: steplib") + } + + if outputPth == "" { + return fmt.Errorf("Missing required input: output") + } + + exportType := exportTypeFull + if exportTypeStr != "" { + var err error + exportType, err = parseExportType(exportTypeStr) + if err != nil { + return err + } + } + + log.Infof("Exporting StepLib (%s) spec, export-type: %s, output: %s", steplibURI, exportTypeStr, outputPth) + + // Setup StepLib + if exist, err := stepman.RootExistForLibrary(steplibURI); err != nil { + return fmt.Errorf("Failed to check if setup was done for StepLib, error: %s", err) + } else if !exist { + log.Infof("StepLib does not exist, setup...") + if err := stepman.SetupLibrary(steplibURI); err != nil { + return fmt.Errorf("Failed to setup StepLib, error: %s", err) + } + } + + // Prepare spec + stepLibSpec, err := stepman.ReadStepSpec(steplibURI) + if err != nil { + log.Fatalf("Failed to read StepLib spec, error: %s", err) + } + + switch exportType { + case exportTypeMinimal: + stepLibSpec = convertToMinimalSpec(stepLibSpec) + case exportTypeLatest: + stepLibSpec = convertToLatestSpec(stepLibSpec) + } + + stepLibSpecBytes, err := json.Marshal(stepLibSpec) + if err != nil { + return fmt.Errorf("Failed to marshal StepLib, error: %s", err) + } + + // Export spec + outputDir := filepath.Dir(outputPth) + + exist, err := pathutil.IsDirExists(outputDir) + if err != nil { + return fmt.Errorf("Failed to check if dir (%s) exist, error: %s", outputDir, err) + } + if !exist { + if err := os.MkdirAll(outputDir, 0777); err != nil { + return fmt.Errorf("Failed to create dir (%s), error: %s", outputDir, err) + } + } + + if err := fileutil.WriteBytesToFile(outputPth, stepLibSpecBytes); err != nil { + return fmt.Errorf("Failed to write StepLib spec to: %s, error: %s", outputPth, err) + } + + log.Infof("StepLib spec exported to: %s", outputPth) + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/flags.go b/vendor/github.com/bitrise-io/stepman/cli/flags.go new file mode 100644 index 00000000..68e52d98 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/flags.go @@ -0,0 +1,168 @@ +package cli + +import "github.com/urfave/cli" + +const ( + // DebugEnvKey ... + DebugEnvKey = "STEPMAN_DEBUG" + // LogLevelEnvKey ... + LogLevelEnvKey = "LOGLEVEL" + // CollectionPathEnvKey ... + CollectionPathEnvKey = "STEPMAN_COLLECTION" + + // HelpKey ... + HelpKey = "help" + helpKeyShort = "h" + + // VersionKey ... + VersionKey = "version" + versionKeyShort = "v" + + // CollectionKey ... + CollectionKey = "collection" + collectionKeyShort = "c" + // LocalCollectionKey ... + LocalCollectionKey = "local" + // CopySpecJSONKey ... + CopySpecJSONKey = "copy-spec-json" + + // DebugKey ... + DebugKey = "debug" + debugKeyShort = "d" + + // LogLevelKey ... + LogLevelKey = "loglevel" + logLevelKeyShort = "l" + + // IDKey ... + IDKey = "id" + idKeyShort = "i" + + // PathKey ... + PathKey = "path" + pathKeyShort = "p" + + // CopyYMLKey ... + CopyYMLKey = "copyyml" + copyYMLKeyShort = "y" + + // UpdateKey ... + UpdateKey = "update" + updateKeyShort = "u" + + // TagKey ... + TagKey = "tag" + tagKeyShort = "t" + + // GitKey ... + GitKey = "git" + gitKeyShort = "g" + + // StepIDKEy ... + StepIDKEy = "stepid" + stepIDKeyShort = "s" + + // ShortKey ... + ShortKey = "short" + + // ToolMode ... + ToolMode = "toolmode" + + // FormatKey ... + FormatKey = "format" + formatKeyShort = "f" + // OutputFormatRaw ... + OutputFormatRaw = "raw" + // OutputFormatJSON ... + OutputFormatJSON = "json" + + // StepYMLKey ... + StepYMLKey = "step-yml" +) + +var ( + // App flags + flLogLevel = cli.StringFlag{ + Name: LogLevelKey + ", " + logLevelKeyShort, + Value: "info", + Usage: "Log level (options: debug, info, warn, error, fatal, panic).", + EnvVar: LogLevelEnvKey, + } + flags = []cli.Flag{ + flLogLevel, + } + // Command flags + flCollection = cli.StringFlag{ + Name: CollectionKey + ", " + collectionKeyShort, + Usage: "Collection of step.", + EnvVar: CollectionPathEnvKey, + } + flLocalCollection = cli.BoolFlag{ + Name: LocalCollectionKey, + Usage: "[Deprecated!!!][Use 'file://' in steplib uri instead] Allow the --collection to be a local path.", + } + flCopySpecJSON = cli.StringFlag{ + Name: CopySpecJSONKey, + Usage: "If setup succeeds copy the generates spec.json to this path.", + } + flID = cli.StringFlag{ + Name: IDKey + ", " + idKeyShort, + Usage: "Step id.", + } + flVersion = cli.StringFlag{ + Name: VersionKey + ", " + versionKeyShort, + Usage: "Step version.", + } + flPath = cli.StringFlag{ + Name: PathKey + ", " + pathKeyShort, + Usage: "Path where the step will copied.", + } + flCopyYML = cli.StringFlag{ + Name: CopyYMLKey + ", " + copyYMLKeyShort, + Usage: "Path where the selected/activated step's step.yml will be copied.", + } + flUpdate = cli.BoolFlag{ + Name: UpdateKey + ", " + updateKeyShort, + Usage: "If flag is set, and collection doesn't contains the specified step, the collection will updated.", + } + flTag = cli.StringFlag{ + Name: TagKey + ", " + tagKeyShort, + Usage: "Git (version) tag.", + } + flGit = cli.StringFlag{ + Name: GitKey + ", " + gitKeyShort, + Usage: "Git clone url of the step repository.", + } + flStepID = cli.StringFlag{ + Name: StepIDKEy + ", " + stepIDKeyShort, + Usage: "ID of the step.", + } + flFormat = cli.StringFlag{ + Name: FormatKey + ", " + formatKeyShort, + Usage: "Output format (options: raw, json).", + } + flShort = cli.BoolFlag{ + Name: ShortKey, + Usage: "Show short version of infos.", + } + flStepYML = cli.StringFlag{ + Name: StepYMLKey, + Usage: "Path of step.yml", + } + flToolMode = cli.BoolFlag{ + Name: ToolMode, + Usage: "Stepman called as tool.", + } +) + +func initHelpAndVersionFlags() { + cli.HelpFlag = cli.BoolFlag{ + Name: HelpKey + ", " + helpKeyShort, + Usage: "Show help.", + } + + cli.VersionFlag = cli.BoolFlag{ + Name: VersionKey + ", " + versionKeyShort, + Usage: "Print the version.", + } +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/help.go b/vendor/github.com/bitrise-io/stepman/cli/help.go new file mode 100644 index 00000000..445ae83e --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/help.go @@ -0,0 +1,26 @@ +package cli + +import "github.com/urfave/cli" + +func initAppHelpTemplate() { + cli.AppHelpTemplate = ` +NAME: {{.Name}} - {{.Usage}} + +USAGE: {{.Name}} {{if .Flags}}[OPTIONS] {{end}}COMMAND [arg...] + +VERSION: {{.Version}}{{if or .Author .Email}} + +AUTHOR:{{if .Author}} + {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} + {{.Email}}{{end}}{{end}} +{{if .Flags}} +GLOBAL OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{end}} +COMMANDS: + {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}} +COMMAND HELP: {{.Name}} COMMAND --help/-h + +` +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/setup.go b/vendor/github.com/bitrise-io/stepman/cli/setup.go new file mode 100644 index 00000000..a8c1a5b4 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/setup.go @@ -0,0 +1,58 @@ +package cli + +import ( + "fmt" + "strings" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/stepman/stepman" + "github.com/urfave/cli" +) + +func setup(c *cli.Context) error { + // Input validation + steplibURI := c.String(CollectionKey) + if steplibURI == "" { + log.Fatal("No step collection specified") + } + + copySpecJSONPath := c.String(CopySpecJSONKey) + + if c.IsSet(LocalCollectionKey) { + log.Warn("'local' flag is deprecated") + log.Warn("use 'file://' prefix in steplib path instead") + fmt.Println() + } + + if c.Bool(LocalCollectionKey) { + if !strings.HasPrefix(steplibURI, "file://") { + log.Warnf("Appending file path prefix (file://) to StepLib (%s)", steplibURI) + steplibURI = "file://" + steplibURI + log.Warnf("From now you can refer to this StepLib with URI: %s", steplibURI) + log.Warnf("For example, to delete StepLib call: `stepman delete --collection %s`", steplibURI) + } + } + + // Setup + if err := stepman.SetupLibrary(steplibURI); err != nil { + log.Fatalf("Setup failed, error: %s", err) + } + + // Copy spec.json + if copySpecJSONPath != "" { + log.Infof("Copying spec YML to path: %s", copySpecJSONPath) + + route, found := stepman.ReadRoute(steplibURI) + if !found { + log.Fatalf("No route found for steplib (%s)", steplibURI) + } + + sourceSpecJSONPth := stepman.GetStepSpecPath(route) + if err := command.CopyFile(sourceSpecJSONPth, copySpecJSONPath); err != nil { + log.Fatalf("Failed to copy spec.json from (%s) to (%s), error: %s", sourceSpecJSONPth, copySpecJSONPath, err) + } + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/share.go b/vendor/github.com/bitrise-io/stepman/cli/share.go new file mode 100644 index 00000000..4f07daf2 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/share.go @@ -0,0 +1,188 @@ +package cli + +import ( + "encoding/json" + "errors" + "fmt" + "path" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/stepman/stepman" + "github.com/bitrise-tools/colorstring" + "github.com/urfave/cli" +) + +const ( + // ShareFilename ... + ShareFilename string = "share.json" +) + +// ShareModel ... +type ShareModel struct { + Collection string + StepID string + StepTag string +} + +// ShareBranchName ... +func (share ShareModel) ShareBranchName() string { + return share.StepID + "-" + share.StepTag +} + +// DeleteShareSteplibFile ... +func DeleteShareSteplibFile() error { + return command.RemoveDir(getShareFilePath()) +} + +// ReadShareSteplibFromFile ... +func ReadShareSteplibFromFile() (ShareModel, error) { + if exist, err := pathutil.IsPathExists(getShareFilePath()); err != nil { + return ShareModel{}, err + } else if !exist { + return ShareModel{}, errors.New("No share steplib found") + } + + bytes, err := fileutil.ReadBytesFromFile(getShareFilePath()) + if err != nil { + return ShareModel{}, err + } + + share := ShareModel{} + if err := json.Unmarshal(bytes, &share); err != nil { + return ShareModel{}, err + } + + return share, nil +} + +// WriteShareSteplibToFile ... +func WriteShareSteplibToFile(share ShareModel) error { + var bytes []byte + bytes, err := json.MarshalIndent(share, "", "\t") + if err != nil { + log.Errorf("Failed to parse json, error: %s", err) + return err + } + + return fileutil.WriteBytesToFile(getShareFilePath(), bytes) +} + +// GuideTextForStepAudit ... +func GuideTextForStepAudit(toolMode bool) string { + name := "stepman" + if toolMode { + name = "bitrise" + } + + b := colorstring.NewBuilder() + b.Plain("First, you need to ensure that your step is stored in a ").Blue("public git repository").NewLine() + b.Plain("and it follows our ").Blue("step development guideline").Plain(": https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md.").NewLine() + b.NewLine() + b.Plain("To audit your step on your local machine call ").Blue("$ %s audit --step-yml path/to/your/step.yml", name) + return b.String() +} + +// GuideTextForStart ... +func GuideTextForStart() string { + b := colorstring.NewBuilder() + b.Blue("Fork the StepLib repository ").Plain("you want to share your Step in.").NewLine() + b.Plain(`You can find the main ("official") StepLib repository at `).Plain("https://github.com/bitrise-io/bitrise-steplib") + return b.String() +} + +// GuideTextForShareStart ... +func GuideTextForShareStart(toolMode bool) string { + name := "stepman" + if toolMode { + name = "bitrise" + } + + b := colorstring.NewBuilder() + b.Plain("Call ").Blue("$ %s share start -c https://github.com/[your-username]/bitrise-steplib.git", name).Plain(", with the git clone URL of your forked StepLib repository.").NewLine() + b.Plain("This will prepare your forked StepLib locally for sharing.") + return b.String() +} + +// GuideTextForShareCreate ... +func GuideTextForShareCreate(toolMode bool) string { + name := "stepman" + if toolMode { + name = "bitrise" + } + + b := colorstring.NewBuilder() + b.Plain("Next, call ").Blue("$ %s share create --tag [step-version-tag] --git [step-git-uri].git --stepid [step-id]", name).Plain(",").NewLine() + b.Plain("to add your Step to your forked StepLib repository (locally).").NewLine() + b.NewLine() + b.Yellow("Important: ").Plain("you have to add the (version) tag to your Step's repository.") + return b.String() +} + +// GuideTextForAudit ... +func GuideTextForAudit(toolMode bool) string { + name := "stepman" + if toolMode { + name = "bitrise" + } + + b := colorstring.NewBuilder() + b.Plain("You can call ").Blue("$ %s audit -c https://github.com/[your-username]/bitrise-steplib.git ", name).NewLine() + b.Plain("to perform a complete health-check on your forked StepLib before submitting your Pull Request.").NewLine() + b.NewLine() + b.Plain("This can help you catch issues which might prevent your Step from being accepted.") + return b.String() +} + +// GuideTextForShareFinish ... +func GuideTextForShareFinish(toolMode bool) string { + name := "stepman" + if toolMode { + name = "bitrise" + } + + b := colorstring.NewBuilder() + b.Plain("Almost done! You should review your Step's step.yml file (the one added to the local StepLib),").NewLine() + b.Plain("and once you're happy with it call ").Blue("$ %s share finish", name).NewLine() + b.NewLine() + b.Plain("This will commit & push the step.yml into your forked StepLib repository.") + return b.String() +} + +// GuideTextForFinish ... +func GuideTextForFinish() string { + b := colorstring.NewBuilder() + b.Plain("The only remaining thing is to ").Blue("create a Pull Request").Plain(" in the original StepLib repository. And you are done!") + return b.String() +} + +func share(c *cli.Context) { + toolMode := c.Bool(ToolMode) + + b := colorstring.NewBuilder() + b.Plain("Do you want to share your own Step with the world? Awesome!").NewLine() + b.NewLine() + b.Plain("Just follow these steps:").NewLine() + b.NewLine() + b.Plain("0. ").Plain(GuideTextForStepAudit(toolMode)).NewLine() + b.NewLine() + b.Plain("1. ").Plain(GuideTextForStart()).NewLine() + b.NewLine() + b.Plain("2. ").Plain(GuideTextForShareStart(toolMode)).NewLine() + b.NewLine() + b.Plain("3. ").Plain(GuideTextForShareCreate(toolMode)).NewLine() + b.NewLine() + b.Plain("4. ").Plain(GuideTextForAudit(toolMode)).NewLine() + b.NewLine() + b.Plain("5. ").Plain(GuideTextForShareFinish(toolMode)).NewLine() + b.NewLine() + b.Plain("6. ").Plain(GuideTextForFinish()).NewLine() + b.NewLine() + fmt.Printf(b.String()) +} + +func getShareFilePath() string { + return path.Join(stepman.GetStepmanDirPath(), ShareFilename) +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/share_audit.go b/vendor/github.com/bitrise-io/stepman/cli/share_audit.go new file mode 100644 index 00000000..74a6d4e6 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/share_audit.go @@ -0,0 +1,46 @@ +package cli + +import ( + "fmt" + + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/stepman/stepman" + "github.com/bitrise-tools/colorstring" + "github.com/urfave/cli" +) + +func printFinishAudit(share ShareModel, toolMode bool) { + b := colorstring.NewBuilder() + b.Green("your step (%s@%s) is valid", share.StepID, share.StepTag).NewLine() + b.NewLine() + b.Plain(GuideTextForShareFinish(toolMode)) + fmt.Println(b.String()) +} + +func shareAudit(c *cli.Context) error { + toolMode := c.Bool(ToolMode) + + log.Infof("Validating Step share params...") + share, err := ReadShareSteplibFromFile() + if err != nil { + log.Errorf(err.Error()) + fail("You have to start sharing with `stepman share start`, or you can read instructions with `stepman share`") + } + log.Donef("all inputs are valid") + + fmt.Println() + log.Infof("Auditing the StepLib...") + _, found := stepman.ReadRoute(share.Collection) + if !found { + fail("No route found for collectionURI (%s)", share.Collection) + } + + if err := auditStepLibBeforeSharePullRequest(share.Collection); err != nil { + fail("Audit Step Collection failed, err: %s", err) + } + + printFinishAudit(share, toolMode) + fmt.Println() + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/share_create.go b/vendor/github.com/bitrise-io/stepman/cli/share_create.go new file mode 100644 index 00000000..134b5d11 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/share_create.go @@ -0,0 +1,238 @@ +package cli + +import ( + "fmt" + "os" + "path" + "regexp" + "strconv" + "strings" + "time" + "unicode/utf8" + + "gopkg.in/yaml.v2" + + "github.com/bitrise-io/go-utils/command/git" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/pointers" + "github.com/bitrise-io/go-utils/retry" + "github.com/bitrise-io/goinp/goinp" + "github.com/bitrise-io/stepman/models" + "github.com/bitrise-io/stepman/stepman" + "github.com/urfave/cli" +) + +const maxSummaryLength = 100 + +func getStepIDFromGit(git string) string { + splits := strings.Split(git, "/") + lastPart := splits[len(splits)-1] + splits = strings.Split(lastPart, ".") + return splits[0] +} + +func validateTag(tag string) error { + if tag == "" { + return fmt.Errorf("no Step tag specified") + } + + parts := strings.Split(tag, ".") + n := len(parts) + + if n != 3 { + return fmt.Errorf("invalid semver format %s: %d parts instead of 3", tag, n) + } + + for _, part := range parts { + if _, err := strconv.Atoi(part); err != nil { + return fmt.Errorf("invalid semver format %s: %s", tag, err) + } + } + + return nil +} + +func create(c *cli.Context) error { + toolMode := c.Bool(ToolMode) + + log.Infof("Validating Step share params...") + + share, err := ReadShareSteplibFromFile() + if err != nil { + log.Errorf(err.Error()) + fail("You have to start sharing with `stepman share start`, or you can read instructions with `stepman share`") + } + + // Input validation + tag := c.String(TagKey) + if err := validateTag(tag); err != nil { + fail("validate tag: %s", err) + } + + gitURI := c.String(GitKey) + if gitURI == "" { + fail("No Step url specified") + } + + stepID := c.String(StepIDKEy) + if stepID == "" { + stepID = getStepIDFromGit(gitURI) + } + if stepID == "" { + fail("No Step id specified") + } + r := regexp.MustCompile(`[a-z0-9-]+`) + if find := r.FindString(stepID); find != stepID { + fail("StepID doesn't conforms to: [a-z0-9-]") + } + + route, found := stepman.ReadRoute(share.Collection) + if !found { + fail("No route found for collectionURI (%s)", share.Collection) + } + stepDirInSteplib := stepman.GetStepCollectionDirPath(route, stepID, tag) + stepYMLPathInSteplib := path.Join(stepDirInSteplib, "step.yml") + if exist, err := pathutil.IsPathExists(stepYMLPathInSteplib); err != nil { + fail("Failed to check step.yml path in steplib, err: %s", err) + } else if exist { + log.Printf("Step already exist in path: %s", stepDirInSteplib) + log.Warnf("For sharing it's required to work with a clean Step repository.") + if val, err := goinp.AskForBool("Would you like to overwrite local version of Step?"); err != nil { + fail("Failed to get bool, err: %s", err) + } else { + if !val { + log.Errorf("Unfortunately we can't continue with sharing without an overwrite exist step.yml.") + fail("Please finish your changes, run this command again and allow it to overwrite the exist step.yml!") + } + } + } + log.Donef("all inputs are valid") + + // Clone Step to tmp dir + fmt.Println() + log.Infof("Validating the Step...") + + tmp, err := pathutil.NormalizedOSTempDirPath("") + if err != nil { + fail("Failed to get temp directory, err: %s", err) + } + + log.Printf("cloning Step repo from (%s) with tag (%s) to: %s", gitURI, tag, tmp) + + repo, err := git.New(tmp) + if err != nil { + return err + } + + if err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { + return repo.CloneTagOrBranch(gitURI, tag).Run() + }); err != nil { + fail("Failed to git-clone (url: %s) version (%s), error: %s", + gitURI, tag, err) + } + + // Update step.yml + tmpStepYMLPath := path.Join(tmp, "step.yml") + bytes, err := fileutil.ReadBytesFromFile(tmpStepYMLPath) + if err != nil { + fail("Failed to read Step from file, err: %s", err) + } + var stepModel models.StepModel + if err := yaml.Unmarshal(bytes, &stepModel); err != nil { + fail("Failed to unmarchal Step, err: %s", err) + } + + commit, err := repo.RevParse("HEAD").RunAndReturnTrimmedCombinedOutput() + if err != nil { + fail("Failed to get commit hash, err: %s", err) + } + + stepModel.Source = &models.StepSourceModel{ + Git: gitURI, + Commit: commit, + } + stepModel.PublishedAt = pointers.NewTimePtr(time.Now()) + + // Validate step-yml + if err := stepModel.Audit(); err != nil { + fail("Failed to validate Step, err: %s", err) + } + for _, input := range stepModel.Inputs { + key, value, err := input.GetKeyValuePair() + if err != nil { + fail("Failed to get Step input key-value pair, err: %s", err) + } + + options, err := input.GetOptions() + if err != nil { + fail("Failed to get Step input (%s) options, err: %s", key, err) + } + + if len(options.ValueOptions) > 0 && value == "" { + log.Warnf("Step input with 'value_options', should contain default value!") + fail("Missing default value for Step input (%s).", key) + } + } + if strings.Contains(*stepModel.Summary, "\n") { + log.Warnf("Step summary should be one line!") + } + if utf8.RuneCountInString(*stepModel.Summary) > maxSummaryLength { + log.Warnf("Step summary should contains maximum (%d) characters, actual: (%d)!", maxSummaryLength, utf8.RuneCountInString(*stepModel.Summary)) + } + log.Donef("step is valid") + + // Copy step.yml to steplib + fmt.Println() + log.Infof("Integrating the Step into the Steplib...") + + share.StepID = stepID + share.StepTag = tag + if err := WriteShareSteplibToFile(share); err != nil { + fail("Failed to save share steplib to file, err: %s", err) + } + + log.Printf("step dir in collection: %s", stepDirInSteplib) + if exist, err := pathutil.IsPathExists(stepDirInSteplib); err != nil { + fail("Failed to check path (%s), err: %s", stepDirInSteplib, err) + } else if !exist { + if err := os.MkdirAll(stepDirInSteplib, 0777); err != nil { + fail("Failed to create path (%s), err: %s", stepDirInSteplib, err) + } + } + + collectionDir := stepman.GetLibraryBaseDirPath(route) + steplibRepo, err := git.New(collectionDir) + if err != nil { + fail("Failed to init setplib repo: %s", err) + } + if err := steplibRepo.Checkout(share.ShareBranchName()).Run(); err != nil { + if err := steplibRepo.NewBranch(share.ShareBranchName()).Run(); err != nil { + fail("Git failed to create and checkout branch, err: %s", err) + } + } + + stepBytes, err := yaml.Marshal(stepModel) + if err != nil { + fail("Failed to marcshal Step model, err: %s", err) + } + if err := fileutil.WriteBytesToFile(stepYMLPathInSteplib, stepBytes); err != nil { + fail("Failed to write Step to file, err: %s", err) + } + + log.Printf("your Step (%s@%s) added to the local StepLib (%s).", share.StepID, share.StepTag, stepDirInSteplib) + + // Update spec.json + if err := stepman.ReGenerateLibrarySpec(route); err != nil { + fail("Failed to re-create steplib, err: %s", err) + } + + log.Donef("the StepLib changes prepared on branch: %s", share.ShareBranchName()) + + fmt.Println() + log.Printf(GuideTextForShareFinish(toolMode)) + fmt.Println() + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/share_create_test.go b/vendor/github.com/bitrise-io/stepman/cli/share_create_test.go new file mode 100644 index 00000000..52713c9a --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/share_create_test.go @@ -0,0 +1,23 @@ +package cli + +import "testing" + +func TestValidateTag(t *testing.T) { + cases := []struct { + tag string + valid bool + }{ + {tag: "1.0.0", valid: true}, + {tag: "1.0", valid: false}, + {tag: "v1.0.0", valid: false}, + } + + for _, tc := range cases { + got := validateTag(tc.tag) + valid := got == nil + + if valid != tc.valid { + t.Errorf("validateTag(%s) == nil should be %t but got %s", tc.tag, tc.valid, got) + } + } +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/share_finish.go b/vendor/github.com/bitrise-io/stepman/cli/share_finish.go new file mode 100644 index 00000000..4ab2bc96 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/share_finish.go @@ -0,0 +1,81 @@ +package cli + +import ( + "fmt" + + "github.com/bitrise-io/go-utils/command/git" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/stepman/stepman" + "github.com/bitrise-tools/colorstring" + "github.com/urfave/cli" +) + +func printFinishShare() { + b := colorstring.NewBuilder() + b.Plain(GuideTextForFinish()).NewLine() + b.NewLine() + b.Plain("On GitHub you can find a ").Blue("Compare & pull request").Plain(" button, in the section called ").Blue("Your recently pushed branches:").Plain(",").NewLine() + b.Plain("which will bring you to the page to ").Blue("Open a pull request").Plain(", where you can review and create your Pull Request.") + fmt.Println(b.String()) +} + +func finish(c *cli.Context) error { + log.Infof("Validating Step share params...") + + share, err := ReadShareSteplibFromFile() + if err != nil { + log.Errorf(err.Error()) + fail("You have to start sharing with `stepman share start`, or you can read instructions with `stepman share`") + } + + route, found := stepman.ReadRoute(share.Collection) + if !found { + fail("No route found for collectionURI (%s)", share.Collection) + } + + collectionDir := stepman.GetLibraryBaseDirPath(route) + log.Donef("all inputs are valid") + + fmt.Println() + log.Infof("Checking StepLib changes...") + repo, err := git.New(collectionDir) + if err != nil { + fail(err.Error()) + } + + out, err := repo.Status("--porcelain").RunAndReturnTrimmedCombinedOutput() + if err != nil { + fail(err.Error()) + } + if out == "" { + log.Warnf("No git changes, it seems you already called this command") + fmt.Println() + printFinishShare() + return nil + } + + stepDirInSteplib := stepman.GetStepCollectionDirPath(route, share.StepID, share.StepTag) + stepYMLPathInSteplib := stepDirInSteplib + "/step.yml" + log.Printf("new step.yml: %s", stepYMLPathInSteplib) + if err := repo.Add(stepYMLPathInSteplib).Run(); err != nil { + fail(err.Error()) + } + + fmt.Println() + log.Infof("Submitting the changes...") + msg := share.StepID + " " + share.StepTag + if err := repo.Commit(msg).Run(); err != nil { + fail(err.Error()) + } + + log.Printf("pushing to your fork: %s", share.Collection) + if out, err := repo.Push(share.ShareBranchName()).RunAndReturnTrimmedCombinedOutput(); err != nil { + fail(out) + } + + fmt.Println() + printFinishShare() + fmt.Println() + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/share_start.go b/vendor/github.com/bitrise-io/stepman/cli/share_start.go new file mode 100644 index 00000000..a831f6ba --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/share_start.go @@ -0,0 +1,128 @@ +package cli + +import ( + "fmt" + "os" + "time" + + "github.com/bitrise-io/go-utils/command/git" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/retry" + "github.com/bitrise-io/goinp/goinp" + "github.com/bitrise-io/stepman/stepman" + "github.com/urfave/cli" +) + +func fail(format string, v ...interface{}) { + log.Errorf(format, v...) + os.Exit(1) +} + +func showSubcommandHelp(c *cli.Context) { + if err := cli.ShowSubcommandHelp(c); err != nil { + log.Warnf("Failed to show help, error: %s", err) + } +} + +func start(c *cli.Context) error { + // Input validation + log.Infof("Validating Step share params...") + + toolMode := c.Bool(ToolMode) + + collectionURI := c.String(CollectionKey) + if collectionURI == "" { + log.Errorf("No step collection specified\n") + showSubcommandHelp(c) + os.Exit(1) + } + + log.Donef("all inputs are valid") + + fmt.Println() + log.Infof("Preparing StepLib...") + + if route, found := stepman.ReadRoute(collectionURI); found { + collLocalPth := stepman.GetLibraryBaseDirPath(route) + log.Printf("StepLib found locally at: %s", collLocalPth) + log.Warnf("For sharing it's required to work with a clean StepLib repository.") + if val, err := goinp.AskForBool("Would you like to remove the local version (your forked StepLib repository) and re-clone it?"); err != nil { + fail("Failed to ask for input, error: %s", err) + } else { + if !val { + log.Errorf("Unfortunately we can't continue with sharing without a clean StepLib repository.") + fail("Please finish your changes, run this command again and allow it to remove the local StepLib folder!") + } + if err := stepman.CleanupRoute(route); err != nil { + log.Errorf("Failed to cleanup route for uri: %s", collectionURI) + } + } + } + + // cleanup + if err := DeleteShareSteplibFile(); err != nil { + fail("Failed to delete share steplib file, error: %s", err) + } + + var route stepman.SteplibRoute + isSuccess := false + defer func() { + if !isSuccess { + if err := stepman.CleanupRoute(route); err != nil { + log.Errorf("Failed to cleanup route for uri: %s", collectionURI) + } + if err := DeleteShareSteplibFile(); err != nil { + fail("Failed to delete share steplib file, error: %s", err) + } + } + }() + + // Preparing steplib + alias := stepman.GenerateFolderAlias() + route = stepman.SteplibRoute{ + SteplibURI: collectionURI, + FolderAlias: alias, + } + + pth := stepman.GetLibraryBaseDirPath(route) + if err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { + repo, err := git.New(pth) + if err != nil { + return err + } + return repo.Clone(collectionURI).Run() + }); err != nil { + fail("Failed to setup step spec (url: %s) version (%s), error: %s", collectionURI, pth, err) + } + + specPth := pth + "/steplib.yml" + collection, err := stepman.ParseStepCollection(specPth) + if err != nil { + fail("Failed to read step spec, error: %s", err) + } + + if err := stepman.WriteStepSpecToFile(collection, route); err != nil { + fail("Failed to save step spec, error: %s", err) + } + + if err := stepman.AddRoute(route); err != nil { + fail("Failed to setup routing, error: %s", err) + } + + log.Donef("StepLib prepared at: %s", pth) + + share := ShareModel{ + Collection: collectionURI, + } + if err := WriteShareSteplibToFile(share); err != nil { + fail("Failed to save share steplib to file, error: %s", err) + } + + isSuccess = true + + fmt.Println() + fmt.Println(GuideTextForShareCreate(toolMode)) + fmt.Println() + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/step_info.go b/vendor/github.com/bitrise-io/stepman/cli/step_info.go new file mode 100644 index 00000000..e6f71e8a --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/step_info.go @@ -0,0 +1,194 @@ +package cli + +import ( + "fmt" + "log" + "time" + + "path/filepath" + + "github.com/bitrise-io/go-utils/command/git" + flog "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/retry" + "github.com/bitrise-io/stepman/models" + "github.com/bitrise-io/stepman/stepman" + "github.com/urfave/cli" +) + +var stepInfoCommand = cli.Command{ + Name: "step-info", + Usage: "Prints the step definition (step.yml content).", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "library", + Usage: "Library of the step (options: LIBRARY_URI, git, path).", + EnvVar: "STEPMAN_LIBRARY_URI", + }, + cli.StringFlag{ + Name: "id", + Usage: "ID of the step (options: ID_IN_LIBRARY, GIT_URI, LOCAL_STEP_DIRECTORY_PATH).", + }, + cli.StringFlag{ + Name: "version", + Usage: "Version of the step (options: VERSION_IN_LIBRARY, GIT_BRANCH_OR_TAG).", + }, + cli.StringFlag{ + Name: "format", + Usage: "Output format (options: raw, json).", + }, + cli.StringFlag{ + Name: "collection, c", + Usage: "[DEPRECATED] Collection of step.", + EnvVar: CollectionPathEnvKey, + }, + cli.BoolFlag{ + Name: "short", + Usage: "[DEPRECATED] Show short version of infos.", + }, + cli.StringFlag{ + Name: "step-yml", + Usage: "[DEPRECATED] Path of step.yml", + }, + }, + Action: func(c *cli.Context) error { + if err := stepInfo(c); err != nil { + log.Fatalf("Command failed, error: %s", err) + } + return nil + }, +} + +func stepInfo(c *cli.Context) error { + // Input parsing + library := c.String("library") + if library == "" { + collection := c.String(CollectionKey) + library = collection + } + + id := c.String(IDKey) + if id == "" { + stepYMLPath := c.String(StepYMLKey) + if stepYMLPath != "" { + id = stepYMLPath + library = "path" + } + } + + if library == "" { + return fmt.Errorf("Missing required input: library") + } + if id == "" { + return fmt.Errorf("Missing required input: id") + } + + version := c.String(VersionKey) + + format := c.String(FormatKey) + if format == "" { + format = OutputFormatRaw + } + if format != OutputFormatRaw && format != OutputFormatJSON { + return fmt.Errorf("Invalid input value: format = %s", format) + } + + var log flog.Logger + log = flog.NewDefaultRawLogger() + if format == OutputFormatJSON { + log = flog.NewDefaultJSONLoger() + } + // --- + + stepInfo := models.StepInfoModel{ + Library: library, + ID: id, + Version: version, + } + + switch library { + case "git": + stepGitSourceURI := id + tmpStepDir, err := pathutil.NormalizedOSTempDirPath("__step__") + if err != nil { + return fmt.Errorf("failed to create tmp dir, error: %s", err) + } + + tagOrBranch := version + if tagOrBranch == "" { + tagOrBranch = "master" + } + + if err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { + repo, err := git.New(tmpStepDir) + if err != nil { + return err + } + return repo.CloneTagOrBranch(stepGitSourceURI, tagOrBranch).Run() + }); err != nil { + return fmt.Errorf("failed to clone step from: %s, error: %s", stepGitSourceURI, err) + } + + stepDefinitionPth := filepath.Join(tmpStepDir, "step.yml") + if exist, err := pathutil.IsPathExists(stepDefinitionPth); err != nil { + return fmt.Errorf("failed to check if step definition (step.yml) exist at: %s, error: %s", stepDefinitionPth, err) + } else if !exist { + return fmt.Errorf("step definition (step.yml) does not exist at: %s", stepDefinitionPth) + } + + step, err := stepman.ParseStepDefinition(stepDefinitionPth, false) + if err != nil { + return fmt.Errorf("failed to parse step definition at: %s, error: %s", stepDefinitionPth, err) + } + + stepInfo.Version = tagOrBranch + stepInfo.Step = step + stepInfo.DefinitionPth = stepDefinitionPth + case "path": + stepDir := id + stepDefinitionPth := filepath.Join(stepDir, "step.yml") + if exist, err := pathutil.IsPathExists(stepDefinitionPth); err != nil { + return fmt.Errorf("failed to check if step definition (step.yml) exist at: %s, error: %s", stepDefinitionPth, err) + } else if !exist { + return fmt.Errorf("step definition (step.yml) does not exist at: %s", stepDefinitionPth) + } + + step, err := stepman.ParseStepDefinition(stepDefinitionPth, false) + if err != nil { + return fmt.Errorf("failed to parse step definition at: %s, error: %s", stepDefinitionPth, err) + } + + stepInfo.Step = step + stepInfo.DefinitionPth = stepDefinitionPth + default: // library step + // Check if setup was done for collection + if exist, err := stepman.RootExistForLibrary(library); err != nil { + return fmt.Errorf("Failed to check if setup was done for steplib (%s), error: %s", library, err) + } else if !exist { + if err := stepman.SetupLibrary(library); err != nil { + return fmt.Errorf("Failed to setup steplib (%s), error: %s", library, err) + } + } + + stepVersion, err := stepman.ReadStepVersionInfo(library, id, version) + if err != nil { + return fmt.Errorf("Failed to read Step information, error: %s", err) + } + + route, found := stepman.ReadRoute(library) + if !found { + return fmt.Errorf("No route found for library: %s", library) + } + + stepDir := stepman.GetStepCollectionDirPath(route, id, stepVersion.Version) + stepDefinitionPth := filepath.Join(stepDir, "step.yml") + + stepInfo.Step = stepVersion.Step + stepInfo.Version = stepVersion.Version + stepInfo.LatestVersion = stepVersion.LatestAvailableVersion + stepInfo.DefinitionPth = stepDefinitionPth + } + + log.Print(stepInfo) + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/step_list.go b/vendor/github.com/bitrise-io/stepman/cli/step_list.go new file mode 100644 index 00000000..fab7034e --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/step_list.go @@ -0,0 +1,117 @@ +package cli + +import ( + "encoding/json" + "fmt" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/pointers" + "github.com/bitrise-io/go-utils/stringutil" + "github.com/bitrise-io/stepman/models" + "github.com/bitrise-io/stepman/stepman" + "github.com/urfave/cli" +) + +func printRawStepList(stepLibURI string, stepLib models.StepCollectionModel, isShort bool) { + fmt.Println(colorstring.Bluef("Steps in StepLib (%s):", stepLibURI)) + fmt.Println() + for stepID, stepGroupInfo := range stepLib.Steps { + if isShort { + // print only step IDs + fmt.Printf("%s\n", stepID) + continue + } + + latestStepVerInfos, isFound := stepGroupInfo.LatestVersion() + if !isFound { + log.Errorf("No version found for step: %s", stepID) + continue + } + fmt.Printf(" * %s\n", pointers.String(latestStepVerInfos.Title)) + fmt.Printf(" ID: %s\n", stepID) + fmt.Printf(" Latest Version: %s\n", stepGroupInfo.LatestVersionNumber) + summaryText := "no summary specified" + if latestStepVerInfos.Summary != nil { + stepSumText := *latestStepVerInfos.Summary + // stepSumText = strings.Replace(stepSumText, "\n", " ", -1) + summaryText = stringutil.IndentTextWithMaxLength(stepSumText, " ", 130, false) + } + fmt.Printf(" Summary: %s\n", summaryText) + fmt.Println() + } + fmt.Println() +} + +func printJSONStepList(stepLibURI string, stepLib models.StepCollectionModel, isShort bool) error { + stepList := models.StepListModel{ + StepLib: stepLibURI, + } + for stepID := range stepLib.Steps { + stepList.Steps = append(stepList.Steps, stepID) + } + + bytes, err := json.Marshal(stepList) + if err != nil { + return err + } + + fmt.Println(string(bytes)) + return nil +} + +func listSteps(stepLibURI, format string) error { + // Check if setup was done for collection + if exist, err := stepman.RootExistForLibrary(stepLibURI); err != nil { + return err + } else if !exist { + if err := stepman.SetupLibrary(stepLibURI); err != nil { + log.Fatal("Failed to setup steplib") + } + } + + stepLib, err := stepman.ReadStepSpec(stepLibURI) + if err != nil { + return err + } + + switch format { + case OutputFormatRaw: + printRawStepList(stepLibURI, stepLib, false) + break + case OutputFormatJSON: + if err := printJSONStepList(stepLibURI, stepLib, false); err != nil { + return err + } + break + default: + return fmt.Errorf("Invalid format: %s", format) + } + return nil +} + +func stepList(c *cli.Context) error { + // Input validation + stepLibURIs := []string{} + stepLibURI := c.String(CollectionKey) + if stepLibURI == "" { + stepLibURIs = stepman.GetAllStepCollectionPath() + } else { + stepLibURIs = []string{stepLibURI} + } + + format := c.String(FormatKey) + if format == "" { + format = OutputFormatRaw + } else if !(format == OutputFormatRaw || format == OutputFormatJSON) { + log.Fatalf("Invalid format: %s", format) + } + + for _, URI := range stepLibURIs { + if err := listSteps(URI, format); err != nil { + log.Errorf("Failed to list steps in StepLib (%s), err: %s", URI, err) + } + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/update.go b/vendor/github.com/bitrise-io/stepman/cli/update.go new file mode 100644 index 00000000..40b2cb3e --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/update.go @@ -0,0 +1,35 @@ +package cli + +import ( + "fmt" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/stepman/stepman" + "github.com/urfave/cli" +) + +func update(c *cli.Context) error { + collectionURIs := []string{} + + // StepSpec collection path + collectionURI := c.String(CollectionKey) + if collectionURI == "" { + log.Info("No StepLib specified, update all...") + collectionURIs = stepman.GetAllStepCollectionPath() + } else { + collectionURIs = []string{collectionURI} + } + + if len(collectionURIs) == 0 { + log.Info("No local StepLib found, nothing to update...") + } + + for _, URI := range collectionURIs { + log.Infof("Update StepLib (%s)...", URI) + if _, err := stepman.UpdateLibrary(URI); err != nil { + return fmt.Errorf("Failed to update StepLib (%s), error: %s", URI, err) + } + } + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/cli/version.go b/vendor/github.com/bitrise-io/stepman/cli/version.go new file mode 100644 index 00000000..b6c598a5 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/cli/version.go @@ -0,0 +1,82 @@ +package cli + +import ( + "encoding/json" + "fmt" + "os" + "runtime" + + flog "github.com/bitrise-io/go-utils/log" + ver "github.com/bitrise-io/stepman/version" + "github.com/urfave/cli" +) + +// VersionOutputModel ... +type VersionOutputModel struct { + Version string `json:"version,omitempty"` + OS string `json:"os,omitempty"` + GO string `json:"go,omitempty"` + BuildNumber string `json:"build_number,omitempty"` + Commit string `json:"commit,omitempty"` + + FullVersion bool `json:"-"` +} + +// String ... +func (version VersionOutputModel) String() string { + str := "" + if version.FullVersion { + str += fmt.Sprintf("version: %s\n", version.Version) + str += fmt.Sprintf("os: %s\n", version.OS) + str += fmt.Sprintf("go: %s\n", version.GO) + str += fmt.Sprintf("build_number: %s\n", version.BuildNumber) + str += fmt.Sprintf("commit: %s", version.Commit) + } else { + str = version.Version + } + + return str +} + +// JSON ... +func (version VersionOutputModel) JSON() string { + if version.FullVersion { + bytes, err := json.Marshal(version) + if err != nil { + return fmt.Sprintf(`"Failed to marshal version (%#v), err: %s"`, version, err) + } + return string(bytes) + "\n" + } + + return fmt.Sprintf(`"%s"`+"\n", version.Version) +} + +func printVersionCmd(c *cli.Context) error { + fullVersion := c.Bool("full") + format := c.String("format") + if format == "" { + format = "raw" + } + + var log flog.Logger + if format == "raw" { + log = flog.NewDefaultRawLogger() + } else if format == "json" { + log = flog.NewDefaultJSONLoger() + } else { + flog.Errorf("Invalid format: %s\n", format) + os.Exit(1) + } + + versionOutput := VersionOutputModel{} + versionOutput.Version = ver.VERSION + versionOutput.OS = fmt.Sprintf("%s (%s)", runtime.GOOS, runtime.GOARCH) + versionOutput.GO = runtime.Version() + versionOutput.BuildNumber = ver.BuildNumber + versionOutput.Commit = ver.Commit + versionOutput.FullVersion = fullVersion + + log.Print(versionOutput) + + return nil +} diff --git a/vendor/github.com/bitrise-io/stepman/docker-compose.yml b/vendor/github.com/bitrise-io/stepman/docker-compose.yml new file mode 100644 index 00000000..f9128202 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/docker-compose.yml @@ -0,0 +1,4 @@ +app: + build: . + volumes: + - .:/go/src/github.com/bitrise-io/stepman diff --git a/vendor/github.com/bitrise-io/stepman/gows.yml b/vendor/github.com/bitrise-io/stepman/gows.yml new file mode 100644 index 00000000..58b5ed7d --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/gows.yml @@ -0,0 +1 @@ +package_name: github.com/bitrise-io/stepman diff --git a/vendor/github.com/bitrise-io/stepman/main.go b/vendor/github.com/bitrise-io/stepman/main.go new file mode 100644 index 00000000..e7688d36 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/bitrise-io/stepman/cli" + +func main() { + cli.Run() +} diff --git a/vendor/github.com/bitrise-io/stepman/models/models.go b/vendor/github.com/bitrise-io/stepman/models/models.go new file mode 100644 index 00000000..989c650e --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/models/models.go @@ -0,0 +1,189 @@ +package models + +import ( + "time" + + envmanModels "github.com/bitrise-io/envman/models" +) + +// StepSourceModel ... +type StepSourceModel struct { + Git string `json:"git,omitempty" yaml:"git,omitempty"` + Commit string `json:"commit,omitempty" yaml:"commit,omitempty"` +} + +// DependencyModel ... +type DependencyModel struct { + Manager string `json:"manager,omitempty" yaml:"manager,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` +} + +// BrewDepModel ... +type BrewDepModel struct { + // Name is the package name for Brew + Name string `json:"name,omitempty" yaml:"name,omitempty"` + // BinName is the binary's name, if it doesn't match the package's name. + // Can be used for e.g. calling `which`. + // E.g. in case of "AWS CLI" the package is `awscli` and the binary is `aws`. + // If BinName is empty Name will be used as BinName too. + BinName string `json:"bin_name,omitempty" yaml:"bin_name,omitempty"` +} + +// AptGetDepModel ... +type AptGetDepModel struct { + // Name is the package name for Apt-get + Name string `json:"name,omitempty" yaml:"name,omitempty"` + // BinName is the binary's name, if it doesn't match the package's name. + // Can be used for e.g. calling `which`. + // E.g. in case of "AWS CLI" the package is `awscli` and the binary is `aws`. + // If BinName is empty Name will be used as BinName too. + BinName string `json:"bin_name,omitempty" yaml:"bin_name,omitempty"` +} + +// CheckOnlyDepModel ... +type CheckOnlyDepModel struct { + Name string `json:"name,omitempty" yaml:"name,omitempty"` +} + +// DepsModel ... +type DepsModel struct { + Brew []BrewDepModel `json:"brew,omitempty" yaml:"brew,omitempty"` + AptGet []AptGetDepModel `json:"apt_get,omitempty" yaml:"apt_get,omitempty"` + CheckOnly []CheckOnlyDepModel `json:"check_only,omitempty" yaml:"check_only,omitempty"` +} + +// BashStepToolkitModel ... +type BashStepToolkitModel struct { + EntryFile string `json:"entry_file,omitempty" yaml:"entry_file,omitempty"` +} + +// GoStepToolkitModel ... +type GoStepToolkitModel struct { + // PackageName - required + PackageName string `json:"package_name" yaml:"package_name"` +} + +// StepToolkitModel ... +type StepToolkitModel struct { + Bash *BashStepToolkitModel `json:"bash,omitempty" yaml:"bash,omitempty"` + Go *GoStepToolkitModel `json:"go,omitempty" yaml:"go,omitempty"` +} + +// StepModel ... +type StepModel struct { + Title *string `json:"title,omitempty" yaml:"title,omitempty"` + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` + // + Website *string `json:"website,omitempty" yaml:"website,omitempty"` + SourceCodeURL *string `json:"source_code_url,omitempty" yaml:"source_code_url,omitempty"` + SupportURL *string `json:"support_url,omitempty" yaml:"support_url,omitempty"` + // auto-generated at share + PublishedAt *time.Time `json:"published_at,omitempty" yaml:"published_at,omitempty"` + Source *StepSourceModel `json:"source,omitempty" yaml:"source,omitempty"` + AssetURLs map[string]string `json:"asset_urls,omitempty" yaml:"asset_urls,omitempty"` + // + HostOsTags []string `json:"host_os_tags,omitempty" yaml:"host_os_tags,omitempty"` + ProjectTypeTags []string `json:"project_type_tags,omitempty" yaml:"project_type_tags,omitempty"` + TypeTags []string `json:"type_tags,omitempty" yaml:"type_tags,omitempty"` + Dependencies []DependencyModel `json:"dependencies,omitempty" yaml:"dependencies,omitempty"` + Toolkit *StepToolkitModel `json:"toolkit,omitempty" yaml:"toolkit,omitempty"` + Deps *DepsModel `json:"deps,omitempty" yaml:"deps,omitempty"` + IsRequiresAdminUser *bool `json:"is_requires_admin_user,omitempty" yaml:"is_requires_admin_user,omitempty"` + // IsAlwaysRun : if true then this step will always run, + // even if a previous step fails. + IsAlwaysRun *bool `json:"is_always_run,omitempty" yaml:"is_always_run,omitempty"` + // IsSkippable : if true and this step fails the build will still continue. + // If false then the build will be marked as failed and only those + // steps will run which are marked with IsAlwaysRun. + IsSkippable *bool `json:"is_skippable,omitempty" yaml:"is_skippable,omitempty"` + // RunIf : only run the step if the template example evaluates to true + RunIf *string `json:"run_if,omitempty" yaml:"run_if,omitempty"` + Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty"` + // + Inputs []envmanModels.EnvironmentItemModel `json:"inputs,omitempty" yaml:"inputs,omitempty"` + Outputs []envmanModels.EnvironmentItemModel `json:"outputs,omitempty" yaml:"outputs,omitempty"` +} + +// StepVersionModel ... +type StepVersionModel struct { + Step StepModel + Version string + LatestAvailableVersion string +} + +// StepGroupInfoModel ... +type StepGroupInfoModel struct { + RemovalDate string `json:"removal_date,omitempty" yaml:"removal_date,omitempty"` + DeprecateNotes string `json:"deprecate_notes,omitempty" yaml:"deprecate_notes,omitempty"` + AssetURLs map[string]string `json:"asset_urls,omitempty" yaml:"asset_urls,omitempty"` +} + +// StepGroupModel ... +type StepGroupModel struct { + Info StepGroupInfoModel `json:"info,omitempty" yaml:"info,omitempty"` + LatestVersionNumber string `json:"latest_version_number,omitempty" yaml:"latest_version_number,omitempty"` + Versions map[string]StepModel `json:"versions,omitempty" yaml:"versions,omitempty"` +} + +// LatestVersion ... +func (stepGroup StepGroupModel) LatestVersion() (StepModel, bool) { + step, found := stepGroup.Versions[stepGroup.LatestVersionNumber] + if !found { + return StepModel{}, false + } + return step, true +} + +// StepHash ... +type StepHash map[string]StepGroupModel + +// DownloadLocationModel ... +type DownloadLocationModel struct { + Type string `json:"type"` + Src string `json:"src"` +} + +// StepCollectionModel ... +type StepCollectionModel struct { + FormatVersion string `json:"format_version" yaml:"format_version"` + GeneratedAtTimeStamp int64 `json:"generated_at_timestamp" yaml:"generated_at_timestamp"` + SteplibSource string `json:"steplib_source" yaml:"steplib_source"` + DownloadLocations []DownloadLocationModel `json:"download_locations" yaml:"download_locations"` + AssetsDownloadBaseURI string `json:"assets_download_base_uri" yaml:"assets_download_base_uri"` + Steps StepHash `json:"steps" yaml:"steps"` +} + +// EnvInfoModel ... +type EnvInfoModel struct { + Key string `json:"key,omitempty" yaml:"key,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + ValueOptions []string `json:"value_options,omitempty" yaml:"value_options,omitempty"` + DefaultValue string `json:"default_value,omitempty" yaml:"default_value,omitempty"` + IsExpand bool `json:"is_expand" yaml:"is_expand"` + IsSensitive bool `json:"is_sensitive" yaml:"is_sensitive"` +} + +// StepInfoModel ... +type StepInfoModel struct { + Library string `json:"library,omitempty" yaml:"library,omitempty"` + ID string `json:"id,omitempty" yaml:"id,omitempty"` + Version string `json:"version,omitempty" yaml:"version,omitempty"` + LatestVersion string `json:"latest_version,omitempty" yaml:"latest_version,omitempty"` + GroupInfo StepGroupInfoModel `json:"info,omitempty" yaml:"info,omitempty"` + Step StepModel `json:"step,omitempty" yaml:"step,omitempty"` + DefinitionPth string `json:"definition_pth,omitempty" yaml:"definition_pth,omitempty"` +} + +// StepListModel ... +type StepListModel struct { + StepLib string `json:"steplib,omitempty" yaml:"steplib,omitempty"` + Steps []string `json:"steps,omitempty" yaml:"steps,omitempty"` +} + +// SteplibInfoModel ... +type SteplibInfoModel struct { + URI string `json:"uri,omitempty" yaml:"uri,omitempty"` + SpecPath string `json:"spec_path,omitempty" yaml:"spec_path,omitempty"` +} diff --git a/vendor/github.com/bitrise-io/stepman/models/models_methods.go b/vendor/github.com/bitrise-io/stepman/models/models_methods.go new file mode 100644 index 00000000..1fdfb6ad --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/models/models_methods.go @@ -0,0 +1,342 @@ +package models + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pointers" +) + +const ( + // DefaultIsAlwaysRun ... + DefaultIsAlwaysRun = false + // DefaultIsRequiresAdminUser ... + DefaultIsRequiresAdminUser = false + // DefaultIsSkippable ... + DefaultIsSkippable = false + // DefaultTimeout ... + DefaultTimeout = 0 +) + +// String ... +func (stepInfo StepInfoModel) String() string { + str := "" + if stepInfo.GroupInfo.DeprecateNotes != "" { + str += colorstring.Red("This step is deprecated") + + if stepInfo.GroupInfo.RemovalDate != "" { + str += colorstring.Redf(" and will be removed: %s", stepInfo.GroupInfo.RemovalDate) + } + str += "\n" + } + str += fmt.Sprintf("%s %s\n", colorstring.Blue("Library:"), stepInfo.Library) + str += fmt.Sprintf("%s %s\n", colorstring.Blue("ID:"), stepInfo.ID) + str += fmt.Sprintf("%s %s\n", colorstring.Blue("Version:"), stepInfo.Version) + str += fmt.Sprintf("%s %s\n", colorstring.Blue("LatestVersion:"), stepInfo.LatestVersion) + str += fmt.Sprintf("%s\n\n", colorstring.Blue("Definition:")) + + definition, err := fileutil.ReadStringFromFile(stepInfo.DefinitionPth) + if err != nil { + str += colorstring.Redf("Failed to read step definition, error: %s", err) + return str + } + + str += definition + return str +} + +// JSON ... +func (stepInfo StepInfoModel) JSON() string { + bytes, err := json.Marshal(stepInfo) + if err != nil { + return fmt.Sprintf(`"Failed to marshal step info (%#v), err: %s"`, stepInfo, err) + } + return string(bytes) +} + +// CreateFromJSON ... +func (stepInfo StepInfoModel) CreateFromJSON(jsonStr string) (StepInfoModel, error) { + info := StepInfoModel{} + if err := json.Unmarshal([]byte(jsonStr), &info); err != nil { + return StepInfoModel{}, err + } + return info, nil +} + +// Normalize ... +func (step StepModel) Normalize() error { + for _, input := range step.Inputs { + if err := input.Normalize(); err != nil { + return err + } + } + for _, output := range step.Outputs { + if err := output.Normalize(); err != nil { + return err + } + } + return nil +} + +// ValidateSource ... +func (source StepSourceModel) validateSource() error { + if source.Git == "" { + return errors.New("Invalid step: missing or empty required 'source.git' property") + } + + if !strings.HasPrefix(source.Git, "http://") && !strings.HasPrefix(source.Git, "https://") { + return errors.New("Invalid step: step source should start with http:// or https://") + } + if !strings.HasSuffix(source.Git, ".git") { + return errors.New("Invalid step: step source should end with .git") + } + + if source.Commit == "" { + return errors.New("Invalid step: missing or empty required 'source.commit' property") + } + return nil +} + +// ValidateInputAndOutputEnvs ... +func (step StepModel) ValidateInputAndOutputEnvs(checkRequiredFields bool) error { + validateEnvs := func(envs []envmanModels.EnvironmentItemModel) error { + for _, env := range envs { + key, _, err := env.GetKeyValuePair() + if err != nil { + return fmt.Errorf("Invalid environment (%v), err: %s", env, err) + } + + if err := env.Validate(); err != nil { + return fmt.Errorf("Invalid environment (%s), err: %s", key, err) + } + + if checkRequiredFields { + options, err := env.GetOptions() + if err != nil { + return fmt.Errorf("Invalid environment (%s), err: %s", key, err) + } + + if options.Title == nil || *options.Title == "" { + return fmt.Errorf("Invalid environment (%s), err: missing or empty title", key) + } + + isSensitive := options.IsSensitive + if isSensitive == nil { + isSensitive = pointers.NewBoolPtr(envmanModels.DefaultIsSensitive) + } + + isExpand := options.IsExpand + if isExpand == nil { + isExpand = pointers.NewBoolPtr(envmanModels.DefaultIsExpand) + } + + if *isSensitive && !(*isExpand) { + return fmt.Errorf("Invalid environment (%s), err: is_sensitive option is true but is_expand option is not. For sensitive inputs direct value is not allowed", key) + } + } + } + return nil + } + + return validateEnvs(append(step.Inputs, step.Outputs...)) +} + +// AuditBeforeShare ... +func (step StepModel) AuditBeforeShare() error { + if step.Title == nil || *step.Title == "" { + return errors.New("Invalid step: missing or empty required 'title' property") + } + if step.Summary == nil || *step.Summary == "" { + return errors.New("Invalid step: missing or empty required 'summary' property") + } + if step.Website == nil || *step.Website == "" { + return errors.New("Invalid step: missing or empty required 'website' property") + } + + if step.Timeout != nil && *step.Timeout < 0 { + return errors.New("Invalid step: timeout less then 0") + } + + return step.ValidateInputAndOutputEnvs(true) +} + +// Audit ... +func (step StepModel) Audit() error { + if err := step.AuditBeforeShare(); err != nil { + return err + } + + if step.PublishedAt == nil || (*step.PublishedAt).Equal(time.Time{}) { + return errors.New("Invalid step: missing or empty required 'PublishedAt' property") + } + if step.Source == nil { + return errors.New("Invalid step: missing or empty required 'Source' property") + } + return step.Source.validateSource() +} + +// FillMissingDefaults ... +func (step *StepModel) FillMissingDefaults() error { + if step.Title == nil { + step.Title = pointers.NewStringPtr("") + } + if step.Description == nil { + step.Description = pointers.NewStringPtr("") + } + if step.Summary == nil { + step.Summary = pointers.NewStringPtr("") + } + if step.Website == nil { + step.Website = pointers.NewStringPtr("") + } + if step.SourceCodeURL == nil { + step.SourceCodeURL = pointers.NewStringPtr("") + } + if step.SupportURL == nil { + step.SupportURL = pointers.NewStringPtr("") + } + if step.IsRequiresAdminUser == nil { + step.IsRequiresAdminUser = pointers.NewBoolPtr(DefaultIsRequiresAdminUser) + } + if step.IsAlwaysRun == nil { + step.IsAlwaysRun = pointers.NewBoolPtr(DefaultIsAlwaysRun) + } + if step.IsSkippable == nil { + step.IsSkippable = pointers.NewBoolPtr(DefaultIsSkippable) + } + if step.RunIf == nil { + step.RunIf = pointers.NewStringPtr("") + } + if step.Timeout == nil { + step.Timeout = pointers.NewIntPtr(DefaultTimeout) + } + + for _, input := range step.Inputs { + err := input.FillMissingDefaults() + if err != nil { + return err + } + } + for _, output := range step.Outputs { + err := output.FillMissingDefaults() + if err != nil { + return err + } + } + return nil +} + +// IsStepExist ... +func (collection StepCollectionModel) IsStepExist(id, version string) bool { + _, stepFound, versionFound := collection.GetStep(id, version) + return (stepFound && versionFound) +} + +// GetStep ... +func (collection StepCollectionModel) GetStep(id, version string) (StepModel, bool, bool) { + stepVer, isStepFound, isVersionFound := collection.GetStepVersion(id, version) + return stepVer.Step, isStepFound, isVersionFound +} + +// GetStepVersion ... +func (collection StepCollectionModel) GetStepVersion(id, version string) (StepVersionModel, bool, bool) { + stepHash := collection.Steps + stepVersions, stepFound := stepHash[id] + + if !stepFound { + return StepVersionModel{}, stepFound, false + } + + if version == "" { + version = stepVersions.LatestVersionNumber + } + + step, versionFound := stepVersions.Versions[version] + + if !stepFound || !versionFound { + return StepVersionModel{}, stepFound, versionFound + } + + return StepVersionModel{ + Step: step, + Version: version, + LatestAvailableVersion: stepVersions.LatestVersionNumber, + }, true, true +} + +// GetDownloadLocations ... +func (collection StepCollectionModel) GetDownloadLocations(id, version string) ([]DownloadLocationModel, error) { + step, stepFound, versionFound := collection.GetStep(id, version) + if !stepFound { + return []DownloadLocationModel{}, fmt.Errorf("Collection (%s) doesn't contains step with id: %s", collection.SteplibSource, id) + } + if !versionFound { + return []DownloadLocationModel{}, fmt.Errorf("Collection (%s) doesn't contains step (%s) with version: %s", collection.SteplibSource, id, version) + } + + if step.Source == nil { + return []DownloadLocationModel{}, errors.New("Missing Source property") + } + + locations := []DownloadLocationModel{} + for _, downloadLocation := range collection.DownloadLocations { + switch downloadLocation.Type { + case "zip": + url := downloadLocation.Src + id + "/" + version + "/step.zip" + location := DownloadLocationModel{ + Type: downloadLocation.Type, + Src: url, + } + locations = append(locations, location) + case "git": + location := DownloadLocationModel{ + Type: downloadLocation.Type, + Src: step.Source.Git, + } + locations = append(locations, location) + default: + return []DownloadLocationModel{}, fmt.Errorf("Invalid download location (%#v) for step (%#v)", downloadLocation, id) + } + } + if len(locations) < 1 { + return []DownloadLocationModel{}, fmt.Errorf("No download location found for step (%#v)", id) + } + return locations, nil +} + +// GetLatestStepVersion ... +func (collection StepCollectionModel) GetLatestStepVersion(id string) (string, error) { + stepHash := collection.Steps + stepGroup, found := stepHash[id] + if !found { + return "", fmt.Errorf("Collection (%s) doesn't contains step (%s)", collection.SteplibSource, id) + } + + if stepGroup.LatestVersionNumber == "" { + return "", fmt.Errorf("Failed to find latest version of step %s", id) + } + + return stepGroup.LatestVersionNumber, nil +} + +// GetBinaryName ... +func (brewDep BrewDepModel) GetBinaryName() string { + if brewDep.BinName != "" { + return brewDep.BinName + } + return brewDep.Name +} + +// GetBinaryName ... +func (aptGetDep AptGetDepModel) GetBinaryName() string { + if aptGetDep.BinName != "" { + return aptGetDep.BinName + } + return aptGetDep.Name +} diff --git a/vendor/github.com/bitrise-io/stepman/models/models_methods_test.go b/vendor/github.com/bitrise-io/stepman/models/models_methods_test.go new file mode 100644 index 00000000..941f22d9 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/models/models_methods_test.go @@ -0,0 +1,467 @@ +package models + +import ( + "testing" + "time" + + envmanModels "github.com/bitrise-io/envman/models" + "github.com/bitrise-io/go-utils/pointers" + "github.com/stretchr/testify/require" +) + +func TestValidate(t *testing.T) { + step := StepModel{ + Title: pointers.NewStringPtr("title"), + Summary: pointers.NewStringPtr("summary"), + Website: pointers.NewStringPtr("website"), + PublishedAt: pointers.NewTimePtr(time.Date(2012, time.January, 1, 0, 0, 0, 0, time.UTC)), + Source: &StepSourceModel{ + Git: "https://github.com/bitrise-io/bitrise.git", + Commit: "1e1482141079fc12def64d88cb7825b8f1cb1dc3", + }, + } + + require.Equal(t, nil, step.Audit()) + + step.Title = nil + require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'title' property") + + step.Title = new(string) + *step.Title = "" + require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'title' property") + *step.Title = "title" + + step.PublishedAt = nil + require.NotEqual(t, nil, step.Audit()) + require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'PublishedAt' property") + step.PublishedAt = new(time.Time) + + *step.PublishedAt = time.Time{} + require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'PublishedAt' property") + step.PublishedAt = pointers.NewTimePtr(time.Date(2012, time.January, 1, 0, 0, 0, 0, time.UTC)) + + step.Website = nil + require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'website' property") + + step.Website = new(string) + *step.Website = "" + require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'website' property") + *step.Website = "website" + + step.Source.Git = "" + require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'source.git' property") + step.Source.Git = "git" + + step.Source.Git = "git@github.com:bitrise-io/bitrise.git" + require.EqualError(t, step.Audit(), "Invalid step: step source should start with http:// or https://") + + step.Source.Git = "https://github.com/bitrise-io/bitrise" + require.EqualError(t, step.Audit(), "Invalid step: step source should end with .git") + step.Source.Git = "https://github.com/bitrise-io/bitrise.git" + + step.Source.Commit = "" + require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'source.commit' property") + step.Source.Commit = "1e1482141079fc12def64d88cb7825b8f1cb1dc3" + + step.Timeout = new(int) + *step.Timeout = -1 + require.EqualError(t, step.Audit(), "Invalid step: timeout less then 0") +} + +func TestValidateStepInputOutputModel(t *testing.T) { + // Filled env + env := envmanModels.EnvironmentItemModel{ + "test_key": "test_value", + envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + Description: pointers.NewStringPtr("test_description"), + Summary: pointers.NewStringPtr("test_summary"), + ValueOptions: []string{"test_key2", "test_value2"}, + IsRequired: pointers.NewBoolPtr(true), + IsExpand: pointers.NewBoolPtr(false), + IsDontChangeValue: pointers.NewBoolPtr(true), + }, + } + + step := StepModel{ + Inputs: []envmanModels.EnvironmentItemModel{env}, + } + + require.NoError(t, step.ValidateInputAndOutputEnvs(true)) + + // Empty key + env = envmanModels.EnvironmentItemModel{ + "": "test_value", + envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + Description: pointers.NewStringPtr("test_description"), + Summary: pointers.NewStringPtr("test_summary"), + ValueOptions: []string{"test_key2", "test_value2"}, + IsRequired: pointers.NewBoolPtr(true), + IsExpand: pointers.NewBoolPtr(false), + IsDontChangeValue: pointers.NewBoolPtr(true), + }, + } + + step = StepModel{ + Inputs: []envmanModels.EnvironmentItemModel{env}, + } + + require.Error(t, step.ValidateInputAndOutputEnvs(true)) + + // Title is empty + env = envmanModels.EnvironmentItemModel{ + "test_key": "test_value", + envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ + Description: pointers.NewStringPtr("test_description"), + ValueOptions: []string{"test_key2", "test_value2"}, + IsRequired: pointers.NewBoolPtr(true), + IsExpand: pointers.NewBoolPtr(false), + IsDontChangeValue: pointers.NewBoolPtr(true), + }, + } + + step = StepModel{ + Inputs: []envmanModels.EnvironmentItemModel{env}, + } + + require.Error(t, step.ValidateInputAndOutputEnvs(true)) + + // IsSensitive is true but IsExpand is not + env = envmanModels.EnvironmentItemModel{ + "test_key": "test_value", + envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + Description: pointers.NewStringPtr("test_description"), + ValueOptions: []string{"test_key2", "test_value2"}, + IsRequired: pointers.NewBoolPtr(true), + IsExpand: pointers.NewBoolPtr(false), + IsSensitive: pointers.NewBoolPtr(true), + IsDontChangeValue: pointers.NewBoolPtr(true), + }, + } + + step = StepModel{ + Inputs: []envmanModels.EnvironmentItemModel{env}, + } + + require.Error(t, step.ValidateInputAndOutputEnvs(true)) + + // IsSensitive is not set + env = envmanModels.EnvironmentItemModel{ + "test_key": "test_value", + envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + Description: pointers.NewStringPtr("test_description"), + ValueOptions: []string{"test_key2", "test_value2"}, + IsRequired: pointers.NewBoolPtr(true), + IsExpand: pointers.NewBoolPtr(false), + IsDontChangeValue: pointers.NewBoolPtr(true), + }, + } + + step = StepModel{ + Inputs: []envmanModels.EnvironmentItemModel{env}, + } + + require.NoError(t, step.ValidateInputAndOutputEnvs(true)) + + // IsSensitive is set to false + env = envmanModels.EnvironmentItemModel{ + "test_key": "test_value", + envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + Description: pointers.NewStringPtr("test_description"), + ValueOptions: []string{"test_key2", "test_value2"}, + IsRequired: pointers.NewBoolPtr(true), + IsExpand: pointers.NewBoolPtr(false), + IsSensitive: pointers.NewBoolPtr(false), + IsDontChangeValue: pointers.NewBoolPtr(true), + }, + } + + step = StepModel{ + Inputs: []envmanModels.EnvironmentItemModel{env}, + } + + require.NoError(t, step.ValidateInputAndOutputEnvs(true)) + + // IsExpand not set and IsSensitive set + env = envmanModels.EnvironmentItemModel{ + "test_key": "test_value", + envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ + Title: pointers.NewStringPtr("test_title"), + Description: pointers.NewStringPtr("test_description"), + ValueOptions: []string{"test_key2", "test_value2"}, + IsRequired: pointers.NewBoolPtr(true), + IsSensitive: pointers.NewBoolPtr(true), + IsDontChangeValue: pointers.NewBoolPtr(true), + }, + } + + step = StepModel{ + Inputs: []envmanModels.EnvironmentItemModel{env}, + } + + require.NoError(t, step.ValidateInputAndOutputEnvs(true)) +} + +func TestFillMissingDefaults(t *testing.T) { + title := "name 1" + // "desc 1" := ""desc 1" 1" + website := "web/1" + git := "https://git.url" + // fork := "fork/1" + + step := StepModel{ + Title: pointers.NewStringPtr(title), + Website: pointers.NewStringPtr(website), + Source: &StepSourceModel{ + Git: git, + }, + HostOsTags: []string{"osx"}, + ProjectTypeTags: []string{"ios"}, + TypeTags: []string{"test"}, + } + + require.Equal(t, nil, step.FillMissingDefaults()) + + if step.Description == nil || *step.Description != "" { + t.Fatal("Description missing") + } + if step.SourceCodeURL == nil || *step.SourceCodeURL != "" { + t.Fatal("SourceCodeURL missing") + } + if step.SupportURL == nil || *step.SupportURL != "" { + t.Fatal("SourceCodeURL missing") + } + if step.IsRequiresAdminUser == nil || *step.IsRequiresAdminUser != DefaultIsRequiresAdminUser { + t.Fatal("IsRequiresAdminUser missing") + } + if step.IsAlwaysRun == nil || *step.IsAlwaysRun != DefaultIsAlwaysRun { + t.Fatal("IsAlwaysRun missing") + } + if step.IsSkippable == nil || *step.IsSkippable != DefaultIsSkippable { + t.Fatal("IsSkippable missing") + } + if step.RunIf == nil || *step.RunIf != "" { + t.Fatal("RunIf missing") + } + if step.Timeout == nil || *step.Timeout != 0 { + t.Fatal("Timeout missing") + } +} + +func TestGetStep(t *testing.T) { + defaultIsRequiresAdminUser := DefaultIsRequiresAdminUser + + step := StepModel{ + Title: pointers.NewStringPtr("name 1"), + Description: pointers.NewStringPtr("desc 1"), + Website: pointers.NewStringPtr("web/1"), + SourceCodeURL: pointers.NewStringPtr("fork/1"), + Source: &StepSourceModel{ + Git: "https://git.url", + }, + HostOsTags: []string{"osx"}, + ProjectTypeTags: []string{"ios"}, + TypeTags: []string{"test"}, + IsRequiresAdminUser: pointers.NewBoolPtr(defaultIsRequiresAdminUser), + Inputs: []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "KEY_1": "Value 1", + }, + envmanModels.EnvironmentItemModel{ + "KEY_2": "Value 2", + }, + }, + Outputs: []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "KEY_3": "Value 3", + }, + }, + } + + collection := StepCollectionModel{ + FormatVersion: "1.0.0", + GeneratedAtTimeStamp: 0, + Steps: StepHash{ + "step": StepGroupModel{ + Versions: map[string]StepModel{ + "1.0.0": step, + }, + }, + }, + SteplibSource: "source", + DownloadLocations: []DownloadLocationModel{ + DownloadLocationModel{ + Type: "zip", + Src: "amazon/", + }, + DownloadLocationModel{ + Type: "git", + Src: "step.git", + }, + }, + } + + _, stepFound, versionFound := collection.GetStep("step", "1.0.0") + require.Equal(t, true, (stepFound && versionFound)) +} + +func TestGetDownloadLocations(t *testing.T) { + defaultIsRequiresAdminUser := DefaultIsRequiresAdminUser + + // Zip & git download locations + step := StepModel{ + Title: pointers.NewStringPtr("name 1"), + Description: pointers.NewStringPtr("desc 1"), + Website: pointers.NewStringPtr("web/1"), + SourceCodeURL: pointers.NewStringPtr("fork/1"), + Source: &StepSourceModel{ + Git: "https://git.url", + }, + HostOsTags: []string{"osx"}, + ProjectTypeTags: []string{"ios"}, + TypeTags: []string{"test"}, + IsRequiresAdminUser: pointers.NewBoolPtr(defaultIsRequiresAdminUser), + Inputs: []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "KEY_1": "Value 1", + }, + envmanModels.EnvironmentItemModel{ + "KEY_2": "Value 2", + }, + }, + Outputs: []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "KEY_3": "Value 3", + }, + }, + } + + collection := StepCollectionModel{ + FormatVersion: "1.0.0", + GeneratedAtTimeStamp: 0, + Steps: StepHash{ + "step": StepGroupModel{ + Versions: map[string]StepModel{ + "1.0.0": step, + }, + }, + }, + SteplibSource: "source", + DownloadLocations: []DownloadLocationModel{ + DownloadLocationModel{ + Type: "zip", + Src: "amazon/", + }, + DownloadLocationModel{ + Type: "git", + Src: "step.git", + }, + }, + } + + locations, err := collection.GetDownloadLocations("step", "1.0.0") + require.Equal(t, nil, err) + + zipFound := false + gitFount := false + zipIdx := -1 + gitIdx := -1 + + for idx, location := range locations { + if location.Type == "zip" { + if location.Src != "amazon/step/1.0.0/step.zip" { + t.Fatalf("Incorrect zip location (%s)", location.Src) + } + zipFound = true + zipIdx = idx + } else if location.Type == "git" { + if location.Src != "https://git.url" { + t.Fatalf("Incorrect git location (%s)", location.Src) + } + gitFount = true + gitIdx = idx + } + } + + require.Equal(t, true, zipFound) + require.Equal(t, true, gitFount) + if gitIdx < zipIdx { + t.Fatal("Incorrect download locations order") + } +} + +func TestGetLatestStepVersion(t *testing.T) { + defaultIsRequiresAdminUser := DefaultIsRequiresAdminUser + + step := StepModel{ + Title: pointers.NewStringPtr("name 1"), + Description: pointers.NewStringPtr("desc 1"), + Website: pointers.NewStringPtr("web/1"), + SourceCodeURL: pointers.NewStringPtr("fork/1"), + Source: &StepSourceModel{ + Git: "https://git.url", + }, + HostOsTags: []string{"osx"}, + ProjectTypeTags: []string{"ios"}, + TypeTags: []string{"test"}, + IsRequiresAdminUser: pointers.NewBoolPtr(defaultIsRequiresAdminUser), + Inputs: []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "KEY_1": "Value 1", + }, + envmanModels.EnvironmentItemModel{ + "KEY_2": "Value 2", + }, + }, + Outputs: []envmanModels.EnvironmentItemModel{ + envmanModels.EnvironmentItemModel{ + "KEY_3": "Value 3", + }, + }, + } + + collection := StepCollectionModel{ + FormatVersion: "1.0.0", + GeneratedAtTimeStamp: 0, + Steps: StepHash{ + "step": StepGroupModel{ + Versions: map[string]StepModel{ + "1.0.0": step, + "2.0.0": step, + }, + LatestVersionNumber: "2.0.0", + }, + }, + SteplibSource: "source", + DownloadLocations: []DownloadLocationModel{ + DownloadLocationModel{ + Type: "zip", + Src: "amazon/", + }, + DownloadLocationModel{ + Type: "git", + Src: "step.git", + }, + }, + } + + latest, err := collection.GetLatestStepVersion("step") + require.Equal(t, nil, err) + require.Equal(t, "2.0.0", latest) +} + +func Test_BrewDepModel_GetBinaryName(t *testing.T) { + require.Equal(t, "", BrewDepModel{}.GetBinaryName()) + require.Equal(t, "awscli", BrewDepModel{Name: "awscli"}.GetBinaryName()) + require.Equal(t, "aws", BrewDepModel{Name: "awscli", BinName: "aws"}.GetBinaryName()) +} + +func Test_AptGetDepModel_GetBinaryName(t *testing.T) { + require.Equal(t, "", AptGetDepModel{}.GetBinaryName()) + require.Equal(t, "awscli", AptGetDepModel{Name: "awscli"}.GetBinaryName()) + require.Equal(t, "aws", AptGetDepModel{Name: "awscli", BinName: "aws"}.GetBinaryName()) +} diff --git a/vendor/github.com/bitrise-io/stepman/models/models_test.go b/vendor/github.com/bitrise-io/stepman/models/models_test.go new file mode 100644 index 00000000..6f2924cb --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/models/models_test.go @@ -0,0 +1,70 @@ +package models + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v2" + + "github.com/bitrise-io/go-utils/pointers" + "github.com/stretchr/testify/require" +) + +func Test_serialize_StepModel(t *testing.T) { + t.Log("Empty") + { + step := StepModel{} + + // JSON + { + bytes, err := json.Marshal(step) + require.NoError(t, err) + require.Equal(t, `{}`, string(bytes)) + } + + // YAML + { + bytes, err := yaml.Marshal(step) + require.NoError(t, err) + require.Equal(t, `{} +`, + string(bytes)) + } + } + + t.Log("Toolkit") + { + step := StepModel{ + Title: pointers.NewStringPtr("Test Step"), + Toolkit: &StepToolkitModel{ + Go: &GoStepToolkitModel{ + PackageName: "go/package", + }, + Bash: &BashStepToolkitModel{ + EntryFile: "step.sh", + }, + }, + } + + // JSON + { + bytes, err := json.Marshal(step) + require.NoError(t, err) + require.Equal(t, `{"title":"Test Step","toolkit":{"bash":{"entry_file":"step.sh"},"go":{"package_name":"go/package"}}}`, string(bytes)) + } + + // YAML + { + bytes, err := yaml.Marshal(step) + require.NoError(t, err) + require.Equal(t, `title: Test Step +toolkit: + bash: + entry_file: step.sh + go: + package_name: go/package +`, + string(bytes)) + } + } +} diff --git a/vendor/github.com/bitrise-io/stepman/stepman/library.go b/vendor/github.com/bitrise-io/stepman/stepman/library.go new file mode 100644 index 00000000..9a79ed02 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/stepman/library.go @@ -0,0 +1,130 @@ +package stepman + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/command/git" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/retry" + "github.com/bitrise-io/stepman/models" +) + +const filePathPrefix = "file://" + +// SetupLibrary ... +func SetupLibrary(libraryURI string) error { + if exist, err := RootExistForLibrary(libraryURI); err != nil { + return fmt.Errorf("failed to check if routing exist for library (%s), error: %s", libraryURI, err) + } else if exist { + return nil + } + + alias := GenerateFolderAlias() + route := SteplibRoute{ + SteplibURI: libraryURI, + FolderAlias: alias, + } + + // Cleanup + isSuccess := false + defer func() { + if !isSuccess { + if err := CleanupRoute(route); err != nil { + log.Errorf("Failed to cleanup routing for library (%s), error: %s", libraryURI, err) + } + } + }() + + // Setup + isLocalLibrary := strings.HasPrefix(libraryURI, filePathPrefix) + + pth := GetLibraryBaseDirPath(route) + if !isLocalLibrary { + if err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { + repo, err := git.New(pth) + if err != nil { + return err + } + return repo.Clone(libraryURI).Run() + }); err != nil { + return fmt.Errorf("failed to clone library (%s), error: %s", libraryURI, err) + } + } else { + // Local spec path + if err := os.MkdirAll(pth, 0777); err != nil { + return fmt.Errorf("failed to create library dir (%s), error: %s", pth, err) + } + + libraryFilePath := libraryURI + if strings.HasPrefix(libraryURI, filePathPrefix) { + libraryFilePath = strings.TrimPrefix(libraryURI, filePathPrefix) + } + + if err := command.CopyDir(libraryFilePath, pth, true); err != nil { + return fmt.Errorf("failed to copy dir (%s) to (%s), error: %s", libraryFilePath, pth, err) + } + } + + if err := ReGenerateLibrarySpec(route); err != nil { + return fmt.Errorf("failed to re-generate library (%s), error: %s", libraryURI, err) + } + + if err := AddRoute(route); err != nil { + return fmt.Errorf("failed to add routing, error: %s", err) + } + + isSuccess = true + + return nil +} + +// UpdateLibrary ... +func UpdateLibrary(libraryURI string) (models.StepCollectionModel, error) { + route, found := ReadRoute(libraryURI) + if !found { + if err := CleanupDanglingLibrary(libraryURI); err != nil { + log.Errorf("Failed to cleaning up library (%s), error: %s", libraryURI, err) + } + return models.StepCollectionModel{}, fmt.Errorf("no route found for library: %s", libraryURI) + } + + isLocalLibrary := strings.HasPrefix(libraryURI, filePathPrefix) + + if isLocalLibrary { + if err := CleanupRoute(route); err != nil { + return models.StepCollectionModel{}, fmt.Errorf("failed to cleanup route for library (%s), error: %s", libraryURI, err) + } + + if err := SetupLibrary(libraryURI); err != nil { + return models.StepCollectionModel{}, fmt.Errorf("failed to setup library (%s), error: %s", libraryURI, err) + } + } else { + pth := GetLibraryBaseDirPath(route) + if exists, err := pathutil.IsPathExists(pth); err != nil { + return models.StepCollectionModel{}, fmt.Errorf("failed to check if library (%s) directory (%s) exist, error: %s", libraryURI, pth, err) + } else if !exists { + return models.StepCollectionModel{}, fmt.Errorf("library (%s) not initialized", libraryURI) + } + + if err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { + repo, err := git.New(pth) + if err != nil { + return err + } + return repo.Pull().Run() + }); err != nil { + return models.StepCollectionModel{}, fmt.Errorf("failed to pull library (%s), error: %s", libraryURI, err) + } + + if err := ReGenerateLibrarySpec(route); err != nil { + return models.StepCollectionModel{}, fmt.Errorf("failed to generate spec for library (%s), error: %s", libraryURI, err) + } + } + + return ReadStepSpec(libraryURI) +} diff --git a/vendor/github.com/bitrise-io/stepman/stepman/paths.go b/vendor/github.com/bitrise-io/stepman/stepman/paths.go new file mode 100644 index 00000000..7cb8f5fc --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/stepman/paths.go @@ -0,0 +1,248 @@ +package stepman + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path" + "time" + + log "github.com/Sirupsen/logrus" + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" +) + +const ( + // StepmanDirname ... + StepmanDirname = ".stepman" + // RoutingFilename ... + RoutingFilename = "routing.json" + // CollectionsDirname ... + CollectionsDirname = "step_collections" +) + +// SteplibRoute ... +type SteplibRoute struct { + SteplibURI string + FolderAlias string +} + +// SteplibRoutes ... +type SteplibRoutes []SteplibRoute + +// GetRoute ... +func (routes SteplibRoutes) GetRoute(URI string) (route SteplibRoute, found bool) { + for _, route := range routes { + if route.SteplibURI == URI { + pth := path.Join(GetCollectionsDirPath(), route.FolderAlias) + exist, err := pathutil.IsPathExists(pth) + if err != nil { + log.Warnf("Failed to read path %s", pth) + return SteplibRoute{}, false + } else if !exist { + log.Warnf("Failed to read path %s", pth) + return SteplibRoute{}, false + } + return route, true + } + } + return SteplibRoute{}, false +} + +// ReadRoute ... +func ReadRoute(uri string) (route SteplibRoute, found bool) { + routes, err := readRouteMap() + if err != nil { + return SteplibRoute{}, false + } + + return routes.GetRoute(uri) +} + +func (routes SteplibRoutes) writeToFile() error { + routeMap := map[string]string{} + for _, route := range routes { + routeMap[route.SteplibURI] = route.FolderAlias + } + bytes, err := json.MarshalIndent(routeMap, "", "\t") + if err != nil { + return err + } + return fileutil.WriteBytesToFile(getRoutingFilePath(), bytes) +} + +// CleanupRoute ... +func CleanupRoute(route SteplibRoute) error { + pth := path.Join(GetCollectionsDirPath(), route.FolderAlias) + if err := command.RemoveDir(pth); err != nil { + return err + } + return RemoveRoute(route) +} + +// CleanupDanglingLibrary ... +func CleanupDanglingLibrary(URI string) error { + route := SteplibRoute{ + SteplibURI: URI, + FolderAlias: "", + } + return RemoveRoute(route) +} + +// RootExistForLibrary ... +func RootExistForLibrary(collectionURI string) (bool, error) { + routes, err := readRouteMap() + if err != nil { + return false, err + } + + _, found := routes.GetRoute(collectionURI) + return found, nil +} + +func getAlias(uri string) (string, error) { + routes, err := readRouteMap() + if err != nil { + return "", err + } + + route, found := routes.GetRoute(uri) + if found == false { + return "", errors.New("No routes exist for uri:" + uri) + } + return route.FolderAlias, nil +} + +// RemoveRoute ... +func RemoveRoute(route SteplibRoute) error { + routes, err := readRouteMap() + if err != nil { + return err + } + + newRoutes := SteplibRoutes{} + for _, aRoute := range routes { + if aRoute.SteplibURI != route.SteplibURI { + newRoutes = append(newRoutes, aRoute) + } + } + return newRoutes.writeToFile() +} + +// AddRoute ... +func AddRoute(route SteplibRoute) error { + routes, err := readRouteMap() + if err != nil { + return err + } + + routes = append(routes, route) + return routes.writeToFile() +} + +// GenerateFolderAlias ... +func GenerateFolderAlias() string { + return fmt.Sprintf("%v", time.Now().Unix()) +} + +func readRouteMap() (SteplibRoutes, error) { + exist, err := pathutil.IsPathExists(getRoutingFilePath()) + if err != nil { + return SteplibRoutes{}, err + } else if !exist { + return SteplibRoutes{}, nil + } + + bytes, err := fileutil.ReadBytesFromFile(getRoutingFilePath()) + if err != nil { + return SteplibRoutes{}, err + } + var routeMap map[string]string + if err := json.Unmarshal(bytes, &routeMap); err != nil { + return SteplibRoutes{}, err + } + + routes := []SteplibRoute{} + for key, value := range routeMap { + routes = append(routes, SteplibRoute{ + SteplibURI: key, + FolderAlias: value, + }) + } + + return routes, nil +} + +// CreateStepManDirIfNeeded ... +func CreateStepManDirIfNeeded() error { + return os.MkdirAll(GetStepmanDirPath(), 0777) +} + +// GetStepSpecPath ... +func GetStepSpecPath(route SteplibRoute) string { + return path.Join(GetCollectionsDirPath(), route.FolderAlias, "spec", "spec.json") +} + +// GetSlimStepSpecPath ... +func GetSlimStepSpecPath(route SteplibRoute) string { + return path.Join(GetCollectionsDirPath(), route.FolderAlias, "spec", "slim-spec.json") +} + +// GetCacheBaseDir ... +func GetCacheBaseDir(route SteplibRoute) string { + return path.Join(GetCollectionsDirPath(), route.FolderAlias, "cache") +} + +// GetLibraryBaseDirPath ... +func GetLibraryBaseDirPath(route SteplibRoute) string { + return path.Join(GetCollectionsDirPath(), route.FolderAlias, "collection") +} + +// GetAllStepCollectionPath ... +func GetAllStepCollectionPath() []string { + routes, err := readRouteMap() + if err != nil { + log.Errorf("Failed to read step specs path, error: %s", err) + return []string{} + } + + sources := []string{} + for _, route := range routes { + sources = append(sources, route.SteplibURI) + } + + return sources +} + +// GetStepCacheDirPath ... +// Step's Cache dir path, where it's code lives. +func GetStepCacheDirPath(route SteplibRoute, id, version string) string { + return path.Join(GetCacheBaseDir(route), id, version) +} + +// GetStepGlobalInfoPath ... +func GetStepGlobalInfoPath(route SteplibRoute, id string) string { + return path.Join(GetLibraryBaseDirPath(route), "steps", id, "step-info.yml") +} + +// GetStepCollectionDirPath ... +// Step's Collection dir path, where it's spec (step.yml) lives. +func GetStepCollectionDirPath(route SteplibRoute, id, version string) string { + return path.Join(GetLibraryBaseDirPath(route), "steps", id, version) +} + +// GetStepmanDirPath ... +func GetStepmanDirPath() string { + return path.Join(pathutil.UserHomeDir(), StepmanDirname) +} + +// GetCollectionsDirPath ... +func GetCollectionsDirPath() string { + return path.Join(GetStepmanDirPath(), CollectionsDirname) +} + +func getRoutingFilePath() string { + return path.Join(GetStepmanDirPath(), RoutingFilename) +} diff --git a/vendor/github.com/bitrise-io/stepman/stepman/util.go b/vendor/github.com/bitrise-io/stepman/stepman/util.go new file mode 100644 index 00000000..1699feaa --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/stepman/util.go @@ -0,0 +1,427 @@ +package stepman + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/command/git" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/retry" + "github.com/bitrise-io/go-utils/urlutil" + "github.com/bitrise-io/go-utils/versions" + "github.com/bitrise-io/stepman/models" + "gopkg.in/yaml.v2" +) + +// ParseStepGroupInfoModel ... +func ParseStepGroupInfoModel(pth string) (models.StepGroupInfoModel, bool, error) { + if exist, err := pathutil.IsPathExists(pth); err != nil { + return models.StepGroupInfoModel{}, false, err + } else if !exist { + return models.StepGroupInfoModel{}, false, nil + } + + bytes, err := fileutil.ReadBytesFromFile(pth) + if err != nil { + return models.StepGroupInfoModel{}, true, err + } + + var globalStepInfo models.StepGroupInfoModel + if err := yaml.Unmarshal(bytes, &globalStepInfo); err != nil { + return models.StepGroupInfoModel{}, true, err + } + + return globalStepInfo, true, nil +} + +// ParseStepDefinition ... +func ParseStepDefinition(pth string, validate bool) (models.StepModel, error) { + bytes, err := fileutil.ReadBytesFromFile(pth) + if err != nil { + return models.StepModel{}, err + } + + var stepModel models.StepModel + if err := yaml.Unmarshal(bytes, &stepModel); err != nil { + return models.StepModel{}, err + } + + if err := stepModel.Normalize(); err != nil { + return models.StepModel{}, err + } + + if validate { + if err := stepModel.Audit(); err != nil { + return models.StepModel{}, err + } + } + + if err := stepModel.FillMissingDefaults(); err != nil { + return models.StepModel{}, err + } + + return stepModel, nil +} + +// ParseStepGroupInfo ... +func ParseStepGroupInfo(pth string) (models.StepGroupInfoModel, error) { + bytes, err := fileutil.ReadBytesFromFile(pth) + if err != nil { + return models.StepGroupInfoModel{}, err + } + + var stepGroupInfo models.StepGroupInfoModel + if err := yaml.Unmarshal(bytes, &stepGroupInfo); err != nil { + return models.StepGroupInfoModel{}, err + } + + return stepGroupInfo, nil +} + +// ParseStepCollection ... +func ParseStepCollection(pth string) (models.StepCollectionModel, error) { + bytes, err := fileutil.ReadBytesFromFile(pth) + if err != nil { + return models.StepCollectionModel{}, err + } + + var stepCollection models.StepCollectionModel + if err := yaml.Unmarshal(bytes, &stepCollection); err != nil { + return models.StepCollectionModel{}, err + } + return stepCollection, nil +} + +// DownloadStep ... +func DownloadStep(collectionURI string, collection models.StepCollectionModel, id, version, commithash string) error { + downloadLocations, err := collection.GetDownloadLocations(id, version) + if err != nil { + return err + } + + route, found := ReadRoute(collectionURI) + if !found { + return fmt.Errorf("No routing found for lib: %s", collectionURI) + } + + stepPth := GetStepCacheDirPath(route, id, version) + if exist, err := pathutil.IsPathExists(stepPth); err != nil { + return err + } else if exist { + return nil + } + + success := false + for _, downloadLocation := range downloadLocations { + switch downloadLocation.Type { + case "zip": + err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { + return command.DownloadAndUnZIP(downloadLocation.Src, stepPth) + }) + + if err != nil { + log.Warnf("Failed to download step.zip: %s", err) + } else { + success = true + return nil + } + case "git": + err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { + repo, err := git.New(stepPth) + if err != nil { + return err + } + + if err := repo.CloneTagOrBranch(downloadLocation.Src, version).Run(); err != nil { + return err + } + + hash, err := repo.RevParse("HEAD").RunAndReturnTrimmedCombinedOutput() + if err != nil { + return err + } + + if hash != commithash { + return fmt.Errorf("commit hash (%s) doesn't match the one specified (%s) for the version tag (%s)", hash, commithash, version) + } + return nil + }) + + if err != nil { + log.Warnf("Failed to clone step (%s): %v", downloadLocation.Src, err) + } else { + success = true + return nil + } + default: + return fmt.Errorf("Failed to download: Invalid download location (%#v) for step %#v (%#v)", downloadLocation, id, version) + } + } + + if !success { + return errors.New("Failed to download step") + } + return nil +} + +func addStepVersionToStepGroup(step models.StepModel, version string, stepGroup models.StepGroupModel) (models.StepGroupModel, error) { + if stepGroup.LatestVersionNumber != "" { + r, err := versions.CompareVersions(stepGroup.LatestVersionNumber, version) + if err != nil { + return models.StepGroupModel{}, err + } + if r == 1 { + stepGroup.LatestVersionNumber = version + } + } else { + stepGroup.LatestVersionNumber = version + } + stepGroup.Versions[version] = step + return stepGroup, nil +} + +func generateStepLib(route SteplibRoute, templateCollection models.StepCollectionModel) (models.StepCollectionModel, error) { + collection := models.StepCollectionModel{ + FormatVersion: templateCollection.FormatVersion, + GeneratedAtTimeStamp: time.Now().Unix(), + SteplibSource: templateCollection.SteplibSource, + DownloadLocations: templateCollection.DownloadLocations, + AssetsDownloadBaseURI: templateCollection.AssetsDownloadBaseURI, + } + + stepHash := models.StepHash{} + + stepsSpecDirPth := GetLibraryBaseDirPath(route) + if err := filepath.Walk(stepsSpecDirPth, func(pth string, f os.FileInfo, err error) error { + truncatedPath := strings.Replace(pth, stepsSpecDirPth+"/", "", -1) + match, matchErr := regexp.MatchString("([a-z]+).yml", truncatedPath) + if matchErr != nil { + return matchErr + } + + if match { + components := strings.Split(truncatedPath, "/") + if len(components) == 4 { + stepsDirName := components[0] + stepID := components[1] + stepVersion := components[2] + + step, parseErr := ParseStepDefinition(pth, true) + if parseErr != nil { + return parseErr + } + + stepGroupInfo := models.StepGroupInfoModel{} + + // Check for step-info.yml - STEP_SPEC_DIR/steps/step-id/step-info.yml + stepGroupInfoPth := filepath.Join(stepsSpecDirPth, stepsDirName, stepID, "step-info.yml") + if exist, err := pathutil.IsPathExists(stepGroupInfoPth); err != nil { + return err + } else if exist { + deprecationInfo, err := ParseStepGroupInfo(stepGroupInfoPth) + if err != nil { + return err + } + + stepGroupInfo.RemovalDate = deprecationInfo.RemovalDate + stepGroupInfo.DeprecateNotes = deprecationInfo.DeprecateNotes + } + + // Check for assets - STEP_SPEC_DIR/steps/step-id/assets + if collection.AssetsDownloadBaseURI != "" { + assetsFolderPth := path.Join(stepsSpecDirPth, stepsDirName, stepID, "assets") + exist, err := pathutil.IsPathExists(assetsFolderPth) + if err != nil { + return err + } + if exist { + assetsMap := map[string]string{} + err := filepath.Walk(assetsFolderPth, func(pth string, f os.FileInfo, err error) error { + _, file := filepath.Split(pth) + if pth != assetsFolderPth && file != "" { + assetURI, err := urlutil.Join(collection.AssetsDownloadBaseURI, stepID, "assets", file) + if err != nil { + return err + } + assetsMap[file] = assetURI + } + return nil + }) + + if err != nil { + return err + } + + step.AssetURLs = assetsMap + stepGroupInfo.AssetURLs = assetsMap + } + } + + // Add to stepgroup + stepGroup, found := stepHash[stepID] + if !found { + stepGroup = models.StepGroupModel{ + Versions: map[string]models.StepModel{}, + } + } + stepGroup, err = addStepVersionToStepGroup(step, stepVersion, stepGroup) + if err != nil { + return err + } + + stepGroup.Info = stepGroupInfo + + stepHash[stepID] = stepGroup + } else { + } + } + + return err + }); err != nil { + return models.StepCollectionModel{}, fmt.Errorf("Failed to walk through path, error: %s", err) + } + + collection.Steps = stepHash + + return collection, nil +} + +func generateSlimStepLib(collection models.StepCollectionModel) models.StepCollectionModel { + + slimCollection := models.StepCollectionModel{ + FormatVersion: collection.FormatVersion, + GeneratedAtTimeStamp: collection.GeneratedAtTimeStamp, + SteplibSource: collection.SteplibSource, + DownloadLocations: collection.DownloadLocations, + AssetsDownloadBaseURI: collection.AssetsDownloadBaseURI, + } + steps := models.StepHash{} + + for stepID, stepGroupModel := range collection.Steps { + steps[stepID] = models.StepGroupModel{ + Info: stepGroupModel.Info, + Versions: map[string]models.StepModel{stepGroupModel.LatestVersionNumber: stepGroupModel.Versions[stepGroupModel.LatestVersionNumber]}, + } + } + + slimCollection.Steps = steps + + return slimCollection +} + +// WriteStepSpecToFile ... +func WriteStepSpecToFile(templateCollection models.StepCollectionModel, route SteplibRoute) error { + pth := GetStepSpecPath(route) + + if exist, err := pathutil.IsPathExists(pth); err != nil { + return err + } else if !exist { + dir, _ := path.Split(pth) + err := os.MkdirAll(dir, 0777) + if err != nil { + return err + } + } else { + err := os.Remove(pth) + if err != nil { + return err + } + } + + collection, err := generateStepLib(route, templateCollection) + if err != nil { + return err + } + + bytes, err := json.MarshalIndent(collection, "", "\t") + if err != nil { + return err + } + + if err := fileutil.WriteBytesToFile(pth, bytes); err != nil { + return err + } + + pth = GetSlimStepSpecPath(route) + slimCollection := generateSlimStepLib(collection) + if err != nil { + return err + } + + bytes, err = json.MarshalIndent(slimCollection, "", "\t") + if err != nil { + return err + } + + return fileutil.WriteBytesToFile(pth, bytes) +} + +// ReadStepSpec ... +func ReadStepSpec(uri string) (models.StepCollectionModel, error) { + route, found := ReadRoute(uri) + if !found { + return models.StepCollectionModel{}, errors.New("No route found for lib: " + uri) + } + pth := GetStepSpecPath(route) + bytes, err := fileutil.ReadBytesFromFile(pth) + if err != nil { + return models.StepCollectionModel{}, err + } + var stepLib models.StepCollectionModel + if err := json.Unmarshal(bytes, &stepLib); err != nil { + return models.StepCollectionModel{}, err + } + + return stepLib, nil +} + +// ReadStepVersionInfo ... +func ReadStepVersionInfo(collectionURI, stepID, stepVersionID string) (models.StepVersionModel, error) { + // Input validation + if stepID == "" { + return models.StepVersionModel{}, errors.New("Missing required input: step id") + } + + // Check if step exist in collection + collection, err := ReadStepSpec(collectionURI) + if err != nil { + return models.StepVersionModel{}, fmt.Errorf("Failed to read steps spec (spec.json), err: %s", err) + } + + stepWithVersion, stepFound, versionFound := collection.GetStepVersion(stepID, stepVersionID) + if !stepFound { + return models.StepVersionModel{}, fmt.Errorf("Collection doesn't contain step with id: %s", stepID) + } else if !versionFound { + return models.StepVersionModel{}, fmt.Errorf("Collection doesn't contain step (%s) with version: %s", stepID, stepVersionID) + } + + return stepWithVersion, nil +} + +// ReGenerateLibrarySpec ... +func ReGenerateLibrarySpec(route SteplibRoute) error { + pth := GetLibraryBaseDirPath(route) + if exists, err := pathutil.IsPathExists(pth); err != nil { + return err + } else if !exists { + return errors.New("Not initialized") + } + + specPth := pth + "/steplib.yml" + collection, err := ParseStepCollection(specPth) + if err != nil { + return err + } + + return WriteStepSpecToFile(collection, route) +} diff --git a/vendor/github.com/bitrise-io/stepman/stepman/util_test.go b/vendor/github.com/bitrise-io/stepman/stepman/util_test.go new file mode 100644 index 00000000..ec4c1471 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/stepman/util_test.go @@ -0,0 +1,28 @@ +package stepman + +import ( + "testing" + + "github.com/bitrise-io/go-utils/pointers" + "github.com/bitrise-io/stepman/models" + "github.com/stretchr/testify/require" +) + +func TestAddStepVersionToStepGroup(t *testing.T) { + step := models.StepModel{ + Title: pointers.NewStringPtr("name 1"), + } + + group := models.StepGroupModel{ + Versions: map[string]models.StepModel{ + "1.0.0": step, + "2.0.0": step, + }, + LatestVersionNumber: "2.0.0", + } + + group, err := addStepVersionToStepGroup(step, "2.1.0", group) + require.Equal(t, nil, err) + require.Equal(t, 3, len(group.Versions)) + require.Equal(t, "2.1.0", group.LatestVersionNumber) +} diff --git a/vendor/github.com/bitrise-io/stepman/version/build.go b/vendor/github.com/bitrise-io/stepman/version/build.go new file mode 100644 index 00000000..06c70c10 --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/version/build.go @@ -0,0 +1,7 @@ +package version + +// BuildNumber ... +var BuildNumber = "" + +// Commit ... +var Commit = "" diff --git a/vendor/github.com/bitrise-io/stepman/version/version.go b/vendor/github.com/bitrise-io/stepman/version/version.go new file mode 100644 index 00000000..d33c31ac --- /dev/null +++ b/vendor/github.com/bitrise-io/stepman/version/version.go @@ -0,0 +1,4 @@ +package version + +// VERSION ... +const VERSION = "0.11.0" diff --git a/vendor/gopkg.in/yaml.v2/.travis.yml b/vendor/gopkg.in/yaml.v2/.travis.yml new file mode 100644 index 00000000..9f556934 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/.travis.yml @@ -0,0 +1,12 @@ +language: go + +go: + - 1.4 + - 1.5 + - 1.6 + - 1.7 + - 1.8 + - 1.9 + - tip + +go_import_path: gopkg.in/yaml.v2 diff --git a/vendor/gopkg.in/yaml.v2/LICENSE b/vendor/gopkg.in/yaml.v2/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/gopkg.in/yaml.v2/LICENSE.libyaml b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml new file mode 100644 index 00000000..8da58fbf --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml @@ -0,0 +1,31 @@ +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original copyright and license: + + apic.go + emitterc.go + parserc.go + readerc.go + scannerc.go + writerc.go + yamlh.go + yamlprivateh.go + +Copyright (c) 2006 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/gopkg.in/yaml.v2/NOTICE b/vendor/gopkg.in/yaml.v2/NOTICE new file mode 100644 index 00000000..866d74a7 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/NOTICE @@ -0,0 +1,13 @@ +Copyright 2011-2016 Canonical Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/gopkg.in/yaml.v2/README.md b/vendor/gopkg.in/yaml.v2/README.md new file mode 100644 index 00000000..b50c6e87 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/README.md @@ -0,0 +1,133 @@ +# YAML support for the Go language + +Introduction +------------ + +The yaml package enables Go programs to comfortably encode and decode YAML +values. It was developed within [Canonical](https://www.canonical.com) as +part of the [juju](https://juju.ubuntu.com) project, and is based on a +pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML) +C library to parse and generate YAML data quickly and reliably. + +Compatibility +------------- + +The yaml package supports most of YAML 1.1 and 1.2, including support for +anchors, tags, map merging, etc. Multi-document unmarshalling is not yet +implemented, and base-60 floats from YAML 1.1 are purposefully not +supported since they're a poor design and are gone in YAML 1.2. + +Installation and usage +---------------------- + +The import path for the package is *gopkg.in/yaml.v2*. + +To install it, run: + + go get gopkg.in/yaml.v2 + +API documentation +----------------- + +If opened in a browser, the import path itself leads to the API documentation: + + * [https://gopkg.in/yaml.v2](https://gopkg.in/yaml.v2) + +API stability +------------- + +The package API for yaml v2 will remain stable as described in [gopkg.in](https://gopkg.in). + + +License +------- + +The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details. + + +Example +------- + +```Go +package main + +import ( + "fmt" + "log" + + "gopkg.in/yaml.v2" +) + +var data = ` +a: Easy! +b: + c: 2 + d: [3, 4] +` + +// Note: struct fields must be public in order for unmarshal to +// correctly populate the data. +type T struct { + A string + B struct { + RenamedC int `yaml:"c"` + D []int `yaml:",flow"` + } +} + +func main() { + t := T{} + + err := yaml.Unmarshal([]byte(data), &t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t:\n%v\n\n", t) + + d, err := yaml.Marshal(&t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t dump:\n%s\n\n", string(d)) + + m := make(map[interface{}]interface{}) + + err = yaml.Unmarshal([]byte(data), &m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m:\n%v\n\n", m) + + d, err = yaml.Marshal(&m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m dump:\n%s\n\n", string(d)) +} +``` + +This example will generate the following output: + +``` +--- t: +{Easy! {2 [3 4]}} + +--- t dump: +a: Easy! +b: + c: 2 + d: [3, 4] + + +--- m: +map[a:Easy! b:map[c:2 d:[3 4]]] + +--- m dump: +a: Easy! +b: + c: 2 + d: + - 3 + - 4 +``` + diff --git a/vendor/gopkg.in/yaml.v2/apic.go b/vendor/gopkg.in/yaml.v2/apic.go new file mode 100644 index 00000000..1f7e87e6 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/apic.go @@ -0,0 +1,739 @@ +package yaml + +import ( + "io" +) + +func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) { + //fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens)) + + // Check if we can move the queue at the beginning of the buffer. + if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) { + if parser.tokens_head != len(parser.tokens) { + copy(parser.tokens, parser.tokens[parser.tokens_head:]) + } + parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head] + parser.tokens_head = 0 + } + parser.tokens = append(parser.tokens, *token) + if pos < 0 { + return + } + copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:]) + parser.tokens[parser.tokens_head+pos] = *token +} + +// Create a new parser object. +func yaml_parser_initialize(parser *yaml_parser_t) bool { + *parser = yaml_parser_t{ + raw_buffer: make([]byte, 0, input_raw_buffer_size), + buffer: make([]byte, 0, input_buffer_size), + } + return true +} + +// Destroy a parser object. +func yaml_parser_delete(parser *yaml_parser_t) { + *parser = yaml_parser_t{} +} + +// String read handler. +func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + if parser.input_pos == len(parser.input) { + return 0, io.EOF + } + n = copy(buffer, parser.input[parser.input_pos:]) + parser.input_pos += n + return n, nil +} + +// Reader read handler. +func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + return parser.input_reader.Read(buffer) +} + +// Set a string input. +func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_string_read_handler + parser.input = input + parser.input_pos = 0 +} + +// Set a file input. +func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_reader_read_handler + parser.input_reader = r +} + +// Set the source encoding. +func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) { + if parser.encoding != yaml_ANY_ENCODING { + panic("must set the encoding only once") + } + parser.encoding = encoding +} + +// Create a new emitter object. +func yaml_emitter_initialize(emitter *yaml_emitter_t) { + *emitter = yaml_emitter_t{ + buffer: make([]byte, output_buffer_size), + raw_buffer: make([]byte, 0, output_raw_buffer_size), + states: make([]yaml_emitter_state_t, 0, initial_stack_size), + events: make([]yaml_event_t, 0, initial_queue_size), + } +} + +// Destroy an emitter object. +func yaml_emitter_delete(emitter *yaml_emitter_t) { + *emitter = yaml_emitter_t{} +} + +// String write handler. +func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + *emitter.output_buffer = append(*emitter.output_buffer, buffer...) + return nil +} + +// yaml_writer_write_handler uses emitter.output_writer to write the +// emitted text. +func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + _, err := emitter.output_writer.Write(buffer) + return err +} + +// Set a string output. +func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_string_write_handler + emitter.output_buffer = output_buffer +} + +// Set a file output. +func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_writer_write_handler + emitter.output_writer = w +} + +// Set the output encoding. +func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) { + if emitter.encoding != yaml_ANY_ENCODING { + panic("must set the output encoding only once") + } + emitter.encoding = encoding +} + +// Set the canonical output style. +func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) { + emitter.canonical = canonical +} + +//// Set the indentation increment. +func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) { + if indent < 2 || indent > 9 { + indent = 2 + } + emitter.best_indent = indent +} + +// Set the preferred line width. +func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) { + if width < 0 { + width = -1 + } + emitter.best_width = width +} + +// Set if unescaped non-ASCII characters are allowed. +func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) { + emitter.unicode = unicode +} + +// Set the preferred line break character. +func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) { + emitter.line_break = line_break +} + +///* +// * Destroy a token object. +// */ +// +//YAML_DECLARE(void) +//yaml_token_delete(yaml_token_t *token) +//{ +// assert(token); // Non-NULL token object expected. +// +// switch (token.type) +// { +// case YAML_TAG_DIRECTIVE_TOKEN: +// yaml_free(token.data.tag_directive.handle); +// yaml_free(token.data.tag_directive.prefix); +// break; +// +// case YAML_ALIAS_TOKEN: +// yaml_free(token.data.alias.value); +// break; +// +// case YAML_ANCHOR_TOKEN: +// yaml_free(token.data.anchor.value); +// break; +// +// case YAML_TAG_TOKEN: +// yaml_free(token.data.tag.handle); +// yaml_free(token.data.tag.suffix); +// break; +// +// case YAML_SCALAR_TOKEN: +// yaml_free(token.data.scalar.value); +// break; +// +// default: +// break; +// } +// +// memset(token, 0, sizeof(yaml_token_t)); +//} +// +///* +// * Check if a string is a valid UTF-8 sequence. +// * +// * Check 'reader.c' for more details on UTF-8 encoding. +// */ +// +//static int +//yaml_check_utf8(yaml_char_t *start, size_t length) +//{ +// yaml_char_t *end = start+length; +// yaml_char_t *pointer = start; +// +// while (pointer < end) { +// unsigned char octet; +// unsigned int width; +// unsigned int value; +// size_t k; +// +// octet = pointer[0]; +// width = (octet & 0x80) == 0x00 ? 1 : +// (octet & 0xE0) == 0xC0 ? 2 : +// (octet & 0xF0) == 0xE0 ? 3 : +// (octet & 0xF8) == 0xF0 ? 4 : 0; +// value = (octet & 0x80) == 0x00 ? octet & 0x7F : +// (octet & 0xE0) == 0xC0 ? octet & 0x1F : +// (octet & 0xF0) == 0xE0 ? octet & 0x0F : +// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; +// if (!width) return 0; +// if (pointer+width > end) return 0; +// for (k = 1; k < width; k ++) { +// octet = pointer[k]; +// if ((octet & 0xC0) != 0x80) return 0; +// value = (value << 6) + (octet & 0x3F); +// } +// if (!((width == 1) || +// (width == 2 && value >= 0x80) || +// (width == 3 && value >= 0x800) || +// (width == 4 && value >= 0x10000))) return 0; +// +// pointer += width; +// } +// +// return 1; +//} +// + +// Create STREAM-START. +func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) { + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + encoding: encoding, + } +} + +// Create STREAM-END. +func yaml_stream_end_event_initialize(event *yaml_event_t) { + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + } +} + +// Create DOCUMENT-START. +func yaml_document_start_event_initialize( + event *yaml_event_t, + version_directive *yaml_version_directive_t, + tag_directives []yaml_tag_directive_t, + implicit bool, +) { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: implicit, + } +} + +// Create DOCUMENT-END. +func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + implicit: implicit, + } +} + +///* +// * Create ALIAS. +// */ +// +//YAML_DECLARE(int) +//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t) +//{ +// mark yaml_mark_t = { 0, 0, 0 } +// anchor_copy *yaml_char_t = NULL +// +// assert(event) // Non-NULL event object is expected. +// assert(anchor) // Non-NULL anchor is expected. +// +// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0 +// +// anchor_copy = yaml_strdup(anchor) +// if (!anchor_copy) +// return 0 +// +// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark) +// +// return 1 +//} + +// Create SCALAR. +func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + anchor: anchor, + tag: tag, + value: value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-START. +func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-END. +func yaml_sequence_end_event_initialize(event *yaml_event_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + } + return true +} + +// Create MAPPING-START. +func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) { + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } +} + +// Create MAPPING-END. +func yaml_mapping_end_event_initialize(event *yaml_event_t) { + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + } +} + +// Destroy an event object. +func yaml_event_delete(event *yaml_event_t) { + *event = yaml_event_t{} +} + +///* +// * Create a document object. +// */ +// +//YAML_DECLARE(int) +//yaml_document_initialize(document *yaml_document_t, +// version_directive *yaml_version_directive_t, +// tag_directives_start *yaml_tag_directive_t, +// tag_directives_end *yaml_tag_directive_t, +// start_implicit int, end_implicit int) +//{ +// struct { +// error yaml_error_type_t +// } context +// struct { +// start *yaml_node_t +// end *yaml_node_t +// top *yaml_node_t +// } nodes = { NULL, NULL, NULL } +// version_directive_copy *yaml_version_directive_t = NULL +// struct { +// start *yaml_tag_directive_t +// end *yaml_tag_directive_t +// top *yaml_tag_directive_t +// } tag_directives_copy = { NULL, NULL, NULL } +// value yaml_tag_directive_t = { NULL, NULL } +// mark yaml_mark_t = { 0, 0, 0 } +// +// assert(document) // Non-NULL document object is expected. +// assert((tag_directives_start && tag_directives_end) || +// (tag_directives_start == tag_directives_end)) +// // Valid tag directives are expected. +// +// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error +// +// if (version_directive) { +// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t)) +// if (!version_directive_copy) goto error +// version_directive_copy.major = version_directive.major +// version_directive_copy.minor = version_directive.minor +// } +// +// if (tag_directives_start != tag_directives_end) { +// tag_directive *yaml_tag_directive_t +// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE)) +// goto error +// for (tag_directive = tag_directives_start +// tag_directive != tag_directives_end; tag_directive ++) { +// assert(tag_directive.handle) +// assert(tag_directive.prefix) +// if (!yaml_check_utf8(tag_directive.handle, +// strlen((char *)tag_directive.handle))) +// goto error +// if (!yaml_check_utf8(tag_directive.prefix, +// strlen((char *)tag_directive.prefix))) +// goto error +// value.handle = yaml_strdup(tag_directive.handle) +// value.prefix = yaml_strdup(tag_directive.prefix) +// if (!value.handle || !value.prefix) goto error +// if (!PUSH(&context, tag_directives_copy, value)) +// goto error +// value.handle = NULL +// value.prefix = NULL +// } +// } +// +// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy, +// tag_directives_copy.start, tag_directives_copy.top, +// start_implicit, end_implicit, mark, mark) +// +// return 1 +// +//error: +// STACK_DEL(&context, nodes) +// yaml_free(version_directive_copy) +// while (!STACK_EMPTY(&context, tag_directives_copy)) { +// value yaml_tag_directive_t = POP(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// } +// STACK_DEL(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// +// return 0 +//} +// +///* +// * Destroy a document object. +// */ +// +//YAML_DECLARE(void) +//yaml_document_delete(document *yaml_document_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// tag_directive *yaml_tag_directive_t +// +// context.error = YAML_NO_ERROR // Eliminate a compiler warning. +// +// assert(document) // Non-NULL document object is expected. +// +// while (!STACK_EMPTY(&context, document.nodes)) { +// node yaml_node_t = POP(&context, document.nodes) +// yaml_free(node.tag) +// switch (node.type) { +// case YAML_SCALAR_NODE: +// yaml_free(node.data.scalar.value) +// break +// case YAML_SEQUENCE_NODE: +// STACK_DEL(&context, node.data.sequence.items) +// break +// case YAML_MAPPING_NODE: +// STACK_DEL(&context, node.data.mapping.pairs) +// break +// default: +// assert(0) // Should not happen. +// } +// } +// STACK_DEL(&context, document.nodes) +// +// yaml_free(document.version_directive) +// for (tag_directive = document.tag_directives.start +// tag_directive != document.tag_directives.end +// tag_directive++) { +// yaml_free(tag_directive.handle) +// yaml_free(tag_directive.prefix) +// } +// yaml_free(document.tag_directives.start) +// +// memset(document, 0, sizeof(yaml_document_t)) +//} +// +///** +// * Get a document node. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_node(document *yaml_document_t, index int) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (index > 0 && document.nodes.start + index <= document.nodes.top) { +// return document.nodes.start + index - 1 +// } +// return NULL +//} +// +///** +// * Get the root object. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_root_node(document *yaml_document_t) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (document.nodes.top != document.nodes.start) { +// return document.nodes.start +// } +// return NULL +//} +// +///* +// * Add a scalar node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_scalar(document *yaml_document_t, +// tag *yaml_char_t, value *yaml_char_t, length int, +// style yaml_scalar_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// value_copy *yaml_char_t = NULL +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// assert(value) // Non-NULL value is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (length < 0) { +// length = strlen((char *)value) +// } +// +// if (!yaml_check_utf8(value, length)) goto error +// value_copy = yaml_malloc(length+1) +// if (!value_copy) goto error +// memcpy(value_copy, value, length) +// value_copy[length] = '\0' +// +// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// yaml_free(tag_copy) +// yaml_free(value_copy) +// +// return 0 +//} +// +///* +// * Add a sequence node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_sequence(document *yaml_document_t, +// tag *yaml_char_t, style yaml_sequence_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_item_t +// end *yaml_node_item_t +// top *yaml_node_item_t +// } items = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error +// +// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, items) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Add a mapping node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_mapping(document *yaml_document_t, +// tag *yaml_char_t, style yaml_mapping_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_pair_t +// end *yaml_node_pair_t +// top *yaml_node_pair_t +// } pairs = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error +// +// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, pairs) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Append an item to a sequence node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_sequence_item(document *yaml_document_t, +// sequence int, item int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// assert(document) // Non-NULL document is required. +// assert(sequence > 0 +// && document.nodes.start + sequence <= document.nodes.top) +// // Valid sequence id is required. +// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE) +// // A sequence node is required. +// assert(item > 0 && document.nodes.start + item <= document.nodes.top) +// // Valid item id is required. +// +// if (!PUSH(&context, +// document.nodes.start[sequence-1].data.sequence.items, item)) +// return 0 +// +// return 1 +//} +// +///* +// * Append a pair of a key and a value to a mapping node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_mapping_pair(document *yaml_document_t, +// mapping int, key int, value int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// pair yaml_node_pair_t +// +// assert(document) // Non-NULL document is required. +// assert(mapping > 0 +// && document.nodes.start + mapping <= document.nodes.top) +// // Valid mapping id is required. +// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE) +// // A mapping node is required. +// assert(key > 0 && document.nodes.start + key <= document.nodes.top) +// // Valid key id is required. +// assert(value > 0 && document.nodes.start + value <= document.nodes.top) +// // Valid value id is required. +// +// pair.key = key +// pair.value = value +// +// if (!PUSH(&context, +// document.nodes.start[mapping-1].data.mapping.pairs, pair)) +// return 0 +// +// return 1 +//} +// +// diff --git a/vendor/gopkg.in/yaml.v2/decode.go b/vendor/gopkg.in/yaml.v2/decode.go new file mode 100644 index 00000000..e4e56e28 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/decode.go @@ -0,0 +1,775 @@ +package yaml + +import ( + "encoding" + "encoding/base64" + "fmt" + "io" + "math" + "reflect" + "strconv" + "time" +) + +const ( + documentNode = 1 << iota + mappingNode + sequenceNode + scalarNode + aliasNode +) + +type node struct { + kind int + line, column int + tag string + // For an alias node, alias holds the resolved alias. + alias *node + value string + implicit bool + children []*node + anchors map[string]*node +} + +// ---------------------------------------------------------------------------- +// Parser, produces a node tree out of a libyaml event stream. + +type parser struct { + parser yaml_parser_t + event yaml_event_t + doc *node + doneInit bool +} + +func newParser(b []byte) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") + } + if len(b) == 0 { + b = []byte{'\n'} + } + yaml_parser_set_input_string(&p.parser, b) + return &p +} + +func newParserFromReader(r io.Reader) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") + } + yaml_parser_set_input_reader(&p.parser, r) + return &p +} + +func (p *parser) init() { + if p.doneInit { + return + } + p.expect(yaml_STREAM_START_EVENT) + p.doneInit = true +} + +func (p *parser) destroy() { + if p.event.typ != yaml_NO_EVENT { + yaml_event_delete(&p.event) + } + yaml_parser_delete(&p.parser) +} + +// expect consumes an event from the event stream and +// checks that it's of the expected type. +func (p *parser) expect(e yaml_event_type_t) { + if p.event.typ == yaml_NO_EVENT { + if !yaml_parser_parse(&p.parser, &p.event) { + p.fail() + } + } + if p.event.typ == yaml_STREAM_END_EVENT { + failf("attempted to go past the end of stream; corrupted value?") + } + if p.event.typ != e { + p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ) + p.fail() + } + yaml_event_delete(&p.event) + p.event.typ = yaml_NO_EVENT +} + +// peek peeks at the next event in the event stream, +// puts the results into p.event and returns the event type. +func (p *parser) peek() yaml_event_type_t { + if p.event.typ != yaml_NO_EVENT { + return p.event.typ + } + if !yaml_parser_parse(&p.parser, &p.event) { + p.fail() + } + return p.event.typ +} + +func (p *parser) fail() { + var where string + var line int + if p.parser.problem_mark.line != 0 { + line = p.parser.problem_mark.line + // Scanner errors don't iterate line before returning error + if p.parser.error == yaml_SCANNER_ERROR { + line++ + } + } else if p.parser.context_mark.line != 0 { + line = p.parser.context_mark.line + } + if line != 0 { + where = "line " + strconv.Itoa(line) + ": " + } + var msg string + if len(p.parser.problem) > 0 { + msg = p.parser.problem + } else { + msg = "unknown problem parsing YAML content" + } + failf("%s%s", where, msg) +} + +func (p *parser) anchor(n *node, anchor []byte) { + if anchor != nil { + p.doc.anchors[string(anchor)] = n + } +} + +func (p *parser) parse() *node { + p.init() + switch p.peek() { + case yaml_SCALAR_EVENT: + return p.scalar() + case yaml_ALIAS_EVENT: + return p.alias() + case yaml_MAPPING_START_EVENT: + return p.mapping() + case yaml_SEQUENCE_START_EVENT: + return p.sequence() + case yaml_DOCUMENT_START_EVENT: + return p.document() + case yaml_STREAM_END_EVENT: + // Happens when attempting to decode an empty buffer. + return nil + default: + panic("attempted to parse unknown event: " + p.event.typ.String()) + } +} + +func (p *parser) node(kind int) *node { + return &node{ + kind: kind, + line: p.event.start_mark.line, + column: p.event.start_mark.column, + } +} + +func (p *parser) document() *node { + n := p.node(documentNode) + n.anchors = make(map[string]*node) + p.doc = n + p.expect(yaml_DOCUMENT_START_EVENT) + n.children = append(n.children, p.parse()) + p.expect(yaml_DOCUMENT_END_EVENT) + return n +} + +func (p *parser) alias() *node { + n := p.node(aliasNode) + n.value = string(p.event.anchor) + n.alias = p.doc.anchors[n.value] + if n.alias == nil { + failf("unknown anchor '%s' referenced", n.value) + } + p.expect(yaml_ALIAS_EVENT) + return n +} + +func (p *parser) scalar() *node { + n := p.node(scalarNode) + n.value = string(p.event.value) + n.tag = string(p.event.tag) + n.implicit = p.event.implicit + p.anchor(n, p.event.anchor) + p.expect(yaml_SCALAR_EVENT) + return n +} + +func (p *parser) sequence() *node { + n := p.node(sequenceNode) + p.anchor(n, p.event.anchor) + p.expect(yaml_SEQUENCE_START_EVENT) + for p.peek() != yaml_SEQUENCE_END_EVENT { + n.children = append(n.children, p.parse()) + } + p.expect(yaml_SEQUENCE_END_EVENT) + return n +} + +func (p *parser) mapping() *node { + n := p.node(mappingNode) + p.anchor(n, p.event.anchor) + p.expect(yaml_MAPPING_START_EVENT) + for p.peek() != yaml_MAPPING_END_EVENT { + n.children = append(n.children, p.parse(), p.parse()) + } + p.expect(yaml_MAPPING_END_EVENT) + return n +} + +// ---------------------------------------------------------------------------- +// Decoder, unmarshals a node into a provided value. + +type decoder struct { + doc *node + aliases map[*node]bool + mapType reflect.Type + terrors []string + strict bool +} + +var ( + mapItemType = reflect.TypeOf(MapItem{}) + durationType = reflect.TypeOf(time.Duration(0)) + defaultMapType = reflect.TypeOf(map[interface{}]interface{}{}) + ifaceType = defaultMapType.Elem() + timeType = reflect.TypeOf(time.Time{}) + ptrTimeType = reflect.TypeOf(&time.Time{}) +) + +func newDecoder(strict bool) *decoder { + d := &decoder{mapType: defaultMapType, strict: strict} + d.aliases = make(map[*node]bool) + return d +} + +func (d *decoder) terror(n *node, tag string, out reflect.Value) { + if n.tag != "" { + tag = n.tag + } + value := n.value + if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG { + if len(value) > 10 { + value = " `" + value[:7] + "...`" + } else { + value = " `" + value + "`" + } + } + d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type())) +} + +func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) { + terrlen := len(d.terrors) + err := u.UnmarshalYAML(func(v interface{}) (err error) { + defer handleErr(&err) + d.unmarshal(n, reflect.ValueOf(v)) + if len(d.terrors) > terrlen { + issues := d.terrors[terrlen:] + d.terrors = d.terrors[:terrlen] + return &TypeError{issues} + } + return nil + }) + if e, ok := err.(*TypeError); ok { + d.terrors = append(d.terrors, e.Errors...) + return false + } + if err != nil { + fail(err) + } + return true +} + +// d.prepare initializes and dereferences pointers and calls UnmarshalYAML +// if a value is found to implement it. +// It returns the initialized and dereferenced out value, whether +// unmarshalling was already done by UnmarshalYAML, and if so whether +// its types unmarshalled appropriately. +// +// If n holds a null value, prepare returns before doing anything. +func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) { + if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) { + return out, false, false + } + again := true + for again { + again = false + if out.Kind() == reflect.Ptr { + if out.IsNil() { + out.Set(reflect.New(out.Type().Elem())) + } + out = out.Elem() + again = true + } + if out.CanAddr() { + if u, ok := out.Addr().Interface().(Unmarshaler); ok { + good = d.callUnmarshaler(n, u) + return out, true, good + } + } + } + return out, false, false +} + +func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) { + switch n.kind { + case documentNode: + return d.document(n, out) + case aliasNode: + return d.alias(n, out) + } + out, unmarshaled, good := d.prepare(n, out) + if unmarshaled { + return good + } + switch n.kind { + case scalarNode: + good = d.scalar(n, out) + case mappingNode: + good = d.mapping(n, out) + case sequenceNode: + good = d.sequence(n, out) + default: + panic("internal error: unknown node kind: " + strconv.Itoa(n.kind)) + } + return good +} + +func (d *decoder) document(n *node, out reflect.Value) (good bool) { + if len(n.children) == 1 { + d.doc = n + d.unmarshal(n.children[0], out) + return true + } + return false +} + +func (d *decoder) alias(n *node, out reflect.Value) (good bool) { + if d.aliases[n] { + // TODO this could actually be allowed in some circumstances. + failf("anchor '%s' value contains itself", n.value) + } + d.aliases[n] = true + good = d.unmarshal(n.alias, out) + delete(d.aliases, n) + return good +} + +var zeroValue reflect.Value + +func resetMap(out reflect.Value) { + for _, k := range out.MapKeys() { + out.SetMapIndex(k, zeroValue) + } +} + +func (d *decoder) scalar(n *node, out reflect.Value) bool { + var tag string + var resolved interface{} + if n.tag == "" && !n.implicit { + tag = yaml_STR_TAG + resolved = n.value + } else { + tag, resolved = resolve(n.tag, n.value) + if tag == yaml_BINARY_TAG { + data, err := base64.StdEncoding.DecodeString(resolved.(string)) + if err != nil { + failf("!!binary value contains invalid base64 data") + } + resolved = string(data) + } + } + if resolved == nil { + if out.Kind() == reflect.Map && !out.CanAddr() { + resetMap(out) + } else { + out.Set(reflect.Zero(out.Type())) + } + return true + } + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + // We've resolved to exactly the type we want, so use that. + out.Set(resolvedv) + return true + } + // Perhaps we can use the value as a TextUnmarshaler to + // set its value. + if out.CanAddr() { + u, ok := out.Addr().Interface().(encoding.TextUnmarshaler) + if ok { + var text []byte + if tag == yaml_BINARY_TAG { + text = []byte(resolved.(string)) + } else { + // We let any value be unmarshaled into TextUnmarshaler. + // That might be more lax than we'd like, but the + // TextUnmarshaler itself should bowl out any dubious values. + text = []byte(n.value) + } + err := u.UnmarshalText(text) + if err != nil { + fail(err) + } + return true + } + } + switch out.Kind() { + case reflect.String: + if tag == yaml_BINARY_TAG { + out.SetString(resolved.(string)) + return true + } + if resolved != nil { + out.SetString(n.value) + return true + } + case reflect.Interface: + if resolved == nil { + out.Set(reflect.Zero(out.Type())) + } else if tag == yaml_TIMESTAMP_TAG { + // It looks like a timestamp but for backward compatibility + // reasons we set it as a string, so that code that unmarshals + // timestamp-like values into interface{} will continue to + // see a string and not a time.Time. + // TODO(v3) Drop this. + out.Set(reflect.ValueOf(n.value)) + } else { + out.Set(reflect.ValueOf(resolved)) + } + return true + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch resolved := resolved.(type) { + case int: + if !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case int64: + if !out.OverflowInt(resolved) { + out.SetInt(resolved) + return true + } + case uint64: + if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case float64: + if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case string: + if out.Type() == durationType { + d, err := time.ParseDuration(resolved) + if err == nil { + out.SetInt(int64(d)) + return true + } + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch resolved := resolved.(type) { + case int: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case int64: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case uint64: + if !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case float64: + if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + } + case reflect.Bool: + switch resolved := resolved.(type) { + case bool: + out.SetBool(resolved) + return true + } + case reflect.Float32, reflect.Float64: + switch resolved := resolved.(type) { + case int: + out.SetFloat(float64(resolved)) + return true + case int64: + out.SetFloat(float64(resolved)) + return true + case uint64: + out.SetFloat(float64(resolved)) + return true + case float64: + out.SetFloat(resolved) + return true + } + case reflect.Struct: + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + out.Set(resolvedv) + return true + } + case reflect.Ptr: + if out.Type().Elem() == reflect.TypeOf(resolved) { + // TODO DOes this make sense? When is out a Ptr except when decoding a nil value? + elem := reflect.New(out.Type().Elem()) + elem.Elem().Set(reflect.ValueOf(resolved)) + out.Set(elem) + return true + } + } + d.terror(n, tag, out) + return false +} + +func settableValueOf(i interface{}) reflect.Value { + v := reflect.ValueOf(i) + sv := reflect.New(v.Type()).Elem() + sv.Set(v) + return sv +} + +func (d *decoder) sequence(n *node, out reflect.Value) (good bool) { + l := len(n.children) + + var iface reflect.Value + switch out.Kind() { + case reflect.Slice: + out.Set(reflect.MakeSlice(out.Type(), l, l)) + case reflect.Array: + if l != out.Len() { + failf("invalid array: want %d elements but got %d", out.Len(), l) + } + case reflect.Interface: + // No type hints. Will have to use a generic sequence. + iface = out + out = settableValueOf(make([]interface{}, l)) + default: + d.terror(n, yaml_SEQ_TAG, out) + return false + } + et := out.Type().Elem() + + j := 0 + for i := 0; i < l; i++ { + e := reflect.New(et).Elem() + if ok := d.unmarshal(n.children[i], e); ok { + out.Index(j).Set(e) + j++ + } + } + if out.Kind() != reflect.Array { + out.Set(out.Slice(0, j)) + } + if iface.IsValid() { + iface.Set(out) + } + return true +} + +func (d *decoder) mapping(n *node, out reflect.Value) (good bool) { + switch out.Kind() { + case reflect.Struct: + return d.mappingStruct(n, out) + case reflect.Slice: + return d.mappingSlice(n, out) + case reflect.Map: + // okay + case reflect.Interface: + if d.mapType.Kind() == reflect.Map { + iface := out + out = reflect.MakeMap(d.mapType) + iface.Set(out) + } else { + slicev := reflect.New(d.mapType).Elem() + if !d.mappingSlice(n, slicev) { + return false + } + out.Set(slicev) + return true + } + default: + d.terror(n, yaml_MAP_TAG, out) + return false + } + outt := out.Type() + kt := outt.Key() + et := outt.Elem() + + mapType := d.mapType + if outt.Key() == ifaceType && outt.Elem() == ifaceType { + d.mapType = outt + } + + if out.IsNil() { + out.Set(reflect.MakeMap(outt)) + } + l := len(n.children) + for i := 0; i < l; i += 2 { + if isMerge(n.children[i]) { + d.merge(n.children[i+1], out) + continue + } + k := reflect.New(kt).Elem() + if d.unmarshal(n.children[i], k) { + kkind := k.Kind() + if kkind == reflect.Interface { + kkind = k.Elem().Kind() + } + if kkind == reflect.Map || kkind == reflect.Slice { + failf("invalid map key: %#v", k.Interface()) + } + e := reflect.New(et).Elem() + if d.unmarshal(n.children[i+1], e) { + d.setMapIndex(n.children[i+1], out, k, e) + } + } + } + d.mapType = mapType + return true +} + +func (d *decoder) setMapIndex(n *node, out, k, v reflect.Value) { + if d.strict && out.MapIndex(k) != zeroValue { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: key %#v already set in map", n.line+1, k.Interface())) + return + } + out.SetMapIndex(k, v) +} + +func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) { + outt := out.Type() + if outt.Elem() != mapItemType { + d.terror(n, yaml_MAP_TAG, out) + return false + } + + mapType := d.mapType + d.mapType = outt + + var slice []MapItem + var l = len(n.children) + for i := 0; i < l; i += 2 { + if isMerge(n.children[i]) { + d.merge(n.children[i+1], out) + continue + } + item := MapItem{} + k := reflect.ValueOf(&item.Key).Elem() + if d.unmarshal(n.children[i], k) { + v := reflect.ValueOf(&item.Value).Elem() + if d.unmarshal(n.children[i+1], v) { + slice = append(slice, item) + } + } + } + out.Set(reflect.ValueOf(slice)) + d.mapType = mapType + return true +} + +func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) { + sinfo, err := getStructInfo(out.Type()) + if err != nil { + panic(err) + } + name := settableValueOf("") + l := len(n.children) + + var inlineMap reflect.Value + var elemType reflect.Type + if sinfo.InlineMap != -1 { + inlineMap = out.Field(sinfo.InlineMap) + inlineMap.Set(reflect.New(inlineMap.Type()).Elem()) + elemType = inlineMap.Type().Elem() + } + + var doneFields []bool + if d.strict { + doneFields = make([]bool, len(sinfo.FieldsList)) + } + for i := 0; i < l; i += 2 { + ni := n.children[i] + if isMerge(ni) { + d.merge(n.children[i+1], out) + continue + } + if !d.unmarshal(ni, name) { + continue + } + if info, ok := sinfo.FieldsMap[name.String()]; ok { + if d.strict { + if doneFields[info.Id] { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.line+1, name.String(), out.Type())) + continue + } + doneFields[info.Id] = true + } + var field reflect.Value + if info.Inline == nil { + field = out.Field(info.Num) + } else { + field = out.FieldByIndex(info.Inline) + } + d.unmarshal(n.children[i+1], field) + } else if sinfo.InlineMap != -1 { + if inlineMap.IsNil() { + inlineMap.Set(reflect.MakeMap(inlineMap.Type())) + } + value := reflect.New(elemType).Elem() + d.unmarshal(n.children[i+1], value) + d.setMapIndex(n.children[i+1], inlineMap, name, value) + } else if d.strict { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.line+1, name.String(), out.Type())) + } + } + return true +} + +func failWantMap() { + failf("map merge requires map or sequence of maps as the value") +} + +func (d *decoder) merge(n *node, out reflect.Value) { + switch n.kind { + case mappingNode: + d.unmarshal(n, out) + case aliasNode: + an, ok := d.doc.anchors[n.value] + if ok && an.kind != mappingNode { + failWantMap() + } + d.unmarshal(n, out) + case sequenceNode: + // Step backwards as earlier nodes take precedence. + for i := len(n.children) - 1; i >= 0; i-- { + ni := n.children[i] + if ni.kind == aliasNode { + an, ok := d.doc.anchors[ni.value] + if ok && an.kind != mappingNode { + failWantMap() + } + } else if ni.kind != mappingNode { + failWantMap() + } + d.unmarshal(ni, out) + } + default: + failWantMap() + } +} + +func isMerge(n *node) bool { + return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG) +} diff --git a/vendor/gopkg.in/yaml.v2/decode_test.go b/vendor/gopkg.in/yaml.v2/decode_test.go new file mode 100644 index 00000000..b05c466e --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/decode_test.go @@ -0,0 +1,1334 @@ +package yaml_test + +import ( + "errors" + "io" + "math" + "reflect" + "strings" + "time" + + . "gopkg.in/check.v1" + "gopkg.in/yaml.v2" +) + +var unmarshalIntTest = 123 + +var unmarshalTests = []struct { + data string + value interface{} +}{ + { + "", + (*struct{})(nil), + }, + { + "{}", &struct{}{}, + }, { + "v: hi", + map[string]string{"v": "hi"}, + }, { + "v: hi", map[string]interface{}{"v": "hi"}, + }, { + "v: true", + map[string]string{"v": "true"}, + }, { + "v: true", + map[string]interface{}{"v": true}, + }, { + "v: 10", + map[string]interface{}{"v": 10}, + }, { + "v: 0b10", + map[string]interface{}{"v": 2}, + }, { + "v: 0xA", + map[string]interface{}{"v": 10}, + }, { + "v: 4294967296", + map[string]int64{"v": 4294967296}, + }, { + "v: 0.1", + map[string]interface{}{"v": 0.1}, + }, { + "v: .1", + map[string]interface{}{"v": 0.1}, + }, { + "v: .Inf", + map[string]interface{}{"v": math.Inf(+1)}, + }, { + "v: -.Inf", + map[string]interface{}{"v": math.Inf(-1)}, + }, { + "v: -10", + map[string]interface{}{"v": -10}, + }, { + "v: -.1", + map[string]interface{}{"v": -0.1}, + }, + + // Simple values. + { + "123", + &unmarshalIntTest, + }, + + // Floats from spec + { + "canonical: 6.8523e+5", + map[string]interface{}{"canonical": 6.8523e+5}, + }, { + "expo: 685.230_15e+03", + map[string]interface{}{"expo": 685.23015e+03}, + }, { + "fixed: 685_230.15", + map[string]interface{}{"fixed": 685230.15}, + }, { + "neginf: -.inf", + map[string]interface{}{"neginf": math.Inf(-1)}, + }, { + "fixed: 685_230.15", + map[string]float64{"fixed": 685230.15}, + }, + //{"sexa: 190:20:30.15", map[string]interface{}{"sexa": 0}}, // Unsupported + //{"notanum: .NaN", map[string]interface{}{"notanum": math.NaN()}}, // Equality of NaN fails. + + // Bools from spec + { + "canonical: y", + map[string]interface{}{"canonical": true}, + }, { + "answer: NO", + map[string]interface{}{"answer": false}, + }, { + "logical: True", + map[string]interface{}{"logical": true}, + }, { + "option: on", + map[string]interface{}{"option": true}, + }, { + "option: on", + map[string]bool{"option": true}, + }, + // Ints from spec + { + "canonical: 685230", + map[string]interface{}{"canonical": 685230}, + }, { + "decimal: +685_230", + map[string]interface{}{"decimal": 685230}, + }, { + "octal: 02472256", + map[string]interface{}{"octal": 685230}, + }, { + "hexa: 0x_0A_74_AE", + map[string]interface{}{"hexa": 685230}, + }, { + "bin: 0b1010_0111_0100_1010_1110", + map[string]interface{}{"bin": 685230}, + }, { + "bin: -0b101010", + map[string]interface{}{"bin": -42}, + }, { + "bin: -0b1000000000000000000000000000000000000000000000000000000000000000", + map[string]interface{}{"bin": -9223372036854775808}, + }, { + "decimal: +685_230", + map[string]int{"decimal": 685230}, + }, + + //{"sexa: 190:20:30", map[string]interface{}{"sexa": 0}}, // Unsupported + + // Nulls from spec + { + "empty:", + map[string]interface{}{"empty": nil}, + }, { + "canonical: ~", + map[string]interface{}{"canonical": nil}, + }, { + "english: null", + map[string]interface{}{"english": nil}, + }, { + "~: null key", + map[interface{}]string{nil: "null key"}, + }, { + "empty:", + map[string]*bool{"empty": nil}, + }, + + // Flow sequence + { + "seq: [A,B]", + map[string]interface{}{"seq": []interface{}{"A", "B"}}, + }, { + "seq: [A,B,C,]", + map[string][]string{"seq": []string{"A", "B", "C"}}, + }, { + "seq: [A,1,C]", + map[string][]string{"seq": []string{"A", "1", "C"}}, + }, { + "seq: [A,1,C]", + map[string][]int{"seq": []int{1}}, + }, { + "seq: [A,1,C]", + map[string]interface{}{"seq": []interface{}{"A", 1, "C"}}, + }, + // Block sequence + { + "seq:\n - A\n - B", + map[string]interface{}{"seq": []interface{}{"A", "B"}}, + }, { + "seq:\n - A\n - B\n - C", + map[string][]string{"seq": []string{"A", "B", "C"}}, + }, { + "seq:\n - A\n - 1\n - C", + map[string][]string{"seq": []string{"A", "1", "C"}}, + }, { + "seq:\n - A\n - 1\n - C", + map[string][]int{"seq": []int{1}}, + }, { + "seq:\n - A\n - 1\n - C", + map[string]interface{}{"seq": []interface{}{"A", 1, "C"}}, + }, + + // Literal block scalar + { + "scalar: | # Comment\n\n literal\n\n \ttext\n\n", + map[string]string{"scalar": "\nliteral\n\n\ttext\n"}, + }, + + // Folded block scalar + { + "scalar: > # Comment\n\n folded\n line\n \n next\n line\n * one\n * two\n\n last\n line\n\n", + map[string]string{"scalar": "\nfolded line\nnext line\n * one\n * two\n\nlast line\n"}, + }, + + // Map inside interface with no type hints. + { + "a: {b: c}", + map[interface{}]interface{}{"a": map[interface{}]interface{}{"b": "c"}}, + }, + + // Structs and type conversions. + { + "hello: world", + &struct{ Hello string }{"world"}, + }, { + "a: {b: c}", + &struct{ A struct{ B string } }{struct{ B string }{"c"}}, + }, { + "a: {b: c}", + &struct{ A *struct{ B string } }{&struct{ B string }{"c"}}, + }, { + "a: {b: c}", + &struct{ A map[string]string }{map[string]string{"b": "c"}}, + }, { + "a: {b: c}", + &struct{ A *map[string]string }{&map[string]string{"b": "c"}}, + }, { + "a:", + &struct{ A map[string]string }{}, + }, { + "a: 1", + &struct{ A int }{1}, + }, { + "a: 1", + &struct{ A float64 }{1}, + }, { + "a: 1.0", + &struct{ A int }{1}, + }, { + "a: 1.0", + &struct{ A uint }{1}, + }, { + "a: [1, 2]", + &struct{ A []int }{[]int{1, 2}}, + }, { + "a: [1, 2]", + &struct{ A [2]int }{[2]int{1, 2}}, + }, { + "a: 1", + &struct{ B int }{0}, + }, { + "a: 1", + &struct { + B int "a" + }{1}, + }, { + "a: y", + &struct{ A bool }{true}, + }, + + // Some cross type conversions + { + "v: 42", + map[string]uint{"v": 42}, + }, { + "v: -42", + map[string]uint{}, + }, { + "v: 4294967296", + map[string]uint64{"v": 4294967296}, + }, { + "v: -4294967296", + map[string]uint64{}, + }, + + // int + { + "int_max: 2147483647", + map[string]int{"int_max": math.MaxInt32}, + }, + { + "int_min: -2147483648", + map[string]int{"int_min": math.MinInt32}, + }, + { + "int_overflow: 9223372036854775808", // math.MaxInt64 + 1 + map[string]int{}, + }, + + // int64 + { + "int64_max: 9223372036854775807", + map[string]int64{"int64_max": math.MaxInt64}, + }, + { + "int64_max_base2: 0b111111111111111111111111111111111111111111111111111111111111111", + map[string]int64{"int64_max_base2": math.MaxInt64}, + }, + { + "int64_min: -9223372036854775808", + map[string]int64{"int64_min": math.MinInt64}, + }, + { + "int64_neg_base2: -0b111111111111111111111111111111111111111111111111111111111111111", + map[string]int64{"int64_neg_base2": -math.MaxInt64}, + }, + { + "int64_overflow: 9223372036854775808", // math.MaxInt64 + 1 + map[string]int64{}, + }, + + // uint + { + "uint_min: 0", + map[string]uint{"uint_min": 0}, + }, + { + "uint_max: 4294967295", + map[string]uint{"uint_max": math.MaxUint32}, + }, + { + "uint_underflow: -1", + map[string]uint{}, + }, + + // uint64 + { + "uint64_min: 0", + map[string]uint{"uint64_min": 0}, + }, + { + "uint64_max: 18446744073709551615", + map[string]uint64{"uint64_max": math.MaxUint64}, + }, + { + "uint64_max_base2: 0b1111111111111111111111111111111111111111111111111111111111111111", + map[string]uint64{"uint64_max_base2": math.MaxUint64}, + }, + { + "uint64_maxint64: 9223372036854775807", + map[string]uint64{"uint64_maxint64": math.MaxInt64}, + }, + { + "uint64_underflow: -1", + map[string]uint64{}, + }, + + // float32 + { + "float32_max: 3.40282346638528859811704183484516925440e+38", + map[string]float32{"float32_max": math.MaxFloat32}, + }, + { + "float32_nonzero: 1.401298464324817070923729583289916131280e-45", + map[string]float32{"float32_nonzero": math.SmallestNonzeroFloat32}, + }, + { + "float32_maxuint64: 18446744073709551615", + map[string]float32{"float32_maxuint64": float32(math.MaxUint64)}, + }, + { + "float32_maxuint64+1: 18446744073709551616", + map[string]float32{"float32_maxuint64+1": float32(math.MaxUint64 + 1)}, + }, + + // float64 + { + "float64_max: 1.797693134862315708145274237317043567981e+308", + map[string]float64{"float64_max": math.MaxFloat64}, + }, + { + "float64_nonzero: 4.940656458412465441765687928682213723651e-324", + map[string]float64{"float64_nonzero": math.SmallestNonzeroFloat64}, + }, + { + "float64_maxuint64: 18446744073709551615", + map[string]float64{"float64_maxuint64": float64(math.MaxUint64)}, + }, + { + "float64_maxuint64+1: 18446744073709551616", + map[string]float64{"float64_maxuint64+1": float64(math.MaxUint64 + 1)}, + }, + + // Overflow cases. + { + "v: 4294967297", + map[string]int32{}, + }, { + "v: 128", + map[string]int8{}, + }, + + // Quoted values. + { + "'1': '\"2\"'", + map[interface{}]interface{}{"1": "\"2\""}, + }, { + "v:\n- A\n- 'B\n\n C'\n", + map[string][]string{"v": []string{"A", "B\nC"}}, + }, + + // Explicit tags. + { + "v: !!float '1.1'", + map[string]interface{}{"v": 1.1}, + }, { + "v: !!float 0", + map[string]interface{}{"v": float64(0)}, + }, { + "v: !!float -1", + map[string]interface{}{"v": float64(-1)}, + }, { + "v: !!null ''", + map[string]interface{}{"v": nil}, + }, { + "%TAG !y! tag:yaml.org,2002:\n---\nv: !y!int '1'", + map[string]interface{}{"v": 1}, + }, + + // Non-specific tag (Issue #75) + { + "v: ! test", + map[string]interface{}{"v": "test"}, + }, + + // Anchors and aliases. + { + "a: &x 1\nb: &y 2\nc: *x\nd: *y\n", + &struct{ A, B, C, D int }{1, 2, 1, 2}, + }, { + "a: &a {c: 1}\nb: *a", + &struct { + A, B struct { + C int + } + }{struct{ C int }{1}, struct{ C int }{1}}, + }, { + "a: &a [1, 2]\nb: *a", + &struct{ B []int }{[]int{1, 2}}, + }, + + // Bug #1133337 + { + "foo: ''", + map[string]*string{"foo": new(string)}, + }, { + "foo: null", + map[string]*string{"foo": nil}, + }, { + "foo: null", + map[string]string{"foo": ""}, + }, { + "foo: null", + map[string]interface{}{"foo": nil}, + }, + + // Support for ~ + { + "foo: ~", + map[string]*string{"foo": nil}, + }, { + "foo: ~", + map[string]string{"foo": ""}, + }, { + "foo: ~", + map[string]interface{}{"foo": nil}, + }, + + // Ignored field + { + "a: 1\nb: 2\n", + &struct { + A int + B int "-" + }{1, 0}, + }, + + // Bug #1191981 + { + "" + + "%YAML 1.1\n" + + "--- !!str\n" + + `"Generic line break (no glyph)\n\` + "\n" + + ` Generic line break (glyphed)\n\` + "\n" + + ` Line separator\u2028\` + "\n" + + ` Paragraph separator\u2029"` + "\n", + "" + + "Generic line break (no glyph)\n" + + "Generic line break (glyphed)\n" + + "Line separator\u2028Paragraph separator\u2029", + }, + + // Struct inlining + { + "a: 1\nb: 2\nc: 3\n", + &struct { + A int + C inlineB `yaml:",inline"` + }{1, inlineB{2, inlineC{3}}}, + }, + + // Map inlining + { + "a: 1\nb: 2\nc: 3\n", + &struct { + A int + C map[string]int `yaml:",inline"` + }{1, map[string]int{"b": 2, "c": 3}}, + }, + + // bug 1243827 + { + "a: -b_c", + map[string]interface{}{"a": "-b_c"}, + }, + { + "a: +b_c", + map[string]interface{}{"a": "+b_c"}, + }, + { + "a: 50cent_of_dollar", + map[string]interface{}{"a": "50cent_of_dollar"}, + }, + + // issue #295 (allow scalars with colons in flow mappings and sequences) + { + "a: {b: https://github.com/go-yaml/yaml}", + map[string]interface{}{"a": map[interface{}]interface{}{ + "b": "https://github.com/go-yaml/yaml", + }}, + }, + { + "a: [https://github.com/go-yaml/yaml]", + map[string]interface{}{"a": []interface{}{"https://github.com/go-yaml/yaml"}}, + }, + + // Duration + { + "a: 3s", + map[string]time.Duration{"a": 3 * time.Second}, + }, + + // Issue #24. + { + "a: ", + map[string]string{"a": ""}, + }, + + // Base 60 floats are obsolete and unsupported. + { + "a: 1:1\n", + map[string]string{"a": "1:1"}, + }, + + // Binary data. + { + "a: !!binary gIGC\n", + map[string]string{"a": "\x80\x81\x82"}, + }, { + "a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n", + map[string]string{"a": strings.Repeat("\x90", 54)}, + }, { + "a: !!binary |\n " + strings.Repeat("A", 70) + "\n ==\n", + map[string]string{"a": strings.Repeat("\x00", 52)}, + }, + + // Ordered maps. + { + "{b: 2, a: 1, d: 4, c: 3, sub: {e: 5}}", + &yaml.MapSlice{{"b", 2}, {"a", 1}, {"d", 4}, {"c", 3}, {"sub", yaml.MapSlice{{"e", 5}}}}, + }, + + // Issue #39. + { + "a:\n b:\n c: d\n", + map[string]struct{ B interface{} }{"a": {map[interface{}]interface{}{"c": "d"}}}, + }, + + // Custom map type. + { + "a: {b: c}", + M{"a": M{"b": "c"}}, + }, + + // Support encoding.TextUnmarshaler. + { + "a: 1.2.3.4\n", + map[string]textUnmarshaler{"a": textUnmarshaler{S: "1.2.3.4"}}, + }, + { + "a: 2015-02-24T18:19:39Z\n", + map[string]textUnmarshaler{"a": textUnmarshaler{"2015-02-24T18:19:39Z"}}, + }, + + // Timestamps + { + // Date only. + "a: 2015-01-01\n", + map[string]time.Time{"a": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, + }, + { + // RFC3339 + "a: 2015-02-24T18:19:39.12Z\n", + map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, .12e9, time.UTC)}, + }, + { + // RFC3339 with short dates. + "a: 2015-2-3T3:4:5Z", + map[string]time.Time{"a": time.Date(2015, 2, 3, 3, 4, 5, 0, time.UTC)}, + }, + { + // ISO8601 lower case t + "a: 2015-02-24t18:19:39Z\n", + map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)}, + }, + { + // space separate, no time zone + "a: 2015-02-24 18:19:39\n", + map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)}, + }, + // Some cases not currently handled. Uncomment these when + // the code is fixed. + // { + // // space separated with time zone + // "a: 2001-12-14 21:59:43.10 -5", + // map[string]interface{}{"a": time.Date(2001, 12, 14, 21, 59, 43, .1e9, time.UTC)}, + // }, + // { + // // arbitrary whitespace between fields + // "a: 2001-12-14 \t\t \t21:59:43.10 \t Z", + // map[string]interface{}{"a": time.Date(2001, 12, 14, 21, 59, 43, .1e9, time.UTC)}, + // }, + { + // explicit string tag + "a: !!str 2015-01-01", + map[string]interface{}{"a": "2015-01-01"}, + }, + { + // explicit timestamp tag on quoted string + "a: !!timestamp \"2015-01-01\"", + map[string]time.Time{"a": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, + }, + { + // explicit timestamp tag on unquoted string + "a: !!timestamp 2015-01-01", + map[string]time.Time{"a": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, + }, + { + // quoted string that's a valid timestamp + "a: \"2015-01-01\"", + map[string]interface{}{"a": "2015-01-01"}, + }, + { + // explicit timestamp tag into interface. + "a: !!timestamp \"2015-01-01\"", + map[string]interface{}{"a": "2015-01-01"}, + }, + { + // implicit timestamp tag into interface. + "a: 2015-01-01", + map[string]interface{}{"a": "2015-01-01"}, + }, + + // Encode empty lists as zero-length slices. + { + "a: []", + &struct{ A []int }{[]int{}}, + }, + + // UTF-16-LE + { + "\xff\xfe\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00\n\x00", + M{"ñoño": "very yes"}, + }, + // UTF-16-LE with surrogate. + { + "\xff\xfe\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00 \x00=\xd8\xd4\xdf\n\x00", + M{"ñoño": "very yes 🟔"}, + }, + + // UTF-16-BE + { + "\xfe\xff\x00\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00\n", + M{"ñoño": "very yes"}, + }, + // UTF-16-BE with surrogate. + { + "\xfe\xff\x00\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00 \xd8=\xdf\xd4\x00\n", + M{"ñoño": "very yes 🟔"}, + }, + + // YAML Float regex shouldn't match this + { + "a: 123456e1\n", + M{"a": "123456e1"}, + }, { + "a: 123456E1\n", + M{"a": "123456E1"}, + }, + // yaml-test-suite 3GZX: Spec Example 7.1. Alias Nodes + { + "First occurrence: &anchor Foo\nSecond occurrence: *anchor\nOverride anchor: &anchor Bar\nReuse anchor: *anchor\n", + map[interface{}]interface{}{ + "Reuse anchor": "Bar", + "First occurrence": "Foo", + "Second occurrence": "Foo", + "Override anchor": "Bar", + }, + }, + // Single document with garbage following it. + { + "---\nhello\n...\n}not yaml", + "hello", + }, + { + "a: 5\n", + &struct{ A jsonNumberT }{"5"}, + }, + { + "a: 5.5\n", + &struct{ A jsonNumberT }{"5.5"}, + }, +} + +type M map[interface{}]interface{} + +type inlineB struct { + B int + inlineC `yaml:",inline"` +} + +type inlineC struct { + C int +} + +func (s *S) TestUnmarshal(c *C) { + for i, item := range unmarshalTests { + c.Logf("test %d: %q", i, item.data) + t := reflect.ValueOf(item.value).Type() + value := reflect.New(t) + err := yaml.Unmarshal([]byte(item.data), value.Interface()) + if _, ok := err.(*yaml.TypeError); !ok { + c.Assert(err, IsNil) + } + c.Assert(value.Elem().Interface(), DeepEquals, item.value, Commentf("error: %v", err)) + } +} + +// TODO(v3): This test should also work when unmarshaling onto an interface{}. +func (s *S) TestUnmarshalFullTimestamp(c *C) { + // Full timestamp in same format as encoded. This is confirmed to be + // properly decoded by Python as a timestamp as well. + var str = "2015-02-24T18:19:39.123456789-03:00" + var t time.Time + err := yaml.Unmarshal([]byte(str), &t) + c.Assert(err, IsNil) + c.Assert(t, Equals, time.Date(2015, 2, 24, 18, 19, 39, 123456789, t.Location())) + c.Assert(t.In(time.UTC), Equals, time.Date(2015, 2, 24, 21, 19, 39, 123456789, time.UTC)) +} + +func (s *S) TestDecoderSingleDocument(c *C) { + // Test that Decoder.Decode works as expected on + // all the unmarshal tests. + for i, item := range unmarshalTests { + c.Logf("test %d: %q", i, item.data) + if item.data == "" { + // Behaviour differs when there's no YAML. + continue + } + t := reflect.ValueOf(item.value).Type() + value := reflect.New(t) + err := yaml.NewDecoder(strings.NewReader(item.data)).Decode(value.Interface()) + if _, ok := err.(*yaml.TypeError); !ok { + c.Assert(err, IsNil) + } + c.Assert(value.Elem().Interface(), DeepEquals, item.value) + } +} + +var decoderTests = []struct { + data string + values []interface{} +}{{ + "", + nil, +}, { + "a: b", + []interface{}{ + map[interface{}]interface{}{"a": "b"}, + }, +}, { + "---\na: b\n...\n", + []interface{}{ + map[interface{}]interface{}{"a": "b"}, + }, +}, { + "---\n'hello'\n...\n---\ngoodbye\n...\n", + []interface{}{ + "hello", + "goodbye", + }, +}} + +func (s *S) TestDecoder(c *C) { + for i, item := range decoderTests { + c.Logf("test %d: %q", i, item.data) + var values []interface{} + dec := yaml.NewDecoder(strings.NewReader(item.data)) + for { + var value interface{} + err := dec.Decode(&value) + if err == io.EOF { + break + } + c.Assert(err, IsNil) + values = append(values, value) + } + c.Assert(values, DeepEquals, item.values) + } +} + +type errReader struct{} + +func (errReader) Read([]byte) (int, error) { + return 0, errors.New("some read error") +} + +func (s *S) TestDecoderReadError(c *C) { + err := yaml.NewDecoder(errReader{}).Decode(&struct{}{}) + c.Assert(err, ErrorMatches, `yaml: input error: some read error`) +} + +func (s *S) TestUnmarshalNaN(c *C) { + value := map[string]interface{}{} + err := yaml.Unmarshal([]byte("notanum: .NaN"), &value) + c.Assert(err, IsNil) + c.Assert(math.IsNaN(value["notanum"].(float64)), Equals, true) +} + +var unmarshalErrorTests = []struct { + data, error string +}{ + {"v: !!float 'error'", "yaml: cannot decode !!str `error` as a !!float"}, + {"v: [A,", "yaml: line 1: did not find expected node content"}, + {"v:\n- [A,", "yaml: line 2: did not find expected node content"}, + {"a:\n- b: *,", "yaml: line 2: did not find expected alphabetic or numeric character"}, + {"a: *b\n", "yaml: unknown anchor 'b' referenced"}, + {"a: &a\n b: *a\n", "yaml: anchor 'a' value contains itself"}, + {"value: -", "yaml: block sequence entries are not allowed in this context"}, + {"a: !!binary ==", "yaml: !!binary value contains invalid base64 data"}, + {"{[.]}", `yaml: invalid map key: \[\]interface \{\}\{"\."\}`}, + {"{{.}}", `yaml: invalid map key: map\[interface\ \{\}\]interface \{\}\{".":interface \{\}\(nil\)\}`}, + {"b: *a\na: &a {c: 1}", `yaml: unknown anchor 'a' referenced`}, + {"%TAG !%79! tag:yaml.org,2002:\n---\nv: !%79!int '1'", "yaml: did not find expected whitespace"}, +} + +func (s *S) TestUnmarshalErrors(c *C) { + for i, item := range unmarshalErrorTests { + c.Logf("test %d: %q", i, item.data) + var value interface{} + err := yaml.Unmarshal([]byte(item.data), &value) + c.Assert(err, ErrorMatches, item.error, Commentf("Partial unmarshal: %#v", value)) + } +} + +func (s *S) TestDecoderErrors(c *C) { + for _, item := range unmarshalErrorTests { + var value interface{} + err := yaml.NewDecoder(strings.NewReader(item.data)).Decode(&value) + c.Assert(err, ErrorMatches, item.error, Commentf("Partial unmarshal: %#v", value)) + } +} + +var unmarshalerTests = []struct { + data, tag string + value interface{} +}{ + {"_: {hi: there}", "!!map", map[interface{}]interface{}{"hi": "there"}}, + {"_: [1,A]", "!!seq", []interface{}{1, "A"}}, + {"_: 10", "!!int", 10}, + {"_: null", "!!null", nil}, + {`_: BAR!`, "!!str", "BAR!"}, + {`_: "BAR!"`, "!!str", "BAR!"}, + {"_: !!foo 'BAR!'", "!!foo", "BAR!"}, + {`_: ""`, "!!str", ""}, +} + +var unmarshalerResult = map[int]error{} + +type unmarshalerType struct { + value interface{} +} + +func (o *unmarshalerType) UnmarshalYAML(unmarshal func(v interface{}) error) error { + if err := unmarshal(&o.value); err != nil { + return err + } + if i, ok := o.value.(int); ok { + if result, ok := unmarshalerResult[i]; ok { + return result + } + } + return nil +} + +type unmarshalerPointer struct { + Field *unmarshalerType "_" +} + +type unmarshalerValue struct { + Field unmarshalerType "_" +} + +func (s *S) TestUnmarshalerPointerField(c *C) { + for _, item := range unmarshalerTests { + obj := &unmarshalerPointer{} + err := yaml.Unmarshal([]byte(item.data), obj) + c.Assert(err, IsNil) + if item.value == nil { + c.Assert(obj.Field, IsNil) + } else { + c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value)) + c.Assert(obj.Field.value, DeepEquals, item.value) + } + } +} + +func (s *S) TestUnmarshalerValueField(c *C) { + for _, item := range unmarshalerTests { + obj := &unmarshalerValue{} + err := yaml.Unmarshal([]byte(item.data), obj) + c.Assert(err, IsNil) + c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value)) + c.Assert(obj.Field.value, DeepEquals, item.value) + } +} + +func (s *S) TestUnmarshalerWholeDocument(c *C) { + obj := &unmarshalerType{} + err := yaml.Unmarshal([]byte(unmarshalerTests[0].data), obj) + c.Assert(err, IsNil) + value, ok := obj.value.(map[interface{}]interface{}) + c.Assert(ok, Equals, true, Commentf("value: %#v", obj.value)) + c.Assert(value["_"], DeepEquals, unmarshalerTests[0].value) +} + +func (s *S) TestUnmarshalerTypeError(c *C) { + unmarshalerResult[2] = &yaml.TypeError{[]string{"foo"}} + unmarshalerResult[4] = &yaml.TypeError{[]string{"bar"}} + defer func() { + delete(unmarshalerResult, 2) + delete(unmarshalerResult, 4) + }() + + type T struct { + Before int + After int + M map[string]*unmarshalerType + } + var v T + data := `{before: A, m: {abc: 1, def: 2, ghi: 3, jkl: 4}, after: B}` + err := yaml.Unmarshal([]byte(data), &v) + c.Assert(err, ErrorMatches, ""+ + "yaml: unmarshal errors:\n"+ + " line 1: cannot unmarshal !!str `A` into int\n"+ + " foo\n"+ + " bar\n"+ + " line 1: cannot unmarshal !!str `B` into int") + c.Assert(v.M["abc"], NotNil) + c.Assert(v.M["def"], IsNil) + c.Assert(v.M["ghi"], NotNil) + c.Assert(v.M["jkl"], IsNil) + + c.Assert(v.M["abc"].value, Equals, 1) + c.Assert(v.M["ghi"].value, Equals, 3) +} + +type proxyTypeError struct{} + +func (v *proxyTypeError) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + var a int32 + var b int64 + if err := unmarshal(&s); err != nil { + panic(err) + } + if s == "a" { + if err := unmarshal(&b); err == nil { + panic("should have failed") + } + return unmarshal(&a) + } + if err := unmarshal(&a); err == nil { + panic("should have failed") + } + return unmarshal(&b) +} + +func (s *S) TestUnmarshalerTypeErrorProxying(c *C) { + type T struct { + Before int + After int + M map[string]*proxyTypeError + } + var v T + data := `{before: A, m: {abc: a, def: b}, after: B}` + err := yaml.Unmarshal([]byte(data), &v) + c.Assert(err, ErrorMatches, ""+ + "yaml: unmarshal errors:\n"+ + " line 1: cannot unmarshal !!str `A` into int\n"+ + " line 1: cannot unmarshal !!str `a` into int32\n"+ + " line 1: cannot unmarshal !!str `b` into int64\n"+ + " line 1: cannot unmarshal !!str `B` into int") +} + +type failingUnmarshaler struct{} + +var failingErr = errors.New("failingErr") + +func (ft *failingUnmarshaler) UnmarshalYAML(unmarshal func(interface{}) error) error { + return failingErr +} + +func (s *S) TestUnmarshalerError(c *C) { + err := yaml.Unmarshal([]byte("a: b"), &failingUnmarshaler{}) + c.Assert(err, Equals, failingErr) +} + +type sliceUnmarshaler []int + +func (su *sliceUnmarshaler) UnmarshalYAML(unmarshal func(interface{}) error) error { + var slice []int + err := unmarshal(&slice) + if err == nil { + *su = slice + return nil + } + + var intVal int + err = unmarshal(&intVal) + if err == nil { + *su = []int{intVal} + return nil + } + + return err +} + +func (s *S) TestUnmarshalerRetry(c *C) { + var su sliceUnmarshaler + err := yaml.Unmarshal([]byte("[1, 2, 3]"), &su) + c.Assert(err, IsNil) + c.Assert(su, DeepEquals, sliceUnmarshaler([]int{1, 2, 3})) + + err = yaml.Unmarshal([]byte("1"), &su) + c.Assert(err, IsNil) + c.Assert(su, DeepEquals, sliceUnmarshaler([]int{1})) +} + +// From http://yaml.org/type/merge.html +var mergeTests = ` +anchors: + list: + - &CENTER { "x": 1, "y": 2 } + - &LEFT { "x": 0, "y": 2 } + - &BIG { "r": 10 } + - &SMALL { "r": 1 } + +# All the following maps are equal: + +plain: + # Explicit keys + "x": 1 + "y": 2 + "r": 10 + label: center/big + +mergeOne: + # Merge one map + << : *CENTER + "r": 10 + label: center/big + +mergeMultiple: + # Merge multiple maps + << : [ *CENTER, *BIG ] + label: center/big + +override: + # Override + << : [ *BIG, *LEFT, *SMALL ] + "x": 1 + label: center/big + +shortTag: + # Explicit short merge tag + !!merge "<<" : [ *CENTER, *BIG ] + label: center/big + +longTag: + # Explicit merge long tag + ! "<<" : [ *CENTER, *BIG ] + label: center/big + +inlineMap: + # Inlined map + << : {"x": 1, "y": 2, "r": 10} + label: center/big + +inlineSequenceMap: + # Inlined map in sequence + << : [ *CENTER, {"r": 10} ] + label: center/big +` + +func (s *S) TestMerge(c *C) { + var want = map[interface{}]interface{}{ + "x": 1, + "y": 2, + "r": 10, + "label": "center/big", + } + + var m map[interface{}]interface{} + err := yaml.Unmarshal([]byte(mergeTests), &m) + c.Assert(err, IsNil) + for name, test := range m { + if name == "anchors" { + continue + } + c.Assert(test, DeepEquals, want, Commentf("test %q failed", name)) + } +} + +func (s *S) TestMergeStruct(c *C) { + type Data struct { + X, Y, R int + Label string + } + want := Data{1, 2, 10, "center/big"} + + var m map[string]Data + err := yaml.Unmarshal([]byte(mergeTests), &m) + c.Assert(err, IsNil) + for name, test := range m { + if name == "anchors" { + continue + } + c.Assert(test, Equals, want, Commentf("test %q failed", name)) + } +} + +var unmarshalNullTests = []func() interface{}{ + func() interface{} { var v interface{}; v = "v"; return &v }, + func() interface{} { var s = "s"; return &s }, + func() interface{} { var s = "s"; sptr := &s; return &sptr }, + func() interface{} { var i = 1; return &i }, + func() interface{} { var i = 1; iptr := &i; return &iptr }, + func() interface{} { m := map[string]int{"s": 1}; return &m }, + func() interface{} { m := map[string]int{"s": 1}; return m }, +} + +func (s *S) TestUnmarshalNull(c *C) { + for _, test := range unmarshalNullTests { + item := test() + zero := reflect.Zero(reflect.TypeOf(item).Elem()).Interface() + err := yaml.Unmarshal([]byte("null"), item) + c.Assert(err, IsNil) + if reflect.TypeOf(item).Kind() == reflect.Map { + c.Assert(reflect.ValueOf(item).Interface(), DeepEquals, reflect.MakeMap(reflect.TypeOf(item)).Interface()) + } else { + c.Assert(reflect.ValueOf(item).Elem().Interface(), DeepEquals, zero) + } + } +} + +func (s *S) TestUnmarshalSliceOnPreset(c *C) { + // Issue #48. + v := struct{ A []int }{[]int{1}} + yaml.Unmarshal([]byte("a: [2]"), &v) + c.Assert(v.A, DeepEquals, []int{2}) +} + +var unmarshalStrictTests = []struct { + data string + value interface{} + error string +}{{ + data: "a: 1\nc: 2\n", + value: struct{ A, B int }{A: 1}, + error: `yaml: unmarshal errors:\n line 2: field c not found in type struct { A int; B int }`, +}, { + data: "a: 1\nb: 2\na: 3\n", + value: struct{ A, B int }{A: 3, B: 2}, + error: `yaml: unmarshal errors:\n line 3: field a already set in type struct { A int; B int }`, +}, { + data: "c: 3\na: 1\nb: 2\nc: 4\n", + value: struct { + A int + inlineB `yaml:",inline"` + }{ + A: 1, + inlineB: inlineB{ + B: 2, + inlineC: inlineC{ + C: 4, + }, + }, + }, + error: `yaml: unmarshal errors:\n line 4: field c already set in type struct { A int; yaml_test.inlineB "yaml:\\",inline\\"" }`, +}, { + data: "c: 0\na: 1\nb: 2\nc: 1\n", + value: struct { + A int + inlineB `yaml:",inline"` + }{ + A: 1, + inlineB: inlineB{ + B: 2, + inlineC: inlineC{ + C: 1, + }, + }, + }, + error: `yaml: unmarshal errors:\n line 4: field c already set in type struct { A int; yaml_test.inlineB "yaml:\\",inline\\"" }`, +}, { + data: "c: 1\na: 1\nb: 2\nc: 3\n", + value: struct { + A int + M map[string]interface{} `yaml:",inline"` + }{ + A: 1, + M: map[string]interface{}{ + "b": 2, + "c": 3, + }, + }, + error: `yaml: unmarshal errors:\n line 4: key "c" already set in map`, +}, { + data: "a: 1\n9: 2\nnull: 3\n9: 4", + value: map[interface{}]interface{}{ + "a": 1, + nil: 3, + 9: 4, + }, + error: `yaml: unmarshal errors:\n line 4: key 9 already set in map`, +}} + +func (s *S) TestUnmarshalStrict(c *C) { + for i, item := range unmarshalStrictTests { + c.Logf("test %d: %q", i, item.data) + // First test that normal Unmarshal unmarshals to the expected value. + t := reflect.ValueOf(item.value).Type() + value := reflect.New(t) + err := yaml.Unmarshal([]byte(item.data), value.Interface()) + c.Assert(err, Equals, nil) + c.Assert(value.Elem().Interface(), DeepEquals, item.value) + + // Then test that UnmarshalStrict fails on the same thing. + t = reflect.ValueOf(item.value).Type() + value = reflect.New(t) + err = yaml.UnmarshalStrict([]byte(item.data), value.Interface()) + c.Assert(err, ErrorMatches, item.error) + } +} + +type textUnmarshaler struct { + S string +} + +func (t *textUnmarshaler) UnmarshalText(s []byte) error { + t.S = string(s) + return nil +} + +func (s *S) TestFuzzCrashers(c *C) { + cases := []string{ + // runtime error: index out of range + "\"\\0\\\r\n", + + // should not happen + " 0: [\n] 0", + "? ? \"\n\" 0", + " - {\n000}0", + "0:\n 0: [0\n] 0", + " - \"\n000\"0", + " - \"\n000\"\"", + "0:\n - {\n000}0", + "0:\n - \"\n000\"0", + "0:\n - \"\n000\"\"", + + // runtime error: index out of range + " \ufeff\n", + "? \ufeff\n", + "? \ufeff:\n", + "0: \ufeff\n", + "? \ufeff: \ufeff\n", + } + for _, data := range cases { + var v interface{} + _ = yaml.Unmarshal([]byte(data), &v) + } +} + +//var data []byte +//func init() { +// var err error +// data, err = ioutil.ReadFile("/tmp/file.yaml") +// if err != nil { +// panic(err) +// } +//} +// +//func (s *S) BenchmarkUnmarshal(c *C) { +// var err error +// for i := 0; i < c.N; i++ { +// var v map[string]interface{} +// err = yaml.Unmarshal(data, &v) +// } +// if err != nil { +// panic(err) +// } +//} +// +//func (s *S) BenchmarkMarshal(c *C) { +// var v map[string]interface{} +// yaml.Unmarshal(data, &v) +// c.ResetTimer() +// for i := 0; i < c.N; i++ { +// yaml.Marshal(&v) +// } +//} diff --git a/vendor/gopkg.in/yaml.v2/emitterc.go b/vendor/gopkg.in/yaml.v2/emitterc.go new file mode 100644 index 00000000..a1c2cc52 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/emitterc.go @@ -0,0 +1,1685 @@ +package yaml + +import ( + "bytes" + "fmt" +) + +// Flush the buffer if needed. +func flush(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) { + return yaml_emitter_flush(emitter) + } + return true +} + +// Put a character to the output buffer. +func put(emitter *yaml_emitter_t, value byte) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + emitter.buffer[emitter.buffer_pos] = value + emitter.buffer_pos++ + emitter.column++ + return true +} + +// Put a line break to the output buffer. +func put_break(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + switch emitter.line_break { + case yaml_CR_BREAK: + emitter.buffer[emitter.buffer_pos] = '\r' + emitter.buffer_pos += 1 + case yaml_LN_BREAK: + emitter.buffer[emitter.buffer_pos] = '\n' + emitter.buffer_pos += 1 + case yaml_CRLN_BREAK: + emitter.buffer[emitter.buffer_pos+0] = '\r' + emitter.buffer[emitter.buffer_pos+1] = '\n' + emitter.buffer_pos += 2 + default: + panic("unknown line break setting") + } + emitter.column = 0 + emitter.line++ + return true +} + +// Copy a character from a string into buffer. +func write(emitter *yaml_emitter_t, s []byte, i *int) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + p := emitter.buffer_pos + w := width(s[*i]) + switch w { + case 4: + emitter.buffer[p+3] = s[*i+3] + fallthrough + case 3: + emitter.buffer[p+2] = s[*i+2] + fallthrough + case 2: + emitter.buffer[p+1] = s[*i+1] + fallthrough + case 1: + emitter.buffer[p+0] = s[*i+0] + default: + panic("unknown character width") + } + emitter.column++ + emitter.buffer_pos += w + *i += w + return true +} + +// Write a whole string into buffer. +func write_all(emitter *yaml_emitter_t, s []byte) bool { + for i := 0; i < len(s); { + if !write(emitter, s, &i) { + return false + } + } + return true +} + +// Copy a line break character from a string into buffer. +func write_break(emitter *yaml_emitter_t, s []byte, i *int) bool { + if s[*i] == '\n' { + if !put_break(emitter) { + return false + } + *i++ + } else { + if !write(emitter, s, i) { + return false + } + emitter.column = 0 + emitter.line++ + } + return true +} + +// Set an emitter error and return false. +func yaml_emitter_set_emitter_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_EMITTER_ERROR + emitter.problem = problem + return false +} + +// Emit an event. +func yaml_emitter_emit(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.events = append(emitter.events, *event) + for !yaml_emitter_need_more_events(emitter) { + event := &emitter.events[emitter.events_head] + if !yaml_emitter_analyze_event(emitter, event) { + return false + } + if !yaml_emitter_state_machine(emitter, event) { + return false + } + yaml_event_delete(event) + emitter.events_head++ + } + return true +} + +// Check if we need to accumulate more events before emitting. +// +// We accumulate extra +// - 1 event for DOCUMENT-START +// - 2 events for SEQUENCE-START +// - 3 events for MAPPING-START +// +func yaml_emitter_need_more_events(emitter *yaml_emitter_t) bool { + if emitter.events_head == len(emitter.events) { + return true + } + var accumulate int + switch emitter.events[emitter.events_head].typ { + case yaml_DOCUMENT_START_EVENT: + accumulate = 1 + break + case yaml_SEQUENCE_START_EVENT: + accumulate = 2 + break + case yaml_MAPPING_START_EVENT: + accumulate = 3 + break + default: + return false + } + if len(emitter.events)-emitter.events_head > accumulate { + return false + } + var level int + for i := emitter.events_head; i < len(emitter.events); i++ { + switch emitter.events[i].typ { + case yaml_STREAM_START_EVENT, yaml_DOCUMENT_START_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT: + level++ + case yaml_STREAM_END_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_END_EVENT, yaml_MAPPING_END_EVENT: + level-- + } + if level == 0 { + return false + } + } + return true +} + +// Append a directive to the directives stack. +func yaml_emitter_append_tag_directive(emitter *yaml_emitter_t, value *yaml_tag_directive_t, allow_duplicates bool) bool { + for i := 0; i < len(emitter.tag_directives); i++ { + if bytes.Equal(value.handle, emitter.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_emitter_set_emitter_error(emitter, "duplicate %TAG directive") + } + } + + // [Go] Do we actually need to copy this given garbage collection + // and the lack of deallocating destructors? + tag_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(tag_copy.handle, value.handle) + copy(tag_copy.prefix, value.prefix) + emitter.tag_directives = append(emitter.tag_directives, tag_copy) + return true +} + +// Increase the indentation level. +func yaml_emitter_increase_indent(emitter *yaml_emitter_t, flow, indentless bool) bool { + emitter.indents = append(emitter.indents, emitter.indent) + if emitter.indent < 0 { + if flow { + emitter.indent = emitter.best_indent + } else { + emitter.indent = 0 + } + } else if !indentless { + emitter.indent += emitter.best_indent + } + return true +} + +// State dispatcher. +func yaml_emitter_state_machine(emitter *yaml_emitter_t, event *yaml_event_t) bool { + switch emitter.state { + default: + case yaml_EMIT_STREAM_START_STATE: + return yaml_emitter_emit_stream_start(emitter, event) + + case yaml_EMIT_FIRST_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, true) + + case yaml_EMIT_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, false) + + case yaml_EMIT_DOCUMENT_CONTENT_STATE: + return yaml_emitter_emit_document_content(emitter, event) + + case yaml_EMIT_DOCUMENT_END_STATE: + return yaml_emitter_emit_document_end(emitter, event) + + case yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, true) + + case yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, false) + + case yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, true) + + case yaml_EMIT_FLOW_MAPPING_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, false) + + case yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, true) + + case yaml_EMIT_FLOW_MAPPING_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, false) + + case yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, true) + + case yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, false) + + case yaml_EMIT_END_STATE: + return yaml_emitter_set_emitter_error(emitter, "expected nothing after STREAM-END") + } + panic("invalid emitter state") +} + +// Expect STREAM-START. +func yaml_emitter_emit_stream_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_STREAM_START_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected STREAM-START") + } + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = event.encoding + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = yaml_UTF8_ENCODING + } + } + if emitter.best_indent < 2 || emitter.best_indent > 9 { + emitter.best_indent = 2 + } + if emitter.best_width >= 0 && emitter.best_width <= emitter.best_indent*2 { + emitter.best_width = 80 + } + if emitter.best_width < 0 { + emitter.best_width = 1<<31 - 1 + } + if emitter.line_break == yaml_ANY_BREAK { + emitter.line_break = yaml_LN_BREAK + } + + emitter.indent = -1 + emitter.line = 0 + emitter.column = 0 + emitter.whitespace = true + emitter.indention = true + + if emitter.encoding != yaml_UTF8_ENCODING { + if !yaml_emitter_write_bom(emitter) { + return false + } + } + emitter.state = yaml_EMIT_FIRST_DOCUMENT_START_STATE + return true +} + +// Expect DOCUMENT-START or STREAM-END. +func yaml_emitter_emit_document_start(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + + if event.typ == yaml_DOCUMENT_START_EVENT { + + if event.version_directive != nil { + if !yaml_emitter_analyze_version_directive(emitter, event.version_directive) { + return false + } + } + + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_analyze_tag_directive(emitter, tag_directive) { + return false + } + if !yaml_emitter_append_tag_directive(emitter, tag_directive, false) { + return false + } + } + + for i := 0; i < len(default_tag_directives); i++ { + tag_directive := &default_tag_directives[i] + if !yaml_emitter_append_tag_directive(emitter, tag_directive, true) { + return false + } + } + + implicit := event.implicit + if !first || emitter.canonical { + implicit = false + } + + if emitter.open_ended && (event.version_directive != nil || len(event.tag_directives) > 0) { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if event.version_directive != nil { + implicit = false + if !yaml_emitter_write_indicator(emitter, []byte("%YAML"), true, false, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("1.1"), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if len(event.tag_directives) > 0 { + implicit = false + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_write_indicator(emitter, []byte("%TAG"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_handle(emitter, tag_directive.handle) { + return false + } + if !yaml_emitter_write_tag_content(emitter, tag_directive.prefix, true) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + if yaml_emitter_check_empty_document(emitter) { + implicit = false + } + if !implicit { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("---"), true, false, false) { + return false + } + if emitter.canonical { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + emitter.state = yaml_EMIT_DOCUMENT_CONTENT_STATE + return true + } + + if event.typ == yaml_STREAM_END_EVENT { + if emitter.open_ended { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_END_STATE + return true + } + + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-START or STREAM-END") +} + +// Expect the root node. +func yaml_emitter_emit_document_content(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.states = append(emitter.states, yaml_EMIT_DOCUMENT_END_STATE) + return yaml_emitter_emit_node(emitter, event, true, false, false, false) +} + +// Expect DOCUMENT-END. +func yaml_emitter_emit_document_end(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_DOCUMENT_END_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-END") + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !event.implicit { + // [Go] Allocate the slice elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_DOCUMENT_START_STATE + emitter.tag_directives = emitter.tag_directives[:0] + return true +} + +// Expect a flow item node. +func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'['}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_SEQUENCE_END_EVENT { + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{']'}, false, false, false) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + + return true + } + + if !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE) + return yaml_emitter_emit_node(emitter, event, false, true, false, false) +} + +// Expect a flow key node. +func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'{'}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_MAPPING_END_EVENT { + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'}'}, false, false, false) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + + if !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if !emitter.canonical && yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, false) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a flow value node. +func yaml_emitter_emit_flow_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, false) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_KEY_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a block item node. +func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, emitter.mapping_context && !emitter.indention) { + return false + } + } + if event.typ == yaml_SEQUENCE_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'-'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE) + return yaml_emitter_emit_node(emitter, event, false, true, false, false) +} + +// Expect a block key node. +func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, false) { + return false + } + } + if event.typ == yaml_MAPPING_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a block value node. +func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, true) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a node. +func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t, + root bool, sequence bool, mapping bool, simple_key bool) bool { + + emitter.root_context = root + emitter.sequence_context = sequence + emitter.mapping_context = mapping + emitter.simple_key_context = simple_key + + switch event.typ { + case yaml_ALIAS_EVENT: + return yaml_emitter_emit_alias(emitter, event) + case yaml_SCALAR_EVENT: + return yaml_emitter_emit_scalar(emitter, event) + case yaml_SEQUENCE_START_EVENT: + return yaml_emitter_emit_sequence_start(emitter, event) + case yaml_MAPPING_START_EVENT: + return yaml_emitter_emit_mapping_start(emitter, event) + default: + return yaml_emitter_set_emitter_error(emitter, + fmt.Sprintf("expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS, but got %v", event.typ)) + } +} + +// Expect ALIAS. +func yaml_emitter_emit_alias(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SCALAR. +func yaml_emitter_emit_scalar(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_select_scalar_style(emitter, event) { + return false + } + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + if !yaml_emitter_process_scalar(emitter) { + return false + } + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SEQUENCE-START. +func yaml_emitter_emit_sequence_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.sequence_style() == yaml_FLOW_SEQUENCE_STYLE || + yaml_emitter_check_empty_sequence(emitter) { + emitter.state = yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE + } + return true +} + +// Expect MAPPING-START. +func yaml_emitter_emit_mapping_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.mapping_style() == yaml_FLOW_MAPPING_STYLE || + yaml_emitter_check_empty_mapping(emitter) { + emitter.state = yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE + } + return true +} + +// Check if the document content is an empty scalar. +func yaml_emitter_check_empty_document(emitter *yaml_emitter_t) bool { + return false // [Go] Huh? +} + +// Check if the next events represent an empty sequence. +func yaml_emitter_check_empty_sequence(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_SEQUENCE_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_SEQUENCE_END_EVENT +} + +// Check if the next events represent an empty mapping. +func yaml_emitter_check_empty_mapping(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_MAPPING_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_MAPPING_END_EVENT +} + +// Check if the next node can be expressed as a simple key. +func yaml_emitter_check_simple_key(emitter *yaml_emitter_t) bool { + length := 0 + switch emitter.events[emitter.events_head].typ { + case yaml_ALIAS_EVENT: + length += len(emitter.anchor_data.anchor) + case yaml_SCALAR_EVENT: + if emitter.scalar_data.multiline { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + + len(emitter.scalar_data.value) + case yaml_SEQUENCE_START_EVENT: + if !yaml_emitter_check_empty_sequence(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + case yaml_MAPPING_START_EVENT: + if !yaml_emitter_check_empty_mapping(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + default: + return false + } + return length <= 128 +} + +// Determine an acceptable scalar style. +func yaml_emitter_select_scalar_style(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + no_tag := len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 + if no_tag && !event.implicit && !event.quoted_implicit { + return yaml_emitter_set_emitter_error(emitter, "neither tag nor implicit flags are specified") + } + + style := event.scalar_style() + if style == yaml_ANY_SCALAR_STYLE { + style = yaml_PLAIN_SCALAR_STYLE + } + if emitter.canonical { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + if emitter.simple_key_context && emitter.scalar_data.multiline { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + + if style == yaml_PLAIN_SCALAR_STYLE { + if emitter.flow_level > 0 && !emitter.scalar_data.flow_plain_allowed || + emitter.flow_level == 0 && !emitter.scalar_data.block_plain_allowed { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if len(emitter.scalar_data.value) == 0 && (emitter.flow_level > 0 || emitter.simple_key_context) { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if no_tag && !event.implicit { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_SINGLE_QUOTED_SCALAR_STYLE { + if !emitter.scalar_data.single_quoted_allowed { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_LITERAL_SCALAR_STYLE || style == yaml_FOLDED_SCALAR_STYLE { + if !emitter.scalar_data.block_allowed || emitter.flow_level > 0 || emitter.simple_key_context { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + + if no_tag && !event.quoted_implicit && style != yaml_PLAIN_SCALAR_STYLE { + emitter.tag_data.handle = []byte{'!'} + } + emitter.scalar_data.style = style + return true +} + +// Write an anchor. +func yaml_emitter_process_anchor(emitter *yaml_emitter_t) bool { + if emitter.anchor_data.anchor == nil { + return true + } + c := []byte{'&'} + if emitter.anchor_data.alias { + c[0] = '*' + } + if !yaml_emitter_write_indicator(emitter, c, true, false, false) { + return false + } + return yaml_emitter_write_anchor(emitter, emitter.anchor_data.anchor) +} + +// Write a tag. +func yaml_emitter_process_tag(emitter *yaml_emitter_t) bool { + if len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 { + return true + } + if len(emitter.tag_data.handle) > 0 { + if !yaml_emitter_write_tag_handle(emitter, emitter.tag_data.handle) { + return false + } + if len(emitter.tag_data.suffix) > 0 { + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + } + } else { + // [Go] Allocate these slices elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("!<"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, false, false, false) { + return false + } + } + return true +} + +// Write a scalar. +func yaml_emitter_process_scalar(emitter *yaml_emitter_t) bool { + switch emitter.scalar_data.style { + case yaml_PLAIN_SCALAR_STYLE: + return yaml_emitter_write_plain_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_SINGLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_single_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_DOUBLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_double_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_LITERAL_SCALAR_STYLE: + return yaml_emitter_write_literal_scalar(emitter, emitter.scalar_data.value) + + case yaml_FOLDED_SCALAR_STYLE: + return yaml_emitter_write_folded_scalar(emitter, emitter.scalar_data.value) + } + panic("unknown scalar style") +} + +// Check if a %YAML directive is valid. +func yaml_emitter_analyze_version_directive(emitter *yaml_emitter_t, version_directive *yaml_version_directive_t) bool { + if version_directive.major != 1 || version_directive.minor != 1 { + return yaml_emitter_set_emitter_error(emitter, "incompatible %YAML directive") + } + return true +} + +// Check if a %TAG directive is valid. +func yaml_emitter_analyze_tag_directive(emitter *yaml_emitter_t, tag_directive *yaml_tag_directive_t) bool { + handle := tag_directive.handle + prefix := tag_directive.prefix + if len(handle) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag handle must not be empty") + } + if handle[0] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must start with '!'") + } + if handle[len(handle)-1] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must end with '!'") + } + for i := 1; i < len(handle)-1; i += width(handle[i]) { + if !is_alpha(handle, i) { + return yaml_emitter_set_emitter_error(emitter, "tag handle must contain alphanumerical characters only") + } + } + if len(prefix) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag prefix must not be empty") + } + return true +} + +// Check if an anchor is valid. +func yaml_emitter_analyze_anchor(emitter *yaml_emitter_t, anchor []byte, alias bool) bool { + if len(anchor) == 0 { + problem := "anchor value must not be empty" + if alias { + problem = "alias value must not be empty" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + for i := 0; i < len(anchor); i += width(anchor[i]) { + if !is_alpha(anchor, i) { + problem := "anchor value must contain alphanumerical characters only" + if alias { + problem = "alias value must contain alphanumerical characters only" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + } + emitter.anchor_data.anchor = anchor + emitter.anchor_data.alias = alias + return true +} + +// Check if a tag is valid. +func yaml_emitter_analyze_tag(emitter *yaml_emitter_t, tag []byte) bool { + if len(tag) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag value must not be empty") + } + for i := 0; i < len(emitter.tag_directives); i++ { + tag_directive := &emitter.tag_directives[i] + if bytes.HasPrefix(tag, tag_directive.prefix) { + emitter.tag_data.handle = tag_directive.handle + emitter.tag_data.suffix = tag[len(tag_directive.prefix):] + return true + } + } + emitter.tag_data.suffix = tag + return true +} + +// Check if a scalar is valid. +func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool { + var ( + block_indicators = false + flow_indicators = false + line_breaks = false + special_characters = false + + leading_space = false + leading_break = false + trailing_space = false + trailing_break = false + break_space = false + space_break = false + + preceded_by_whitespace = false + followed_by_whitespace = false + previous_space = false + previous_break = false + ) + + emitter.scalar_data.value = value + + if len(value) == 0 { + emitter.scalar_data.multiline = false + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = false + return true + } + + if len(value) >= 3 && ((value[0] == '-' && value[1] == '-' && value[2] == '-') || (value[0] == '.' && value[1] == '.' && value[2] == '.')) { + block_indicators = true + flow_indicators = true + } + + preceded_by_whitespace = true + for i, w := 0, 0; i < len(value); i += w { + w = width(value[i]) + followed_by_whitespace = i+w >= len(value) || is_blank(value, i+w) + + if i == 0 { + switch value[i] { + case '#', ',', '[', ']', '{', '}', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`': + flow_indicators = true + block_indicators = true + case '?', ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '-': + if followed_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } else { + switch value[i] { + case ',', '?', '[', ']', '{', '}': + flow_indicators = true + case ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '#': + if preceded_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } + + if !is_printable(value, i) || !is_ascii(value, i) && !emitter.unicode { + special_characters = true + } + if is_space(value, i) { + if i == 0 { + leading_space = true + } + if i+width(value[i]) == len(value) { + trailing_space = true + } + if previous_break { + break_space = true + } + previous_space = true + previous_break = false + } else if is_break(value, i) { + line_breaks = true + if i == 0 { + leading_break = true + } + if i+width(value[i]) == len(value) { + trailing_break = true + } + if previous_space { + space_break = true + } + previous_space = false + previous_break = true + } else { + previous_space = false + previous_break = false + } + + // [Go]: Why 'z'? Couldn't be the end of the string as that's the loop condition. + preceded_by_whitespace = is_blankz(value, i) + } + + emitter.scalar_data.multiline = line_breaks + emitter.scalar_data.flow_plain_allowed = true + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = true + + if leading_space || leading_break || trailing_space || trailing_break { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if trailing_space { + emitter.scalar_data.block_allowed = false + } + if break_space { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + } + if space_break || special_characters { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + emitter.scalar_data.block_allowed = false + } + if line_breaks { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if flow_indicators { + emitter.scalar_data.flow_plain_allowed = false + } + if block_indicators { + emitter.scalar_data.block_plain_allowed = false + } + return true +} + +// Check if the event data is valid. +func yaml_emitter_analyze_event(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + emitter.anchor_data.anchor = nil + emitter.tag_data.handle = nil + emitter.tag_data.suffix = nil + emitter.scalar_data.value = nil + + switch event.typ { + case yaml_ALIAS_EVENT: + if !yaml_emitter_analyze_anchor(emitter, event.anchor, true) { + return false + } + + case yaml_SCALAR_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || (!event.implicit && !event.quoted_implicit)) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + if !yaml_emitter_analyze_scalar(emitter, event.value) { + return false + } + + case yaml_SEQUENCE_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + + case yaml_MAPPING_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + } + return true +} + +// Write the BOM character. +func yaml_emitter_write_bom(emitter *yaml_emitter_t) bool { + if !flush(emitter) { + return false + } + pos := emitter.buffer_pos + emitter.buffer[pos+0] = '\xEF' + emitter.buffer[pos+1] = '\xBB' + emitter.buffer[pos+2] = '\xBF' + emitter.buffer_pos += 3 + return true +} + +func yaml_emitter_write_indent(emitter *yaml_emitter_t) bool { + indent := emitter.indent + if indent < 0 { + indent = 0 + } + if !emitter.indention || emitter.column > indent || (emitter.column == indent && !emitter.whitespace) { + if !put_break(emitter) { + return false + } + } + for emitter.column < indent { + if !put(emitter, ' ') { + return false + } + } + emitter.whitespace = true + emitter.indention = true + return true +} + +func yaml_emitter_write_indicator(emitter *yaml_emitter_t, indicator []byte, need_whitespace, is_whitespace, is_indention bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, indicator) { + return false + } + emitter.whitespace = is_whitespace + emitter.indention = (emitter.indention && is_indention) + emitter.open_ended = false + return true +} + +func yaml_emitter_write_anchor(emitter *yaml_emitter_t, value []byte) bool { + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_handle(emitter *yaml_emitter_t, value []byte) bool { + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_content(emitter *yaml_emitter_t, value []byte, need_whitespace bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + for i := 0; i < len(value); { + var must_write bool + switch value[i] { + case ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '_', '.', '~', '*', '\'', '(', ')', '[', ']': + must_write = true + default: + must_write = is_alpha(value, i) + } + if must_write { + if !write(emitter, value, &i) { + return false + } + } else { + w := width(value[i]) + for k := 0; k < w; k++ { + octet := value[i] + i++ + if !put(emitter, '%') { + return false + } + + c := octet >> 4 + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + + c = octet & 0x0f + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + } + } + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_plain_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + + emitter.whitespace = false + emitter.indention = false + if emitter.root_context { + emitter.open_ended = true + } + + return true +} + +func yaml_emitter_write_single_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, true, false, false) { + return false + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if value[i] == '\'' { + if !put(emitter, '\'') { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_double_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + spaces := false + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, true, false, false) { + return false + } + + for i := 0; i < len(value); { + if !is_printable(value, i) || (!emitter.unicode && !is_ascii(value, i)) || + is_bom(value, i) || is_break(value, i) || + value[i] == '"' || value[i] == '\\' { + + octet := value[i] + + var w int + var v rune + switch { + case octet&0x80 == 0x00: + w, v = 1, rune(octet&0x7F) + case octet&0xE0 == 0xC0: + w, v = 2, rune(octet&0x1F) + case octet&0xF0 == 0xE0: + w, v = 3, rune(octet&0x0F) + case octet&0xF8 == 0xF0: + w, v = 4, rune(octet&0x07) + } + for k := 1; k < w; k++ { + octet = value[i+k] + v = (v << 6) + (rune(octet) & 0x3F) + } + i += w + + if !put(emitter, '\\') { + return false + } + + var ok bool + switch v { + case 0x00: + ok = put(emitter, '0') + case 0x07: + ok = put(emitter, 'a') + case 0x08: + ok = put(emitter, 'b') + case 0x09: + ok = put(emitter, 't') + case 0x0A: + ok = put(emitter, 'n') + case 0x0b: + ok = put(emitter, 'v') + case 0x0c: + ok = put(emitter, 'f') + case 0x0d: + ok = put(emitter, 'r') + case 0x1b: + ok = put(emitter, 'e') + case 0x22: + ok = put(emitter, '"') + case 0x5c: + ok = put(emitter, '\\') + case 0x85: + ok = put(emitter, 'N') + case 0xA0: + ok = put(emitter, '_') + case 0x2028: + ok = put(emitter, 'L') + case 0x2029: + ok = put(emitter, 'P') + default: + if v <= 0xFF { + ok = put(emitter, 'x') + w = 2 + } else if v <= 0xFFFF { + ok = put(emitter, 'u') + w = 4 + } else { + ok = put(emitter, 'U') + w = 8 + } + for k := (w - 1) * 4; ok && k >= 0; k -= 4 { + digit := byte((v >> uint(k)) & 0x0F) + if digit < 10 { + ok = put(emitter, digit+'0') + } else { + ok = put(emitter, digit+'A'-10) + } + } + } + if !ok { + return false + } + spaces = false + } else if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 { + if !yaml_emitter_write_indent(emitter) { + return false + } + if is_space(value, i+1) { + if !put(emitter, '\\') { + return false + } + } + i += width(value[i]) + } else if !write(emitter, value, &i) { + return false + } + spaces = true + } else { + if !write(emitter, value, &i) { + return false + } + spaces = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_block_scalar_hints(emitter *yaml_emitter_t, value []byte) bool { + if is_space(value, 0) || is_break(value, 0) { + indent_hint := []byte{'0' + byte(emitter.best_indent)} + if !yaml_emitter_write_indicator(emitter, indent_hint, false, false, false) { + return false + } + } + + emitter.open_ended = false + + var chomp_hint [1]byte + if len(value) == 0 { + chomp_hint[0] = '-' + } else { + i := len(value) - 1 + for value[i]&0xC0 == 0x80 { + i-- + } + if !is_break(value, i) { + chomp_hint[0] = '-' + } else if i == 0 { + chomp_hint[0] = '+' + emitter.open_ended = true + } else { + i-- + for value[i]&0xC0 == 0x80 { + i-- + } + if is_break(value, i) { + chomp_hint[0] = '+' + emitter.open_ended = true + } + } + } + if chomp_hint[0] != 0 { + if !yaml_emitter_write_indicator(emitter, chomp_hint[:], false, false, false) { + return false + } + } + return true +} + +func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'|'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + if !put_break(emitter) { + return false + } + emitter.indention = true + emitter.whitespace = true + breaks := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + breaks = false + } + } + + return true +} + +func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + + if !put_break(emitter) { + return false + } + emitter.indention = true + emitter.whitespace = true + + breaks := true + leading_spaces := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !breaks && !leading_spaces && value[i] == '\n' { + k := 0 + for is_break(value, k) { + k += width(value[k]) + } + if !is_blankz(value, k) { + if !put_break(emitter) { + return false + } + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + leading_spaces = is_blank(value, i) + } + if !breaks && is_space(value, i) && !is_space(value, i+1) && emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + emitter.indention = false + breaks = false + } + } + return true +} diff --git a/vendor/gopkg.in/yaml.v2/encode.go b/vendor/gopkg.in/yaml.v2/encode.go new file mode 100644 index 00000000..0ee738e1 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/encode.go @@ -0,0 +1,390 @@ +package yaml + +import ( + "encoding" + "fmt" + "io" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +// jsonNumber is the interface of the encoding/json.Number datatype. +// Repeating the interface here avoids a dependency on encoding/json, and also +// supports other libraries like jsoniter, which use a similar datatype with +// the same interface. Detecting this interface is useful when dealing with +// structures containing json.Number, which is a string under the hood. The +// encoder should prefer the use of Int64(), Float64() and string(), in that +// order, when encoding this type. +type jsonNumber interface { + Float64() (float64, error) + Int64() (int64, error) + String() string +} + +type encoder struct { + emitter yaml_emitter_t + event yaml_event_t + out []byte + flow bool + // doneInit holds whether the initial stream_start_event has been + // emitted. + doneInit bool +} + +func newEncoder() *encoder { + e := &encoder{} + yaml_emitter_initialize(&e.emitter) + yaml_emitter_set_output_string(&e.emitter, &e.out) + yaml_emitter_set_unicode(&e.emitter, true) + return e +} + +func newEncoderWithWriter(w io.Writer) *encoder { + e := &encoder{} + yaml_emitter_initialize(&e.emitter) + yaml_emitter_set_output_writer(&e.emitter, w) + yaml_emitter_set_unicode(&e.emitter, true) + return e +} + +func (e *encoder) init() { + if e.doneInit { + return + } + yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING) + e.emit() + e.doneInit = true +} + +func (e *encoder) finish() { + e.emitter.open_ended = false + yaml_stream_end_event_initialize(&e.event) + e.emit() +} + +func (e *encoder) destroy() { + yaml_emitter_delete(&e.emitter) +} + +func (e *encoder) emit() { + // This will internally delete the e.event value. + e.must(yaml_emitter_emit(&e.emitter, &e.event)) +} + +func (e *encoder) must(ok bool) { + if !ok { + msg := e.emitter.problem + if msg == "" { + msg = "unknown problem generating YAML content" + } + failf("%s", msg) + } +} + +func (e *encoder) marshalDoc(tag string, in reflect.Value) { + e.init() + yaml_document_start_event_initialize(&e.event, nil, nil, true) + e.emit() + e.marshal(tag, in) + yaml_document_end_event_initialize(&e.event, true) + e.emit() +} + +func (e *encoder) marshal(tag string, in reflect.Value) { + if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() { + e.nilv() + return + } + iface := in.Interface() + switch m := iface.(type) { + case jsonNumber: + integer, err := m.Int64() + if err == nil { + // In this case the json.Number is a valid int64 + in = reflect.ValueOf(integer) + break + } + float, err := m.Float64() + if err == nil { + // In this case the json.Number is a valid float64 + in = reflect.ValueOf(float) + break + } + // fallback case - no number could be obtained + in = reflect.ValueOf(m.String()) + case time.Time, *time.Time: + // Although time.Time implements TextMarshaler, + // we don't want to treat it as a string for YAML + // purposes because YAML has special support for + // timestamps. + case Marshaler: + v, err := m.MarshalYAML() + if err != nil { + fail(err) + } + if v == nil { + e.nilv() + return + } + in = reflect.ValueOf(v) + case encoding.TextMarshaler: + text, err := m.MarshalText() + if err != nil { + fail(err) + } + in = reflect.ValueOf(string(text)) + case nil: + e.nilv() + return + } + switch in.Kind() { + case reflect.Interface: + e.marshal(tag, in.Elem()) + case reflect.Map: + e.mapv(tag, in) + case reflect.Ptr: + if in.Type() == ptrTimeType { + e.timev(tag, in.Elem()) + } else { + e.marshal(tag, in.Elem()) + } + case reflect.Struct: + if in.Type() == timeType { + e.timev(tag, in) + } else { + e.structv(tag, in) + } + case reflect.Slice, reflect.Array: + if in.Type().Elem() == mapItemType { + e.itemsv(tag, in) + } else { + e.slicev(tag, in) + } + case reflect.String: + e.stringv(tag, in) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if in.Type() == durationType { + e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String())) + } else { + e.intv(tag, in) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + e.uintv(tag, in) + case reflect.Float32, reflect.Float64: + e.floatv(tag, in) + case reflect.Bool: + e.boolv(tag, in) + default: + panic("cannot marshal type: " + in.Type().String()) + } +} + +func (e *encoder) mapv(tag string, in reflect.Value) { + e.mappingv(tag, func() { + keys := keyList(in.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + e.marshal("", k) + e.marshal("", in.MapIndex(k)) + } + }) +} + +func (e *encoder) itemsv(tag string, in reflect.Value) { + e.mappingv(tag, func() { + slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem) + for _, item := range slice { + e.marshal("", reflect.ValueOf(item.Key)) + e.marshal("", reflect.ValueOf(item.Value)) + } + }) +} + +func (e *encoder) structv(tag string, in reflect.Value) { + sinfo, err := getStructInfo(in.Type()) + if err != nil { + panic(err) + } + e.mappingv(tag, func() { + for _, info := range sinfo.FieldsList { + var value reflect.Value + if info.Inline == nil { + value = in.Field(info.Num) + } else { + value = in.FieldByIndex(info.Inline) + } + if info.OmitEmpty && isZero(value) { + continue + } + e.marshal("", reflect.ValueOf(info.Key)) + e.flow = info.Flow + e.marshal("", value) + } + if sinfo.InlineMap >= 0 { + m := in.Field(sinfo.InlineMap) + if m.Len() > 0 { + e.flow = false + keys := keyList(m.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + if _, found := sinfo.FieldsMap[k.String()]; found { + panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String())) + } + e.marshal("", k) + e.flow = false + e.marshal("", m.MapIndex(k)) + } + } + } + }) +} + +func (e *encoder) mappingv(tag string, f func()) { + implicit := tag == "" + style := yaml_BLOCK_MAPPING_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_MAPPING_STYLE + } + yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style) + e.emit() + f() + yaml_mapping_end_event_initialize(&e.event) + e.emit() +} + +func (e *encoder) slicev(tag string, in reflect.Value) { + implicit := tag == "" + style := yaml_BLOCK_SEQUENCE_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_SEQUENCE_STYLE + } + e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)) + e.emit() + n := in.Len() + for i := 0; i < n; i++ { + e.marshal("", in.Index(i)) + } + e.must(yaml_sequence_end_event_initialize(&e.event)) + e.emit() +} + +// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1. +// +// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported +// in YAML 1.2 and by this package, but these should be marshalled quoted for +// the time being for compatibility with other parsers. +func isBase60Float(s string) (result bool) { + // Fast path. + if s == "" { + return false + } + c := s[0] + if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 { + return false + } + // Do the full match. + return base60float.MatchString(s) +} + +// From http://yaml.org/type/float.html, except the regular expression there +// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix. +var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`) + +func (e *encoder) stringv(tag string, in reflect.Value) { + var style yaml_scalar_style_t + s := in.String() + canUsePlain := true + switch { + case !utf8.ValidString(s): + if tag == yaml_BINARY_TAG { + failf("explicitly tagged !!binary data must be base64-encoded") + } + if tag != "" { + failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag)) + } + // It can't be encoded directly as YAML so use a binary tag + // and encode it as base64. + tag = yaml_BINARY_TAG + s = encodeBase64(s) + case tag == "": + // Check to see if it would resolve to a specific + // tag when encoded unquoted. If it doesn't, + // there's no need to quote it. + rtag, _ := resolve("", s) + canUsePlain = rtag == yaml_STR_TAG && !isBase60Float(s) + } + // Note: it's possible for user code to emit invalid YAML + // if they explicitly specify a tag and a string containing + // text that's incompatible with that tag. + switch { + case strings.Contains(s, "\n"): + style = yaml_LITERAL_SCALAR_STYLE + case canUsePlain: + style = yaml_PLAIN_SCALAR_STYLE + default: + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + e.emitScalar(s, "", tag, style) +} + +func (e *encoder) boolv(tag string, in reflect.Value) { + var s string + if in.Bool() { + s = "true" + } else { + s = "false" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) intv(tag string, in reflect.Value) { + s := strconv.FormatInt(in.Int(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) uintv(tag string, in reflect.Value) { + s := strconv.FormatUint(in.Uint(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) timev(tag string, in reflect.Value) { + t := in.Interface().(time.Time) + s := t.Format(time.RFC3339Nano) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) floatv(tag string, in reflect.Value) { + // Issue #352: When formatting, use the precision of the underlying value + precision := 64 + if in.Kind() == reflect.Float32 { + precision = 32 + } + + s := strconv.FormatFloat(in.Float(), 'g', -1, precision) + switch s { + case "+Inf": + s = ".inf" + case "-Inf": + s = "-.inf" + case "NaN": + s = ".nan" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) nilv() { + e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) { + implicit := tag == "" + e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style)) + e.emit() +} diff --git a/vendor/gopkg.in/yaml.v2/encode_test.go b/vendor/gopkg.in/yaml.v2/encode_test.go new file mode 100644 index 00000000..4a266008 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/encode_test.go @@ -0,0 +1,625 @@ +package yaml_test + +import ( + "bytes" + "fmt" + "math" + "strconv" + "strings" + "time" + + "net" + "os" + + . "gopkg.in/check.v1" + "gopkg.in/yaml.v2" +) + +type jsonNumberT string + +func (j jsonNumberT) Int64() (int64, error) { + val, err := strconv.Atoi(string(j)) + if err != nil { + return 0, err + } + return int64(val), nil +} + +func (j jsonNumberT) Float64() (float64, error) { + return strconv.ParseFloat(string(j), 64) +} + +func (j jsonNumberT) String() string { + return string(j) +} + +var marshalIntTest = 123 + +var marshalTests = []struct { + value interface{} + data string +}{ + { + nil, + "null\n", + }, { + (*marshalerType)(nil), + "null\n", + }, { + &struct{}{}, + "{}\n", + }, { + map[string]string{"v": "hi"}, + "v: hi\n", + }, { + map[string]interface{}{"v": "hi"}, + "v: hi\n", + }, { + map[string]string{"v": "true"}, + "v: \"true\"\n", + }, { + map[string]string{"v": "false"}, + "v: \"false\"\n", + }, { + map[string]interface{}{"v": true}, + "v: true\n", + }, { + map[string]interface{}{"v": false}, + "v: false\n", + }, { + map[string]interface{}{"v": 10}, + "v: 10\n", + }, { + map[string]interface{}{"v": -10}, + "v: -10\n", + }, { + map[string]uint{"v": 42}, + "v: 42\n", + }, { + map[string]interface{}{"v": int64(4294967296)}, + "v: 4294967296\n", + }, { + map[string]int64{"v": int64(4294967296)}, + "v: 4294967296\n", + }, { + map[string]uint64{"v": 4294967296}, + "v: 4294967296\n", + }, { + map[string]interface{}{"v": "10"}, + "v: \"10\"\n", + }, { + map[string]interface{}{"v": 0.1}, + "v: 0.1\n", + }, { + map[string]interface{}{"v": float64(0.1)}, + "v: 0.1\n", + }, { + map[string]interface{}{"v": float32(0.99)}, + "v: 0.99\n", + }, { + map[string]interface{}{"v": -0.1}, + "v: -0.1\n", + }, { + map[string]interface{}{"v": math.Inf(+1)}, + "v: .inf\n", + }, { + map[string]interface{}{"v": math.Inf(-1)}, + "v: -.inf\n", + }, { + map[string]interface{}{"v": math.NaN()}, + "v: .nan\n", + }, { + map[string]interface{}{"v": nil}, + "v: null\n", + }, { + map[string]interface{}{"v": ""}, + "v: \"\"\n", + }, { + map[string][]string{"v": []string{"A", "B"}}, + "v:\n- A\n- B\n", + }, { + map[string][]string{"v": []string{"A", "B\nC"}}, + "v:\n- A\n- |-\n B\n C\n", + }, { + map[string][]interface{}{"v": []interface{}{"A", 1, map[string][]int{"B": []int{2, 3}}}}, + "v:\n- A\n- 1\n- B:\n - 2\n - 3\n", + }, { + map[string]interface{}{"a": map[interface{}]interface{}{"b": "c"}}, + "a:\n b: c\n", + }, { + map[string]interface{}{"a": "-"}, + "a: '-'\n", + }, + + // Simple values. + { + &marshalIntTest, + "123\n", + }, + + // Structures + { + &struct{ Hello string }{"world"}, + "hello: world\n", + }, { + &struct { + A struct { + B string + } + }{struct{ B string }{"c"}}, + "a:\n b: c\n", + }, { + &struct { + A *struct { + B string + } + }{&struct{ B string }{"c"}}, + "a:\n b: c\n", + }, { + &struct { + A *struct { + B string + } + }{}, + "a: null\n", + }, { + &struct{ A int }{1}, + "a: 1\n", + }, { + &struct{ A []int }{[]int{1, 2}}, + "a:\n- 1\n- 2\n", + }, { + &struct{ A [2]int }{[2]int{1, 2}}, + "a:\n- 1\n- 2\n", + }, { + &struct { + B int "a" + }{1}, + "a: 1\n", + }, { + &struct{ A bool }{true}, + "a: true\n", + }, + + // Conditional flag + { + &struct { + A int "a,omitempty" + B int "b,omitempty" + }{1, 0}, + "a: 1\n", + }, { + &struct { + A int "a,omitempty" + B int "b,omitempty" + }{0, 0}, + "{}\n", + }, { + &struct { + A *struct{ X, y int } "a,omitempty,flow" + }{&struct{ X, y int }{1, 2}}, + "a: {x: 1}\n", + }, { + &struct { + A *struct{ X, y int } "a,omitempty,flow" + }{nil}, + "{}\n", + }, { + &struct { + A *struct{ X, y int } "a,omitempty,flow" + }{&struct{ X, y int }{}}, + "a: {x: 0}\n", + }, { + &struct { + A struct{ X, y int } "a,omitempty,flow" + }{struct{ X, y int }{1, 2}}, + "a: {x: 1}\n", + }, { + &struct { + A struct{ X, y int } "a,omitempty,flow" + }{struct{ X, y int }{0, 1}}, + "{}\n", + }, { + &struct { + A float64 "a,omitempty" + B float64 "b,omitempty" + }{1, 0}, + "a: 1\n", + }, + { + &struct { + T1 time.Time "t1,omitempty" + T2 time.Time "t2,omitempty" + T3 *time.Time "t3,omitempty" + T4 *time.Time "t4,omitempty" + }{ + T2: time.Date(2018, 1, 9, 10, 40, 47, 0, time.UTC), + T4: newTime(time.Date(2098, 1, 9, 10, 40, 47, 0, time.UTC)), + }, + "t2: 2018-01-09T10:40:47Z\nt4: 2098-01-09T10:40:47Z\n", + }, + // Nil interface that implements Marshaler. + { + map[string]yaml.Marshaler{ + "a": nil, + }, + "a: null\n", + }, + + // Flow flag + { + &struct { + A []int "a,flow" + }{[]int{1, 2}}, + "a: [1, 2]\n", + }, { + &struct { + A map[string]string "a,flow" + }{map[string]string{"b": "c", "d": "e"}}, + "a: {b: c, d: e}\n", + }, { + &struct { + A struct { + B, D string + } "a,flow" + }{struct{ B, D string }{"c", "e"}}, + "a: {b: c, d: e}\n", + }, + + // Unexported field + { + &struct { + u int + A int + }{0, 1}, + "a: 1\n", + }, + + // Ignored field + { + &struct { + A int + B int "-" + }{1, 2}, + "a: 1\n", + }, + + // Struct inlining + { + &struct { + A int + C inlineB `yaml:",inline"` + }{1, inlineB{2, inlineC{3}}}, + "a: 1\nb: 2\nc: 3\n", + }, + + // Map inlining + { + &struct { + A int + C map[string]int `yaml:",inline"` + }{1, map[string]int{"b": 2, "c": 3}}, + "a: 1\nb: 2\nc: 3\n", + }, + + // Duration + { + map[string]time.Duration{"a": 3 * time.Second}, + "a: 3s\n", + }, + + // Issue #24: bug in map merging logic. + { + map[string]string{"a": ""}, + "a: \n", + }, + + // Issue #34: marshal unsupported base 60 floats quoted for compatibility + // with old YAML 1.1 parsers. + { + map[string]string{"a": "1:1"}, + "a: \"1:1\"\n", + }, + + // Binary data. + { + map[string]string{"a": "\x00"}, + "a: \"\\0\"\n", + }, { + map[string]string{"a": "\x80\x81\x82"}, + "a: !!binary gIGC\n", + }, { + map[string]string{"a": strings.Repeat("\x90", 54)}, + "a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n", + }, + + // Ordered maps. + { + &yaml.MapSlice{{"b", 2}, {"a", 1}, {"d", 4}, {"c", 3}, {"sub", yaml.MapSlice{{"e", 5}}}}, + "b: 2\na: 1\nd: 4\nc: 3\nsub:\n e: 5\n", + }, + + // Encode unicode as utf-8 rather than in escaped form. + { + map[string]string{"a": "你好"}, + "a: 你好\n", + }, + + // Support encoding.TextMarshaler. + { + map[string]net.IP{"a": net.IPv4(1, 2, 3, 4)}, + "a: 1.2.3.4\n", + }, + // time.Time gets a timestamp tag. + { + map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)}, + "a: 2015-02-24T18:19:39Z\n", + }, + { + map[string]*time.Time{"a": newTime(time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC))}, + "a: 2015-02-24T18:19:39Z\n", + }, + { + // This is confirmed to be properly decoded in Python (libyaml) without a timestamp tag. + map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 123456789, time.FixedZone("FOO", -3*60*60))}, + "a: 2015-02-24T18:19:39.123456789-03:00\n", + }, + // Ensure timestamp-like strings are quoted. + { + map[string]string{"a": "2015-02-24T18:19:39Z"}, + "a: \"2015-02-24T18:19:39Z\"\n", + }, + + // Ensure strings containing ": " are quoted (reported as PR #43, but not reproducible). + { + map[string]string{"a": "b: c"}, + "a: 'b: c'\n", + }, + + // Containing hash mark ('#') in string should be quoted + { + map[string]string{"a": "Hello #comment"}, + "a: 'Hello #comment'\n", + }, + { + map[string]string{"a": "你好 #comment"}, + "a: '你好 #comment'\n", + }, + { + map[string]interface{}{"a": jsonNumberT("5")}, + "a: 5\n", + }, + { + map[string]interface{}{"a": jsonNumberT("100.5")}, + "a: 100.5\n", + }, + { + map[string]interface{}{"a": jsonNumberT("bogus")}, + "a: bogus\n", + }, +} + +func (s *S) TestMarshal(c *C) { + defer os.Setenv("TZ", os.Getenv("TZ")) + os.Setenv("TZ", "UTC") + for i, item := range marshalTests { + c.Logf("test %d: %q", i, item.data) + data, err := yaml.Marshal(item.value) + c.Assert(err, IsNil) + c.Assert(string(data), Equals, item.data) + } +} + +func (s *S) TestEncoderSingleDocument(c *C) { + for i, item := range marshalTests { + c.Logf("test %d. %q", i, item.data) + var buf bytes.Buffer + enc := yaml.NewEncoder(&buf) + err := enc.Encode(item.value) + c.Assert(err, Equals, nil) + err = enc.Close() + c.Assert(err, Equals, nil) + c.Assert(buf.String(), Equals, item.data) + } +} + +func (s *S) TestEncoderMultipleDocuments(c *C) { + var buf bytes.Buffer + enc := yaml.NewEncoder(&buf) + err := enc.Encode(map[string]string{"a": "b"}) + c.Assert(err, Equals, nil) + err = enc.Encode(map[string]string{"c": "d"}) + c.Assert(err, Equals, nil) + err = enc.Close() + c.Assert(err, Equals, nil) + c.Assert(buf.String(), Equals, "a: b\n---\nc: d\n") +} + +func (s *S) TestEncoderWriteError(c *C) { + enc := yaml.NewEncoder(errorWriter{}) + err := enc.Encode(map[string]string{"a": "b"}) + c.Assert(err, ErrorMatches, `yaml: write error: some write error`) // Data not flushed yet +} + +type errorWriter struct{} + +func (errorWriter) Write([]byte) (int, error) { + return 0, fmt.Errorf("some write error") +} + +var marshalErrorTests = []struct { + value interface{} + error string + panic string +}{{ + value: &struct { + B int + inlineB ",inline" + }{1, inlineB{2, inlineC{3}}}, + panic: `Duplicated key 'b' in struct struct \{ B int; .*`, +}, { + value: &struct { + A int + B map[string]int ",inline" + }{1, map[string]int{"a": 2}}, + panic: `Can't have key "a" in inlined map; conflicts with struct field`, +}} + +func (s *S) TestMarshalErrors(c *C) { + for _, item := range marshalErrorTests { + if item.panic != "" { + c.Assert(func() { yaml.Marshal(item.value) }, PanicMatches, item.panic) + } else { + _, err := yaml.Marshal(item.value) + c.Assert(err, ErrorMatches, item.error) + } + } +} + +func (s *S) TestMarshalTypeCache(c *C) { + var data []byte + var err error + func() { + type T struct{ A int } + data, err = yaml.Marshal(&T{}) + c.Assert(err, IsNil) + }() + func() { + type T struct{ B int } + data, err = yaml.Marshal(&T{}) + c.Assert(err, IsNil) + }() + c.Assert(string(data), Equals, "b: 0\n") +} + +var marshalerTests = []struct { + data string + value interface{} +}{ + {"_:\n hi: there\n", map[interface{}]interface{}{"hi": "there"}}, + {"_:\n- 1\n- A\n", []interface{}{1, "A"}}, + {"_: 10\n", 10}, + {"_: null\n", nil}, + {"_: BAR!\n", "BAR!"}, +} + +type marshalerType struct { + value interface{} +} + +func (o marshalerType) MarshalText() ([]byte, error) { + panic("MarshalText called on type with MarshalYAML") +} + +func (o marshalerType) MarshalYAML() (interface{}, error) { + return o.value, nil +} + +type marshalerValue struct { + Field marshalerType "_" +} + +func (s *S) TestMarshaler(c *C) { + for _, item := range marshalerTests { + obj := &marshalerValue{} + obj.Field.value = item.value + data, err := yaml.Marshal(obj) + c.Assert(err, IsNil) + c.Assert(string(data), Equals, string(item.data)) + } +} + +func (s *S) TestMarshalerWholeDocument(c *C) { + obj := &marshalerType{} + obj.value = map[string]string{"hello": "world!"} + data, err := yaml.Marshal(obj) + c.Assert(err, IsNil) + c.Assert(string(data), Equals, "hello: world!\n") +} + +type failingMarshaler struct{} + +func (ft *failingMarshaler) MarshalYAML() (interface{}, error) { + return nil, failingErr +} + +func (s *S) TestMarshalerError(c *C) { + _, err := yaml.Marshal(&failingMarshaler{}) + c.Assert(err, Equals, failingErr) +} + +func (s *S) TestSortedOutput(c *C) { + order := []interface{}{ + false, + true, + 1, + uint(1), + 1.0, + 1.1, + 1.2, + 2, + uint(2), + 2.0, + 2.1, + "", + ".1", + ".2", + ".a", + "1", + "2", + "a!10", + "a/0001", + "a/002", + "a/3", + "a/10", + "a/11", + "a/0012", + "a/100", + "a~10", + "ab/1", + "b/1", + "b/01", + "b/2", + "b/02", + "b/3", + "b/03", + "b1", + "b01", + "b3", + "c2.10", + "c10.2", + "d1", + "d7", + "d7abc", + "d12", + "d12a", + } + m := make(map[interface{}]int) + for _, k := range order { + m[k] = 1 + } + data, err := yaml.Marshal(m) + c.Assert(err, IsNil) + out := "\n" + string(data) + last := 0 + for i, k := range order { + repr := fmt.Sprint(k) + if s, ok := k.(string); ok { + if _, err = strconv.ParseFloat(repr, 32); s == "" || err == nil { + repr = `"` + repr + `"` + } + } + index := strings.Index(out, "\n"+repr+":") + if index == -1 { + c.Fatalf("%#v is not in the output: %#v", k, out) + } + if index < last { + c.Fatalf("%#v was generated before %#v: %q", k, order[i-1], out) + } + last = index + } +} + +func newTime(t time.Time) *time.Time { + return &t +} diff --git a/vendor/gopkg.in/yaml.v2/example_embedded_test.go b/vendor/gopkg.in/yaml.v2/example_embedded_test.go new file mode 100644 index 00000000..171c0931 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/example_embedded_test.go @@ -0,0 +1,41 @@ +package yaml_test + +import ( + "fmt" + "log" + + "gopkg.in/yaml.v2" +) + +// An example showing how to unmarshal embedded +// structs from YAML. + +type StructA struct { + A string `yaml:"a"` +} + +type StructB struct { + // Embedded structs are not treated as embedded in YAML by default. To do that, + // add the ",inline" annotation below + StructA `yaml:",inline"` + B string `yaml:"b"` +} + +var data = ` +a: a string from struct A +b: a string from struct B +` + +func ExampleUnmarshal_embedded() { + var b StructB + + err := yaml.Unmarshal([]byte(data), &b) + if err != nil { + log.Fatalf("cannot unmarshal data: %v", err) + } + fmt.Println(b.A) + fmt.Println(b.B) + // Output: + // a string from struct A + // a string from struct B +} diff --git a/vendor/gopkg.in/yaml.v2/go.mod b/vendor/gopkg.in/yaml.v2/go.mod new file mode 100644 index 00000000..1934e876 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/go.mod @@ -0,0 +1,5 @@ +module "gopkg.in/yaml.v2" + +require ( + "gopkg.in/check.v1" v0.0.0-20161208181325-20d25e280405 +) diff --git a/vendor/gopkg.in/yaml.v2/parserc.go b/vendor/gopkg.in/yaml.v2/parserc.go new file mode 100644 index 00000000..81d05dfe --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/parserc.go @@ -0,0 +1,1095 @@ +package yaml + +import ( + "bytes" +) + +// The parser implements the following grammar: +// +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// implicit_document ::= block_node DOCUMENT-END* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// block_node_or_indentless_sequence ::= +// ALIAS +// | properties (block_content | indentless_block_sequence)? +// | block_content +// | indentless_block_sequence +// block_node ::= ALIAS +// | properties block_content? +// | block_content +// flow_node ::= ALIAS +// | properties flow_content? +// | flow_content +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// block_content ::= block_collection | flow_collection | SCALAR +// flow_content ::= flow_collection | SCALAR +// block_collection ::= block_sequence | block_mapping +// flow_collection ::= flow_sequence | flow_mapping +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// block_mapping ::= BLOCK-MAPPING_START +// ((KEY block_node_or_indentless_sequence?)? +// (VALUE block_node_or_indentless_sequence?)?)* +// BLOCK-END +// flow_sequence ::= FLOW-SEQUENCE-START +// (flow_sequence_entry FLOW-ENTRY)* +// flow_sequence_entry? +// FLOW-SEQUENCE-END +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// flow_mapping ::= FLOW-MAPPING-START +// (flow_mapping_entry FLOW-ENTRY)* +// flow_mapping_entry? +// FLOW-MAPPING-END +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + +// Peek the next token in the token queue. +func peek_token(parser *yaml_parser_t) *yaml_token_t { + if parser.token_available || yaml_parser_fetch_more_tokens(parser) { + return &parser.tokens[parser.tokens_head] + } + return nil +} + +// Remove the next token from the queue (must be called after peek_token). +func skip_token(parser *yaml_parser_t) { + parser.token_available = false + parser.tokens_parsed++ + parser.stream_end_produced = parser.tokens[parser.tokens_head].typ == yaml_STREAM_END_TOKEN + parser.tokens_head++ +} + +// Get the next event. +func yaml_parser_parse(parser *yaml_parser_t, event *yaml_event_t) bool { + // Erase the event object. + *event = yaml_event_t{} + + // No events after the end of the stream or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR || parser.state == yaml_PARSE_END_STATE { + return true + } + + // Generate the next event. + return yaml_parser_state_machine(parser, event) +} + +// Set parser error. +func yaml_parser_set_parser_error(parser *yaml_parser_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +func yaml_parser_set_parser_error_context(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +// State dispatcher. +func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool { + //trace("yaml_parser_state_machine", "state:", parser.state.String()) + + switch parser.state { + case yaml_PARSE_STREAM_START_STATE: + return yaml_parser_parse_stream_start(parser, event) + + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, true) + + case yaml_PARSE_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, false) + + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return yaml_parser_parse_document_content(parser, event) + + case yaml_PARSE_DOCUMENT_END_STATE: + return yaml_parser_parse_document_end(parser, event) + + case yaml_PARSE_BLOCK_NODE_STATE: + return yaml_parser_parse_node(parser, event, true, false) + + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return yaml_parser_parse_node(parser, event, true, true) + + case yaml_PARSE_FLOW_NODE_STATE: + return yaml_parser_parse_node(parser, event, false, false) + + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, true) + + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, false) + + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_indentless_sequence_entry(parser, event) + + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, true) + + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, false) + + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return yaml_parser_parse_block_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, true) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, false) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_key(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_end(parser, event) + + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, true) + + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, true) + + default: + panic("invalid parser state") + } +} + +// Parse the production: +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// ************ +func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_STREAM_START_TOKEN { + return yaml_parser_set_parser_error(parser, "did not find expected ", token.start_mark) + } + parser.state = yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + encoding: token.encoding, + } + skip_token(parser) + return true +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// * +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// ************************* +func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t, implicit bool) bool { + + token := peek_token(parser) + if token == nil { + return false + } + + // Parse extra document end indicators. + if !implicit { + for token.typ == yaml_DOCUMENT_END_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + if implicit && token.typ != yaml_VERSION_DIRECTIVE_TOKEN && + token.typ != yaml_TAG_DIRECTIVE_TOKEN && + token.typ != yaml_DOCUMENT_START_TOKEN && + token.typ != yaml_STREAM_END_TOKEN { + // Parse an implicit document. + if !yaml_parser_process_directives(parser, nil, nil) { + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_BLOCK_NODE_STATE + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + } else if token.typ != yaml_STREAM_END_TOKEN { + // Parse an explicit document. + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + start_mark := token.start_mark + if !yaml_parser_process_directives(parser, &version_directive, &tag_directives) { + return false + } + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_DOCUMENT_START_TOKEN { + yaml_parser_set_parser_error(parser, + "did not find expected ", token.start_mark) + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_DOCUMENT_CONTENT_STATE + end_mark := token.end_mark + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: false, + } + skip_token(parser) + + } else { + // Parse the stream end. + parser.state = yaml_PARSE_END_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + } + + return true +} + +// Parse the productions: +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// *********** +// +func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN || + token.typ == yaml_TAG_DIRECTIVE_TOKEN || + token.typ == yaml_DOCUMENT_START_TOKEN || + token.typ == yaml_DOCUMENT_END_TOKEN || + token.typ == yaml_STREAM_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + return yaml_parser_process_empty_scalar(parser, event, + token.start_mark) + } + return yaml_parser_parse_node(parser, event, true, false) +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// ************* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// +func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + start_mark := token.start_mark + end_mark := token.start_mark + + implicit := true + if token.typ == yaml_DOCUMENT_END_TOKEN { + end_mark = token.end_mark + skip_token(parser) + implicit = false + } + + parser.tag_directives = parser.tag_directives[:0] + + parser.state = yaml_PARSE_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + start_mark: start_mark, + end_mark: end_mark, + implicit: implicit, + } + return true +} + +// Parse the productions: +// block_node_or_indentless_sequence ::= +// ALIAS +// ***** +// | properties (block_content | indentless_block_sequence)? +// ********** * +// | block_content | indentless_block_sequence +// * +// block_node ::= ALIAS +// ***** +// | properties block_content? +// ********** * +// | block_content +// * +// flow_node ::= ALIAS +// ***** +// | properties flow_content? +// ********** * +// | flow_content +// * +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// ************************* +// block_content ::= block_collection | flow_collection | SCALAR +// ****** +// flow_content ::= flow_collection | SCALAR +// ****** +func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, indentless_sequence bool) bool { + //defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)() + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_ALIAS_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + *event = yaml_event_t{ + typ: yaml_ALIAS_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + anchor: token.value, + } + skip_token(parser) + return true + } + + start_mark := token.start_mark + end_mark := token.start_mark + + var tag_token bool + var tag_handle, tag_suffix, anchor []byte + var tag_mark yaml_mark_t + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + start_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } else if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + start_mark = token.start_mark + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + var tag []byte + if tag_token { + if len(tag_handle) == 0 { + tag = tag_suffix + tag_suffix = nil + } else { + for i := range parser.tag_directives { + if bytes.Equal(parser.tag_directives[i].handle, tag_handle) { + tag = append([]byte(nil), parser.tag_directives[i].prefix...) + tag = append(tag, tag_suffix...) + break + } + } + if len(tag) == 0 { + yaml_parser_set_parser_error_context(parser, + "while parsing a node", start_mark, + "found undefined tag handle", tag_mark) + return false + } + } + } + + implicit := len(tag) == 0 + if indentless_sequence && token.typ == yaml_BLOCK_ENTRY_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + return true + } + if token.typ == yaml_SCALAR_TOKEN { + var plain_implicit, quoted_implicit bool + end_mark = token.end_mark + if (len(tag) == 0 && token.style == yaml_PLAIN_SCALAR_STYLE) || (len(tag) == 1 && tag[0] == '!') { + plain_implicit = true + } else if len(tag) == 0 { + quoted_implicit = true + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + value: token.value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(token.style), + } + skip_token(parser) + return true + } + if token.typ == yaml_FLOW_SEQUENCE_START_TOKEN { + // [Go] Some of the events below can be merged as they differ only on style. + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_SEQUENCE_STYLE), + } + return true + } + if token.typ == yaml_FLOW_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + return true + } + if block && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + return true + } + if block && token.typ == yaml_BLOCK_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_MAPPING_STYLE), + } + return true + } + if len(anchor) > 0 || len(tag) > 0 { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + quoted_implicit: false, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true + } + + context := "while parsing a flow node" + if block { + context = "while parsing a block node" + } + yaml_parser_set_parser_error_context(parser, context, start_mark, + "did not find expected node content", token.start_mark) + return false +} + +// Parse the productions: +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// ******************** *********** * ********* +// +func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } else { + parser.state = yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } + if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block collection", context_mark, + "did not find expected '-' indicator", token.start_mark) +} + +// Parse the productions: +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// *********** * +func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && + token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be token.end_mark? + } + return true +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// ******************* +// ((KEY block_node_or_indentless_sequence?)? +// *** * +// (VALUE block_node_or_indentless_sequence?)?)* +// +// BLOCK-END +// ********* +// +func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_KEY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } else { + parser.state = yaml_PARSE_BLOCK_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } else if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block mapping", context_mark, + "did not find expected key", token.start_mark) +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// +// ((KEY block_node_or_indentless_sequence?)? +// +// (VALUE block_node_or_indentless_sequence?)?)* +// ***** * +// BLOCK-END +// +// +func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence ::= FLOW-SEQUENCE-START +// ******************* +// (flow_sequence_entry FLOW-ENTRY)* +// * ********** +// flow_sequence_entry? +// * +// FLOW-SEQUENCE-END +// ***************** +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +// +func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow sequence", context_mark, + "did not find expected ',' or ']'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + implicit: true, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + skip_token(parser) + return true + } else if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + skip_token(parser) + return true +} + +// +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// *** * +// +func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + mark := token.end_mark + skip_token(parser) + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// ***** * +// +func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +// +func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be end_mark? + } + return true +} + +// Parse the productions: +// flow_mapping ::= FLOW-MAPPING-START +// ****************** +// (flow_mapping_entry FLOW-ENTRY)* +// * ********** +// flow_mapping_entry? +// ****************** +// FLOW-MAPPING-END +// **************** +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * *** * +// +func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow mapping", context_mark, + "did not find expected ',' or '}'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } else { + parser.state = yaml_PARSE_FLOW_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + } else if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + return true +} + +// Parse the productions: +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * ***** * +// +func yaml_parser_parse_flow_mapping_value(parser *yaml_parser_t, event *yaml_event_t, empty bool) bool { + token := peek_token(parser) + if token == nil { + return false + } + if empty { + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Generate an empty scalar event. +func yaml_parser_process_empty_scalar(parser *yaml_parser_t, event *yaml_event_t, mark yaml_mark_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: mark, + end_mark: mark, + value: nil, // Empty + implicit: true, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true +} + +var default_tag_directives = []yaml_tag_directive_t{ + {[]byte("!"), []byte("!")}, + {[]byte("!!"), []byte("tag:yaml.org,2002:")}, +} + +// Parse directives. +func yaml_parser_process_directives(parser *yaml_parser_t, + version_directive_ref **yaml_version_directive_t, + tag_directives_ref *[]yaml_tag_directive_t) bool { + + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + + token := peek_token(parser) + if token == nil { + return false + } + + for token.typ == yaml_VERSION_DIRECTIVE_TOKEN || token.typ == yaml_TAG_DIRECTIVE_TOKEN { + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN { + if version_directive != nil { + yaml_parser_set_parser_error(parser, + "found duplicate %YAML directive", token.start_mark) + return false + } + if token.major != 1 || token.minor != 1 { + yaml_parser_set_parser_error(parser, + "found incompatible YAML document", token.start_mark) + return false + } + version_directive = &yaml_version_directive_t{ + major: token.major, + minor: token.minor, + } + } else if token.typ == yaml_TAG_DIRECTIVE_TOKEN { + value := yaml_tag_directive_t{ + handle: token.value, + prefix: token.prefix, + } + if !yaml_parser_append_tag_directive(parser, value, false, token.start_mark) { + return false + } + tag_directives = append(tag_directives, value) + } + + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + + for i := range default_tag_directives { + if !yaml_parser_append_tag_directive(parser, default_tag_directives[i], true, token.start_mark) { + return false + } + } + + if version_directive_ref != nil { + *version_directive_ref = version_directive + } + if tag_directives_ref != nil { + *tag_directives_ref = tag_directives + } + return true +} + +// Append a tag directive to the directives stack. +func yaml_parser_append_tag_directive(parser *yaml_parser_t, value yaml_tag_directive_t, allow_duplicates bool, mark yaml_mark_t) bool { + for i := range parser.tag_directives { + if bytes.Equal(value.handle, parser.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_parser_set_parser_error(parser, "found duplicate %TAG directive", mark) + } + } + + // [Go] I suspect the copy is unnecessary. This was likely done + // because there was no way to track ownership of the data. + value_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(value_copy.handle, value.handle) + copy(value_copy.prefix, value.prefix) + parser.tag_directives = append(parser.tag_directives, value_copy) + return true +} diff --git a/vendor/gopkg.in/yaml.v2/readerc.go b/vendor/gopkg.in/yaml.v2/readerc.go new file mode 100644 index 00000000..7c1f5fac --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/readerc.go @@ -0,0 +1,412 @@ +package yaml + +import ( + "io" +) + +// Set the reader error and return 0. +func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool { + parser.error = yaml_READER_ERROR + parser.problem = problem + parser.problem_offset = offset + parser.problem_value = value + return false +} + +// Byte order marks. +const ( + bom_UTF8 = "\xef\xbb\xbf" + bom_UTF16LE = "\xff\xfe" + bom_UTF16BE = "\xfe\xff" +) + +// Determine the input stream encoding by checking the BOM symbol. If no BOM is +// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure. +func yaml_parser_determine_encoding(parser *yaml_parser_t) bool { + // Ensure that we had enough bytes in the raw buffer. + for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 { + if !yaml_parser_update_raw_buffer(parser) { + return false + } + } + + // Determine the encoding. + buf := parser.raw_buffer + pos := parser.raw_buffer_pos + avail := len(buf) - pos + if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] { + parser.encoding = yaml_UTF16LE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] { + parser.encoding = yaml_UTF16BE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] { + parser.encoding = yaml_UTF8_ENCODING + parser.raw_buffer_pos += 3 + parser.offset += 3 + } else { + parser.encoding = yaml_UTF8_ENCODING + } + return true +} + +// Update the raw buffer. +func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool { + size_read := 0 + + // Return if the raw buffer is full. + if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) { + return true + } + + // Return on EOF. + if parser.eof { + return true + } + + // Move the remaining bytes in the raw buffer to the beginning. + if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) { + copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:]) + } + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos] + parser.raw_buffer_pos = 0 + + // Call the read handler to fill the buffer. + size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)]) + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read] + if err == io.EOF { + parser.eof = true + } else if err != nil { + return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1) + } + return true +} + +// Ensure that the buffer contains at least `length` characters. +// Return true on success, false on failure. +// +// The length is supposed to be significantly less that the buffer size. +func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool { + if parser.read_handler == nil { + panic("read handler must be set") + } + + // [Go] This function was changed to guarantee the requested length size at EOF. + // The fact we need to do this is pretty awful, but the description above implies + // for that to be the case, and there are tests + + // If the EOF flag is set and the raw buffer is empty, do nothing. + if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) { + // [Go] ACTUALLY! Read the documentation of this function above. + // This is just broken. To return true, we need to have the + // given length in the buffer. Not doing that means every single + // check that calls this function to make sure the buffer has a + // given length is Go) panicking; or C) accessing invalid memory. + //return true + } + + // Return if the buffer contains enough characters. + if parser.unread >= length { + return true + } + + // Determine the input encoding if it is not known yet. + if parser.encoding == yaml_ANY_ENCODING { + if !yaml_parser_determine_encoding(parser) { + return false + } + } + + // Move the unread characters to the beginning of the buffer. + buffer_len := len(parser.buffer) + if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len { + copy(parser.buffer, parser.buffer[parser.buffer_pos:]) + buffer_len -= parser.buffer_pos + parser.buffer_pos = 0 + } else if parser.buffer_pos == buffer_len { + buffer_len = 0 + parser.buffer_pos = 0 + } + + // Open the whole buffer for writing, and cut it before returning. + parser.buffer = parser.buffer[:cap(parser.buffer)] + + // Fill the buffer until it has enough characters. + first := true + for parser.unread < length { + + // Fill the raw buffer if necessary. + if !first || parser.raw_buffer_pos == len(parser.raw_buffer) { + if !yaml_parser_update_raw_buffer(parser) { + parser.buffer = parser.buffer[:buffer_len] + return false + } + } + first = false + + // Decode the raw buffer. + inner: + for parser.raw_buffer_pos != len(parser.raw_buffer) { + var value rune + var width int + + raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos + + // Decode the next character. + switch parser.encoding { + case yaml_UTF8_ENCODING: + // Decode a UTF-8 character. Check RFC 3629 + // (http://www.ietf.org/rfc/rfc3629.txt) for more details. + // + // The following table (taken from the RFC) is used for + // decoding. + // + // Char. number range | UTF-8 octet sequence + // (hexadecimal) | (binary) + // --------------------+------------------------------------ + // 0000 0000-0000 007F | 0xxxxxxx + // 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + // 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + // 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // + // Additionally, the characters in the range 0xD800-0xDFFF + // are prohibited as they are reserved for use with UTF-16 + // surrogate pairs. + + // Determine the length of the UTF-8 sequence. + octet := parser.raw_buffer[parser.raw_buffer_pos] + switch { + case octet&0x80 == 0x00: + width = 1 + case octet&0xE0 == 0xC0: + width = 2 + case octet&0xF0 == 0xE0: + width = 3 + case octet&0xF8 == 0xF0: + width = 4 + default: + // The leading octet is invalid. + return yaml_parser_set_reader_error(parser, + "invalid leading UTF-8 octet", + parser.offset, int(octet)) + } + + // Check if the raw buffer contains an incomplete character. + if width > raw_unread { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-8 octet sequence", + parser.offset, -1) + } + break inner + } + + // Decode the leading octet. + switch { + case octet&0x80 == 0x00: + value = rune(octet & 0x7F) + case octet&0xE0 == 0xC0: + value = rune(octet & 0x1F) + case octet&0xF0 == 0xE0: + value = rune(octet & 0x0F) + case octet&0xF8 == 0xF0: + value = rune(octet & 0x07) + default: + value = 0 + } + + // Check and decode the trailing octets. + for k := 1; k < width; k++ { + octet = parser.raw_buffer[parser.raw_buffer_pos+k] + + // Check if the octet is valid. + if (octet & 0xC0) != 0x80 { + return yaml_parser_set_reader_error(parser, + "invalid trailing UTF-8 octet", + parser.offset+k, int(octet)) + } + + // Decode the octet. + value = (value << 6) + rune(octet&0x3F) + } + + // Check the length of the sequence against the value. + switch { + case width == 1: + case width == 2 && value >= 0x80: + case width == 3 && value >= 0x800: + case width == 4 && value >= 0x10000: + default: + return yaml_parser_set_reader_error(parser, + "invalid length of a UTF-8 sequence", + parser.offset, -1) + } + + // Check the range of the value. + if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF { + return yaml_parser_set_reader_error(parser, + "invalid Unicode character", + parser.offset, int(value)) + } + + case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING: + var low, high int + if parser.encoding == yaml_UTF16LE_ENCODING { + low, high = 0, 1 + } else { + low, high = 1, 0 + } + + // The UTF-16 encoding is not as simple as one might + // naively think. Check RFC 2781 + // (http://www.ietf.org/rfc/rfc2781.txt). + // + // Normally, two subsequent bytes describe a Unicode + // character. However a special technique (called a + // surrogate pair) is used for specifying character + // values larger than 0xFFFF. + // + // A surrogate pair consists of two pseudo-characters: + // high surrogate area (0xD800-0xDBFF) + // low surrogate area (0xDC00-0xDFFF) + // + // The following formulas are used for decoding + // and encoding characters using surrogate pairs: + // + // U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF) + // U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF) + // W1 = 110110yyyyyyyyyy + // W2 = 110111xxxxxxxxxx + // + // where U is the character value, W1 is the high surrogate + // area, W2 is the low surrogate area. + + // Check for incomplete UTF-16 character. + if raw_unread < 2 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 character", + parser.offset, -1) + } + break inner + } + + // Get the character. + value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8) + + // Check for unexpected low surrogate area. + if value&0xFC00 == 0xDC00 { + return yaml_parser_set_reader_error(parser, + "unexpected low surrogate area", + parser.offset, int(value)) + } + + // Check for a high surrogate area. + if value&0xFC00 == 0xD800 { + width = 4 + + // Check for incomplete surrogate pair. + if raw_unread < 4 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 surrogate pair", + parser.offset, -1) + } + break inner + } + + // Get the next character. + value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8) + + // Check for a low surrogate area. + if value2&0xFC00 != 0xDC00 { + return yaml_parser_set_reader_error(parser, + "expected low surrogate area", + parser.offset+2, int(value2)) + } + + // Generate the value of the surrogate pair. + value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF) + } else { + width = 2 + } + + default: + panic("impossible") + } + + // Check if the character is in the allowed range: + // #x9 | #xA | #xD | [#x20-#x7E] (8 bit) + // | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit) + // | [#x10000-#x10FFFF] (32 bit) + switch { + case value == 0x09: + case value == 0x0A: + case value == 0x0D: + case value >= 0x20 && value <= 0x7E: + case value == 0x85: + case value >= 0xA0 && value <= 0xD7FF: + case value >= 0xE000 && value <= 0xFFFD: + case value >= 0x10000 && value <= 0x10FFFF: + default: + return yaml_parser_set_reader_error(parser, + "control characters are not allowed", + parser.offset, int(value)) + } + + // Move the raw pointers. + parser.raw_buffer_pos += width + parser.offset += width + + // Finally put the character into the buffer. + if value <= 0x7F { + // 0000 0000-0000 007F . 0xxxxxxx + parser.buffer[buffer_len+0] = byte(value) + buffer_len += 1 + } else if value <= 0x7FF { + // 0000 0080-0000 07FF . 110xxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6)) + parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F)) + buffer_len += 2 + } else if value <= 0xFFFF { + // 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F)) + buffer_len += 3 + } else { + // 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F)) + buffer_len += 4 + } + + parser.unread++ + } + + // On EOF, put NUL into the buffer and return. + if parser.eof { + parser.buffer[buffer_len] = 0 + buffer_len++ + parser.unread++ + break + } + } + // [Go] Read the documentation of this function above. To return true, + // we need to have the given length in the buffer. Not doing that means + // every single check that calls this function to make sure the buffer + // has a given length is Go) panicking; or C) accessing invalid memory. + // This happens here due to the EOF above breaking early. + for buffer_len < length { + parser.buffer[buffer_len] = 0 + buffer_len++ + } + parser.buffer = parser.buffer[:buffer_len] + return true +} diff --git a/vendor/gopkg.in/yaml.v2/resolve.go b/vendor/gopkg.in/yaml.v2/resolve.go new file mode 100644 index 00000000..6c151db6 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/resolve.go @@ -0,0 +1,258 @@ +package yaml + +import ( + "encoding/base64" + "math" + "regexp" + "strconv" + "strings" + "time" +) + +type resolveMapItem struct { + value interface{} + tag string +} + +var resolveTable = make([]byte, 256) +var resolveMap = make(map[string]resolveMapItem) + +func init() { + t := resolveTable + t[int('+')] = 'S' // Sign + t[int('-')] = 'S' + for _, c := range "0123456789" { + t[int(c)] = 'D' // Digit + } + for _, c := range "yYnNtTfFoO~" { + t[int(c)] = 'M' // In map + } + t[int('.')] = '.' // Float (potentially in map) + + var resolveMapList = []struct { + v interface{} + tag string + l []string + }{ + {true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}}, + {true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}}, + {true, yaml_BOOL_TAG, []string{"on", "On", "ON"}}, + {false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}}, + {false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}}, + {false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}}, + {nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}}, + {math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}}, + {math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}}, + {math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}}, + {math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}}, + {"<<", yaml_MERGE_TAG, []string{"<<"}}, + } + + m := resolveMap + for _, item := range resolveMapList { + for _, s := range item.l { + m[s] = resolveMapItem{item.v, item.tag} + } + } +} + +const longTagPrefix = "tag:yaml.org,2002:" + +func shortTag(tag string) string { + // TODO This can easily be made faster and produce less garbage. + if strings.HasPrefix(tag, longTagPrefix) { + return "!!" + tag[len(longTagPrefix):] + } + return tag +} + +func longTag(tag string) string { + if strings.HasPrefix(tag, "!!") { + return longTagPrefix + tag[2:] + } + return tag +} + +func resolvableTag(tag string) bool { + switch tag { + case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG: + return true + } + return false +} + +var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`) + +func resolve(tag string, in string) (rtag string, out interface{}) { + if !resolvableTag(tag) { + return tag, in + } + + defer func() { + switch tag { + case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG: + return + case yaml_FLOAT_TAG: + if rtag == yaml_INT_TAG { + switch v := out.(type) { + case int64: + rtag = yaml_FLOAT_TAG + out = float64(v) + return + case int: + rtag = yaml_FLOAT_TAG + out = float64(v) + return + } + } + } + failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)) + }() + + // Any data is accepted as a !!str or !!binary. + // Otherwise, the prefix is enough of a hint about what it might be. + hint := byte('N') + if in != "" { + hint = resolveTable[in[0]] + } + if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG { + // Handle things we can lookup in a map. + if item, ok := resolveMap[in]; ok { + return item.tag, item.value + } + + // Base 60 floats are a bad idea, were dropped in YAML 1.2, and + // are purposefully unsupported here. They're still quoted on + // the way out for compatibility with other parser, though. + + switch hint { + case 'M': + // We've already checked the map above. + + case '.': + // Not in the map, so maybe a normal float. + floatv, err := strconv.ParseFloat(in, 64) + if err == nil { + return yaml_FLOAT_TAG, floatv + } + + case 'D', 'S': + // Int, float, or timestamp. + // Only try values as a timestamp if the value is unquoted or there's an explicit + // !!timestamp tag. + if tag == "" || tag == yaml_TIMESTAMP_TAG { + t, ok := parseTimestamp(in) + if ok { + return yaml_TIMESTAMP_TAG, t + } + } + + plain := strings.Replace(in, "_", "", -1) + intv, err := strconv.ParseInt(plain, 0, 64) + if err == nil { + if intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + uintv, err := strconv.ParseUint(plain, 0, 64) + if err == nil { + return yaml_INT_TAG, uintv + } + if yamlStyleFloat.MatchString(plain) { + floatv, err := strconv.ParseFloat(plain, 64) + if err == nil { + return yaml_FLOAT_TAG, floatv + } + } + if strings.HasPrefix(plain, "0b") { + intv, err := strconv.ParseInt(plain[2:], 2, 64) + if err == nil { + if intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + uintv, err := strconv.ParseUint(plain[2:], 2, 64) + if err == nil { + return yaml_INT_TAG, uintv + } + } else if strings.HasPrefix(plain, "-0b") { + intv, err := strconv.ParseInt("-" + plain[3:], 2, 64) + if err == nil { + if true || intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + } + default: + panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")") + } + } + return yaml_STR_TAG, in +} + +// encodeBase64 encodes s as base64 that is broken up into multiple lines +// as appropriate for the resulting length. +func encodeBase64(s string) string { + const lineLen = 70 + encLen := base64.StdEncoding.EncodedLen(len(s)) + lines := encLen/lineLen + 1 + buf := make([]byte, encLen*2+lines) + in := buf[0:encLen] + out := buf[encLen:] + base64.StdEncoding.Encode(in, []byte(s)) + k := 0 + for i := 0; i < len(in); i += lineLen { + j := i + lineLen + if j > len(in) { + j = len(in) + } + k += copy(out[k:], in[i:j]) + if lines > 1 { + out[k] = '\n' + k++ + } + } + return string(out[:k]) +} + +// This is a subset of the formats allowed by the regular expression +// defined at http://yaml.org/type/timestamp.html. +var allowedTimestampFormats = []string{ + "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields. + "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t". + "2006-1-2 15:4:5.999999999", // space separated with no time zone + "2006-1-2", // date only + // Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5" + // from the set of examples. +} + +// parseTimestamp parses s as a timestamp string and +// returns the timestamp and reports whether it succeeded. +// Timestamp formats are defined at http://yaml.org/type/timestamp.html +func parseTimestamp(s string) (time.Time, bool) { + // TODO write code to check all the formats supported by + // http://yaml.org/type/timestamp.html instead of using time.Parse. + + // Quick check: all date formats start with YYYY-. + i := 0 + for ; i < len(s); i++ { + if c := s[i]; c < '0' || c > '9' { + break + } + } + if i != 4 || i == len(s) || s[i] != '-' { + return time.Time{}, false + } + for _, format := range allowedTimestampFormats { + if t, err := time.Parse(format, s); err == nil { + return t, true + } + } + return time.Time{}, false +} diff --git a/vendor/gopkg.in/yaml.v2/scannerc.go b/vendor/gopkg.in/yaml.v2/scannerc.go new file mode 100644 index 00000000..077fd1dd --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/scannerc.go @@ -0,0 +1,2696 @@ +package yaml + +import ( + "bytes" + "fmt" +) + +// Introduction +// ************ +// +// The following notes assume that you are familiar with the YAML specification +// (http://yaml.org/spec/1.2/spec.html). We mostly follow it, although in +// some cases we are less restrictive that it requires. +// +// The process of transforming a YAML stream into a sequence of events is +// divided on two steps: Scanning and Parsing. +// +// The Scanner transforms the input stream into a sequence of tokens, while the +// parser transform the sequence of tokens produced by the Scanner into a +// sequence of parsing events. +// +// The Scanner is rather clever and complicated. The Parser, on the contrary, +// is a straightforward implementation of a recursive-descendant parser (or, +// LL(1) parser, as it is usually called). +// +// Actually there are two issues of Scanning that might be called "clever", the +// rest is quite straightforward. The issues are "block collection start" and +// "simple keys". Both issues are explained below in details. +// +// Here the Scanning step is explained and implemented. We start with the list +// of all the tokens produced by the Scanner together with short descriptions. +// +// Now, tokens: +// +// STREAM-START(encoding) # The stream start. +// STREAM-END # The stream end. +// VERSION-DIRECTIVE(major,minor) # The '%YAML' directive. +// TAG-DIRECTIVE(handle,prefix) # The '%TAG' directive. +// DOCUMENT-START # '---' +// DOCUMENT-END # '...' +// BLOCK-SEQUENCE-START # Indentation increase denoting a block +// BLOCK-MAPPING-START # sequence or a block mapping. +// BLOCK-END # Indentation decrease. +// FLOW-SEQUENCE-START # '[' +// FLOW-SEQUENCE-END # ']' +// BLOCK-SEQUENCE-START # '{' +// BLOCK-SEQUENCE-END # '}' +// BLOCK-ENTRY # '-' +// FLOW-ENTRY # ',' +// KEY # '?' or nothing (simple keys). +// VALUE # ':' +// ALIAS(anchor) # '*anchor' +// ANCHOR(anchor) # '&anchor' +// TAG(handle,suffix) # '!handle!suffix' +// SCALAR(value,style) # A scalar. +// +// The following two tokens are "virtual" tokens denoting the beginning and the +// end of the stream: +// +// STREAM-START(encoding) +// STREAM-END +// +// We pass the information about the input stream encoding with the +// STREAM-START token. +// +// The next two tokens are responsible for tags: +// +// VERSION-DIRECTIVE(major,minor) +// TAG-DIRECTIVE(handle,prefix) +// +// Example: +// +// %YAML 1.1 +// %TAG ! !foo +// %TAG !yaml! tag:yaml.org,2002: +// --- +// +// The correspoding sequence of tokens: +// +// STREAM-START(utf-8) +// VERSION-DIRECTIVE(1,1) +// TAG-DIRECTIVE("!","!foo") +// TAG-DIRECTIVE("!yaml","tag:yaml.org,2002:") +// DOCUMENT-START +// STREAM-END +// +// Note that the VERSION-DIRECTIVE and TAG-DIRECTIVE tokens occupy a whole +// line. +// +// The document start and end indicators are represented by: +// +// DOCUMENT-START +// DOCUMENT-END +// +// Note that if a YAML stream contains an implicit document (without '---' +// and '...' indicators), no DOCUMENT-START and DOCUMENT-END tokens will be +// produced. +// +// In the following examples, we present whole documents together with the +// produced tokens. +// +// 1. An implicit document: +// +// 'a scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// STREAM-END +// +// 2. An explicit document: +// +// --- +// 'a scalar' +// ... +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// SCALAR("a scalar",single-quoted) +// DOCUMENT-END +// STREAM-END +// +// 3. Several documents in a stream: +// +// 'a scalar' +// --- +// 'another scalar' +// --- +// 'yet another scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// DOCUMENT-START +// SCALAR("another scalar",single-quoted) +// DOCUMENT-START +// SCALAR("yet another scalar",single-quoted) +// STREAM-END +// +// We have already introduced the SCALAR token above. The following tokens are +// used to describe aliases, anchors, tag, and scalars: +// +// ALIAS(anchor) +// ANCHOR(anchor) +// TAG(handle,suffix) +// SCALAR(value,style) +// +// The following series of examples illustrate the usage of these tokens: +// +// 1. A recursive sequence: +// +// &A [ *A ] +// +// Tokens: +// +// STREAM-START(utf-8) +// ANCHOR("A") +// FLOW-SEQUENCE-START +// ALIAS("A") +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A tagged scalar: +// +// !!float "3.14" # A good approximation. +// +// Tokens: +// +// STREAM-START(utf-8) +// TAG("!!","float") +// SCALAR("3.14",double-quoted) +// STREAM-END +// +// 3. Various scalar styles: +// +// --- # Implicit empty plain scalars do not produce tokens. +// --- a plain scalar +// --- 'a single-quoted scalar' +// --- "a double-quoted scalar" +// --- |- +// a literal scalar +// --- >- +// a folded +// scalar +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// DOCUMENT-START +// SCALAR("a plain scalar",plain) +// DOCUMENT-START +// SCALAR("a single-quoted scalar",single-quoted) +// DOCUMENT-START +// SCALAR("a double-quoted scalar",double-quoted) +// DOCUMENT-START +// SCALAR("a literal scalar",literal) +// DOCUMENT-START +// SCALAR("a folded scalar",folded) +// STREAM-END +// +// Now it's time to review collection-related tokens. We will start with +// flow collections: +// +// FLOW-SEQUENCE-START +// FLOW-SEQUENCE-END +// FLOW-MAPPING-START +// FLOW-MAPPING-END +// FLOW-ENTRY +// KEY +// VALUE +// +// The tokens FLOW-SEQUENCE-START, FLOW-SEQUENCE-END, FLOW-MAPPING-START, and +// FLOW-MAPPING-END represent the indicators '[', ']', '{', and '}' +// correspondingly. FLOW-ENTRY represent the ',' indicator. Finally the +// indicators '?' and ':', which are used for denoting mapping keys and values, +// are represented by the KEY and VALUE tokens. +// +// The following examples show flow collections: +// +// 1. A flow sequence: +// +// [item 1, item 2, item 3] +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-SEQUENCE-START +// SCALAR("item 1",plain) +// FLOW-ENTRY +// SCALAR("item 2",plain) +// FLOW-ENTRY +// SCALAR("item 3",plain) +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A flow mapping: +// +// { +// a simple key: a value, # Note that the KEY token is produced. +// ? a complex key: another value, +// } +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// FLOW-ENTRY +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// FLOW-ENTRY +// FLOW-MAPPING-END +// STREAM-END +// +// A simple key is a key which is not denoted by the '?' indicator. Note that +// the Scanner still produce the KEY token whenever it encounters a simple key. +// +// For scanning block collections, the following tokens are used (note that we +// repeat KEY and VALUE here): +// +// BLOCK-SEQUENCE-START +// BLOCK-MAPPING-START +// BLOCK-END +// BLOCK-ENTRY +// KEY +// VALUE +// +// The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation +// increase that precedes a block collection (cf. the INDENT token in Python). +// The token BLOCK-END denote indentation decrease that ends a block collection +// (cf. the DEDENT token in Python). However YAML has some syntax pecularities +// that makes detections of these tokens more complex. +// +// The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators +// '-', '?', and ':' correspondingly. +// +// The following examples show how the tokens BLOCK-SEQUENCE-START, +// BLOCK-MAPPING-START, and BLOCK-END are emitted by the Scanner: +// +// 1. Block sequences: +// +// - item 1 +// - item 2 +// - +// - item 3.1 +// - item 3.2 +// - +// key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 3.1",plain) +// BLOCK-ENTRY +// SCALAR("item 3.2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Block mappings: +// +// a simple key: a value # The KEY token is produced here. +// ? a complex key +// : another value +// a mapping: +// key 1: value 1 +// key 2: value 2 +// a sequence: +// - item 1 +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// KEY +// SCALAR("a mapping",plain) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML does not always require to start a new block collection from a new +// line. If the current line contains only '-', '?', and ':' indicators, a new +// block collection may start at the current line. The following examples +// illustrate this case: +// +// 1. Collections in a sequence: +// +// - - item 1 +// - item 2 +// - key 1: value 1 +// key 2: value 2 +// - ? complex key +// : complex value +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("complex key") +// VALUE +// SCALAR("complex value") +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Collections in a mapping: +// +// ? a sequence +// : - item 1 +// - item 2 +// ? a mapping +// : key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// KEY +// SCALAR("a mapping",plain) +// VALUE +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML also permits non-indented sequences if they are included into a block +// mapping. In this case, the token BLOCK-SEQUENCE-START is not produced: +// +// key: +// - item 1 # BLOCK-SEQUENCE-START is NOT produced here. +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key",plain) +// VALUE +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// + +// Ensure that the buffer contains the required number of characters. +// Return true on success, false on failure (reader error or memory error). +func cache(parser *yaml_parser_t, length int) bool { + // [Go] This was inlined: !cache(A, B) -> unread < B && !update(A, B) + return parser.unread >= length || yaml_parser_update_buffer(parser, length) +} + +// Advance the buffer pointer. +func skip(parser *yaml_parser_t) { + parser.mark.index++ + parser.mark.column++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) +} + +func skip_line(parser *yaml_parser_t) { + if is_crlf(parser.buffer, parser.buffer_pos) { + parser.mark.index += 2 + parser.mark.column = 0 + parser.mark.line++ + parser.unread -= 2 + parser.buffer_pos += 2 + } else if is_break(parser.buffer, parser.buffer_pos) { + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) + } +} + +// Copy a character to a string buffer and advance pointers. +func read(parser *yaml_parser_t, s []byte) []byte { + w := width(parser.buffer[parser.buffer_pos]) + if w == 0 { + panic("invalid character sequence") + } + if len(s) == 0 { + s = make([]byte, 0, 32) + } + if w == 1 && len(s)+w <= cap(s) { + s = s[:len(s)+1] + s[len(s)-1] = parser.buffer[parser.buffer_pos] + parser.buffer_pos++ + } else { + s = append(s, parser.buffer[parser.buffer_pos:parser.buffer_pos+w]...) + parser.buffer_pos += w + } + parser.mark.index++ + parser.mark.column++ + parser.unread-- + return s +} + +// Copy a line break character to a string buffer and advance pointers. +func read_line(parser *yaml_parser_t, s []byte) []byte { + buf := parser.buffer + pos := parser.buffer_pos + switch { + case buf[pos] == '\r' && buf[pos+1] == '\n': + // CR LF . LF + s = append(s, '\n') + parser.buffer_pos += 2 + parser.mark.index++ + parser.unread-- + case buf[pos] == '\r' || buf[pos] == '\n': + // CR|LF . LF + s = append(s, '\n') + parser.buffer_pos += 1 + case buf[pos] == '\xC2' && buf[pos+1] == '\x85': + // NEL . LF + s = append(s, '\n') + parser.buffer_pos += 2 + case buf[pos] == '\xE2' && buf[pos+1] == '\x80' && (buf[pos+2] == '\xA8' || buf[pos+2] == '\xA9'): + // LS|PS . LS|PS + s = append(s, buf[parser.buffer_pos:pos+3]...) + parser.buffer_pos += 3 + default: + return s + } + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + return s +} + +// Get the next token. +func yaml_parser_scan(parser *yaml_parser_t, token *yaml_token_t) bool { + // Erase the token object. + *token = yaml_token_t{} // [Go] Is this necessary? + + // No tokens after STREAM-END or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR { + return true + } + + // Ensure that the tokens queue contains enough tokens. + if !parser.token_available { + if !yaml_parser_fetch_more_tokens(parser) { + return false + } + } + + // Fetch the next token from the queue. + *token = parser.tokens[parser.tokens_head] + parser.tokens_head++ + parser.tokens_parsed++ + parser.token_available = false + + if token.typ == yaml_STREAM_END_TOKEN { + parser.stream_end_produced = true + } + return true +} + +// Set the scanner error and return false. +func yaml_parser_set_scanner_error(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string) bool { + parser.error = yaml_SCANNER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = parser.mark + return false +} + +func yaml_parser_set_scanner_tag_error(parser *yaml_parser_t, directive bool, context_mark yaml_mark_t, problem string) bool { + context := "while parsing a tag" + if directive { + context = "while parsing a %TAG directive" + } + return yaml_parser_set_scanner_error(parser, context, context_mark, problem) +} + +func trace(args ...interface{}) func() { + pargs := append([]interface{}{"+++"}, args...) + fmt.Println(pargs...) + pargs = append([]interface{}{"---"}, args...) + return func() { fmt.Println(pargs...) } +} + +// Ensure that the tokens queue contains at least one token which can be +// returned to the Parser. +func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool { + // While we need more tokens to fetch, do it. + for { + // Check if we really need to fetch more tokens. + need_more_tokens := false + + if parser.tokens_head == len(parser.tokens) { + // Queue is empty. + need_more_tokens = true + } else { + // Check if any potential simple key may occupy the head position. + if !yaml_parser_stale_simple_keys(parser) { + return false + } + + for i := range parser.simple_keys { + simple_key := &parser.simple_keys[i] + if simple_key.possible && simple_key.token_number == parser.tokens_parsed { + need_more_tokens = true + break + } + } + } + + // We are finished. + if !need_more_tokens { + break + } + // Fetch the next token. + if !yaml_parser_fetch_next_token(parser) { + return false + } + } + + parser.token_available = true + return true +} + +// The dispatcher for token fetchers. +func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool { + // Ensure that the buffer is initialized. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check if we just started scanning. Fetch STREAM-START then. + if !parser.stream_start_produced { + return yaml_parser_fetch_stream_start(parser) + } + + // Eat whitespaces and comments until we reach the next token. + if !yaml_parser_scan_to_next_token(parser) { + return false + } + + // Remove obsolete potential simple keys. + if !yaml_parser_stale_simple_keys(parser) { + return false + } + + // Check the indentation level against the current column. + if !yaml_parser_unroll_indent(parser, parser.mark.column) { + return false + } + + // Ensure that the buffer contains at least 4 characters. 4 is the length + // of the longest indicators ('--- ' and '... '). + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + // Is it the end of the stream? + if is_z(parser.buffer, parser.buffer_pos) { + return yaml_parser_fetch_stream_end(parser) + } + + // Is it a directive? + if parser.mark.column == 0 && parser.buffer[parser.buffer_pos] == '%' { + return yaml_parser_fetch_directive(parser) + } + + buf := parser.buffer + pos := parser.buffer_pos + + // Is it the document start indicator? + if parser.mark.column == 0 && buf[pos] == '-' && buf[pos+1] == '-' && buf[pos+2] == '-' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_START_TOKEN) + } + + // Is it the document end indicator? + if parser.mark.column == 0 && buf[pos] == '.' && buf[pos+1] == '.' && buf[pos+2] == '.' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_END_TOKEN) + } + + // Is it the flow sequence start indicator? + if buf[pos] == '[' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_SEQUENCE_START_TOKEN) + } + + // Is it the flow mapping start indicator? + if parser.buffer[parser.buffer_pos] == '{' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_MAPPING_START_TOKEN) + } + + // Is it the flow sequence end indicator? + if parser.buffer[parser.buffer_pos] == ']' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_SEQUENCE_END_TOKEN) + } + + // Is it the flow mapping end indicator? + if parser.buffer[parser.buffer_pos] == '}' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_MAPPING_END_TOKEN) + } + + // Is it the flow entry indicator? + if parser.buffer[parser.buffer_pos] == ',' { + return yaml_parser_fetch_flow_entry(parser) + } + + // Is it the block entry indicator? + if parser.buffer[parser.buffer_pos] == '-' && is_blankz(parser.buffer, parser.buffer_pos+1) { + return yaml_parser_fetch_block_entry(parser) + } + + // Is it the key indicator? + if parser.buffer[parser.buffer_pos] == '?' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_key(parser) + } + + // Is it the value indicator? + if parser.buffer[parser.buffer_pos] == ':' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_value(parser) + } + + // Is it an alias? + if parser.buffer[parser.buffer_pos] == '*' { + return yaml_parser_fetch_anchor(parser, yaml_ALIAS_TOKEN) + } + + // Is it an anchor? + if parser.buffer[parser.buffer_pos] == '&' { + return yaml_parser_fetch_anchor(parser, yaml_ANCHOR_TOKEN) + } + + // Is it a tag? + if parser.buffer[parser.buffer_pos] == '!' { + return yaml_parser_fetch_tag(parser) + } + + // Is it a literal scalar? + if parser.buffer[parser.buffer_pos] == '|' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, true) + } + + // Is it a folded scalar? + if parser.buffer[parser.buffer_pos] == '>' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, false) + } + + // Is it a single-quoted scalar? + if parser.buffer[parser.buffer_pos] == '\'' { + return yaml_parser_fetch_flow_scalar(parser, true) + } + + // Is it a double-quoted scalar? + if parser.buffer[parser.buffer_pos] == '"' { + return yaml_parser_fetch_flow_scalar(parser, false) + } + + // Is it a plain scalar? + // + // A plain scalar may start with any non-blank characters except + // + // '-', '?', ':', ',', '[', ']', '{', '}', + // '#', '&', '*', '!', '|', '>', '\'', '\"', + // '%', '@', '`'. + // + // In the block context (and, for the '-' indicator, in the flow context + // too), it may also start with the characters + // + // '-', '?', ':' + // + // if it is followed by a non-space character. + // + // The last rule is more restrictive than the specification requires. + // [Go] Make this logic more reasonable. + //switch parser.buffer[parser.buffer_pos] { + //case '-', '?', ':', ',', '?', '-', ',', ':', ']', '[', '}', '{', '&', '#', '!', '*', '>', '|', '"', '\'', '@', '%', '-', '`': + //} + if !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '-' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}' || parser.buffer[parser.buffer_pos] == '#' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '*' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '|' || + parser.buffer[parser.buffer_pos] == '>' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '"' || parser.buffer[parser.buffer_pos] == '%' || + parser.buffer[parser.buffer_pos] == '@' || parser.buffer[parser.buffer_pos] == '`') || + (parser.buffer[parser.buffer_pos] == '-' && !is_blank(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level == 0 && + (parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':') && + !is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_plain_scalar(parser) + } + + // If we don't determine the token type so far, it is an error. + return yaml_parser_set_scanner_error(parser, + "while scanning for the next token", parser.mark, + "found character that cannot start any token") +} + +// Check the list of potential simple keys and remove the positions that +// cannot contain simple keys anymore. +func yaml_parser_stale_simple_keys(parser *yaml_parser_t) bool { + // Check for a potential simple key for each flow level. + for i := range parser.simple_keys { + simple_key := &parser.simple_keys[i] + + // The specification requires that a simple key + // + // - is limited to a single line, + // - is shorter than 1024 characters. + if simple_key.possible && (simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index) { + + // Check if the potential simple key to be removed is required. + if simple_key.required { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", simple_key.mark, + "could not find expected ':'") + } + simple_key.possible = false + } + } + return true +} + +// Check if a simple key may start at the current position and add it if +// needed. +func yaml_parser_save_simple_key(parser *yaml_parser_t) bool { + // A simple key is required at the current position if the scanner is in + // the block context and the current column coincides with the indentation + // level. + + required := parser.flow_level == 0 && parser.indent == parser.mark.column + + // + // If the current position may start a simple key, save it. + // + if parser.simple_key_allowed { + simple_key := yaml_simple_key_t{ + possible: true, + required: required, + token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + } + simple_key.mark = parser.mark + + if !yaml_parser_remove_simple_key(parser) { + return false + } + parser.simple_keys[len(parser.simple_keys)-1] = simple_key + } + return true +} + +// Remove a potential simple key at the current flow level. +func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool { + i := len(parser.simple_keys) - 1 + if parser.simple_keys[i].possible { + // If the key is required, it is an error. + if parser.simple_keys[i].required { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", parser.simple_keys[i].mark, + "could not find expected ':'") + } + } + // Remove the key from the stack. + parser.simple_keys[i].possible = false + return true +} + +// Increase the flow level and resize the simple key list if needed. +func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { + // Reset the simple key on the next level. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + + // Increase the flow level. + parser.flow_level++ + return true +} + +// Decrease the flow level. +func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { + if parser.flow_level > 0 { + parser.flow_level-- + parser.simple_keys = parser.simple_keys[:len(parser.simple_keys)-1] + } + return true +} + +// Push the current indentation level to the stack and set the new level +// the current column is greater than the indentation level. In this case, +// append or insert the specified token into the token queue. +func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml_token_type_t, mark yaml_mark_t) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + if parser.indent < column { + // Push the current indentation level to the stack and set the new + // indentation level. + parser.indents = append(parser.indents, parser.indent) + parser.indent = column + + // Create a token and insert it into the queue. + token := yaml_token_t{ + typ: typ, + start_mark: mark, + end_mark: mark, + } + if number > -1 { + number -= parser.tokens_parsed + } + yaml_insert_token(parser, number, &token) + } + return true +} + +// Pop indentation levels from the indents stack until the current level +// becomes less or equal to the column. For each indentation level, append +// the BLOCK-END token. +func yaml_parser_unroll_indent(parser *yaml_parser_t, column int) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + // Loop through the indentation levels in the stack. + for parser.indent > column { + // Create a token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_END_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + } + yaml_insert_token(parser, -1, &token) + + // Pop the indentation level. + parser.indent = parser.indents[len(parser.indents)-1] + parser.indents = parser.indents[:len(parser.indents)-1] + } + return true +} + +// Initialize the scanner and produce the STREAM-START token. +func yaml_parser_fetch_stream_start(parser *yaml_parser_t) bool { + + // Set the initial indentation. + parser.indent = -1 + + // Initialize the simple key stack. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + + // A simple key is allowed at the beginning of the stream. + parser.simple_key_allowed = true + + // We have started. + parser.stream_start_produced = true + + // Create the STREAM-START token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_START_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + encoding: parser.encoding, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the STREAM-END token and shut down the scanner. +func yaml_parser_fetch_stream_end(parser *yaml_parser_t) bool { + + // Force new line. + if parser.mark.column != 0 { + parser.mark.column = 0 + parser.mark.line++ + } + + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the STREAM-END token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_END_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token. +func yaml_parser_fetch_directive(parser *yaml_parser_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the YAML-DIRECTIVE or TAG-DIRECTIVE token. + token := yaml_token_t{} + if !yaml_parser_scan_directive(parser, &token) { + return false + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the DOCUMENT-START or DOCUMENT-END token. +func yaml_parser_fetch_document_indicator(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Consume the token. + start_mark := parser.mark + + skip(parser) + skip(parser) + skip(parser) + + end_mark := parser.mark + + // Create the DOCUMENT-START or DOCUMENT-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token. +func yaml_parser_fetch_flow_collection_start(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // The indicators '[' and '{' may start a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // Increase the flow level. + if !yaml_parser_increase_flow_level(parser) { + return false + } + + // A simple key may follow the indicators '[' and '{'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-START of FLOW-MAPPING-START token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-END or FLOW-MAPPING-END token. +func yaml_parser_fetch_flow_collection_end(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset any potential simple key on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Decrease the flow level. + if !yaml_parser_decrease_flow_level(parser) { + return false + } + + // No simple keys after the indicators ']' and '}'. + parser.simple_key_allowed = false + + // Consume the token. + + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-END of FLOW-MAPPING-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-ENTRY token. +func yaml_parser_fetch_flow_entry(parser *yaml_parser_t) bool { + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after ','. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_FLOW_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the BLOCK-ENTRY token. +func yaml_parser_fetch_block_entry(parser *yaml_parser_t) bool { + // Check if the scanner is in the block context. + if parser.flow_level == 0 { + // Check if we are allowed to start a new entry. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "block sequence entries are not allowed in this context") + } + // Add the BLOCK-SEQUENCE-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_SEQUENCE_START_TOKEN, parser.mark) { + return false + } + } else { + // It is an error for the '-' indicator to occur in the flow context, + // but we let the Parser detect and report about it because the Parser + // is able to point to the context. + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '-'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the BLOCK-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the KEY token. +func yaml_parser_fetch_key(parser *yaml_parser_t) bool { + + // In the block context, additional checks are required. + if parser.flow_level == 0 { + // Check if we are allowed to start a new key (not nessesary simple). + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping keys are not allowed in this context") + } + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '?' in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the KEY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the VALUE token. +func yaml_parser_fetch_value(parser *yaml_parser_t) bool { + + simple_key := &parser.simple_keys[len(parser.simple_keys)-1] + + // Have we found a simple key? + if simple_key.possible { + // Create the KEY token and insert it into the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: simple_key.mark, + end_mark: simple_key.mark, + } + yaml_insert_token(parser, simple_key.token_number-parser.tokens_parsed, &token) + + // In the block context, we may need to add the BLOCK-MAPPING-START token. + if !yaml_parser_roll_indent(parser, simple_key.mark.column, + simple_key.token_number, + yaml_BLOCK_MAPPING_START_TOKEN, simple_key.mark) { + return false + } + + // Remove the simple key. + simple_key.possible = false + + // A simple key cannot follow another simple key. + parser.simple_key_allowed = false + + } else { + // The ':' indicator follows a complex key. + + // In the block context, extra checks are required. + if parser.flow_level == 0 { + + // Check if we are allowed to start a complex value. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping values are not allowed in this context") + } + + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Simple keys after ':' are allowed in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + } + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the VALUE token and append it to the queue. + token := yaml_token_t{ + typ: yaml_VALUE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the ALIAS or ANCHOR token. +func yaml_parser_fetch_anchor(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // An anchor or an alias could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow an anchor or an alias. + parser.simple_key_allowed = false + + // Create the ALIAS or ANCHOR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_anchor(parser, &token, typ) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the TAG token. +func yaml_parser_fetch_tag(parser *yaml_parser_t) bool { + // A tag could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a tag. + parser.simple_key_allowed = false + + // Create the TAG token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_tag(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,literal) or SCALAR(...,folded) tokens. +func yaml_parser_fetch_block_scalar(parser *yaml_parser_t, literal bool) bool { + // Remove any potential simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // A simple key may follow a block scalar. + parser.simple_key_allowed = true + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_block_scalar(parser, &token, literal) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,single-quoted) or SCALAR(...,double-quoted) tokens. +func yaml_parser_fetch_flow_scalar(parser *yaml_parser_t, single bool) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_flow_scalar(parser, &token, single) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,plain) token. +func yaml_parser_fetch_plain_scalar(parser *yaml_parser_t) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_plain_scalar(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Eat whitespaces and comments until the next token is found. +func yaml_parser_scan_to_next_token(parser *yaml_parser_t) bool { + + // Until the next token is not found. + for { + // Allow the BOM mark to start a line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.mark.column == 0 && is_bom(parser.buffer, parser.buffer_pos) { + skip(parser) + } + + // Eat whitespaces. + // Tabs are allowed: + // - in the flow context + // - in the block context, but not at the beginning of the line or + // after '-', '?', or ':' (complex value). + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for parser.buffer[parser.buffer_pos] == ' ' || ((parser.flow_level > 0 || !parser.simple_key_allowed) && parser.buffer[parser.buffer_pos] == '\t') { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Eat a comment until a line break. + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // If it is a line break, eat it. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + + // In the block context, a new line may start a simple key. + if parser.flow_level == 0 { + parser.simple_key_allowed = true + } + } else { + break // We have found a token. + } + } + + return true +} + +// Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// +func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool { + // Eat '%'. + start_mark := parser.mark + skip(parser) + + // Scan the directive name. + var name []byte + if !yaml_parser_scan_directive_name(parser, start_mark, &name) { + return false + } + + // Is it a YAML directive? + if bytes.Equal(name, []byte("YAML")) { + // Scan the VERSION directive value. + var major, minor int8 + if !yaml_parser_scan_version_directive_value(parser, start_mark, &major, &minor) { + return false + } + end_mark := parser.mark + + // Create a VERSION-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_VERSION_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + major: major, + minor: minor, + } + + // Is it a TAG directive? + } else if bytes.Equal(name, []byte("TAG")) { + // Scan the TAG directive value. + var handle, prefix []byte + if !yaml_parser_scan_tag_directive_value(parser, start_mark, &handle, &prefix) { + return false + } + end_mark := parser.mark + + // Create a TAG-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_TAG_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + prefix: prefix, + } + + // Unknown directive. + } else { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unknown directive name") + return false + } + + // Eat the rest of the line including any comments. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + return true +} + +// Scan the directive name. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^ +// +func yaml_parser_scan_directive_name(parser *yaml_parser_t, start_mark yaml_mark_t, name *[]byte) bool { + // Consume the directive name. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + var s []byte + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the name is empty. + if len(s) == 0 { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "could not find expected directive name") + return false + } + + // Check for an blank character after the name. + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unexpected non-alphabetical character") + return false + } + *name = s + return true +} + +// Scan the value of VERSION-DIRECTIVE. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^^^ +func yaml_parser_scan_version_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, major, minor *int8) bool { + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the major version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, major) { + return false + } + + // Eat '.'. + if parser.buffer[parser.buffer_pos] != '.' { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected digit or '.' character") + } + + skip(parser) + + // Consume the minor version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, minor) { + return false + } + return true +} + +const max_number_length = 2 + +// Scan the version number of VERSION-DIRECTIVE. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^ +// %YAML 1.1 # a comment \n +// ^ +func yaml_parser_scan_version_directive_number(parser *yaml_parser_t, start_mark yaml_mark_t, number *int8) bool { + + // Repeat while the next character is digit. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var value, length int8 + for is_digit(parser.buffer, parser.buffer_pos) { + // Check if the number is too long. + length++ + if length > max_number_length { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "found extremely long version number") + } + value = value*10 + int8(as_digit(parser.buffer, parser.buffer_pos)) + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the number was present. + if length == 0 { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected version number") + } + *number = value + return true +} + +// Scan the value of a TAG-DIRECTIVE token. +// +// Scope: +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// +func yaml_parser_scan_tag_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, handle, prefix *[]byte) bool { + var handle_value, prefix_value []byte + + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a handle. + if !yaml_parser_scan_tag_handle(parser, true, start_mark, &handle_value) { + return false + } + + // Expect a whitespace. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blank(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace") + return false + } + + // Eat whitespaces. + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a prefix. + if !yaml_parser_scan_tag_uri(parser, true, nil, start_mark, &prefix_value) { + return false + } + + // Expect a whitespace or line break. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace or line break") + return false + } + + *handle = handle_value + *prefix = prefix_value + return true +} + +func yaml_parser_scan_anchor(parser *yaml_parser_t, token *yaml_token_t, typ yaml_token_type_t) bool { + var s []byte + + // Eat the indicator character. + start_mark := parser.mark + skip(parser) + + // Consume the value. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + end_mark := parser.mark + + /* + * Check if length of the anchor is greater than 0 and it is followed by + * a whitespace character or one of the indicators: + * + * '?', ':', ',', ']', '}', '%', '@', '`'. + */ + + if len(s) == 0 || + !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '}' || + parser.buffer[parser.buffer_pos] == '%' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '`') { + context := "while scanning an alias" + if typ == yaml_ANCHOR_TOKEN { + context = "while scanning an anchor" + } + yaml_parser_set_scanner_error(parser, context, start_mark, + "did not find expected alphabetic or numeric character") + return false + } + + // Create a token. + *token = yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + value: s, + } + + return true +} + +/* + * Scan a TAG token. + */ + +func yaml_parser_scan_tag(parser *yaml_parser_t, token *yaml_token_t) bool { + var handle, suffix []byte + + start_mark := parser.mark + + // Check if the tag is in the canonical form. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + if parser.buffer[parser.buffer_pos+1] == '<' { + // Keep the handle as '' + + // Eat '!<' + skip(parser) + skip(parser) + + // Consume the tag value. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + + // Check for '>' and eat it. + if parser.buffer[parser.buffer_pos] != '>' { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find the expected '>'") + return false + } + + skip(parser) + } else { + // The tag has either the '!suffix' or the '!handle!suffix' form. + + // First, try to scan a handle. + if !yaml_parser_scan_tag_handle(parser, false, start_mark, &handle) { + return false + } + + // Check if it is, indeed, handle. + if handle[0] == '!' && len(handle) > 1 && handle[len(handle)-1] == '!' { + // Scan the suffix now. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + } else { + // It wasn't a handle after all. Scan the rest of the tag. + if !yaml_parser_scan_tag_uri(parser, false, handle, start_mark, &suffix) { + return false + } + + // Set the handle to '!'. + handle = []byte{'!'} + + // A special case: the '!' tag. Set the handle to '' and the + // suffix to '!'. + if len(suffix) == 0 { + handle, suffix = suffix, handle + } + } + } + + // Check the character which ends the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find expected whitespace or line break") + return false + } + + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_TAG_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + suffix: suffix, + } + return true +} + +// Scan a tag handle. +func yaml_parser_scan_tag_handle(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, handle *[]byte) bool { + // Check the initial '!' character. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] != '!' { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + + var s []byte + + // Copy the '!' character. + s = read(parser, s) + + // Copy all subsequent alphabetical and numerical characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the trailing character is '!' and copy it. + if parser.buffer[parser.buffer_pos] == '!' { + s = read(parser, s) + } else { + // It's either the '!' tag or not really a tag handle. If it's a %TAG + // directive, it's an error. If it's a tag token, it must be a part of URI. + if directive && string(s) != "!" { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + } + + *handle = s + return true +} + +// Scan a tag. +func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte, start_mark yaml_mark_t, uri *[]byte) bool { + //size_t length = head ? strlen((char *)head) : 0 + var s []byte + hasTag := len(head) > 0 + + // Copy the head if needed. + // + // Note that we don't copy the leading '!' character. + if len(head) > 1 { + s = append(s, head[1:]...) + } + + // Scan the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // The set of characters that may appear in URI is as follows: + // + // '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&', + // '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']', + // '%'. + // [Go] Convert this into more reasonable logic. + for is_alpha(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == ';' || + parser.buffer[parser.buffer_pos] == '/' || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '=' || + parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '$' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '.' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '~' || + parser.buffer[parser.buffer_pos] == '*' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '(' || parser.buffer[parser.buffer_pos] == ')' || + parser.buffer[parser.buffer_pos] == '[' || parser.buffer[parser.buffer_pos] == ']' || + parser.buffer[parser.buffer_pos] == '%' { + // Check if it is a URI-escape sequence. + if parser.buffer[parser.buffer_pos] == '%' { + if !yaml_parser_scan_uri_escapes(parser, directive, start_mark, &s) { + return false + } + } else { + s = read(parser, s) + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + hasTag = true + } + + if !hasTag { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected tag URI") + return false + } + *uri = s + return true +} + +// Decode an URI-escape sequence corresponding to a single UTF-8 character. +func yaml_parser_scan_uri_escapes(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, s *[]byte) bool { + + // Decode the required number of characters. + w := 1024 + for w > 0 { + // Check for a URI-escaped octet. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + + if !(parser.buffer[parser.buffer_pos] == '%' && + is_hex(parser.buffer, parser.buffer_pos+1) && + is_hex(parser.buffer, parser.buffer_pos+2)) { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find URI escaped octet") + } + + // Get the octet. + octet := byte((as_hex(parser.buffer, parser.buffer_pos+1) << 4) + as_hex(parser.buffer, parser.buffer_pos+2)) + + // If it is the leading octet, determine the length of the UTF-8 sequence. + if w == 1024 { + w = width(octet) + if w == 0 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect leading UTF-8 octet") + } + } else { + // Check if the trailing octet is correct. + if octet&0xC0 != 0x80 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect trailing UTF-8 octet") + } + } + + // Copy the octet and move the pointers. + *s = append(*s, octet) + skip(parser) + skip(parser) + skip(parser) + w-- + } + return true +} + +// Scan a block scalar. +func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, literal bool) bool { + // Eat the indicator '|' or '>'. + start_mark := parser.mark + skip(parser) + + // Scan the additional block scalar indicators. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check for a chomping indicator. + var chomping, increment int + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + // Set the chomping method and eat the indicator. + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + + // Check for an indentation indicator. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if is_digit(parser.buffer, parser.buffer_pos) { + // Check that the indentation is greater than 0. + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + + // Get the indentation level and eat the indicator. + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + } + + } else if is_digit(parser.buffer, parser.buffer_pos) { + // Do the same as above, but in the opposite order. + + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + } + } + + // Eat whitespaces and comments to the end of the line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + end_mark := parser.mark + + // Set the indentation level if it was specified. + var indent int + if increment > 0 { + if parser.indent >= 0 { + indent = parser.indent + increment + } else { + indent = increment + } + } + + // Scan the leading line breaks and determine the indentation level if needed. + var s, leading_break, trailing_breaks []byte + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + + // Scan the block scalar content. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var leading_blank, trailing_blank bool + for parser.mark.column == indent && !is_z(parser.buffer, parser.buffer_pos) { + // We are at the beginning of a non-empty line. + + // Is it a trailing whitespace? + trailing_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Check if we need to fold the leading line break. + if !literal && !leading_blank && !trailing_blank && len(leading_break) > 0 && leading_break[0] == '\n' { + // Do we need to join the lines by space? + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } + } else { + s = append(s, leading_break...) + } + leading_break = leading_break[:0] + + // Append the remaining line breaks. + s = append(s, trailing_breaks...) + trailing_breaks = trailing_breaks[:0] + + // Is it a leading whitespace? + leading_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Consume the current line. + for !is_breakz(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + leading_break = read_line(parser, leading_break) + + // Eat the following indentation spaces and line breaks. + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + } + + // Chomp the tail. + if chomping != -1 { + s = append(s, leading_break...) + } + if chomping == 1 { + s = append(s, trailing_breaks...) + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_LITERAL_SCALAR_STYLE, + } + if !literal { + token.style = yaml_FOLDED_SCALAR_STYLE + } + return true +} + +// Scan indentation spaces and line breaks for a block scalar. Determine the +// indentation level if needed. +func yaml_parser_scan_block_scalar_breaks(parser *yaml_parser_t, indent *int, breaks *[]byte, start_mark yaml_mark_t, end_mark *yaml_mark_t) bool { + *end_mark = parser.mark + + // Eat the indentation spaces and line breaks. + max_indent := 0 + for { + // Eat the indentation spaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for (*indent == 0 || parser.mark.column < *indent) && is_space(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.mark.column > max_indent { + max_indent = parser.mark.column + } + + // Check for a tab character messing the indentation. + if (*indent == 0 || parser.mark.column < *indent) && is_tab(parser.buffer, parser.buffer_pos) { + return yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found a tab character where an indentation space is expected") + } + + // Have we found a non-empty line? + if !is_break(parser.buffer, parser.buffer_pos) { + break + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + // [Go] Should really be returning breaks instead. + *breaks = read_line(parser, *breaks) + *end_mark = parser.mark + } + + // Determine the indentation level if needed. + if *indent == 0 { + *indent = max_indent + if *indent < parser.indent+1 { + *indent = parser.indent + 1 + } + if *indent < 1 { + *indent = 1 + } + } + return true +} + +// Scan a quoted scalar. +func yaml_parser_scan_flow_scalar(parser *yaml_parser_t, token *yaml_token_t, single bool) bool { + // Eat the left quote. + start_mark := parser.mark + skip(parser) + + // Consume the content of the quoted scalar. + var s, leading_break, trailing_breaks, whitespaces []byte + for { + // Check that there are no document indicators at the beginning of the line. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected document indicator") + return false + } + + // Check for EOF. + if is_z(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected end of stream") + return false + } + + // Consume non-blank characters. + leading_blanks := false + for !is_blankz(parser.buffer, parser.buffer_pos) { + if single && parser.buffer[parser.buffer_pos] == '\'' && parser.buffer[parser.buffer_pos+1] == '\'' { + // Is is an escaped single quote. + s = append(s, '\'') + skip(parser) + skip(parser) + + } else if single && parser.buffer[parser.buffer_pos] == '\'' { + // It is a right single quote. + break + } else if !single && parser.buffer[parser.buffer_pos] == '"' { + // It is a right double quote. + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' && is_break(parser.buffer, parser.buffer_pos+1) { + // It is an escaped line break. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + skip(parser) + skip_line(parser) + leading_blanks = true + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' { + // It is an escape sequence. + code_length := 0 + + // Check the escape character. + switch parser.buffer[parser.buffer_pos+1] { + case '0': + s = append(s, 0) + case 'a': + s = append(s, '\x07') + case 'b': + s = append(s, '\x08') + case 't', '\t': + s = append(s, '\x09') + case 'n': + s = append(s, '\x0A') + case 'v': + s = append(s, '\x0B') + case 'f': + s = append(s, '\x0C') + case 'r': + s = append(s, '\x0D') + case 'e': + s = append(s, '\x1B') + case ' ': + s = append(s, '\x20') + case '"': + s = append(s, '"') + case '\'': + s = append(s, '\'') + case '\\': + s = append(s, '\\') + case 'N': // NEL (#x85) + s = append(s, '\xC2') + s = append(s, '\x85') + case '_': // #xA0 + s = append(s, '\xC2') + s = append(s, '\xA0') + case 'L': // LS (#x2028) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA8') + case 'P': // PS (#x2029) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA9') + case 'x': + code_length = 2 + case 'u': + code_length = 4 + case 'U': + code_length = 8 + default: + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found unknown escape character") + return false + } + + skip(parser) + skip(parser) + + // Consume an arbitrary escape code. + if code_length > 0 { + var value int + + // Scan the character value. + if parser.unread < code_length && !yaml_parser_update_buffer(parser, code_length) { + return false + } + for k := 0; k < code_length; k++ { + if !is_hex(parser.buffer, parser.buffer_pos+k) { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "did not find expected hexdecimal number") + return false + } + value = (value << 4) + as_hex(parser.buffer, parser.buffer_pos+k) + } + + // Check the value and write the character. + if (value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found invalid Unicode character escape code") + return false + } + if value <= 0x7F { + s = append(s, byte(value)) + } else if value <= 0x7FF { + s = append(s, byte(0xC0+(value>>6))) + s = append(s, byte(0x80+(value&0x3F))) + } else if value <= 0xFFFF { + s = append(s, byte(0xE0+(value>>12))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } else { + s = append(s, byte(0xF0+(value>>18))) + s = append(s, byte(0x80+((value>>12)&0x3F))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } + + // Advance the pointer. + for k := 0; k < code_length; k++ { + skip(parser) + } + } + } else { + // It is a non-escaped non-blank character. + s = read(parser, s) + } + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check if we are at the end of the scalar. + if single { + if parser.buffer[parser.buffer_pos] == '\'' { + break + } + } else { + if parser.buffer[parser.buffer_pos] == '"' { + break + } + } + + // Consume blank characters. + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Join the whitespaces or fold line breaks. + if leading_blanks { + // Do we need to fold line breaks? + if len(leading_break) > 0 && leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Eat the right quote. + skip(parser) + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_SINGLE_QUOTED_SCALAR_STYLE, + } + if !single { + token.style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + return true +} + +// Scan a plain scalar. +func yaml_parser_scan_plain_scalar(parser *yaml_parser_t, token *yaml_token_t) bool { + + var s, leading_break, trailing_breaks, whitespaces []byte + var leading_blanks bool + var indent = parser.indent + 1 + + start_mark := parser.mark + end_mark := parser.mark + + // Consume the content of the plain scalar. + for { + // Check for a document indicator. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + break + } + + // Check for a comment. + if parser.buffer[parser.buffer_pos] == '#' { + break + } + + // Consume non-blank characters. + for !is_blankz(parser.buffer, parser.buffer_pos) { + + // Check for indicators that may end a plain scalar. + if (parser.buffer[parser.buffer_pos] == ':' && is_blankz(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level > 0 && + (parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}')) { + break + } + + // Check if we need to join whitespaces and breaks. + if leading_blanks || len(whitespaces) > 0 { + if leading_blanks { + // Do we need to fold line breaks? + if leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + leading_blanks = false + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Copy the character. + s = read(parser, s) + + end_mark = parser.mark + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + // Is it the end? + if !(is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos)) { + break + } + + // Consume blank characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + + // Check for tab characters that abuse indentation. + if leading_blanks && parser.mark.column < indent && is_tab(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", + start_mark, "found a tab character that violates indentation") + return false + } + + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check indentation level. + if parser.flow_level == 0 && parser.mark.column < indent { + break + } + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_PLAIN_SCALAR_STYLE, + } + + // Note that we change the 'simple_key_allowed' flag. + if leading_blanks { + parser.simple_key_allowed = true + } + return true +} diff --git a/vendor/gopkg.in/yaml.v2/sorter.go b/vendor/gopkg.in/yaml.v2/sorter.go new file mode 100644 index 00000000..4c45e660 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/sorter.go @@ -0,0 +1,113 @@ +package yaml + +import ( + "reflect" + "unicode" +) + +type keyList []reflect.Value + +func (l keyList) Len() int { return len(l) } +func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l keyList) Less(i, j int) bool { + a := l[i] + b := l[j] + ak := a.Kind() + bk := b.Kind() + for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() { + a = a.Elem() + ak = a.Kind() + } + for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() { + b = b.Elem() + bk = b.Kind() + } + af, aok := keyFloat(a) + bf, bok := keyFloat(b) + if aok && bok { + if af != bf { + return af < bf + } + if ak != bk { + return ak < bk + } + return numLess(a, b) + } + if ak != reflect.String || bk != reflect.String { + return ak < bk + } + ar, br := []rune(a.String()), []rune(b.String()) + for i := 0; i < len(ar) && i < len(br); i++ { + if ar[i] == br[i] { + continue + } + al := unicode.IsLetter(ar[i]) + bl := unicode.IsLetter(br[i]) + if al && bl { + return ar[i] < br[i] + } + if al || bl { + return bl + } + var ai, bi int + var an, bn int64 + if ar[i] == '0' || br[i] == '0' { + for j := i-1; j >= 0 && unicode.IsDigit(ar[j]); j-- { + if ar[j] != '0' { + an = 1 + bn = 1 + break + } + } + } + for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ { + an = an*10 + int64(ar[ai]-'0') + } + for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ { + bn = bn*10 + int64(br[bi]-'0') + } + if an != bn { + return an < bn + } + if ai != bi { + return ai < bi + } + return ar[i] < br[i] + } + return len(ar) < len(br) +} + +// keyFloat returns a float value for v if it is a number/bool +// and whether it is a number/bool or not. +func keyFloat(v reflect.Value) (f float64, ok bool) { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(v.Int()), true + case reflect.Float32, reflect.Float64: + return v.Float(), true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return float64(v.Uint()), true + case reflect.Bool: + if v.Bool() { + return 1, true + } + return 0, true + } + return 0, false +} + +// numLess returns whether a < b. +// a and b must necessarily have the same kind. +func numLess(a, b reflect.Value) bool { + switch a.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return a.Int() < b.Int() + case reflect.Float32, reflect.Float64: + return a.Float() < b.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return a.Uint() < b.Uint() + case reflect.Bool: + return !a.Bool() && b.Bool() + } + panic("not a number") +} diff --git a/vendor/gopkg.in/yaml.v2/suite_test.go b/vendor/gopkg.in/yaml.v2/suite_test.go new file mode 100644 index 00000000..c5cf1ed4 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/suite_test.go @@ -0,0 +1,12 @@ +package yaml_test + +import ( + . "gopkg.in/check.v1" + "testing" +) + +func Test(t *testing.T) { TestingT(t) } + +type S struct{} + +var _ = Suite(&S{}) diff --git a/vendor/gopkg.in/yaml.v2/writerc.go b/vendor/gopkg.in/yaml.v2/writerc.go new file mode 100644 index 00000000..a2dde608 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/writerc.go @@ -0,0 +1,26 @@ +package yaml + +// Set the writer error and return false. +func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_WRITER_ERROR + emitter.problem = problem + return false +} + +// Flush the output buffer. +func yaml_emitter_flush(emitter *yaml_emitter_t) bool { + if emitter.write_handler == nil { + panic("write handler not set") + } + + // Check if the buffer is empty. + if emitter.buffer_pos == 0 { + return true + } + + if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil { + return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) + } + emitter.buffer_pos = 0 + return true +} diff --git a/vendor/gopkg.in/yaml.v2/yaml.go b/vendor/gopkg.in/yaml.v2/yaml.go new file mode 100644 index 00000000..de85aa4c --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yaml.go @@ -0,0 +1,466 @@ +// Package yaml implements YAML support for the Go language. +// +// Source code and other details for the project are available at GitHub: +// +// https://github.com/go-yaml/yaml +// +package yaml + +import ( + "errors" + "fmt" + "io" + "reflect" + "strings" + "sync" +) + +// MapSlice encodes and decodes as a YAML map. +// The order of keys is preserved when encoding and decoding. +type MapSlice []MapItem + +// MapItem is an item in a MapSlice. +type MapItem struct { + Key, Value interface{} +} + +// The Unmarshaler interface may be implemented by types to customize their +// behavior when being unmarshaled from a YAML document. The UnmarshalYAML +// method receives a function that may be called to unmarshal the original +// YAML value into a field or variable. It is safe to call the unmarshal +// function parameter more than once if necessary. +type Unmarshaler interface { + UnmarshalYAML(unmarshal func(interface{}) error) error +} + +// The Marshaler interface may be implemented by types to customize their +// behavior when being marshaled into a YAML document. The returned value +// is marshaled in place of the original value implementing Marshaler. +// +// If an error is returned by MarshalYAML, the marshaling procedure stops +// and returns with the provided error. +type Marshaler interface { + MarshalYAML() (interface{}, error) +} + +// Unmarshal decodes the first document found within the in byte slice +// and assigns decoded values into the out value. +// +// Maps and pointers (to a struct, string, int, etc) are accepted as out +// values. If an internal pointer within a struct is not initialized, +// the yaml package will initialize it if necessary for unmarshalling +// the provided data. The out parameter must not be nil. +// +// The type of the decoded values should be compatible with the respective +// values in out. If one or more values cannot be decoded due to a type +// mismatches, decoding continues partially until the end of the YAML +// content, and a *yaml.TypeError is returned with details for all +// missed values. +// +// Struct fields are only unmarshalled if they are exported (have an +// upper case first letter), and are unmarshalled using the field name +// lowercased as the default key. Custom keys may be defined via the +// "yaml" name in the field tag: the content preceding the first comma +// is used as the key, and the following comma-separated options are +// used to tweak the marshalling process (see Marshal). +// Conflicting names result in a runtime error. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// var t T +// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t) +// +// See the documentation of Marshal for the format of tags and a list of +// supported tag options. +// +func Unmarshal(in []byte, out interface{}) (err error) { + return unmarshal(in, out, false) +} + +// UnmarshalStrict is like Unmarshal except that any fields that are found +// in the data that do not have corresponding struct members, or mapping +// keys that are duplicates, will result in +// an error. +func UnmarshalStrict(in []byte, out interface{}) (err error) { + return unmarshal(in, out, true) +} + +// A Decorder reads and decodes YAML values from an input stream. +type Decoder struct { + strict bool + parser *parser +} + +// NewDecoder returns a new decoder that reads from r. +// +// The decoder introduces its own buffering and may read +// data from r beyond the YAML values requested. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{ + parser: newParserFromReader(r), + } +} + +// SetStrict sets whether strict decoding behaviour is enabled when +// decoding items in the data (see UnmarshalStrict). By default, decoding is not strict. +func (dec *Decoder) SetStrict(strict bool) { + dec.strict = strict +} + +// Decode reads the next YAML-encoded value from its input +// and stores it in the value pointed to by v. +// +// See the documentation for Unmarshal for details about the +// conversion of YAML into a Go value. +func (dec *Decoder) Decode(v interface{}) (err error) { + d := newDecoder(dec.strict) + defer handleErr(&err) + node := dec.parser.parse() + if node == nil { + return io.EOF + } + out := reflect.ValueOf(v) + if out.Kind() == reflect.Ptr && !out.IsNil() { + out = out.Elem() + } + d.unmarshal(node, out) + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +func unmarshal(in []byte, out interface{}, strict bool) (err error) { + defer handleErr(&err) + d := newDecoder(strict) + p := newParser(in) + defer p.destroy() + node := p.parse() + if node != nil { + v := reflect.ValueOf(out) + if v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + d.unmarshal(node, v) + } + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +// Marshal serializes the value provided into a YAML document. The structure +// of the generated document will reflect the structure of the value itself. +// Maps and pointers (to struct, string, int, etc) are accepted as the in value. +// +// Struct fields are only marshalled if they are exported (have an upper case +// first letter), and are marshalled using the field name lowercased as the +// default key. Custom keys may be defined via the "yaml" name in the field +// tag: the content preceding the first comma is used as the key, and the +// following comma-separated options are used to tweak the marshalling process. +// Conflicting names result in a runtime error. +// +// The field tag format accepted is: +// +// `(...) yaml:"[][,[,]]" (...)` +// +// The following flags are currently supported: +// +// omitempty Only include the field if it's not set to the zero +// value for the type or to empty slices or maps. +// Zero valued structs will be omitted if all their public +// fields are zero, unless they implement an IsZero +// method (see the IsZeroer interface type), in which +// case the field will be included if that method returns true. +// +// flow Marshal using a flow style (useful for structs, +// sequences and maps). +// +// inline Inline the field, which must be a struct or a map, +// causing all of its fields or keys to be processed as if +// they were part of the outer struct. For maps, keys must +// not conflict with the yaml keys of other struct fields. +// +// In addition, if the key is "-", the field is ignored. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n" +// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n" +// +func Marshal(in interface{}) (out []byte, err error) { + defer handleErr(&err) + e := newEncoder() + defer e.destroy() + e.marshalDoc("", reflect.ValueOf(in)) + e.finish() + out = e.out + return +} + +// An Encoder writes YAML values to an output stream. +type Encoder struct { + encoder *encoder +} + +// NewEncoder returns a new encoder that writes to w. +// The Encoder should be closed after use to flush all data +// to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + encoder: newEncoderWithWriter(w), + } +} + +// Encode writes the YAML encoding of v to the stream. +// If multiple items are encoded to the stream, the +// second and subsequent document will be preceded +// with a "---" document separator, but the first will not. +// +// See the documentation for Marshal for details about the conversion of Go +// values to YAML. +func (e *Encoder) Encode(v interface{}) (err error) { + defer handleErr(&err) + e.encoder.marshalDoc("", reflect.ValueOf(v)) + return nil +} + +// Close closes the encoder by writing any remaining data. +// It does not write a stream terminating string "...". +func (e *Encoder) Close() (err error) { + defer handleErr(&err) + e.encoder.finish() + return nil +} + +func handleErr(err *error) { + if v := recover(); v != nil { + if e, ok := v.(yamlError); ok { + *err = e.err + } else { + panic(v) + } + } +} + +type yamlError struct { + err error +} + +func fail(err error) { + panic(yamlError{err}) +} + +func failf(format string, args ...interface{}) { + panic(yamlError{fmt.Errorf("yaml: "+format, args...)}) +} + +// A TypeError is returned by Unmarshal when one or more fields in +// the YAML document cannot be properly decoded into the requested +// types. When this error is returned, the value is still +// unmarshaled partially. +type TypeError struct { + Errors []string +} + +func (e *TypeError) Error() string { + return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n ")) +} + +// -------------------------------------------------------------------------- +// Maintain a mapping of keys to structure field indexes + +// The code in this section was copied from mgo/bson. + +// structInfo holds details for the serialization of fields of +// a given struct. +type structInfo struct { + FieldsMap map[string]fieldInfo + FieldsList []fieldInfo + + // InlineMap is the number of the field in the struct that + // contains an ,inline map, or -1 if there's none. + InlineMap int +} + +type fieldInfo struct { + Key string + Num int + OmitEmpty bool + Flow bool + // Id holds the unique field identifier, so we can cheaply + // check for field duplicates without maintaining an extra map. + Id int + + // Inline holds the field index if the field is part of an inlined struct. + Inline []int +} + +var structMap = make(map[reflect.Type]*structInfo) +var fieldMapMutex sync.RWMutex + +func getStructInfo(st reflect.Type) (*structInfo, error) { + fieldMapMutex.RLock() + sinfo, found := structMap[st] + fieldMapMutex.RUnlock() + if found { + return sinfo, nil + } + + n := st.NumField() + fieldsMap := make(map[string]fieldInfo) + fieldsList := make([]fieldInfo, 0, n) + inlineMap := -1 + for i := 0; i != n; i++ { + field := st.Field(i) + if field.PkgPath != "" && !field.Anonymous { + continue // Private field + } + + info := fieldInfo{Num: i} + + tag := field.Tag.Get("yaml") + if tag == "" && strings.Index(string(field.Tag), ":") < 0 { + tag = string(field.Tag) + } + if tag == "-" { + continue + } + + inline := false + fields := strings.Split(tag, ",") + if len(fields) > 1 { + for _, flag := range fields[1:] { + switch flag { + case "omitempty": + info.OmitEmpty = true + case "flow": + info.Flow = true + case "inline": + inline = true + default: + return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)) + } + } + tag = fields[0] + } + + if inline { + switch field.Type.Kind() { + case reflect.Map: + if inlineMap >= 0 { + return nil, errors.New("Multiple ,inline maps in struct " + st.String()) + } + if field.Type.Key() != reflect.TypeOf("") { + return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String()) + } + inlineMap = info.Num + case reflect.Struct: + sinfo, err := getStructInfo(field.Type) + if err != nil { + return nil, err + } + for _, finfo := range sinfo.FieldsList { + if _, found := fieldsMap[finfo.Key]; found { + msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + if finfo.Inline == nil { + finfo.Inline = []int{i, finfo.Num} + } else { + finfo.Inline = append([]int{i}, finfo.Inline...) + } + finfo.Id = len(fieldsList) + fieldsMap[finfo.Key] = finfo + fieldsList = append(fieldsList, finfo) + } + default: + //return nil, errors.New("Option ,inline needs a struct value or map field") + return nil, errors.New("Option ,inline needs a struct value field") + } + continue + } + + if tag != "" { + info.Key = tag + } else { + info.Key = strings.ToLower(field.Name) + } + + if _, found = fieldsMap[info.Key]; found { + msg := "Duplicated key '" + info.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + + info.Id = len(fieldsList) + fieldsList = append(fieldsList, info) + fieldsMap[info.Key] = info + } + + sinfo = &structInfo{ + FieldsMap: fieldsMap, + FieldsList: fieldsList, + InlineMap: inlineMap, + } + + fieldMapMutex.Lock() + structMap[st] = sinfo + fieldMapMutex.Unlock() + return sinfo, nil +} + +// IsZeroer is used to check whether an object is zero to +// determine whether it should be omitted when marshaling +// with the omitempty flag. One notable implementation +// is time.Time. +type IsZeroer interface { + IsZero() bool +} + +func isZero(v reflect.Value) bool { + kind := v.Kind() + if z, ok := v.Interface().(IsZeroer); ok { + if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() { + return true + } + return z.IsZero() + } + switch kind { + case reflect.String: + return len(v.String()) == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Slice: + return v.Len() == 0 + case reflect.Map: + return v.Len() == 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Struct: + vt := v.Type() + for i := v.NumField() - 1; i >= 0; i-- { + if vt.Field(i).PkgPath != "" { + continue // Private field + } + if !isZero(v.Field(i)) { + return false + } + } + return true + } + return false +} diff --git a/vendor/gopkg.in/yaml.v2/yamlh.go b/vendor/gopkg.in/yaml.v2/yamlh.go new file mode 100644 index 00000000..e25cee56 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yamlh.go @@ -0,0 +1,738 @@ +package yaml + +import ( + "fmt" + "io" +) + +// The version directive data. +type yaml_version_directive_t struct { + major int8 // The major version number. + minor int8 // The minor version number. +} + +// The tag directive data. +type yaml_tag_directive_t struct { + handle []byte // The tag handle. + prefix []byte // The tag prefix. +} + +type yaml_encoding_t int + +// The stream encoding. +const ( + // Let the parser choose the encoding. + yaml_ANY_ENCODING yaml_encoding_t = iota + + yaml_UTF8_ENCODING // The default UTF-8 encoding. + yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM. + yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM. +) + +type yaml_break_t int + +// Line break types. +const ( + // Let the parser choose the break type. + yaml_ANY_BREAK yaml_break_t = iota + + yaml_CR_BREAK // Use CR for line breaks (Mac style). + yaml_LN_BREAK // Use LN for line breaks (Unix style). + yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style). +) + +type yaml_error_type_t int + +// Many bad things could happen with the parser and emitter. +const ( + // No error is produced. + yaml_NO_ERROR yaml_error_type_t = iota + + yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory. + yaml_READER_ERROR // Cannot read or decode the input stream. + yaml_SCANNER_ERROR // Cannot scan the input stream. + yaml_PARSER_ERROR // Cannot parse the input stream. + yaml_COMPOSER_ERROR // Cannot compose a YAML document. + yaml_WRITER_ERROR // Cannot write to the output stream. + yaml_EMITTER_ERROR // Cannot emit a YAML stream. +) + +// The pointer position. +type yaml_mark_t struct { + index int // The position index. + line int // The position line. + column int // The position column. +} + +// Node Styles + +type yaml_style_t int8 + +type yaml_scalar_style_t yaml_style_t + +// Scalar styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = iota + + yaml_PLAIN_SCALAR_STYLE // The plain scalar style. + yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style. + yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style. + yaml_LITERAL_SCALAR_STYLE // The literal scalar style. + yaml_FOLDED_SCALAR_STYLE // The folded scalar style. +) + +type yaml_sequence_style_t yaml_style_t + +// Sequence styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota + + yaml_BLOCK_SEQUENCE_STYLE // The block sequence style. + yaml_FLOW_SEQUENCE_STYLE // The flow sequence style. +) + +type yaml_mapping_style_t yaml_style_t + +// Mapping styles. +const ( + // Let the emitter choose the style. + yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota + + yaml_BLOCK_MAPPING_STYLE // The block mapping style. + yaml_FLOW_MAPPING_STYLE // The flow mapping style. +) + +// Tokens + +type yaml_token_type_t int + +// Token types. +const ( + // An empty token. + yaml_NO_TOKEN yaml_token_type_t = iota + + yaml_STREAM_START_TOKEN // A STREAM-START token. + yaml_STREAM_END_TOKEN // A STREAM-END token. + + yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token. + yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token. + yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token. + yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token. + + yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token. + yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token. + yaml_BLOCK_END_TOKEN // A BLOCK-END token. + + yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token. + yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token. + yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token. + yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token. + + yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token. + yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token. + yaml_KEY_TOKEN // A KEY token. + yaml_VALUE_TOKEN // A VALUE token. + + yaml_ALIAS_TOKEN // An ALIAS token. + yaml_ANCHOR_TOKEN // An ANCHOR token. + yaml_TAG_TOKEN // A TAG token. + yaml_SCALAR_TOKEN // A SCALAR token. +) + +func (tt yaml_token_type_t) String() string { + switch tt { + case yaml_NO_TOKEN: + return "yaml_NO_TOKEN" + case yaml_STREAM_START_TOKEN: + return "yaml_STREAM_START_TOKEN" + case yaml_STREAM_END_TOKEN: + return "yaml_STREAM_END_TOKEN" + case yaml_VERSION_DIRECTIVE_TOKEN: + return "yaml_VERSION_DIRECTIVE_TOKEN" + case yaml_TAG_DIRECTIVE_TOKEN: + return "yaml_TAG_DIRECTIVE_TOKEN" + case yaml_DOCUMENT_START_TOKEN: + return "yaml_DOCUMENT_START_TOKEN" + case yaml_DOCUMENT_END_TOKEN: + return "yaml_DOCUMENT_END_TOKEN" + case yaml_BLOCK_SEQUENCE_START_TOKEN: + return "yaml_BLOCK_SEQUENCE_START_TOKEN" + case yaml_BLOCK_MAPPING_START_TOKEN: + return "yaml_BLOCK_MAPPING_START_TOKEN" + case yaml_BLOCK_END_TOKEN: + return "yaml_BLOCK_END_TOKEN" + case yaml_FLOW_SEQUENCE_START_TOKEN: + return "yaml_FLOW_SEQUENCE_START_TOKEN" + case yaml_FLOW_SEQUENCE_END_TOKEN: + return "yaml_FLOW_SEQUENCE_END_TOKEN" + case yaml_FLOW_MAPPING_START_TOKEN: + return "yaml_FLOW_MAPPING_START_TOKEN" + case yaml_FLOW_MAPPING_END_TOKEN: + return "yaml_FLOW_MAPPING_END_TOKEN" + case yaml_BLOCK_ENTRY_TOKEN: + return "yaml_BLOCK_ENTRY_TOKEN" + case yaml_FLOW_ENTRY_TOKEN: + return "yaml_FLOW_ENTRY_TOKEN" + case yaml_KEY_TOKEN: + return "yaml_KEY_TOKEN" + case yaml_VALUE_TOKEN: + return "yaml_VALUE_TOKEN" + case yaml_ALIAS_TOKEN: + return "yaml_ALIAS_TOKEN" + case yaml_ANCHOR_TOKEN: + return "yaml_ANCHOR_TOKEN" + case yaml_TAG_TOKEN: + return "yaml_TAG_TOKEN" + case yaml_SCALAR_TOKEN: + return "yaml_SCALAR_TOKEN" + } + return "" +} + +// The token structure. +type yaml_token_t struct { + // The token type. + typ yaml_token_type_t + + // The start/end of the token. + start_mark, end_mark yaml_mark_t + + // The stream encoding (for yaml_STREAM_START_TOKEN). + encoding yaml_encoding_t + + // The alias/anchor/scalar value or tag/tag directive handle + // (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN). + value []byte + + // The tag suffix (for yaml_TAG_TOKEN). + suffix []byte + + // The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN). + prefix []byte + + // The scalar style (for yaml_SCALAR_TOKEN). + style yaml_scalar_style_t + + // The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN). + major, minor int8 +} + +// Events + +type yaml_event_type_t int8 + +// Event types. +const ( + // An empty event. + yaml_NO_EVENT yaml_event_type_t = iota + + yaml_STREAM_START_EVENT // A STREAM-START event. + yaml_STREAM_END_EVENT // A STREAM-END event. + yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event. + yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event. + yaml_ALIAS_EVENT // An ALIAS event. + yaml_SCALAR_EVENT // A SCALAR event. + yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event. + yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event. + yaml_MAPPING_START_EVENT // A MAPPING-START event. + yaml_MAPPING_END_EVENT // A MAPPING-END event. +) + +var eventStrings = []string{ + yaml_NO_EVENT: "none", + yaml_STREAM_START_EVENT: "stream start", + yaml_STREAM_END_EVENT: "stream end", + yaml_DOCUMENT_START_EVENT: "document start", + yaml_DOCUMENT_END_EVENT: "document end", + yaml_ALIAS_EVENT: "alias", + yaml_SCALAR_EVENT: "scalar", + yaml_SEQUENCE_START_EVENT: "sequence start", + yaml_SEQUENCE_END_EVENT: "sequence end", + yaml_MAPPING_START_EVENT: "mapping start", + yaml_MAPPING_END_EVENT: "mapping end", +} + +func (e yaml_event_type_t) String() string { + if e < 0 || int(e) >= len(eventStrings) { + return fmt.Sprintf("unknown event %d", e) + } + return eventStrings[e] +} + +// The event structure. +type yaml_event_t struct { + + // The event type. + typ yaml_event_type_t + + // The start and end of the event. + start_mark, end_mark yaml_mark_t + + // The document encoding (for yaml_STREAM_START_EVENT). + encoding yaml_encoding_t + + // The version directive (for yaml_DOCUMENT_START_EVENT). + version_directive *yaml_version_directive_t + + // The list of tag directives (for yaml_DOCUMENT_START_EVENT). + tag_directives []yaml_tag_directive_t + + // The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT). + anchor []byte + + // The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + tag []byte + + // The scalar value (for yaml_SCALAR_EVENT). + value []byte + + // Is the document start/end indicator implicit, or the tag optional? + // (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT). + implicit bool + + // Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT). + quoted_implicit bool + + // The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + style yaml_style_t +} + +func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) } +func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) } +func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) } + +// Nodes + +const ( + yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null. + yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false. + yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values. + yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values. + yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values. + yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values. + + yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences. + yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping. + + // Not in original libyaml. + yaml_BINARY_TAG = "tag:yaml.org,2002:binary" + yaml_MERGE_TAG = "tag:yaml.org,2002:merge" + + yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str. + yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq. + yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map. +) + +type yaml_node_type_t int + +// Node types. +const ( + // An empty node. + yaml_NO_NODE yaml_node_type_t = iota + + yaml_SCALAR_NODE // A scalar node. + yaml_SEQUENCE_NODE // A sequence node. + yaml_MAPPING_NODE // A mapping node. +) + +// An element of a sequence node. +type yaml_node_item_t int + +// An element of a mapping node. +type yaml_node_pair_t struct { + key int // The key of the element. + value int // The value of the element. +} + +// The node structure. +type yaml_node_t struct { + typ yaml_node_type_t // The node type. + tag []byte // The node tag. + + // The node data. + + // The scalar parameters (for yaml_SCALAR_NODE). + scalar struct { + value []byte // The scalar value. + length int // The length of the scalar value. + style yaml_scalar_style_t // The scalar style. + } + + // The sequence parameters (for YAML_SEQUENCE_NODE). + sequence struct { + items_data []yaml_node_item_t // The stack of sequence items. + style yaml_sequence_style_t // The sequence style. + } + + // The mapping parameters (for yaml_MAPPING_NODE). + mapping struct { + pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value). + pairs_start *yaml_node_pair_t // The beginning of the stack. + pairs_end *yaml_node_pair_t // The end of the stack. + pairs_top *yaml_node_pair_t // The top of the stack. + style yaml_mapping_style_t // The mapping style. + } + + start_mark yaml_mark_t // The beginning of the node. + end_mark yaml_mark_t // The end of the node. + +} + +// The document structure. +type yaml_document_t struct { + + // The document nodes. + nodes []yaml_node_t + + // The version directive. + version_directive *yaml_version_directive_t + + // The list of tag directives. + tag_directives_data []yaml_tag_directive_t + tag_directives_start int // The beginning of the tag directives list. + tag_directives_end int // The end of the tag directives list. + + start_implicit int // Is the document start indicator implicit? + end_implicit int // Is the document end indicator implicit? + + // The start/end of the document. + start_mark, end_mark yaml_mark_t +} + +// The prototype of a read handler. +// +// The read handler is called when the parser needs to read more bytes from the +// source. The handler should write not more than size bytes to the buffer. +// The number of written bytes should be set to the size_read variable. +// +// [in,out] data A pointer to an application data specified by +// yaml_parser_set_input(). +// [out] buffer The buffer to write the data from the source. +// [in] size The size of the buffer. +// [out] size_read The actual number of bytes read from the source. +// +// On success, the handler should return 1. If the handler failed, +// the returned value should be 0. On EOF, the handler should set the +// size_read to 0 and return 1. +type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error) + +// This structure holds information about a potential simple key. +type yaml_simple_key_t struct { + possible bool // Is a simple key possible? + required bool // Is a simple key required? + token_number int // The number of the token. + mark yaml_mark_t // The position mark. +} + +// The states of the parser. +type yaml_parser_state_t int + +const ( + yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota + + yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document. + yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START. + yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_PARSE_BLOCK_NODE_STATE // Expect a block node. + yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence. + yaml_PARSE_FLOW_NODE_STATE // Expect a flow node. + yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence. + yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence. + yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence. + yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key. + yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value. + yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry. + yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping. + yaml_PARSE_END_STATE // Expect nothing. +) + +func (ps yaml_parser_state_t) String() string { + switch ps { + case yaml_PARSE_STREAM_START_STATE: + return "yaml_PARSE_STREAM_START_STATE" + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_START_STATE: + return "yaml_PARSE_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return "yaml_PARSE_DOCUMENT_CONTENT_STATE" + case yaml_PARSE_DOCUMENT_END_STATE: + return "yaml_PARSE_DOCUMENT_END_STATE" + case yaml_PARSE_BLOCK_NODE_STATE: + return "yaml_PARSE_BLOCK_NODE_STATE" + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE" + case yaml_PARSE_FLOW_NODE_STATE: + return "yaml_PARSE_FLOW_NODE_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE" + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE" + case yaml_PARSE_END_STATE: + return "yaml_PARSE_END_STATE" + } + return "" +} + +// This structure holds aliases data. +type yaml_alias_data_t struct { + anchor []byte // The anchor. + index int // The node id. + mark yaml_mark_t // The anchor mark. +} + +// The parser structure. +// +// All members are internal. Manage the structure using the +// yaml_parser_ family of functions. +type yaml_parser_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + + problem string // Error description. + + // The byte about which the problem occurred. + problem_offset int + problem_value int + problem_mark yaml_mark_t + + // The error context. + context string + context_mark yaml_mark_t + + // Reader stuff + + read_handler yaml_read_handler_t // Read handler. + + input_reader io.Reader // File input data. + input []byte // String input data. + input_pos int + + eof bool // EOF flag + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + unread int // The number of unread characters in the buffer. + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The input encoding. + + offset int // The offset of the current position (in bytes). + mark yaml_mark_t // The mark of the current position. + + // Scanner stuff + + stream_start_produced bool // Have we started to scan the input stream? + stream_end_produced bool // Have we reached the end of the input stream? + + flow_level int // The number of unclosed '[' and '{' indicators. + + tokens []yaml_token_t // The tokens queue. + tokens_head int // The head of the tokens queue. + tokens_parsed int // The number of tokens fetched from the queue. + token_available bool // Does the tokens queue contain a token ready for dequeueing. + + indent int // The current indentation level. + indents []int // The indentation levels stack. + + simple_key_allowed bool // May a simple key occur at the current position? + simple_keys []yaml_simple_key_t // The stack of simple keys. + + // Parser stuff + + state yaml_parser_state_t // The current parser state. + states []yaml_parser_state_t // The parser states stack. + marks []yaml_mark_t // The stack of marks. + tag_directives []yaml_tag_directive_t // The list of TAG directives. + + // Dumper stuff + + aliases []yaml_alias_data_t // The alias data. + + document *yaml_document_t // The currently parsed document. +} + +// Emitter Definitions + +// The prototype of a write handler. +// +// The write handler is called when the emitter needs to flush the accumulated +// characters to the output. The handler should write @a size bytes of the +// @a buffer to the output. +// +// @param[in,out] data A pointer to an application data specified by +// yaml_emitter_set_output(). +// @param[in] buffer The buffer with bytes to be written. +// @param[in] size The size of the buffer. +// +// @returns On success, the handler should return @c 1. If the handler failed, +// the returned value should be @c 0. +// +type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error + +type yaml_emitter_state_t int + +// The emitter states. +const ( + // Expect STREAM-START. + yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota + + yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence. + yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence. + yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence. + yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence. + yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping. + yaml_EMIT_END_STATE // Expect nothing. +) + +// The emitter structure. +// +// All members are internal. Manage the structure using the @c yaml_emitter_ +// family of functions. +type yaml_emitter_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + problem string // Error description. + + // Writer stuff + + write_handler yaml_write_handler_t // Write handler. + + output_buffer *[]byte // String output data. + output_writer io.Writer // File output data. + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The stream encoding. + + // Emitter stuff + + canonical bool // If the output is in the canonical style? + best_indent int // The number of indentation spaces. + best_width int // The preferred width of the output lines. + unicode bool // Allow unescaped non-ASCII characters? + line_break yaml_break_t // The preferred line break. + + state yaml_emitter_state_t // The current emitter state. + states []yaml_emitter_state_t // The stack of states. + + events []yaml_event_t // The event queue. + events_head int // The head of the event queue. + + indents []int // The stack of indentation levels. + + tag_directives []yaml_tag_directive_t // The list of tag directives. + + indent int // The current indentation level. + + flow_level int // The current flow level. + + root_context bool // Is it the document root context? + sequence_context bool // Is it a sequence context? + mapping_context bool // Is it a mapping context? + simple_key_context bool // Is it a simple mapping key context? + + line int // The current line. + column int // The current column. + whitespace bool // If the last character was a whitespace? + indention bool // If the last character was an indentation character (' ', '-', '?', ':')? + open_ended bool // If an explicit document end is required? + + // Anchor analysis. + anchor_data struct { + anchor []byte // The anchor value. + alias bool // Is it an alias? + } + + // Tag analysis. + tag_data struct { + handle []byte // The tag handle. + suffix []byte // The tag suffix. + } + + // Scalar analysis. + scalar_data struct { + value []byte // The scalar value. + multiline bool // Does the scalar contain line breaks? + flow_plain_allowed bool // Can the scalar be expessed in the flow plain style? + block_plain_allowed bool // Can the scalar be expressed in the block plain style? + single_quoted_allowed bool // Can the scalar be expressed in the single quoted style? + block_allowed bool // Can the scalar be expressed in the literal or folded styles? + style yaml_scalar_style_t // The output style. + } + + // Dumper stuff + + opened bool // If the stream was already opened? + closed bool // If the stream was already closed? + + // The information associated with the document nodes. + anchors *struct { + references int // The number of references. + anchor int // The anchor id. + serialized bool // If the node has been emitted? + } + + last_anchor_id int // The last assigned anchor id. + + document *yaml_document_t // The currently emitted document. +} diff --git a/vendor/gopkg.in/yaml.v2/yamlprivateh.go b/vendor/gopkg.in/yaml.v2/yamlprivateh.go new file mode 100644 index 00000000..8110ce3c --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yamlprivateh.go @@ -0,0 +1,173 @@ +package yaml + +const ( + // The size of the input raw buffer. + input_raw_buffer_size = 512 + + // The size of the input buffer. + // It should be possible to decode the whole raw buffer. + input_buffer_size = input_raw_buffer_size * 3 + + // The size of the output buffer. + output_buffer_size = 128 + + // The size of the output raw buffer. + // It should be possible to encode the whole output buffer. + output_raw_buffer_size = (output_buffer_size*2 + 2) + + // The size of other stacks and queues. + initial_stack_size = 16 + initial_queue_size = 16 + initial_string_size = 16 +) + +// Check if the character at the specified position is an alphabetical +// character, a digit, '_', or '-'. +func is_alpha(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-' +} + +// Check if the character at the specified position is a digit. +func is_digit(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' +} + +// Get the value of a digit. +func as_digit(b []byte, i int) int { + return int(b[i]) - '0' +} + +// Check if the character at the specified position is a hex-digit. +func is_hex(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f' +} + +// Get the value of a hex-digit. +func as_hex(b []byte, i int) int { + bi := b[i] + if bi >= 'A' && bi <= 'F' { + return int(bi) - 'A' + 10 + } + if bi >= 'a' && bi <= 'f' { + return int(bi) - 'a' + 10 + } + return int(bi) - '0' +} + +// Check if the character is ASCII. +func is_ascii(b []byte, i int) bool { + return b[i] <= 0x7F +} + +// Check if the character at the start of the buffer can be printed unescaped. +func is_printable(b []byte, i int) bool { + return ((b[i] == 0x0A) || // . == #x0A + (b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E + (b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF + (b[i] > 0xC2 && b[i] < 0xED) || + (b[i] == 0xED && b[i+1] < 0xA0) || + (b[i] == 0xEE) || + (b[i] == 0xEF && // #xE000 <= . <= #xFFFD + !(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF + !(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF)))) +} + +// Check if the character at the specified position is NUL. +func is_z(b []byte, i int) bool { + return b[i] == 0x00 +} + +// Check if the beginning of the buffer is a BOM. +func is_bom(b []byte, i int) bool { + return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF +} + +// Check if the character at the specified position is space. +func is_space(b []byte, i int) bool { + return b[i] == ' ' +} + +// Check if the character at the specified position is tab. +func is_tab(b []byte, i int) bool { + return b[i] == '\t' +} + +// Check if the character at the specified position is blank (space or tab). +func is_blank(b []byte, i int) bool { + //return is_space(b, i) || is_tab(b, i) + return b[i] == ' ' || b[i] == '\t' +} + +// Check if the character at the specified position is a line break. +func is_break(b []byte, i int) bool { + return (b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029) +} + +func is_crlf(b []byte, i int) bool { + return b[i] == '\r' && b[i+1] == '\n' +} + +// Check if the character is a line break or NUL. +func is_breakz(b []byte, i int) bool { + //return is_break(b, i) || is_z(b, i) + return ( // is_break: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + // is_z: + b[i] == 0) +} + +// Check if the character is a line break, space, or NUL. +func is_spacez(b []byte, i int) bool { + //return is_space(b, i) || is_breakz(b, i) + return ( // is_space: + b[i] == ' ' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Check if the character is a line break, space, tab, or NUL. +func is_blankz(b []byte, i int) bool { + //return is_blank(b, i) || is_breakz(b, i) + return ( // is_blank: + b[i] == ' ' || b[i] == '\t' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Determine the width of the character. +func width(b byte) int { + // Don't replace these by a switch without first + // confirming that it is being inlined. + if b&0x80 == 0x00 { + return 1 + } + if b&0xE0 == 0xC0 { + return 2 + } + if b&0xF0 == 0xE0 { + return 3 + } + if b&0xF8 == 0xF0 { + return 4 + } + return 0 + +} From 611174f17d511910bf02460307335b088adbbd96 Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Mon, 4 Feb 2019 13:19:35 +0100 Subject: [PATCH 07/18] xamarin option for auto scan --- cmd/utils.go | 5 +++-- cmd/xamarin.go | 34 ++++++++++++++++++++++++++++------ cmd/xcode.go | 4 ++-- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/cmd/utils.go b/cmd/utils.go index 4a32131e..ab2d30f9 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -13,7 +13,8 @@ import ( type IDEType string const ( - xcodeIDE IDEType = "iOS" + xcodeIDE IDEType = "iOS" + xamarinIDE IDEType = "xamarin" ) // Scans the root dir for the provided project files @@ -42,7 +43,7 @@ func scanForProjectFiles(ideType IDEType) ([]string, error) { return nil, fmt.Errorf("failed to search for solution files, error: %s", err) } } - } else { + } else if ideType == xamarinIDE { paths, err = xamarin.FilterSolutionFiles(fileList) if err != nil { return nil, fmt.Errorf("failed to search for solution files, error: %s", err) diff --git a/cmd/xamarin.go b/cmd/xamarin.go index 57fba764..ff22c660 100644 --- a/cmd/xamarin.go +++ b/cmd/xamarin.go @@ -3,6 +3,7 @@ package cmd import ( "encoding/json" "fmt" + "path" "path/filepath" "sort" @@ -87,14 +88,35 @@ func scanXamarinProject(cmd *cobra.Command, args []string) error { // Xamarin Solution Path xamarinCmd.SolutionFilePath = paramXamarinSolutionFilePath if xamarinCmd.SolutionFilePath == "" { - askText := `Please drag-and-drop your Xamarin Solution (` + colorstring.Green(".sln") + `) file, -and then hit Enter` - fmt.Println() - projpth, err := goinp.AskForPath(askText) + + var solutionPth string + log.Infof("Scan the directory for solution files") + solPaths, err := scanForProjectFiles(xamarinIDE) if err != nil { - return fmt.Errorf("failed to read input: %s", err) + log.Printf("Failed: %s", err) + fmt.Println() + + log.Infof("Provide the solution file manually") + askText := `Please drag-and-drop your Xamarin Solution (` + colorstring.Green(".sln") + `) file, +and then hit Enter` + solutionPth, err = goinp.AskForPath(askText) + if err != nil { + return fmt.Errorf("failed to read input: %s", err) + } + } else { + if len(solPaths) == 1 { + log.Printf("Found one solution file: %s.", path.Base(solPaths[0])) + solutionPth = solPaths[0] + } else { + log.Printf("Found multiple solution file: %s.", path.Base(solutionPth)) + solutionPth, err = goinp.SelectFromStringsWithDefault("Select the solution file you want to scan", 1, solPaths) + if err != nil { + return fmt.Errorf("failed to select solution file: %s", err) + } + } } - xamarinCmd.SolutionFilePath = projpth + + xamarinCmd.SolutionFilePath = solutionPth } log.Debugf("xamSolutionPth: %s", xamarinCmd.SolutionFilePath) diff --git a/cmd/xcode.go b/cmd/xcode.go index 6b2755ba..c01783ac 100644 --- a/cmd/xcode.go +++ b/cmd/xcode.go @@ -104,9 +104,9 @@ the one you usually open in Xcode, then hit Enter. projpth = projPaths[0] } else { log.Printf("Found multiple project file: %s.", path.Base(projpth)) - projpth, err = goinp.SelectFromStringsWithDefault("Select the Scheme you usually use in Xcode", 1, projPaths) + projpth, err = goinp.SelectFromStringsWithDefault("Select the project file you want to scan", 1, projPaths) if err != nil { - return fmt.Errorf("failed to select Scheme: %s", err) + return fmt.Errorf("failed to select project file: %s", err) } } } From fc75337031e0e0ef682bef78d6c0568303d9bf63 Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Mon, 4 Feb 2019 13:32:54 +0100 Subject: [PATCH 08/18] warning message added about the --file flag usage --- cmd/xamarin.go | 5 ++++- cmd/xcode.go | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/xamarin.go b/cmd/xamarin.go index ff22c660..6d38979f 100644 --- a/cmd/xamarin.go +++ b/cmd/xamarin.go @@ -88,9 +88,12 @@ func scanXamarinProject(cmd *cobra.Command, args []string) error { // Xamarin Solution Path xamarinCmd.SolutionFilePath = paramXamarinSolutionFilePath if xamarinCmd.SolutionFilePath == "" { - var solutionPth string + + fmt.Println() log.Infof("Scan the directory for solution files") + log.Warnf("You can specify the Xamarin Solution file to scan with the --file flag.") + solPaths, err := scanForProjectFiles(xamarinIDE) if err != nil { log.Printf("Failed: %s", err) diff --git a/cmd/xcode.go b/cmd/xcode.go index c01783ac..1d066858 100644 --- a/cmd/xcode.go +++ b/cmd/xcode.go @@ -82,9 +82,11 @@ func scanXcodeProject(cmd *cobra.Command, args []string) error { projectPath := paramXcodeProjectFilePath if projectPath == "" { - var projpth string + log.Infof("Scan the directory for project files") + log.Warnf("You can specify the Xcode project/workscape file to scan with the --file flag.") + projPaths, err := scanForProjectFiles(xcodeIDE) if err != nil { log.Printf("Failed: %s", err) From 2026e7093d3e0ada66adb59c9605ad9ccc708441 Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Fri, 8 Feb 2019 13:21:14 +0100 Subject: [PATCH 09/18] PR: IDEType enum renamed to ProjectType; and changed from String to Int; --- cmd/utils.go | 14 +++++++------- cmd/xamarin.go | 2 +- cmd/xcode.go | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/utils.go b/cmd/utils.go index ab2d30f9..8b0bc863 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -9,16 +9,16 @@ import ( "github.com/bitrise-core/bitrise-init/utility" ) -// IDEType ... -type IDEType string +// ProjectType ... +type ProjectType int const ( - xcodeIDE IDEType = "iOS" - xamarinIDE IDEType = "xamarin" + iOSProjectType ProjectType = iota + xamarinProjectType ) // Scans the root dir for the provided project files -func scanForProjectFiles(ideType IDEType) ([]string, error) { +func scanForProjectFiles(projectType ProjectType) ([]string, error) { searchDir, err := os.Getwd() if err != nil { return nil, err @@ -31,7 +31,7 @@ func scanForProjectFiles(ideType IDEType) ([]string, error) { var paths []string { - if ideType == xcodeIDE { + if projectType == iOSProjectType { paths, err = ios.FilterRelevantWorkspaceFiles(fileList) if err != nil { return nil, fmt.Errorf("failed to search for solution files, error: %s", err) @@ -43,7 +43,7 @@ func scanForProjectFiles(ideType IDEType) ([]string, error) { return nil, fmt.Errorf("failed to search for solution files, error: %s", err) } } - } else if ideType == xamarinIDE { + } else if projectType == xamarinProjectType { paths, err = xamarin.FilterSolutionFiles(fileList) if err != nil { return nil, fmt.Errorf("failed to search for solution files, error: %s", err) diff --git a/cmd/xamarin.go b/cmd/xamarin.go index 6d38979f..058849c0 100644 --- a/cmd/xamarin.go +++ b/cmd/xamarin.go @@ -94,7 +94,7 @@ func scanXamarinProject(cmd *cobra.Command, args []string) error { log.Infof("Scan the directory for solution files") log.Warnf("You can specify the Xamarin Solution file to scan with the --file flag.") - solPaths, err := scanForProjectFiles(xamarinIDE) + solPaths, err := scanForProjectFiles(xamarinProjectType) if err != nil { log.Printf("Failed: %s", err) fmt.Println() diff --git a/cmd/xcode.go b/cmd/xcode.go index 1d066858..e2f4eedf 100644 --- a/cmd/xcode.go +++ b/cmd/xcode.go @@ -87,7 +87,7 @@ func scanXcodeProject(cmd *cobra.Command, args []string) error { log.Infof("Scan the directory for project files") log.Warnf("You can specify the Xcode project/workscape file to scan with the --file flag.") - projPaths, err := scanForProjectFiles(xcodeIDE) + projPaths, err := scanForProjectFiles(iOSProjectType) if err != nil { log.Printf("Failed: %s", err) fmt.Println() From 5311596c354bbeaf882314e63324e48b9a0df343 Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Fri, 8 Feb 2019 13:23:01 +0100 Subject: [PATCH 10/18] PR: fix some log typo: solution => workspace / project --- cmd/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/utils.go b/cmd/utils.go index 8b0bc863..dec783e5 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -34,13 +34,13 @@ func scanForProjectFiles(projectType ProjectType) ([]string, error) { if projectType == iOSProjectType { paths, err = ios.FilterRelevantWorkspaceFiles(fileList) if err != nil { - return nil, fmt.Errorf("failed to search for solution files, error: %s", err) + return nil, fmt.Errorf("failed to search for workspace files, error: %s", err) } if len(paths) == 0 { paths, err = ios.FilterRelevantProjectFiles(fileList) if err != nil { - return nil, fmt.Errorf("failed to search for solution files, error: %s", err) + return nil, fmt.Errorf("failed to search for project files, error: %s", err) } } } else if projectType == xamarinProjectType { From 2ab734ef0a19e800cc4ef305a61d03cb5ea2ed3d Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Fri, 8 Feb 2019 13:55:45 +0100 Subject: [PATCH 11/18] ref: scanXamarinProject: move the "search for the soulition file" block in to a separated func; --- cmd/xamarin.go | 58 ++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/cmd/xamarin.go b/cmd/xamarin.go index 058849c0..149765be 100644 --- a/cmd/xamarin.go +++ b/cmd/xamarin.go @@ -76,6 +76,36 @@ func archivableSolutionConfigNames(projectsByID map[string]project.Model) []stri return archivableSolutionConfigNames } +func findSolution() (string, error) { + var solutionPth string + solPaths, err := scanForProjectFiles(xamarinProjectType) + if err != nil { + log.Printf("Failed: %s", err) + fmt.Println() + + log.Infof("Provide the solution file manually") + askText := `Please drag-and-drop your Xamarin Solution (` + colorstring.Green(".sln") + `) file, +and then hit Enter` + solutionPth, err = goinp.AskForPath(askText) + if err != nil { + return "", fmt.Errorf("failed to read input: %s", err) + } + } else { + if len(solPaths) == 1 { + log.Printf("Found one solution file: %s.", path.Base(solPaths[0])) + solutionPth = solPaths[0] + } else { + log.Printf("Found multiple solution file: %s.", path.Base(solutionPth)) + solutionPth, err = goinp.SelectFromStringsWithDefault("Select the solution file you want to scan", 1, solPaths) + if err != nil { + return "", fmt.Errorf("failed to select solution file: %s", err) + } + } + } + + return solutionPth, nil +} + func scanXamarinProject(cmd *cobra.Command, args []string) error { absExportOutputDirPath, err := initExportOutputDir() if err != nil { @@ -88,38 +118,14 @@ func scanXamarinProject(cmd *cobra.Command, args []string) error { // Xamarin Solution Path xamarinCmd.SolutionFilePath = paramXamarinSolutionFilePath if xamarinCmd.SolutionFilePath == "" { - var solutionPth string - fmt.Println() log.Infof("Scan the directory for solution files") log.Warnf("You can specify the Xamarin Solution file to scan with the --file flag.") - solPaths, err := scanForProjectFiles(xamarinProjectType) + xamarinCmd.SolutionFilePath, err = findSolution() if err != nil { - log.Printf("Failed: %s", err) - fmt.Println() - - log.Infof("Provide the solution file manually") - askText := `Please drag-and-drop your Xamarin Solution (` + colorstring.Green(".sln") + `) file, -and then hit Enter` - solutionPth, err = goinp.AskForPath(askText) - if err != nil { - return fmt.Errorf("failed to read input: %s", err) - } - } else { - if len(solPaths) == 1 { - log.Printf("Found one solution file: %s.", path.Base(solPaths[0])) - solutionPth = solPaths[0] - } else { - log.Printf("Found multiple solution file: %s.", path.Base(solutionPth)) - solutionPth, err = goinp.SelectFromStringsWithDefault("Select the solution file you want to scan", 1, solPaths) - if err != nil { - return fmt.Errorf("failed to select solution file: %s", err) - } - } + return err } - - xamarinCmd.SolutionFilePath = solutionPth } log.Debugf("xamSolutionPth: %s", xamarinCmd.SolutionFilePath) From f8b95cad7b28baafc1c3fa34a93b33e00a93d85b Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Fri, 8 Feb 2019 14:04:03 +0100 Subject: [PATCH 12/18] ref: scanXcodeProject: move the "search for the project file" block in to a separated func; --- cmd/xamarin.go | 5 ++++ cmd/xcode.go | 64 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/cmd/xamarin.go b/cmd/xamarin.go index 149765be..76c454c3 100644 --- a/cmd/xamarin.go +++ b/cmd/xamarin.go @@ -76,6 +76,8 @@ func archivableSolutionConfigNames(projectsByID map[string]project.Model) []stri return archivableSolutionConfigNames } +// findSolution scans the directory for Xamarin.Solution file first +// If can't find any, ask the user to drag-and-drop the file func findSolution() (string, error) { var solutionPth string solPaths, err := scanForProjectFiles(xamarinProjectType) @@ -122,6 +124,9 @@ func scanXamarinProject(cmd *cobra.Command, args []string) error { log.Infof("Scan the directory for solution files") log.Warnf("You can specify the Xamarin Solution file to scan with the --file flag.") + // + // Scan the directory for Xamarin.Solution file first + // If can't find any, ask the user to drag-and-drop the file xamarinCmd.SolutionFilePath, err = findSolution() if err != nil { return err diff --git a/cmd/xcode.go b/cmd/xcode.go index e2f4eedf..e82b65b1 100644 --- a/cmd/xcode.go +++ b/cmd/xcode.go @@ -62,6 +62,40 @@ func initExportOutputDir() (string, error) { return absExportOutputDirPath, nil } +// findProject scans the directory for Xcode Project (.xcworkspace / .xcodeproject) file first +// If can't find any, ask the user to drag-and-drop the file +func findProject() (string, error) { + var projpth string + + projPaths, err := scanForProjectFiles(iOSProjectType) + if err != nil { + log.Printf("Failed: %s", err) + fmt.Println() + + log.Infof("Provide the project file manually") + askText := `Please drag-and-drop your Xcode Project (` + colorstring.Green(".xcodeproj") + `) or Workspace (` + colorstring.Green(".xcworkspace") + `) file, +the one you usually open in Xcode, then hit Enter. +(Note: if you have a Workspace file you should most likely use that)` + projpth, err = goinp.AskForPath(askText) + if err != nil { + return "", fmt.Errorf("failed to read input: %s", err) + } + } else { + if len(projPaths) == 1 { + log.Printf("Found one project file: %s.", path.Base(projPaths[0])) + projpth = projPaths[0] + } else { + log.Printf("Found multiple project file: %s.", path.Base(projpth)) + projpth, err = goinp.SelectFromStringsWithDefault("Select the project file you want to scan", 1, projPaths) + if err != nil { + return "", fmt.Errorf("failed to select project file: %s", err) + } + } + } + + return projpth, nil +} + func scanXcodeProject(cmd *cobra.Command, args []string) error { absExportOutputDirPath, err := initExportOutputDir() if err != nil { @@ -82,37 +116,17 @@ func scanXcodeProject(cmd *cobra.Command, args []string) error { projectPath := paramXcodeProjectFilePath if projectPath == "" { - var projpth string - log.Infof("Scan the directory for project files") log.Warnf("You can specify the Xcode project/workscape file to scan with the --file flag.") - projPaths, err := scanForProjectFiles(iOSProjectType) + projpth, err := findProject() if err != nil { - log.Printf("Failed: %s", err) - fmt.Println() - - log.Infof("Provide the project file manually") - askText := `Please drag-and-drop your Xcode Project (` + colorstring.Green(".xcodeproj") + `) or Workspace (` + colorstring.Green(".xcworkspace") + `) file, -the one you usually open in Xcode, then hit Enter. -(Note: if you have a Workspace file you should most likely use that)` - projpth, err = goinp.AskForPath(askText) - if err != nil { - return fmt.Errorf("failed to read input: %s", err) - } - } else { - if len(projPaths) == 1 { - log.Printf("Found one project file: %s.", path.Base(projPaths[0])) - projpth = projPaths[0] - } else { - log.Printf("Found multiple project file: %s.", path.Base(projpth)) - projpth, err = goinp.SelectFromStringsWithDefault("Select the project file you want to scan", 1, projPaths) - if err != nil { - return fmt.Errorf("failed to select project file: %s", err) - } - } + return err } + // + // Scan the directory for Xcode Project (.xcworkspace / .xcodeproject) file first + // If can't find any, ask the user to drag-and-drop the file projectPath = strings.Trim(strings.TrimSpace(projpth), "'\"") } log.Debugf("projectPath: %s", projectPath) From d97fd76221867d1da52b8c9623a9817621b998d3 Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Mon, 11 Feb 2019 11:02:30 +0100 Subject: [PATCH 13/18] dep ensure --- Gopkg.lock | 258 +- Gopkg.toml | 3 +- .../bitrise-core/bitrise-init/.gitignore | 7 - .../bitrise-core/bitrise-init/Dockerfile | 3 - .../bitrise-core/bitrise-init/Gopkg.lock | 223 -- .../bitrise-core/bitrise-init/Gopkg.toml | 51 - .../bitrise-core/bitrise-init/README.md | 52 - .../bitrise-init/_scripts/set_version.sh | 20 - .../_tests/integration/android_test.go | 545 --- .../_tests/integration/cordova_test.go | 208 -- .../_tests/integration/fastlane_test.go | 182 - .../_tests/integration/flutter_test.go | 1576 -------- .../bitrise-init/_tests/integration/helper.go | 66 - .../_tests/integration/ionic_test.go | 99 - .../_tests/integration/ios_test.go | 573 --- .../_tests/integration/mac_test.go | 133 - .../_tests/integration/manual_config_test.go | 1699 --------- .../integration/reactnative_expo_test.go | 446 --- .../_tests/integration/reactnative_test.go | 299 -- .../_tests/integration/xamarin_test.go | 281 -- .../bitrise-core/bitrise-init/bitrise.yml | 155 - .../bitrise-core/bitrise-init/cli/cli.go | 72 - .../bitrise-core/bitrise-init/cli/config.go | 185 - .../bitrise-init/cli/manual_config.go | 132 - .../bitrise-core/bitrise-init/cli/version.go | 79 - .../bitrise-init/docker-compose.yml | 7 - .../bitrise-core/bitrise-init/gows.yml | 1 - .../bitrise-core/bitrise-init/main.go | 10 - .../bitrise-init/models/model_test.go | 390 -- .../bitrise-init/output/output.go | 120 - .../bitrise-init/release_config.yml | 13 - .../bitrise-init/scanner/config.go | 231 -- .../bitrise-init/scanner/manual_config.go | 38 - .../bitrise-init/scanner/utils.go | 151 - .../bitrise-init/scanners/android/android.go | 123 - .../bitrise-init/scanners/android/const.go | 34 - .../bitrise-init/scanners/android/utility.go | 187 - .../bitrise-init/scanners/cordova/cordova.go | 405 --- .../scanners/cordova/cordova_test.go | 37 - .../bitrise-init/scanners/cordova/utility.go | 47 - .../scanners/fastlane/fastlane.go | 242 -- .../scanners/fastlane/fastlane_test.go | 149 - .../fastlane/fastlane_test_file_contents.go | 310 -- .../bitrise-init/scanners/fastlane/utility.go | 104 - .../bitrise-init/scanners/flutter/flutter.go | 471 --- .../bitrise-init/scanners/ionic/ionic.go | 406 --- .../bitrise-init/scanners/ios/gemutil_test.go | 210 -- .../bitrise-init/scanners/ios/podfile_test.go | 609 ---- .../scanners/ios/rubyscript_test.go | 23 - .../bitrise-init/scanners/ios/utility_test.go | 57 - .../scanners/ios/xcodeproj_test.go | 189 - .../bitrise-init/scanners/macos/macos.go | 70 - .../reactnative-expo/reactnative-expo.go | 785 ---- .../scanners/reactnative/reactnative.go | 501 --- .../scanners/reactnative/utility.go | 56 - .../bitrise-init/scanners/scanners.go | 115 - .../scanners/xamarin/xamarin_test.go | 38 - .../bitrise-init/toolscanner/toolscanner.go | 56 - .../toolscanner/toolscanner_test.go | 170 - .../utility/sortable_path_test.go | 106 - .../bitrise-init/utility/utility_test.go | 306 -- .../bitrise-init/version/build.go | 7 - .../bitrise-init/version/version.go | 4 - .../bitrise-io/bitrise/.codeclimate.yml | 41 - .../github.com/bitrise-io/bitrise/.gitignore | 9 - .../bitrise-io/bitrise/CHANGELOG.md | 3155 ----------------- .../github.com/bitrise-io/bitrise/Dockerfile | 34 - .../bitrise-io/bitrise/Godeps/Godeps.json | 135 - .../bitrise-io/bitrise/Godeps/Readme | 5 - .../github.com/bitrise-io/bitrise/README.md | 120 - .../bitrise-io/bitrise/_docs/README.md | 15 - .../bitrise/_docs/bitrise-yml-format-spec.md | 152 - .../bitrise/_docs/cli-how-to-guide.md | 141 - .../bitrise/_docs/cli-introduction.md | 22 - .../bitrise/_docs/cli-react-native.md | 27 - .../bitrise/_docs/cli-share-guide.md | 18 - .../bitrise/_docs/images/success.gif | Bin 960828 -> 0 bytes .../_docs/step-development-guideline.md | 118 - .../bitrise-io/bitrise/_examples/README.md | 4 - .../experimentals/before-after/bitrise.yml | 73 - .../experimentals/create-new-step/bitrise.yml | 18 - .../experimentals/dependencies/bitrise.yml | 103 - .../experimentals/middleman/bitrise.yml | 24 - .../experimentals/middleman/v2.bitrise.yml | 40 - .../experimentals/templates/bitrise.yml | 131 - .../experimentals/timestamp-gen/bitrise.yml | 22 - .../experimentals/trigger-map/bitrise.yml | 58 - .../upload_download_bitrise_io/.gitignore | 2 - .../upload_download_bitrise_io/bitrise.yml | 32 - .../bitrise/_examples/tutorials/README.md | 21 - .../bitrise.yml | 46 - .../tutorials/inputs-outputs-envs/bitrise.yml | 43 - .../tutorials/react-native/bitrise.yml | 36 - .../tutorials/steps-and-workflows/bitrise.yml | 139 - .../steps-timestamp/README.md | 2 - .../steps-timestamp/_scripts/ci.sh | 25 - .../steps-timestamp/step.go | 41 - .../steps-timestamp/step.sh | 12 - .../steps-timestamp/step.yml | 17 - .../bitrise-io/bitrise/_lessons/README.md | 22 - .../bitrise/_lessons/lesson1_steps/.gitignore | 2 - .../bitrise/_lessons/lesson1_steps/README.md | 37 - .../_lessons/lesson1_steps/bitrise.yml | 91 - .../_lessons/lesson2_workflow/.gitignore | 2 - .../_lessons/lesson2_workflow/README.md | 88 - .../_lessons/lesson2_workflow/bitrise.yml | 53 - .../lesson3_input_output_env/.gitignore | 2 - .../lesson3_input_output_env/README.md | 19 - .../lesson3_input_output_env/bitrise.yml | 39 - .../_lessons/lesson4_errors/.gitignore | 2 - .../bitrise/_lessons/lesson4_errors/README.md | 17 - .../_lessons/lesson4_errors/bitrise.yml | 51 - .../_lessons/lesson5_complex_wf/.gitignore | 2 - .../_lessons/lesson5_complex_wf/README.md | 133 - .../_lessons/lesson5_complex_wf/bitrise.yml | 148 - .../_lessons/lesson6_triggers/.gitignore | 2 - .../_lessons/lesson6_triggers/README.md | 31 - .../_lessons/lesson6_triggers/bitrise.yml | 97 - .../bitrise/_scripts/build_tools_in_docker.sh | 36 - .../bitrise-io/bitrise/_scripts/ci.sh | 22 - .../create_release_with_docker_compose.sh | 13 - .../bitrise/_scripts/get_version.go | 42 - .../bitrise/_scripts/go_install_tools.sh | 35 - .../bitrise/_scripts/set_version.sh | 20 - .../bitrise/_tests/Dockerfile-min-env-debian | 9 - .../bitrise/_tests/Dockerfile-min-env-ubuntu | 9 - .../bitrise-io/bitrise/_tests/bitrise.json | 82 - .../bitrise-io/bitrise/_tests/bitrise.yml | 61 - .../bitrise/_tests/brew_publish.yml | 65 - .../bash_toolkit_step_template/.gitignore | 1 - .../bash_toolkit_step_template/README.md | 19 - .../bash_toolkit_step_template/bitrise.yml | 71 - .../bash_toolkit_step_template/step.sh | 7 - .../bash_toolkit_step_template/step.yml | 59 - .../bash_toolkit_step_template/step_entry.sh | 21 - .../_tests/integration/envstore_test.go | 19 - .../integration/envstore_test_bitrise.yml | 56 - .../_tests/integration/exit_code_test.go | 72 - .../integration/exit_code_test_bitrise.yml | 51 - .../_tests/integration/global_flag_test.go | 163 - .../integration/global_flag_test_bitrise.yml | 34 - .../integration/global_flag_test_secrets.yml | 4 - .../go_toolkit_step_template/.gitignore | 1 - .../go_toolkit_step_template/README.md | 19 - .../go_toolkit_step_template/bitrise.yml | 71 - .../go_toolkit_step_template/main.go | 7 - .../go_toolkit_step_template/step.yml | 59 - .../bitrise/_tests/integration/helper.go | 25 - .../integration/invalid_command_test.go | 16 - .../_tests/integration/json_params_test.go | 112 - .../integration/json_params_test_bitrise.yml | 15 - .../_tests/integration/new_trigger_test.go | 141 - .../integration/new_trigger_test_bitrise.yml | 29 - .../_tests/integration/output_alias_test.go | 24 - .../integration/output_alias_test_bitrise.yml | 89 - .../integration/step_template/.gitignore | 1 - .../integration/step_template/README.md | 19 - .../integration/step_template/bitrise.yml | 71 - .../_tests/integration/step_template/step.sh | 21 - .../_tests/integration/step_template/step.yml | 56 - .../_tests/integration/step_template_test.go | 36 - .../_tests/integration/trigger_check_test.go | 72 - .../trigger_check_test_bitrise.yml | 19 - .../trigger_check_test_empty_bitrise.yml | 12 - .../trigger_check_test_secrets.yml | 2 - .../_tests/integration/trigger_params_test.go | 52 - .../trigger_params_test_bitrise.yml | 26 - .../_tests/integration/validate_test.go | 214 -- .../_tests/integration/version_test.go | 35 - .../github.com/bitrise-io/bitrise/bitrise.yml | 352 -- .../bitrise/bitrise/dependencies.go | 420 --- .../bitrise-io/bitrise/bitrise/print.go | 614 ---- .../bitrise-io/bitrise/bitrise/print_test.go | 389 -- .../bitrise-io/bitrise/bitrise/setup.go | 184 - .../bitrise/bitrise/template_util.go | 90 - .../bitrise/bitrise/template_utils_test.go | 408 --- .../bitrise-io/bitrise/bitrise/util.go | 675 ---- .../bitrise-io/bitrise/bitrise/util_test.go | 498 --- .../github.com/bitrise-io/bitrise/cli/cli.go | 163 - .../bitrise-io/bitrise/cli/commands.go | 237 -- .../bitrise-io/bitrise/cli/export.go | 78 - .../bitrise-io/bitrise/cli/flags.go | 192 - .../github.com/bitrise-io/bitrise/cli/help.go | 59 - .../github.com/bitrise-io/bitrise/cli/init.go | 50 - .../bitrise-io/bitrise/cli/normalize.go | 50 - .../bitrise-io/bitrise/cli/plugin.go | 24 - .../bitrise-io/bitrise/cli/plugin_delete.go | 58 - .../bitrise-io/bitrise/cli/plugin_info.go | 106 - .../bitrise-io/bitrise/cli/plugin_install.go | 75 - .../bitrise-io/bitrise/cli/plugin_list.go | 77 - .../bitrise-io/bitrise/cli/plugin_update.go | 100 - .../github.com/bitrise-io/bitrise/cli/run.go | 237 -- .../bitrise-io/bitrise/cli/run_test.go | 1482 -------- .../bitrise/cli/run_trigger_params.go | 138 - .../bitrise/cli/run_trigger_params_test.go | 483 --- .../bitrise-io/bitrise/cli/run_util.go | 981 ----- .../bitrise-io/bitrise/cli/run_util_test.go | 463 --- .../bitrise-io/bitrise/cli/setup.go | 68 - .../bitrise-io/bitrise/cli/share.go | 16 - .../bitrise-io/bitrise/cli/share_audit.go | 16 - .../bitrise-io/bitrise/cli/share_create.go | 28 - .../bitrise-io/bitrise/cli/share_finish.go | 16 - .../bitrise-io/bitrise/cli/share_start.go | 21 - .../bitrise-io/bitrise/cli/step_info.go | 119 - .../bitrise-io/bitrise/cli/step_list.go | 73 - .../bitrise-io/bitrise/cli/tools.go | 37 - .../bitrise-io/bitrise/cli/trigger.go | 163 - .../bitrise-io/bitrise/cli/trigger_check.go | 221 -- .../bitrise/cli/trigger_check_test.go | 639 ---- .../bitrise-io/bitrise/cli/validate.go | 239 -- .../bitrise-io/bitrise/cli/version.go | 63 - .../bitrise-io/bitrise/cli/workflow_list.go | 129 - .../bitrise-io/bitrise/configs/configs.go | 159 - .../bitrise/configs/configs_test.go | 30 - .../bitrise-io/bitrise/configs/paths.go | 203 -- .../bitrise-io/bitrise/configs/paths_test.go | 104 - vendor/github.com/bitrise-io/bitrise/deps.go | 9 - .../bitrise-io/bitrise/docker-compose.yml | 4 - vendor/github.com/bitrise-io/bitrise/gows.yml | 1 - vendor/github.com/bitrise-io/bitrise/main.go | 7 - .../bitrise/models/models_methods_test.go | 1567 -------- .../bitrise-io/bitrise/output/output.go | 64 - .../bitrise-io/bitrise/plugins/events.go | 72 - .../bitrise-io/bitrise/plugins/git.go | 221 -- .../bitrise-io/bitrise/plugins/git_test.go | 89 - .../bitrise-io/bitrise/plugins/install.go | 289 -- .../bitrise/plugins/install_test.go | 223 -- .../bitrise/plugins/model_methods_test.go | 368 -- .../bitrise-io/bitrise/plugins/models.go | 60 - .../bitrise/plugins/models_methods.go | 344 -- .../bitrise-io/bitrise/plugins/paths.go | 186 - .../bitrise-io/bitrise/plugins/plugins.go | 162 - .../bitrise/plugins/plugins_test.go | 54 - .../bitrise-io/bitrise/plugins/run.go | 188 - .../bitrise-io/bitrise/plugins/run_test.go | 24 - .../bitrise-io/bitrise/release_config.yml | 41 - .../bitrise-io/bitrise/toolkits/bash.go | 77 - .../bitrise-io/bitrise/toolkits/golang.go | 387 -- .../bitrise/toolkits/golang_test.go | 78 - .../bitrise-io/bitrise/toolkits/toolkit.go | 82 - .../bitrise/toolkits/toolkit_test.go | 1 - .../bitrise-io/bitrise/tools/tools.go | 361 -- .../bitrise-io/bitrise/tools/tools_test.go | 113 - .../bitrise-io/bitrise/utils/utils.go | 16 - .../bitrise-io/bitrise/version/build.go | 7 - .../bitrise/version/tool_version.go | 87 - .../bitrise-io/bitrise/version/version.go | 4 - .../github.com/bitrise-io/envman/.gitignore | 6 - .../github.com/bitrise-io/envman/Dockerfile | 50 - .../github.com/bitrise-io/envman/Gopkg.lock | 121 - .../github.com/bitrise-io/envman/Gopkg.toml | 23 - vendor/github.com/bitrise-io/envman/README.md | 196 - .../envman/_tests/integration/add_test.go | 141 - .../envman/_tests/integration/helper.go | 7 - .../github.com/bitrise-io/envman/bitrise.yml | 97 - .../github.com/bitrise-io/envman/cli/add.go | 225 -- .../bitrise-io/envman/cli/add_test.go | 76 - .../github.com/bitrise-io/envman/cli/clear.go | 34 - .../github.com/bitrise-io/envman/cli/cli.go | 96 - .../bitrise-io/envman/cli/commands.go | 72 - .../github.com/bitrise-io/envman/cli/flags.go | 127 - .../github.com/bitrise-io/envman/cli/init.go | 27 - .../github.com/bitrise-io/envman/cli/print.go | 95 - .../bitrise-io/envman/cli/print_test.go | 32 - .../github.com/bitrise-io/envman/cli/run.go | 102 - .../bitrise-io/envman/cli/run_test.go | 237 -- .../bitrise-io/envman/cli/version.go | 46 - .../bitrise-io/envman/docker-compose.yml | 4 - .../bitrise-io/envman/envman/configs.go | 99 - .../bitrise-io/envman/envman/configs_test.go | 51 - .../bitrise-io/envman/envman/util.go | 254 -- .../bitrise-io/envman/envman/util_test.go | 125 - vendor/github.com/bitrise-io/envman/gows.yml | 1 - vendor/github.com/bitrise-io/envman/main.go | 7 - .../bitrise-io/envman/models/models.go | 1 + .../envman/models/models_methods.go | 12 + .../envman/models/models_methods_test.go | 492 --- .../bitrise-io/envman/output/output.go | 64 - .../bitrise-io/envman/version/build.go | 7 - .../bitrise-io/envman/version/version.go | 4 - .../go-utils/command/rubyscript/rubyscript.go | 88 + .../go-utils/parseutil/parseutil.go | 95 + .../bitrise-io/go-utils/pointers/pointers.go | 98 + .../github.com/bitrise-io/stepman/.gitignore | 6 - .../github.com/bitrise-io/stepman/Dockerfile | 31 - .../github.com/bitrise-io/stepman/Gopkg.lock | 152 - .../github.com/bitrise-io/stepman/Gopkg.toml | 27 - .../github.com/bitrise-io/stepman/README.md | 35 - .../stepman/_tests/integration/helper.go | 23 - .../stepman/_tests/integration/setup_test.go | 60 - .../_tests/integration/step_info_test.go | 286 -- .../_tests/integration/test-step/.gitignore | 1 - .../_tests/integration/test-step/README.md | 93 - .../_tests/integration/test-step/bitrise.yml | 84 - .../_tests/integration/test-step/step.sh | 21 - .../_tests/integration/test-step/step.yml | 58 - .../stepman/_tests/integration/update_test.go | 52 - .../_tests/integration/version_test.go | 34 - .../github.com/bitrise-io/stepman/bitrise.yml | 99 - .../bitrise-io/stepman/cli/activate.go | 138 - .../bitrise-io/stepman/cli/audit.go | 164 - .../bitrise-io/stepman/cli/audit_test.go | 41 - .../github.com/bitrise-io/stepman/cli/cli.go | 67 - .../bitrise-io/stepman/cli/collections.go | 107 - .../bitrise-io/stepman/cli/commands.go | 177 - .../bitrise-io/stepman/cli/delete_steplib.go | 35 - .../bitrise-io/stepman/cli/download.go | 80 - .../bitrise-io/stepman/cli/export.go | 147 - .../bitrise-io/stepman/cli/flags.go | 168 - .../github.com/bitrise-io/stepman/cli/help.go | 26 - .../bitrise-io/stepman/cli/setup.go | 58 - .../bitrise-io/stepman/cli/share.go | 188 - .../bitrise-io/stepman/cli/share_audit.go | 46 - .../bitrise-io/stepman/cli/share_create.go | 238 -- .../stepman/cli/share_create_test.go | 23 - .../bitrise-io/stepman/cli/share_finish.go | 81 - .../bitrise-io/stepman/cli/share_start.go | 128 - .../bitrise-io/stepman/cli/step_info.go | 194 - .../bitrise-io/stepman/cli/step_list.go | 117 - .../bitrise-io/stepman/cli/update.go | 35 - .../bitrise-io/stepman/cli/version.go | 82 - .../bitrise-io/stepman/docker-compose.yml | 4 - vendor/github.com/bitrise-io/stepman/gows.yml | 1 - vendor/github.com/bitrise-io/stepman/main.go | 7 - .../stepman/models/models_methods_test.go | 467 --- .../bitrise-io/stepman/models/models_test.go | 70 - .../bitrise-io/stepman/stepman/library.go | 130 - .../bitrise-io/stepman/stepman/paths.go | 248 -- .../bitrise-io/stepman/stepman/util.go | 427 --- .../bitrise-io/stepman/stepman/util_test.go | 28 - .../bitrise-io/stepman/version/build.go | 7 - .../bitrise-io/stepman/version/version.go | 4 - .../go-xcode/xcodeproj/code_sign_info.go | 252 ++ .../xcodeproj/code_sign_info_script.go | 188 + .../xcodeproj/code_sign_info_script.rb | 180 + .../xcodeproj/code_sign_info_script.sh | 10 + .../go-xcode/xcodeproj/project.go | 70 + .../go-xcode/xcodeproj/workspace.go | 78 + .../go-xcode/xcodeproj/xcodeproj.go | 84 + .../xcodeproj/xcodeproj_test_files.go | 1326 +++++++ .../go-xcode/xcodeproj/xcscheme.go | 250 ++ .../go-xcode/xcodeproj/xctarget.go | 368 ++ vendor/golang.org/x/sys/unix/mksyscall.go | 4 + .../golang.org/x/sys/unix/syscall_darwin.go | 1 + .../x/sys/unix/zsyscall_darwin_amd64.go | 15 + .../x/sys/unix/zsyscall_darwin_amd64.s | 2 + .../x/sys/unix/zsysnum_darwin_amd64.go | 6 +- vendor/gopkg.in/yaml.v2/decode_test.go | 1334 ------- vendor/gopkg.in/yaml.v2/encode_test.go | 625 ---- .../gopkg.in/yaml.v2/example_embedded_test.go | 41 - vendor/gopkg.in/yaml.v2/suite_test.go | 12 - 351 files changed, 3380 insertions(+), 47745 deletions(-) delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/.gitignore delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/Dockerfile delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/Gopkg.lock delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/Gopkg.toml delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/README.md delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_scripts/set_version.sh delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/android_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/cordova_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/fastlane_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/flutter_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/helper.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ionic_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ios_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/mac_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/manual_config_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_expo_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/_tests/integration/xamarin_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/bitrise.yml delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/cli/cli.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/cli/config.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/cli/manual_config.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/cli/version.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/docker-compose.yml delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/gows.yml delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/main.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/models/model_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/output/output.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/release_config.yml delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanner/config.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanner/manual_config.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanner/utils.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/android/android.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/android/const.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/android/utility.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/utility.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test_file_contents.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/utility.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/flutter/flutter.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ionic/ionic.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/gemutil_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/podfile_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/rubyscript_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/utility_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/macos/macos.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative-expo/reactnative-expo.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/reactnative.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/utility.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/scanners.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/xamarin_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/utility/sortable_path_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/utility/utility_test.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/version/build.go delete mode 100644 vendor/github.com/bitrise-core/bitrise-init/version/version.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/.codeclimate.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/.gitignore delete mode 100644 vendor/github.com/bitrise-io/bitrise/CHANGELOG.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/Dockerfile delete mode 100644 vendor/github.com/bitrise-io/bitrise/Godeps/Godeps.json delete mode 100644 vendor/github.com/bitrise-io/bitrise/Godeps/Readme delete mode 100644 vendor/github.com/bitrise-io/bitrise/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/bitrise-yml-format-spec.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/cli-how-to-guide.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/cli-introduction.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/cli-react-native.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/cli-share-guide.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/images/success.gif delete mode 100644 vendor/github.com/bitrise-io/bitrise/_docs/step-development-guideline.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/before-after/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/create-new-step/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/dependencies/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/v2.bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/templates/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/timestamp-gen/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/trigger-map/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/.gitignore delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/errors-force-run-and-skippable/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/inputs-outputs-envs/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/react-native/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/_scripts/ci.sh delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.go delete mode 100755 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.sh delete mode 100644 vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/.gitignore delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/.gitignore delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/.gitignore delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/.gitignore delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/.gitignore delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/.gitignore delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_scripts/build_tools_in_docker.sh delete mode 100644 vendor/github.com/bitrise-io/bitrise/_scripts/ci.sh delete mode 100644 vendor/github.com/bitrise-io/bitrise/_scripts/create_release_with_docker_compose.sh delete mode 100644 vendor/github.com/bitrise-io/bitrise/_scripts/get_version.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_scripts/go_install_tools.sh delete mode 100644 vendor/github.com/bitrise-io/bitrise/_scripts/set_version.sh delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/Dockerfile-min-env-debian delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/Dockerfile-min-env-ubuntu delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/bitrise.json delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/brew_publish.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/.gitignore delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/step.sh delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/step.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/bash_toolkit_step_template/step_entry.sh delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/envstore_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/envstore_test_bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/exit_code_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/exit_code_test_bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/global_flag_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/global_flag_test_bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/global_flag_test_secrets.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/go_toolkit_step_template/.gitignore delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/go_toolkit_step_template/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/go_toolkit_step_template/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/go_toolkit_step_template/main.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/go_toolkit_step_template/step.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/helper.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/invalid_command_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/json_params_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/json_params_test_bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/new_trigger_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/new_trigger_test_bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/output_alias_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/output_alias_test_bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template/.gitignore delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template/README.md delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template/step.sh delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template/step.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/step_template_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/trigger_check_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/trigger_check_test_bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/trigger_check_test_empty_bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/trigger_check_test_secrets.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/trigger_params_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/trigger_params_test_bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/validate_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/_tests/integration/version_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/dependencies.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/print.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/print_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/setup.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/template_util.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/template_utils_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/util.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/bitrise/util_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/cli.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/commands.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/export.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/flags.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/help.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/init.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/normalize.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/plugin.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/plugin_delete.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/plugin_info.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/plugin_install.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/plugin_list.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/plugin_update.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/run.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/run_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/run_util.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/run_util_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/setup.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/share.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/share_audit.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/share_create.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/share_finish.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/share_start.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/step_info.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/step_list.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/tools.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/trigger.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/trigger_check.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/trigger_check_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/validate.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/version.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/cli/workflow_list.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/configs/configs.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/configs/configs_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/configs/paths.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/configs/paths_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/deps.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/docker-compose.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/gows.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/main.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/models/models_methods_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/output/output.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/events.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/git.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/git_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/install.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/install_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/model_methods_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/models.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/models_methods.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/paths.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/plugins.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/plugins_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/run.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/plugins/run_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/release_config.yml delete mode 100644 vendor/github.com/bitrise-io/bitrise/toolkits/bash.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/toolkits/golang.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/toolkits/golang_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/toolkits/toolkit.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/toolkits/toolkit_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/tools/tools.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/tools/tools_test.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/utils/utils.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/version/build.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/version/tool_version.go delete mode 100644 vendor/github.com/bitrise-io/bitrise/version/version.go delete mode 100644 vendor/github.com/bitrise-io/envman/.gitignore delete mode 100644 vendor/github.com/bitrise-io/envman/Dockerfile delete mode 100644 vendor/github.com/bitrise-io/envman/Gopkg.lock delete mode 100644 vendor/github.com/bitrise-io/envman/Gopkg.toml delete mode 100644 vendor/github.com/bitrise-io/envman/README.md delete mode 100644 vendor/github.com/bitrise-io/envman/_tests/integration/add_test.go delete mode 100644 vendor/github.com/bitrise-io/envman/_tests/integration/helper.go delete mode 100644 vendor/github.com/bitrise-io/envman/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/envman/cli/add.go delete mode 100644 vendor/github.com/bitrise-io/envman/cli/add_test.go delete mode 100644 vendor/github.com/bitrise-io/envman/cli/clear.go delete mode 100644 vendor/github.com/bitrise-io/envman/cli/cli.go delete mode 100644 vendor/github.com/bitrise-io/envman/cli/commands.go delete mode 100644 vendor/github.com/bitrise-io/envman/cli/flags.go delete mode 100644 vendor/github.com/bitrise-io/envman/cli/init.go delete mode 100644 vendor/github.com/bitrise-io/envman/cli/print.go delete mode 100644 vendor/github.com/bitrise-io/envman/cli/print_test.go delete mode 100644 vendor/github.com/bitrise-io/envman/cli/run.go delete mode 100644 vendor/github.com/bitrise-io/envman/cli/run_test.go delete mode 100644 vendor/github.com/bitrise-io/envman/cli/version.go delete mode 100644 vendor/github.com/bitrise-io/envman/docker-compose.yml delete mode 100644 vendor/github.com/bitrise-io/envman/envman/configs.go delete mode 100644 vendor/github.com/bitrise-io/envman/envman/configs_test.go delete mode 100644 vendor/github.com/bitrise-io/envman/envman/util.go delete mode 100644 vendor/github.com/bitrise-io/envman/envman/util_test.go delete mode 100644 vendor/github.com/bitrise-io/envman/gows.yml delete mode 100644 vendor/github.com/bitrise-io/envman/main.go delete mode 100644 vendor/github.com/bitrise-io/envman/models/models_methods_test.go delete mode 100644 vendor/github.com/bitrise-io/envman/output/output.go delete mode 100644 vendor/github.com/bitrise-io/envman/version/build.go delete mode 100644 vendor/github.com/bitrise-io/envman/version/version.go create mode 100644 vendor/github.com/bitrise-io/go-utils/command/rubyscript/rubyscript.go create mode 100644 vendor/github.com/bitrise-io/go-utils/parseutil/parseutil.go create mode 100644 vendor/github.com/bitrise-io/go-utils/pointers/pointers.go delete mode 100644 vendor/github.com/bitrise-io/stepman/.gitignore delete mode 100644 vendor/github.com/bitrise-io/stepman/Dockerfile delete mode 100644 vendor/github.com/bitrise-io/stepman/Gopkg.lock delete mode 100644 vendor/github.com/bitrise-io/stepman/Gopkg.toml delete mode 100644 vendor/github.com/bitrise-io/stepman/README.md delete mode 100644 vendor/github.com/bitrise-io/stepman/_tests/integration/helper.go delete mode 100644 vendor/github.com/bitrise-io/stepman/_tests/integration/setup_test.go delete mode 100644 vendor/github.com/bitrise-io/stepman/_tests/integration/step_info_test.go delete mode 100755 vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/.gitignore delete mode 100755 vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/README.md delete mode 100755 vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/bitrise.yml delete mode 100755 vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.sh delete mode 100755 vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.yml delete mode 100644 vendor/github.com/bitrise-io/stepman/_tests/integration/update_test.go delete mode 100644 vendor/github.com/bitrise-io/stepman/_tests/integration/version_test.go delete mode 100644 vendor/github.com/bitrise-io/stepman/bitrise.yml delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/activate.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/audit.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/audit_test.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/cli.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/collections.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/commands.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/delete_steplib.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/download.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/export.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/flags.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/help.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/setup.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/share.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/share_audit.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/share_create.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/share_create_test.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/share_finish.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/share_start.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/step_info.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/step_list.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/update.go delete mode 100644 vendor/github.com/bitrise-io/stepman/cli/version.go delete mode 100644 vendor/github.com/bitrise-io/stepman/docker-compose.yml delete mode 100644 vendor/github.com/bitrise-io/stepman/gows.yml delete mode 100644 vendor/github.com/bitrise-io/stepman/main.go delete mode 100644 vendor/github.com/bitrise-io/stepman/models/models_methods_test.go delete mode 100644 vendor/github.com/bitrise-io/stepman/models/models_test.go delete mode 100644 vendor/github.com/bitrise-io/stepman/stepman/library.go delete mode 100644 vendor/github.com/bitrise-io/stepman/stepman/paths.go delete mode 100644 vendor/github.com/bitrise-io/stepman/stepman/util.go delete mode 100644 vendor/github.com/bitrise-io/stepman/stepman/util_test.go delete mode 100644 vendor/github.com/bitrise-io/stepman/version/build.go delete mode 100644 vendor/github.com/bitrise-io/stepman/version/version.go create mode 100644 vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info.go create mode 100644 vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info_script.go create mode 100644 vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info_script.rb create mode 100644 vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info_script.sh create mode 100644 vendor/github.com/bitrise-tools/go-xcode/xcodeproj/project.go create mode 100644 vendor/github.com/bitrise-tools/go-xcode/xcodeproj/workspace.go create mode 100644 vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xcodeproj.go create mode 100644 vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xcodeproj_test_files.go create mode 100644 vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xcscheme.go create mode 100644 vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xctarget.go delete mode 100644 vendor/gopkg.in/yaml.v2/decode_test.go delete mode 100644 vendor/gopkg.in/yaml.v2/encode_test.go delete mode 100644 vendor/gopkg.in/yaml.v2/example_embedded_test.go delete mode 100644 vendor/gopkg.in/yaml.v2/suite_test.go diff --git a/Gopkg.lock b/Gopkg.lock index 66646b2d..72eb74bc 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,7 +2,8 @@ [[projects]] - digest = "1:020a8f7482fc2908fb66380a25d675c750310fc269045690d7a14e40831c1b8b" + branch = "master" + digest = "1:6bd1c59c28e2d5eba8820a1ee9be84f6aa976d9b49db71ff92363d57d8fb3a72" name = "github.com/bitrise-core/bitrise-init" packages = [ "models", @@ -11,14 +12,261 @@ "steps", "utility", ] - pruneopts = "" + pruneopts = "UT" revision = "d2ea1b27c9c491e1f41ab3106e99e100296cceb0" - version = "1.8.0" [[projects]] - digest = "1:5f5022227806c631673aac91c13ca5b0e9f754f4a6c61634ea874f9451ed93b8" + digest = "1:7e26f568122298ee1065b6e264380f1f5387eff732cfc93ad860b6265f9063f9" name = "github.com/bitrise-io/bitrise" packages = ["models"] - pruneopts = "" + pruneopts = "UT" revision = "4ff9e54c15d46ddd638030202326ee02734e8b82" version = "1.8.0" + +[[projects]] + branch = "master" + digest = "1:4e1c0cc22c53c5ebda9bd380afd5b56a2f1fc9e6fd2eed59ab8ba0c1522eee19" + name = "github.com/bitrise-io/envman" + packages = ["models"] + pruneopts = "UT" + revision = "5f433da4a0f73ae27d60d4b01c3866fb094a19e0" + +[[projects]] + branch = "master" + digest = "1:fcc506a39d45ac99741625cbf234a3b037316b71043ab7cc656ac205dad93577" + name = "github.com/bitrise-io/go-utils" + packages = [ + "colorstring", + "command", + "command/rubyscript", + "errorutil", + "fileutil", + "log", + "parseutil", + "pathutil", + "pkcs12", + "pkcs12/internal/rc2", + "pointers", + "progress", + "retry", + "sliceutil", + "urlutil", + ] + pruneopts = "UT" + revision = "2a09aab8380d7842750328aebd5671bcccea89c8" + +[[projects]] + branch = "master" + digest = "1:f9749f95e34724e0bfb71a3bf0fa00e0e303d42f1de172bf134f013fa1e9dbb7" + name = "github.com/bitrise-io/goinp" + packages = ["goinp"] + pruneopts = "UT" + revision = "f451b19ba4d087d8272d411ad91af5f4218fd517" + +[[projects]] + digest = "1:34e5041861da39ff37f982c3cdfd46c0b0b46836d136151e895be7bf8fb50d86" + name = "github.com/bitrise-io/stepman" + packages = ["models"] + pruneopts = "UT" + revision = "076579c0004ea94d98322d3df5d5d21b47412b1c" + version = "0.11.0" + +[[projects]] + branch = "master" + digest = "1:65cd17acf7b05a2b288a9263cc985e42fb283abe63888fad636feaf277175600" + name = "github.com/bitrise-tools/go-xamarin" + packages = [ + "analyzers/project", + "analyzers/solution", + "builder", + "constants", + "tools", + "tools/buildtools", + "tools/buildtools/msbuild", + "tools/buildtools/xbuild", + "tools/nunit", + "utility", + ] + pruneopts = "UT" + revision = "60f614520f9dd2d58ea982488ed8e21ca2c5bb1d" + +[[projects]] + branch = "master" + digest = "1:c5ac1b062ba1470ca0901d2272624bd59d42042f15e92c8d0b15b5711a123d64" + name = "github.com/bitrise-tools/go-xcode" + packages = [ + "certificateutil", + "export", + "exportoptions", + "models", + "plistutil", + "profileutil", + "utility", + "xcarchive", + "xcodeproj", + ] + pruneopts = "UT" + revision = "8e44ecb550c0cadef602edda92e5c10476dd5daa" + +[[projects]] + branch = "master" + digest = "1:f5d050443803354466260c17b1cc500558364967419d09b3b77e0c79c64c9d34" + name = "github.com/bitrise-tools/xcode-project" + packages = [ + "serialized", + "xcodebuild", + "xcodeproj", + "xcscheme", + "xcworkspace", + ] + pruneopts = "UT" + revision = "476ea7a82b2ef7c77d0d41445afc94bdb1cda04d" + +[[projects]] + digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" + name = "github.com/davecgh/go-spew" + packages = ["spew"] + pruneopts = "UT" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" + +[[projects]] + branch = "master" + digest = "1:095c5b12709e45a61ff2820c2bf81c7de7a030629cb728fc7a2abec35b5cbadb" + name = "github.com/fullsailor/pkcs7" + packages = ["."] + pruneopts = "UT" + revision = "8306686428a5fe132eac8cb7c4848af725098bd4" + +[[projects]] + digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" + name = "github.com/inconshreveable/mousetrap" + packages = ["."] + pruneopts = "UT" + revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" + version = "v1.0" + +[[projects]] + branch = "master" + digest = "1:9729648bfd610b93e5636062a4925798e76399b121c0f9403d2b2ad4112545eb" + name = "github.com/pkg/errors" + packages = ["."] + pruneopts = "UT" + revision = "ffb6e22f01932bf7ac35e0bad9be11f01d1c8685" + +[[projects]] + digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + pruneopts = "UT" + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + digest = "1:6baa565fe16f8657cf93469b2b8a6c61a277827734400d27e44d589547297279" + name = "github.com/ryanuber/go-glob" + packages = ["."] + pruneopts = "UT" + revision = "51a8f68e6c24dc43f1e371749c89a267de4ebc53" + version = "v1.0.0" + +[[projects]] + branch = "master" + digest = "1:efc0b1c2f3bff9854d96b12af5eb5fe35c97fa2e79c8efe091a8e38fd521bf23" + name = "github.com/spf13/cobra" + packages = ["."] + pruneopts = "UT" + revision = "7547e83b2d85fd1893c7d76916f67689d761fecb" + +[[projects]] + digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2" + name = "github.com/spf13/pflag" + packages = ["."] + pruneopts = "UT" + revision = "298182f68c66c05229eb03ac171abe6e309ee79a" + version = "v1.0.3" + +[[projects]] + branch = "master" + digest = "1:eeb0a54ba70c22815143c2c3f104cb4f9ebe2060ba49780bd4895a0179b7aee9" + name = "github.com/stretchr/testify" + packages = [ + "assert", + "require", + ] + pruneopts = "UT" + revision = "363ebb24d041ccea8068222281c2e963e997b9dc" + +[[projects]] + branch = "master" + digest = "1:fde12c4da6237363bf36b81b59aa36a43d28061167ec4acb0d41fc49464e28b9" + name = "golang.org/x/crypto" + packages = ["ssh/terminal"] + pruneopts = "UT" + revision = "193df9c0f06f8bb35fba505183eaf0acc0136505" + +[[projects]] + branch = "master" + digest = "1:8975c6b66d95adc1cd08820aba7ad6b60b165725a401c4177c0eaa866170bb10" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows", + ] + pruneopts = "UT" + revision = "3b5209105503162ded1863c307ac66fec31120dd" + +[[projects]] + digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" + name = "gopkg.in/yaml.v2" + packages = ["."] + pruneopts = "UT" + revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" + version = "v2.2.2" + +[[projects]] + branch = "master" + digest = "1:c10265d5a71326618d37e97169eddb3582f78e8ac7dcf87403b4cf619efd519a" + name = "howett.net/plist" + packages = ["."] + pruneopts = "UT" + revision = "591f970eefbbeb04d7b37f334a0c4c3256e32876" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/bitrise-core/bitrise-init/scanners/ios", + "github.com/bitrise-core/bitrise-init/scanners/xamarin", + "github.com/bitrise-core/bitrise-init/utility", + "github.com/bitrise-io/go-utils/colorstring", + "github.com/bitrise-io/go-utils/command", + "github.com/bitrise-io/go-utils/fileutil", + "github.com/bitrise-io/go-utils/log", + "github.com/bitrise-io/go-utils/pathutil", + "github.com/bitrise-io/go-utils/progress", + "github.com/bitrise-io/go-utils/retry", + "github.com/bitrise-io/go-utils/sliceutil", + "github.com/bitrise-io/go-utils/urlutil", + "github.com/bitrise-io/goinp/goinp", + "github.com/bitrise-tools/go-xamarin/analyzers/project", + "github.com/bitrise-tools/go-xamarin/analyzers/solution", + "github.com/bitrise-tools/go-xamarin/builder", + "github.com/bitrise-tools/go-xamarin/constants", + "github.com/bitrise-tools/go-xamarin/tools/buildtools", + "github.com/bitrise-tools/go-xcode/certificateutil", + "github.com/bitrise-tools/go-xcode/export", + "github.com/bitrise-tools/go-xcode/exportoptions", + "github.com/bitrise-tools/go-xcode/plistutil", + "github.com/bitrise-tools/go-xcode/profileutil", + "github.com/bitrise-tools/go-xcode/utility", + "github.com/bitrise-tools/go-xcode/xcarchive", + "github.com/bitrise-tools/xcode-project/xcodeproj", + "github.com/bitrise-tools/xcode-project/xcscheme", + "github.com/bitrise-tools/xcode-project/xcworkspace", + "github.com/pkg/errors", + "github.com/spf13/cobra", + "github.com/stretchr/testify/require", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index dfb40e9c..773e23cc 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -31,10 +31,9 @@ name = "github.com/stretchr/testify" branch = "master" - [[constraint]] name = "github.com/bitrise-core/bitrise-init" - master + branch = "master" [prune] go-tests = true diff --git a/vendor/github.com/bitrise-core/bitrise-init/.gitignore b/vendor/github.com/bitrise-core/bitrise-init/.gitignore deleted file mode 100644 index c1b6fd28..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -_scan_result/ -_defaults/ -_tmp/ -_bin/ -.bitrise* -.gows* -bitrise-init \ No newline at end of file diff --git a/vendor/github.com/bitrise-core/bitrise-init/Dockerfile b/vendor/github.com/bitrise-core/bitrise-init/Dockerfile deleted file mode 100644 index 03dc075e..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM quay.io/bitriseio/android - -WORKDIR /bitrise/go/src/github.com/bitrise-core/bitrise-init \ No newline at end of file diff --git a/vendor/github.com/bitrise-core/bitrise-init/Gopkg.lock b/vendor/github.com/bitrise-core/bitrise-init/Gopkg.lock deleted file mode 100644 index c18e20ca..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/Gopkg.lock +++ /dev/null @@ -1,223 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - digest = "1:87c2e02fb01c27060ccc5ba7c5a407cc91147726f8f40b70cceeedbc52b1f3a8" - name = "github.com/Sirupsen/logrus" - packages = ["."] - pruneopts = "UT" - revision = "e1e72e9de974bd926e5c56f83753fba2df402ce5" - version = "v1.3.0" - -[[projects]] - digest = "1:7e26f568122298ee1065b6e264380f1f5387eff732cfc93ad860b6265f9063f9" - name = "github.com/bitrise-io/bitrise" - packages = ["models"] - pruneopts = "UT" - revision = "4ff9e54c15d46ddd638030202326ee02734e8b82" - version = "1.8.0" - -[[projects]] - branch = "master" - digest = "1:0bdbc9f9b2bbc084d3bb353d8e94d53ee0495f9d434baaddb5e0f07fb88c966a" - name = "github.com/bitrise-io/envman" - packages = ["models"] - pruneopts = "UT" - revision = "41ea1b6f422eabd86189caf7da0c633208ba4760" - -[[projects]] - branch = "master" - digest = "1:6ff15f23762c225970c8624e1ef1b6a8861aa5d1ef4f10702ea158bf24d3ce4a" - name = "github.com/bitrise-io/go-utils" - packages = [ - "colorstring", - "command", - "command/git", - "command/rubyscript", - "errorutil", - "fileutil", - "log", - "parseutil", - "pathutil", - "pointers", - "sliceutil", - ] - pruneopts = "UT" - revision = "2a09aab8380d7842750328aebd5671bcccea89c8" - -[[projects]] - branch = "master" - digest = "1:f9749f95e34724e0bfb71a3bf0fa00e0e303d42f1de172bf134f013fa1e9dbb7" - name = "github.com/bitrise-io/goinp" - packages = ["goinp"] - pruneopts = "UT" - revision = "f451b19ba4d087d8272d411ad91af5f4218fd517" - -[[projects]] - digest = "1:34e5041861da39ff37f982c3cdfd46c0b0b46836d136151e895be7bf8fb50d86" - name = "github.com/bitrise-io/stepman" - packages = ["models"] - pruneopts = "UT" - revision = "076579c0004ea94d98322d3df5d5d21b47412b1c" - version = "0.11.0" - -[[projects]] - branch = "master" - digest = "1:fd63ae4d0b8c60e868266de6575ac33ba5c4ca8705267a35b2824590f24c9a47" - name = "github.com/bitrise-tools/go-xcode" - packages = [ - "plistutil", - "xcodeproj", - ] - pruneopts = "UT" - revision = "b278b67ed298829932d08fbe05e7590bca753bdf" - -[[projects]] - branch = "master" - digest = "1:8208679f767666bb99d70139954a6f5988375c8bcdce77b6ece5c63e3fcf1f1b" - name = "github.com/bitrise-tools/xcode-project" - packages = [ - "serialized", - "xcodebuild", - "xcodeproj", - "xcscheme", - "xcworkspace", - ] - pruneopts = "UT" - revision = "489a630c4fa828115a6265700eb61c3839aad9a7" - -[[projects]] - digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" - name = "github.com/davecgh/go-spew" - packages = ["spew"] - pruneopts = "UT" - revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" - version = "v1.1.1" - -[[projects]] - digest = "1:2e3c336fc7fde5c984d2841455a658a6d626450b1754a854b3b32e7a8f49a07a" - name = "github.com/google/go-cmp" - packages = [ - "cmp", - "cmp/internal/diff", - "cmp/internal/function", - "cmp/internal/value", - ] - pruneopts = "UT" - revision = "3af367b6b30c263d47e8895973edcca9a49cf029" - version = "v0.2.0" - -[[projects]] - digest = "1:0a69a1c0db3591fcefb47f115b224592c8dfa4368b7ba9fae509d5e16cdc95c8" - name = "github.com/konsorten/go-windows-terminal-sequences" - packages = ["."] - pruneopts = "UT" - revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" - version = "v1.0.1" - -[[projects]] - digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b" - name = "github.com/pkg/errors" - packages = ["."] - pruneopts = "UT" - revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" - version = "v0.8.1" - -[[projects]] - digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" - name = "github.com/pmezard/go-difflib" - packages = ["difflib"] - pruneopts = "UT" - revision = "792786c7400a136282c1664665ae0a8db921c6c2" - version = "v1.0.0" - -[[projects]] - digest = "1:0e792eea6c96ec55ff302ef33886acbaa5006e900fefe82689e88d96439dcd84" - name = "github.com/ryanuber/go-glob" - packages = ["."] - pruneopts = "UT" - revision = "572520ed46dbddaed19ea3d9541bdd0494163693" - version = "v0.1" - -[[projects]] - digest = "1:5da8ce674952566deae4dbc23d07c85caafc6cfa815b0b3e03e41979cedb8750" - name = "github.com/stretchr/testify" - packages = [ - "assert", - "require", - ] - pruneopts = "UT" - revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" - version = "v1.3.0" - -[[projects]] - digest = "1:b24d38b282bacf9791408a080f606370efa3d364e4b5fd9ba0f7b87786d3b679" - name = "github.com/urfave/cli" - packages = ["."] - pruneopts = "UT" - revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" - version = "v1.20.0" - -[[projects]] - branch = "master" - digest = "1:38f553aff0273ad6f367cb0a0f8b6eecbaef8dc6cb8b50e57b6a81c1d5b1e332" - name = "golang.org/x/crypto" - packages = ["ssh/terminal"] - pruneopts = "UT" - revision = "ff983b9c42bc9fbf91556e191cc8efb585c16908" - -[[projects]] - branch = "master" - digest = "1:72f402ba458cb14ed7964c8b9a38d992f27834b3cf3479f3b08ea9e5334811b3" - name = "golang.org/x/sys" - packages = [ - "unix", - "windows", - ] - pruneopts = "UT" - revision = "770c60269bf0ef965e9e7ac8bedcb6bca2a1cefd" - -[[projects]] - digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" - name = "gopkg.in/yaml.v2" - packages = ["."] - pruneopts = "UT" - revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" - version = "v2.2.2" - -[[projects]] - branch = "master" - digest = "1:c10265d5a71326618d37e97169eddb3582f78e8ac7dcf87403b4cf619efd519a" - name = "howett.net/plist" - packages = ["."] - pruneopts = "UT" - revision = "591f970eefbbeb04d7b37f334a0c4c3256e32876" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "github.com/Sirupsen/logrus", - "github.com/bitrise-io/bitrise/models", - "github.com/bitrise-io/envman/models", - "github.com/bitrise-io/go-utils/colorstring", - "github.com/bitrise-io/go-utils/command", - "github.com/bitrise-io/go-utils/command/git", - "github.com/bitrise-io/go-utils/errorutil", - "github.com/bitrise-io/go-utils/fileutil", - "github.com/bitrise-io/go-utils/log", - "github.com/bitrise-io/go-utils/pathutil", - "github.com/bitrise-io/go-utils/pointers", - "github.com/bitrise-io/go-utils/sliceutil", - "github.com/bitrise-io/goinp/goinp", - "github.com/bitrise-io/stepman/models", - "github.com/bitrise-tools/go-xcode/xcodeproj", - "github.com/bitrise-tools/xcode-project/serialized", - "github.com/bitrise-tools/xcode-project/xcworkspace", - "github.com/google/go-cmp/cmp", - "github.com/stretchr/testify/require", - "github.com/urfave/cli", - "gopkg.in/yaml.v2", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/vendor/github.com/bitrise-core/bitrise-init/Gopkg.toml b/vendor/github.com/bitrise-core/bitrise-init/Gopkg.toml deleted file mode 100644 index 4df52f0f..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/Gopkg.toml +++ /dev/null @@ -1,51 +0,0 @@ -[[constraint]] - name = "github.com/Sirupsen/logrus" - version = "1.3.0" - -[[constraint]] - name = "github.com/bitrise-io/bitrise" - version = "1.6.2" - -[[constraint]] - branch = "master" - name = "github.com/bitrise-io/envman" - -[[constraint]] - branch = "master" - name = "github.com/bitrise-io/go-utils" - -[[constraint]] - branch = "master" - name = "github.com/bitrise-io/goinp" - -[[constraint]] - name = "github.com/bitrise-io/stepman" - version = "0.11.0" - -[[constraint]] - branch = "master" - name = "github.com/bitrise-tools/go-xcode" - -[[constraint]] - branch = "master" - name = "github.com/bitrise-tools/xcode-project" - -[[constraint]] - name = "github.com/google/go-cmp" - version = "0.2.0" - -[[constraint]] - name = "github.com/stretchr/testify" - version = "1.3.0" - -[[constraint]] - name = "github.com/urfave/cli" - version = "1.20.0" - -[[constraint]] - name = "gopkg.in/yaml.v2" - version = "2.2.2" - -[prune] - go-tests = true - unused-packages = true diff --git a/vendor/github.com/bitrise-core/bitrise-init/README.md b/vendor/github.com/bitrise-core/bitrise-init/README.md deleted file mode 100644 index 304db846..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Bitrise Init Tool - -Initialize bitrise config, step template or plugin template - -## How to build this project -Project is written in [Go](https://golang.org/) language and -uses [dep](github.com/golang/dep/cmd/dep) as dependency management tool. - -You can build this project using sequence of `go` commands or refer to [bitrise.yml](./bitrise.yml) file, -which contains workflows for this project. - -You can run `bitrise` workflows on your local machine using [bitrise CLI](https://www.bitrise.io/cli). - -Before you start, make sure -- `$HOME/go/bin` (or `$GOPATH/bin` in case of custom go workspace) is added to `$PATH` -- `Ruby >= 2.2.2` version is installed -- `bundler` gem installed - -**How to build the project using bitrise workflows** - -Please check available workflows in [bitrise.yml](./bitrise.yml). -`bitrise --ci run ci` will execute `ci` workflow which consists of `prepare/build/run tests` stages. - -**How to build the project using Go commands** -- `go build` command builds the project and generates `bitrise-init` binary at `$HOME/go/bin/bitrise-init` (or `$GOPATH/bin/bitrise-init` in case of custom go workspace). -- `go test ./...` command runs unit tests in every project folder/subfolder. -- `go test -v ./_tests/integration/...` command runs integration tests. This command requires `INTEGRATION_TEST_BINARY_PATH=$HOME/go/bin/bitrise-init` (or `INTEGRATION_TEST_BINARY_PATH=$GOPATH/bin/bitrise-init` in case of custom go workspace) environment variable. - -## How to release new bitrise-init version - -- update the step versions in steps/const.go -- bump `version` in version/version.go -- commit these changes & open PR -- merge to master -- create tag with the new version -- test the generated release and its binaries - -__Update manual config on website__ - -- use the generated binaries in `./_bin/` directory to generate the manual config by calling: `BIN_PATH --ci manual-config` this will generate the manual.config.yml at: `CURRENT_DIR/_defaults/result.yml` -- throw the generated `result.yml` to the frontend team, to update the manual-config on the website -- once they put the new config in the website project, check the git changes to make sure, everything looks great - -__Update the [project-scanner step](https://github.com/bitrise-steplib/steps-project-scanner)__ - -- update bitrise-init dependency -- share a new version into the steplib (check the [README.md](https://github.com/bitrise-steplib/steps-project-scanner/blob/master/README.md)) - -__Update the [bitrise init plugin]((https://github.com/bitrise-core/bitrise-plugins-init))__ - -- update bitrise-init dependency -- release a new version (check the [README.md](https://github.com/bitrise-core/bitrise-plugins-init/blob/master/README.md)) \ No newline at end of file diff --git a/vendor/github.com/bitrise-core/bitrise-init/_scripts/set_version.sh b/vendor/github.com/bitrise-core/bitrise-init/_scripts/set_version.sh deleted file mode 100644 index 901c1438..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/_scripts/set_version.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -x - -version_file_path="$1" -if [ ! -f "$version_file_path" ] ; then - echo " [!] version_file_path not provided, or file doesn't exist at path: $version_file_path" - exit 1 -fi -versionNumber=$next_version -if [[ "$versionNumber" == "" ]] ; then - echo " [!] versionNumber not provided" - exit 1 -fi - -cat >"${version_file_path}" <No Gradle Wrapper (gradlew) found. \nUsing a Gradle Wrapper (gradlew) - is required, as the wrapper is what makes sure\nthat the right Gradle version - is installed and used for the build. More info/guide: https://docs.gradle.org/current/userguide/gradle_wrapper.html" -errors: - general: - - No known platform detected -` - -var sampleAppsAndroid22Versions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.InstallMissingAndroidToolsVersion, - steps.ChangeAndroidVersionCodeAndVersionNameVersion, - steps.AndroidLintVersion, - steps.AndroidUnitTestVersion, - steps.AndroidBuildVersion, - steps.SignAPKVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.InstallMissingAndroidToolsVersion, - steps.AndroidLintVersion, - steps.AndroidUnitTestVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, -} - -var sampleAppsAndroid22ResultYML = fmt.Sprintf(`options: - android: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - .: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - "": - config: android-config -configs: - android: - android-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: android - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - description: | - ## How to get a signed APK - - This workflow contains the **Sign APK** step. To sign your APK all you have to do is to: - - 1. Click on **Code Signing** tab - 1. Find the **ANDROID KEYSTORE FILE** section - 1. Click or drop your file on the upload file field - 1. Fill the displayed 3 input fields: - 1. **Keystore password** - 1. **Keystore alias** - 1. **Private key password** - 1. Click on **[Save metadata]** button - - That's it! From now on, **Sign APK** step will receive your uploaded files. - - ## To run this workflow - - If you want to run this workflow manually: - - 1. Open the app's build list page - 2. Click on **[Start/Schedule a Build]** button - 3. Select **deploy** in **Workflow** dropdown input - 4. Click **[Start Build]** button - - Or if you need this workflow to be started by a GIT event: - - 1. Click on **Triggers** tab - 2. Setup your desired event (push/tag/pull) and select **deploy** workflow - 3. Click on **[Done]** and then **[Save]** buttons - - The next change in your repository that matches any of your trigger map event will start **deploy** workflow. - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - change-android-versioncode-and-versionname@%s: - inputs: - - build_gradle_path: $PROJECT_LOCATION/$MODULE/build.gradle - - android-lint@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - android-unit-test@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - android-build@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - sign-apk@%s: - run_if: '{{getenv "BITRISEIO_ANDROID_KEYSTORE_URL" | ne ""}}' - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - android-lint@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - android-unit-test@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} -warnings: - android: [] -`, sampleAppsAndroid22Versions...) - -var androidNonExecutableGradlewVersions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.InstallMissingAndroidToolsVersion, - steps.ChangeAndroidVersionCodeAndVersionNameVersion, - steps.AndroidLintVersion, - steps.AndroidUnitTestVersion, - steps.AndroidBuildVersion, - steps.SignAPKVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.InstallMissingAndroidToolsVersion, - steps.AndroidLintVersion, - steps.AndroidUnitTestVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, -} - -var androidNonExecutableGradlewResultYML = fmt.Sprintf(`options: - android: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - .: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - "": - config: android-config -configs: - android: - android-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: android - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - description: | - ## How to get a signed APK - - This workflow contains the **Sign APK** step. To sign your APK all you have to do is to: - - 1. Click on **Code Signing** tab - 1. Find the **ANDROID KEYSTORE FILE** section - 1. Click or drop your file on the upload file field - 1. Fill the displayed 3 input fields: - 1. **Keystore password** - 1. **Keystore alias** - 1. **Private key password** - 1. Click on **[Save metadata]** button - - That's it! From now on, **Sign APK** step will receive your uploaded files. - - ## To run this workflow - - If you want to run this workflow manually: - - 1. Open the app's build list page - 2. Click on **[Start/Schedule a Build]** button - 3. Select **deploy** in **Workflow** dropdown input - 4. Click **[Start Build]** button - - Or if you need this workflow to be started by a GIT event: - - 1. Click on **Triggers** tab - 2. Setup your desired event (push/tag/pull) and select **deploy** workflow - 3. Click on **[Done]** and then **[Save]** buttons - - The next change in your repository that matches any of your trigger map event will start **deploy** workflow. - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - change-android-versioncode-and-versionname@%s: - inputs: - - build_gradle_path: $PROJECT_LOCATION/$MODULE/build.gradle - - android-lint@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - android-unit-test@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - android-build@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - sign-apk@%s: - run_if: '{{getenv "BITRISEIO_ANDROID_KEYSTORE_URL" | ne ""}}' - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - android-lint@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - android-unit-test@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} -warnings: - android: [] -`, androidNonExecutableGradlewVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/cordova_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/cordova_test.go deleted file mode 100644 index cb9df9f7..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/cordova_test.go +++ /dev/null @@ -1,208 +0,0 @@ -package integration - -import ( - "fmt" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestCordova(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__cordova__") - require.NoError(t, err) - - t.Log("sample-apps-cordova-with-jasmine") - { - sampleAppDir := filepath.Join(tmpDir, "sample-apps-cordova-with-jasmine") - sampleAppURL := "https://github.com/bitrise-samples/sample-apps-cordova-with-jasmine.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - require.Equal(t, strings.TrimSpace(sampleAppsCordovaWithJasmineResultYML), strings.TrimSpace(result)) - } - - t.Log("sample-apps-cordova-with-karma-jasmine") - { - sampleAppDir := filepath.Join(tmpDir, "sample-apps-cordova-with-karma-jasmine") - sampleAppURL := "https://github.com/bitrise-samples/sample-apps-cordova-with-karma-jasmine.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - require.Equal(t, strings.TrimSpace(sampleAppsCordovaWithKarmaJasmineResultYML), strings.TrimSpace(result)) - } -} - -var sampleAppsCordovaWithJasmineVersions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.NpmVersion, - steps.JasmineTestRunnerVersion, - steps.GenerateCordovaBuildConfigVersion, - steps.CordovaArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.JasmineTestRunnerVersion, - steps.DeployToBitriseIoVersion, -} - -var sampleAppsCordovaWithJasmineResultYML = fmt.Sprintf(`options: - cordova: - title: Platform to use in cordova-cli commands - env_key: CORDOVA_PLATFORM - value_map: - android: - config: cordova-config - ios: - config: cordova-config - ios,android: - config: cordova-config -configs: - cordova: - cordova-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: cordova - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - npm@%s: - inputs: - - command: install - - jasmine-runner@%s: {} - - generate-cordova-build-configuration@%s: {} - - cordova-archive@%s: - inputs: - - platform: $CORDOVA_PLATFORM - - target: emulator - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - command: install - - jasmine-runner@%s: {} - - deploy-to-bitrise-io@%s: {} -warnings: - cordova: [] -`, sampleAppsCordovaWithJasmineVersions...) - -var sampleAppsCordovaWithKarmaJasmineVersions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.NpmVersion, - steps.KarmaJasmineTestRunnerVersion, - steps.GenerateCordovaBuildConfigVersion, - steps.CordovaArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.KarmaJasmineTestRunnerVersion, - steps.DeployToBitriseIoVersion, -} - -var sampleAppsCordovaWithKarmaJasmineResultYML = fmt.Sprintf(`options: - cordova: - title: Platform to use in cordova-cli commands - env_key: CORDOVA_PLATFORM - value_map: - android: - config: cordova-config - ios: - config: cordova-config - ios,android: - config: cordova-config -configs: - cordova: - cordova-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: cordova - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - npm@%s: - inputs: - - command: install - - karma-jasmine-runner@%s: {} - - generate-cordova-build-configuration@%s: {} - - cordova-archive@%s: - inputs: - - platform: $CORDOVA_PLATFORM - - target: emulator - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - command: install - - karma-jasmine-runner@%s: {} - - deploy-to-bitrise-io@%s: {} -warnings: - cordova: [] - `, sampleAppsCordovaWithKarmaJasmineVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/fastlane_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/fastlane_test.go deleted file mode 100644 index 56b2db35..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/fastlane_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package integration - -import ( - "fmt" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestFastlane(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("fastlane") - require.NoError(t, err) - - t.Log("fastlane") - { - sampleAppDir := filepath.Join(tmpDir, "__fastlane__") - sampleAppURL := "https://github.com/bitrise-samples/fastlane.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - require.Equal(t, strings.TrimSpace(fastlaneResultYML), strings.TrimSpace(result)) - } -} - -var fastlaneVersions = []interface{}{ - // fastlane - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FastlaneVersion, - steps.DeployToBitriseIoVersion, - - // ios - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XcodeTestVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XcodeTestVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, -} - -var fastlaneResultYML = fmt.Sprintf(`options: - fastlane: - title: Project type - value_map: - ios: - title: Working directory - env_key: FASTLANE_WORK_DIR - value_map: - BitriseFastlaneSample: - title: Fastlane lane - env_key: FASTLANE_LANE - value_map: - ios test: - config: fastlane-config_ios - ios: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - BitriseFastlaneSample/BitriseFastlaneSample.xcodeproj: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - BitriseFastlaneSample: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: ios-test-config - app-store: - config: ios-test-config - development: - config: ios-test-config - enterprise: - config: ios-test-config -configs: - fastlane: - fastlane-config_ios: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: ios - app: - envs: - - FASTLANE_XCODE_LIST_TIMEOUT: "120" - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - fastlane@%s: - inputs: - - lane: $FASTLANE_LANE - - work_dir: $FASTLANE_WORK_DIR - - deploy-to-bitrise-io@%s: {} - ios: - ios-test-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: ios - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - xcode-test@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - xcode-test@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} -warnings: - fastlane: [] - ios: [] -`, fastlaneVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/flutter_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/flutter_test.go deleted file mode 100644 index 9ba529db..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/flutter_test.go +++ /dev/null @@ -1,1576 +0,0 @@ -package integration - -import ( - "fmt" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestFlutter(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__flutter__") - require.NoError(t, err) - - t.Log("sample-apps-flutter-ios-android") - { - sampleAppDir := filepath.Join(tmpDir, "sample-apps-flutter-ios-android") - sampleAppURL := "https://github.com/bitrise-samples/sample-apps-flutter-ios-android.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - - validateConfigExpectation(t, "sample-apps-flutter-ios-android", strings.TrimSpace(flutterSampleAppResultYML), strings.TrimSpace(result), flutterSampleAppVersions...) - } - - t.Log("sample-apps-flutter-ios-android-package") - { - sampleAppDir := filepath.Join(tmpDir, "sample-apps-flutter-ios-android-package") - sampleAppURL := "https://github.com/bitrise-samples/sample-apps-flutter-ios-android-package.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - - validateConfigExpectation(t, "sample-apps-flutter-ios-android-package", strings.TrimSpace(flutterSamplePackageResultYML), strings.TrimSpace(result), flutterSamplePackageVersions...) - } - - t.Log("sample-apps-flutter-ios-android-plugin") - { - sampleAppDir := filepath.Join(tmpDir, "sample-apps-flutter-ios-android-plugin") - sampleAppURL := "https://github.com/bitrise-samples/sample-apps-flutter-ios-android-plugin.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - - validateConfigExpectation(t, "sample-apps-flutter-ios-android-plugin", strings.TrimSpace(flutterSamplePluginResultYML), strings.TrimSpace(result), flutterSamplePluginVersions...) - } -} - -var flutterSampleAppVersions = []interface{}{ - // flutter-config - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-app-android - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterBuildVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-app-both - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-app-ios - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-test - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-test-app-android - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.FlutterBuildVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-test-app-both - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-test-app-ios - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, -} - -var flutterSampleAppResultYML = fmt.Sprintf(`options: - flutter: - title: Project Location - env_key: BITRISE_FLUTTER_PROJECT_LOCATION - value_map: - .: - title: Run tests found in the project - value_map: - "no": - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - ios/Runner.xcworkspace: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - Runner: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: flutter-config-app-both - app-store: - config: flutter-config-app-both - development: - config: flutter-config-app-both - enterprise: - config: flutter-config-app-both - "yes": - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - ios/Runner.xcworkspace: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - Runner: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: flutter-config-test-app-both - app-store: - config: flutter-config-test-app-both - development: - config: flutter-config-test-app-both - enterprise: - config: flutter-config-test-app-both -configs: - flutter: - flutter-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-app-android: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: android - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-app-both: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: both - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-app-ios: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: ios - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test-app-android: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: android - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test-app-both: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: both - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test-app-ios: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: ios - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} -warnings: - flutter: [] -`, flutterSampleAppVersions...) - -var flutterSamplePackageVersions = []interface{}{ - // flutter-config - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-app-android - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterBuildVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-app-both - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-app-ios - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-test - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-test-app-android - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.FlutterBuildVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-test-app-both - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-test-app-ios - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, -} - -var flutterSamplePackageResultYML = fmt.Sprintf(`options: - flutter: - title: Project Location - env_key: BITRISE_FLUTTER_PROJECT_LOCATION - value_map: - .: - title: Run tests found in the project - value_map: - "no": - config: flutter-config - "yes": - config: flutter-config-test -configs: - flutter: - flutter-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-app-android: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: android - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-app-both: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: both - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-app-ios: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: ios - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test-app-android: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: android - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test-app-both: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: both - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test-app-ios: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: ios - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} -warnings: - flutter: [] -`, flutterSamplePackageVersions...) - -var flutterSamplePluginVersions = []interface{}{ - // flutter-config - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-app-android - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterBuildVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-app-both - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-app-ios - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-test - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-test-app-android - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.FlutterBuildVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-test-app-both - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - // flutter-config-test-app-ios - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, -} - -var flutterSamplePluginResultYML = fmt.Sprintf(`options: - flutter: - title: Project Location - env_key: BITRISE_FLUTTER_PROJECT_LOCATION - value_map: - .: - config: flutter-config-app-android - example: - title: Run tests found in the project - value_map: - "no": - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - example/ios/Runner.xcworkspace: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - Runner: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: flutter-config-app-both - app-store: - config: flutter-config-app-both - development: - config: flutter-config-app-both - enterprise: - config: flutter-config-app-both - "yes": - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - example/ios/Runner.xcworkspace: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - Runner: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: flutter-config-test-app-both - app-store: - config: flutter-config-test-app-both - development: - config: flutter-config-test-app-both - enterprise: - config: flutter-config-test-app-both -configs: - flutter: - flutter-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-app-android: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: android - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-app-both: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: both - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-app-ios: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: ios - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test-app-android: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: android - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test-app-both: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: both - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test-app-ios: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: ios - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} -warnings: - flutter: [] -`, flutterSamplePluginVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/helper.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/helper.go deleted file mode 100644 index 0c3a06cc..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/helper.go +++ /dev/null @@ -1,66 +0,0 @@ -package integration - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/log" - - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func binPath() string { - return os.Getenv("INTEGRATION_TEST_BINARY_PATH") -} - -func replaceVersions(str string, versions ...interface{}) (string, error) { - for _, f := range versions { - if format, ok := f.(string); ok { - beforeCount := strings.Count(str, format) - if beforeCount < 1 { - return "", fmt.Errorf("format's original value not found") - } - str = strings.Replace(str, format, "%s", 1) - - afterCount := strings.Count(str, format) - if beforeCount-1 != afterCount { - return "", fmt.Errorf("failed to extract all versions") - } - } - } - return str, nil -} - -func validateConfigExpectation(t *testing.T, ID, expected, actual string, versions ...interface{}) { - if !assert.ObjectsAreEqual(expected, actual) { - s, err := replaceVersions(actual, versions...) - require.NoError(t, err) - fmt.Println("---------------------") - fmt.Println("Actual config format:") - fmt.Println("---------------------") - fmt.Println(s) - fmt.Println("---------------------") - - _, err = exec.LookPath("opendiff") - if err == nil { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__diffs__") - require.NoError(t, err) - expPth := filepath.Join(tmpDir, ID+"-expected.yml") - actPth := filepath.Join(tmpDir, ID+"-actual.yml") - require.NoError(t, fileutil.WriteStringToFile(expPth, expected)) - require.NoError(t, fileutil.WriteStringToFile(actPth, actual)) - require.NoError(t, exec.Command("opendiff", expPth, actPth).Start()) - t.FailNow() - return - } - log.Warnf("opendiff not installed, unable to open config diff") - t.FailNow() - } -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ionic_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ionic_test.go deleted file mode 100644 index 9d9f334d..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ionic_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package integration - -import ( - "fmt" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestIonic(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__ionic__") - require.NoError(t, err) - - t.Log("ionic-2") - { - sampleAppDir := filepath.Join(tmpDir, "ionic-2") - sampleAppURL := "https://github.com/bitrise-samples/ionic-2.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - require.Equal(t, strings.TrimSpace(ionic2ResultYML), strings.TrimSpace(result)) - } -} - -var ionic2Versions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.NpmVersion, - steps.GenerateCordovaBuildConfigVersion, - steps.IonicArchiveVersion, - steps.DeployToBitriseIoVersion, -} - -var ionic2ResultYML = fmt.Sprintf(`options: - ionic: - title: Directory of Ionic Config.xml - env_key: IONIC_WORK_DIR - value_map: - cutePuppyPics: - title: Platform to use in ionic-cli commands - env_key: IONIC_PLATFORM - value_map: - android: - config: ionic-config - ios: - config: ionic-config - ios,android: - config: ionic-config -configs: - ionic: - ionic-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: ionic - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - npm@%s: - inputs: - - workdir: $IONIC_WORK_DIR - - command: install - - generate-cordova-build-configuration@%s: {} - - ionic-archive@%s: - inputs: - - platform: $IONIC_PLATFORM - - target: emulator - - workdir: $IONIC_WORK_DIR - - deploy-to-bitrise-io@%s: {} -warnings: - ionic: [] -`, ionic2Versions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ios_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ios_test.go deleted file mode 100644 index d48090e6..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/ios_test.go +++ /dev/null @@ -1,573 +0,0 @@ -package integration - -import ( - "fmt" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestIOS(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__ios__") - require.NoError(t, err) - - t.Log("ios-no-shared-schemes") - { - sampleAppDir := filepath.Join(tmpDir, "ios-no-shared-scheme") - sampleAppURL := "https://github.com/bitrise-samples/ios-no-shared-schemes.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - require.Equal(t, strings.TrimSpace(iosNoSharedSchemesResultYML), strings.TrimSpace(result)) - } - - t.Log("ios-cocoapods-at-root") - { - sampleAppDir := filepath.Join(tmpDir, "ios-cocoapods-at-root") - sampleAppURL := "https://github.com/bitrise-samples/ios-cocoapods-at-root.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - require.Equal(t, strings.TrimSpace(iosCocoapodsAtRootResultYML), strings.TrimSpace(result)) - } - - t.Log("sample-apps-ios-watchkit") - { - sampleAppDir := filepath.Join(tmpDir, "sample-apps-ios-watchkit") - sampleAppURL := "https://github.com/bitrise-io/sample-apps-ios-watchkit.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - require.Equal(t, strings.TrimSpace(sampleAppsIosWatchkitResultYML), strings.TrimSpace(result)) - } - - t.Log("sample-apps-carthage") - { - // - sampleAppDir := filepath.Join(tmpDir, "sample-apps-carthage") - sampleAppURL := "https://github.com/bitrise-samples/sample-apps-carthage.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - require.Equal(t, strings.TrimSpace(sampleAppsCarthageResultYML), strings.TrimSpace(result)) - } -} - -var iosNoSharedSchemesVersions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.RecreateUserSchemesVersion, - steps.XcodeTestVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.RecreateUserSchemesVersion, - steps.XcodeTestVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, -} - -var iosNoSharedSchemesResultYML = fmt.Sprintf(`options: - ios: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - BitriseXcode7Sample.xcodeproj: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - BitriseXcode7Sample: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: ios-test-missing-shared-schemes-config - app-store: - config: ios-test-missing-shared-schemes-config - development: - config: ios-test-missing-shared-schemes-config - enterprise: - config: ios-test-missing-shared-schemes-config -configs: - ios: - ios-test-missing-shared-schemes-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: ios - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - recreate-user-schemes@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - xcode-test@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - recreate-user-schemes@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - xcode-test@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} -warnings: - ios: - - |- - No shared schemes found for project: BitriseXcode7Sample.xcodeproj. - Automatically generated schemes may differ from the ones in your project. - Make sure to share your schemes for the expected behaviour. -`, iosNoSharedSchemesVersions...) - -var iosCocoapodsAtRootVersions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.CocoapodsInstallVersion, - steps.XcodeTestVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.CocoapodsInstallVersion, - steps.XcodeTestVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, -} - -var iosCocoapodsAtRootResultYML = fmt.Sprintf(`options: - ios: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - iOSMinimalCocoaPodsSample.xcworkspace: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - iOSMinimalCocoaPodsSample: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: ios-pod-test-config - app-store: - config: ios-pod-test-config - development: - config: ios-pod-test-config - enterprise: - config: ios-pod-test-config -configs: - ios: - ios-pod-test-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: ios - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - cocoapods-install@%s: {} - - xcode-test@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - cocoapods-install@%s: {} - - xcode-test@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} -warnings: - ios: [] -`, iosCocoapodsAtRootVersions...) - -var sampleAppsIosWatchkitVersions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XcodeTestVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XcodeTestVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, -} - -var sampleAppsIosWatchkitResultYML = fmt.Sprintf(`options: - ios: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - watch-test.xcodeproj: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - Complication - watch-test WatchKit App: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: ios-config - app-store: - config: ios-config - development: - config: ios-config - enterprise: - config: ios-config - Glance - watch-test WatchKit App: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: ios-config - app-store: - config: ios-config - development: - config: ios-config - enterprise: - config: ios-config - Notification - watch-test WatchKit App: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: ios-config - app-store: - config: ios-config - development: - config: ios-config - enterprise: - config: ios-config - watch-test: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: ios-test-config - app-store: - config: ios-test-config - development: - config: ios-test-config - enterprise: - config: ios-test-config - watch-test WatchKit App: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: ios-config - app-store: - config: ios-config - development: - config: ios-config - enterprise: - config: ios-config -configs: - ios: - ios-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: ios - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - ios-test-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: ios - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - xcode-test@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - xcode-test@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} -warnings: - ios: [] -`, sampleAppsIosWatchkitVersions...) - -var sampleAppsCarthageVersions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.CarthageVersion, - steps.XcodeTestVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.CarthageVersion, - steps.XcodeTestVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, -} - -var sampleAppsCarthageResultYML = fmt.Sprintf(`options: - ios: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - sample-apps-carthage.xcodeproj: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - sample-apps-carthage: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: ios-carthage-test-config - app-store: - config: ios-carthage-test-config - development: - config: ios-carthage-test-config - enterprise: - config: ios-carthage-test-config -configs: - ios: - ios-carthage-test-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: ios - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - carthage@%s: - inputs: - - carthage_command: bootstrap - - xcode-test@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - carthage@%s: - inputs: - - carthage_command: bootstrap - - xcode-test@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} -warnings: - ios: [] -`, sampleAppsCarthageVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/mac_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/mac_test.go deleted file mode 100644 index 89636fcf..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/mac_test.go +++ /dev/null @@ -1,133 +0,0 @@ -package integration - -import ( - "fmt" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestMacOS(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__macos__") - require.NoError(t, err) - - t.Log("sample-apps-osx-10-11") - { - sampleAppDir := filepath.Join(tmpDir, "sample-apps-osx-10-11") - sampleAppURL := "https://github.com/bitrise-samples/sample-apps-osx-10-11.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - require.Equal(t, strings.TrimSpace(sampleAppsOSX1011ResultYML), strings.TrimSpace(result)) - } -} - -var sampleAppsOSX1011Versions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XcodeTestMacVersion, - steps.XcodeArchiveMacVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XcodeTestMacVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, -} - -var sampleAppsOSX1011ResultYML = fmt.Sprintf(`options: - macos: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - sample-apps-osx-10-11.xcodeproj: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - sample-apps-osx-10-11: - title: |- - Application export method - NOTE: `+"`none`"+` means: Export a copy of the application without re-signing. - env_key: BITRISE_EXPORT_METHOD - value_map: - app-store: - config: macos-test-config - developer-id: - config: macos-test-config - development: - config: macos-test-config - none: - config: macos-test-config -configs: - macos: - macos-test-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: macos - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - xcode-test-mac@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - xcode-archive-mac@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - xcode-test-mac@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} -warnings: - macos: [] -`, sampleAppsOSX1011Versions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/manual_config_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/manual_config_test.go deleted file mode 100644 index be841d23..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/manual_config_test.go +++ /dev/null @@ -1,1699 +0,0 @@ -package integration - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestManualConfig(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__manual-config__") - require.NoError(t, err) - - t.Log("manual-config") - { - manualConfigDir := filepath.Join(tmpDir, "manual-config") - require.NoError(t, os.MkdirAll(manualConfigDir, 0777)) - fmt.Printf("manualConfigDir: %s\n", manualConfigDir) - - cmd := command.New(binPath(), "--ci", "manual-config", "--output-dir", manualConfigDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(manualConfigDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - - validateConfigExpectation(t, "manual-config", strings.TrimSpace(customConfigResultYML), strings.TrimSpace(result), customConfigVersions...) - } -} - -var customConfigVersions = []interface{}{ - // android - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.InstallMissingAndroidToolsVersion, - steps.ChangeAndroidVersionCodeAndVersionNameVersion, - steps.AndroidLintVersion, - steps.AndroidUnitTestVersion, - steps.AndroidBuildVersion, - steps.SignAPKVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.InstallMissingAndroidToolsVersion, - steps.AndroidLintVersion, - steps.AndroidUnitTestVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - // cordova - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.NpmVersion, - steps.GenerateCordovaBuildConfigVersion, - steps.CordovaArchiveVersion, - steps.DeployToBitriseIoVersion, - - // fastlane - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FastlaneVersion, - steps.DeployToBitriseIoVersion, - - // flutter - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterBuildVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.DeployToBitriseIoVersion, - - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.FlutterBuildVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.FlutterBuildVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.FlutterInstallVersion, - steps.FlutterAnalyzeVersion, - steps.FlutterTestVersion, - steps.DeployToBitriseIoVersion, - - // ionic - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.NpmVersion, - steps.GenerateCordovaBuildConfigVersion, - steps.IonicArchiveVersion, - steps.DeployToBitriseIoVersion, - - // ios - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.RecreateUserSchemesVersion, - steps.CocoapodsInstallVersion, - steps.XcodeTestVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.RecreateUserSchemesVersion, - steps.CocoapodsInstallVersion, - steps.XcodeTestVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - // macos - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.RecreateUserSchemesVersion, - steps.CocoapodsInstallVersion, - steps.XcodeTestMacVersion, - steps.XcodeArchiveMacVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.CachePullVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.RecreateUserSchemesVersion, - steps.CocoapodsInstallVersion, - steps.XcodeTestMacVersion, - steps.DeployToBitriseIoVersion, - steps.CachePushVersion, - - // other - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.DeployToBitriseIoVersion, - - // react native - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.InstallMissingAndroidToolsVersion, - steps.AndroidBuildVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.NpmVersion, - steps.DeployToBitriseIoVersion, - - // react native expo with expo kit - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.ExpoDetachVersion, - steps.InstallMissingAndroidToolsVersion, - steps.AndroidBuildVersion, - steps.CertificateAndProfileInstallerVersion, - steps.CocoapodsInstallVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.NpmVersion, - steps.DeployToBitriseIoVersion, - - // react native expo (plain) - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.ExpoDetachVersion, - steps.InstallMissingAndroidToolsVersion, - steps.AndroidBuildVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.NpmVersion, - steps.DeployToBitriseIoVersion, - - // xamarin - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XamarinUserManagementVersion, - steps.NugetRestoreVersion, - steps.XamarinComponentsRestoreVersion, - steps.XamarinArchiveVersion, - steps.DeployToBitriseIoVersion, -} - -var customConfigResultYML = fmt.Sprintf(`options: - android: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - _: - title: Module - env_key: MODULE - value_map: - _: - title: Variant - env_key: VARIANT - value_map: - "": - config: default-android-config - cordova: - title: Directory of Cordova Config.xml - env_key: CORDOVA_WORK_DIR - value_map: - _: - title: Platform to use in cordova-cli commands - env_key: CORDOVA_PLATFORM - value_map: - android: - config: default-cordova-config - ios: - config: default-cordova-config - ios,android: - config: default-cordova-config - fastlane: - title: Working directory - env_key: FASTLANE_WORK_DIR - value_map: - _: - title: Fastlane lane - env_key: FASTLANE_LANE - value_map: - _: - config: default-fastlane-config - flutter: - title: Project Location - env_key: BITRISE_FLUTTER_PROJECT_LOCATION - value_map: - _: - title: Run tests found in the project - value_map: - "no": - title: Platform - value_map: - android: - config: flutter-config-app-android - both: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - _: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - _: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: flutter-config-app-both - app-store: - config: flutter-config-app-both - development: - config: flutter-config-app-both - enterprise: - config: flutter-config-app-both - ios: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - _: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - _: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: flutter-config-app-ios - app-store: - config: flutter-config-app-ios - development: - config: flutter-config-app-ios - enterprise: - config: flutter-config-app-ios - none: - config: flutter-config - "yes": - title: Platform - value_map: - android: - config: flutter-config-test-app-android - both: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - _: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - _: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: flutter-config-test-app-both - app-store: - config: flutter-config-test-app-both - development: - config: flutter-config-test-app-both - enterprise: - config: flutter-config-test-app-both - ios: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - _: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - _: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: flutter-config-test-app-ios - app-store: - config: flutter-config-test-app-ios - development: - config: flutter-config-test-app-ios - enterprise: - config: flutter-config-test-app-ios - none: - config: flutter-config-test - ionic: - title: Directory of Ionic Config.xml - env_key: IONIC_WORK_DIR - value_map: - _: - title: Platform to use in ionic-cli commands - env_key: IONIC_PLATFORM - value_map: - android: - config: default-ionic-config - ios: - config: default-ionic-config - ios,android: - config: default-ionic-config - ios: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - _: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - _: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: default-ios-config - app-store: - config: default-ios-config - development: - config: default-ios-config - enterprise: - config: default-ios-config - macos: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - _: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - _: - title: |- - Application export method - NOTE: `+"`none`"+` means: Export a copy of the application without re-signing. - env_key: BITRISE_EXPORT_METHOD - value_map: - app-store: - config: default-macos-config - developer-id: - config: default-macos-config - development: - config: default-macos-config - none: - config: default-macos-config - react-native: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - _: - title: Module - env_key: MODULE - value_map: - _: - title: Variant - env_key: VARIANT - value_map: - "": - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - _: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - _: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: default-react-native-config - app-store: - config: default-react-native-config - development: - config: default-react-native-config - enterprise: - config: default-react-native-config - react-native-expo: - title: Project uses Expo Kit (any js file imports expo dependency)? - env_key: USES_EXPO_KIT - value_map: - "no": - title: The iOS project path generated ny the 'expo eject' process - env_key: BITRISE_PROJECT_PATH - value_map: - _: - title: The iOS scheme name generated by the 'expo eject' process - env_key: BITRISE_SCHEME - value_map: - _: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - title: Project root directory (the directory of the project app.json/package.json - file) - env_key: WORKDIR - value_map: - _: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - config: react-native-expo-plain-default-config - app-store: - title: Project root directory (the directory of the project app.json/package.json - file) - env_key: WORKDIR - value_map: - _: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - config: react-native-expo-plain-default-config - development: - title: Project root directory (the directory of the project app.json/package.json - file) - env_key: WORKDIR - value_map: - _: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - config: react-native-expo-plain-default-config - enterprise: - title: Project root directory (the directory of the project app.json/package.json - file) - env_key: WORKDIR - value_map: - _: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - config: react-native-expo-plain-default-config - "yes": - title: The iOS workspace path generated ny the 'expo eject' process - env_key: BITRISE_PROJECT_PATH - value_map: - _: - title: The iOS scheme name generated by the 'expo eject' process - env_key: BITRISE_SCHEME - value_map: - _: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - title: Project root directory (the directory of the project app.json/package.json - file) - env_key: WORKDIR - value_map: - _: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - title: Expo username - env_key: EXPO_USERNAME - value_map: - _: - title: Expo password - env_key: EXPO_PASSWORD - value_map: - _: - config: react-native-expo-expo-kit-default-config - app-store: - title: Project root directory (the directory of the project app.json/package.json - file) - env_key: WORKDIR - value_map: - _: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - title: Expo username - env_key: EXPO_USERNAME - value_map: - _: - title: Expo password - env_key: EXPO_PASSWORD - value_map: - _: - config: react-native-expo-expo-kit-default-config - development: - title: Project root directory (the directory of the project app.json/package.json - file) - env_key: WORKDIR - value_map: - _: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - title: Expo username - env_key: EXPO_USERNAME - value_map: - _: - title: Expo password - env_key: EXPO_PASSWORD - value_map: - _: - config: react-native-expo-expo-kit-default-config - enterprise: - title: Project root directory (the directory of the project app.json/package.json - file) - env_key: WORKDIR - value_map: - _: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - title: Expo username - env_key: EXPO_USERNAME - value_map: - _: - title: Expo password - env_key: EXPO_PASSWORD - value_map: - _: - config: react-native-expo-expo-kit-default-config - xamarin: - title: Path to the Xamarin Solution file - env_key: BITRISE_PROJECT_PATH - value_map: - _: - title: Xamarin solution configuration - env_key: BITRISE_XAMARIN_CONFIGURATION - value_map: - _: - title: Xamarin solution platform - env_key: BITRISE_XAMARIN_PLATFORM - value_map: - _: - config: default-xamarin-config -configs: - android: - default-android-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: android - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - description: | - ## How to get a signed APK - - This workflow contains the **Sign APK** step. To sign your APK all you have to do is to: - - 1. Click on **Code Signing** tab - 1. Find the **ANDROID KEYSTORE FILE** section - 1. Click or drop your file on the upload file field - 1. Fill the displayed 3 input fields: - 1. **Keystore password** - 1. **Keystore alias** - 1. **Private key password** - 1. Click on **[Save metadata]** button - - That's it! From now on, **Sign APK** step will receive your uploaded files. - - ## To run this workflow - - If you want to run this workflow manually: - - 1. Open the app's build list page - 2. Click on **[Start/Schedule a Build]** button - 3. Select **deploy** in **Workflow** dropdown input - 4. Click **[Start Build]** button - - Or if you need this workflow to be started by a GIT event: - - 1. Click on **Triggers** tab - 2. Setup your desired event (push/tag/pull) and select **deploy** workflow - 3. Click on **[Done]** and then **[Save]** buttons - - The next change in your repository that matches any of your trigger map event will start **deploy** workflow. - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - change-android-versioncode-and-versionname@%s: - inputs: - - build_gradle_path: $PROJECT_LOCATION/$MODULE/build.gradle - - android-lint@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - android-unit-test@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - android-build@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - sign-apk@%s: - run_if: '{{getenv "BITRISEIO_ANDROID_KEYSTORE_URL" | ne ""}}' - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - android-lint@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - android-unit-test@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - cordova: - default-cordova-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: cordova - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - npm@%s: - inputs: - - command: install - - workdir: $CORDOVA_WORK_DIR - - generate-cordova-build-configuration@%s: {} - - cordova-archive@%s: - inputs: - - workdir: $CORDOVA_WORK_DIR - - platform: $CORDOVA_PLATFORM - - target: emulator - - deploy-to-bitrise-io@%s: {} - fastlane: - default-fastlane-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: other - app: - envs: - - FASTLANE_XCODE_LIST_TIMEOUT: "120" - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - fastlane@%s: - inputs: - - lane: $FASTLANE_LANE - - work_dir: $FASTLANE_WORK_DIR - - deploy-to-bitrise-io@%s: {} - flutter: - flutter-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-app-android: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: android - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-app-both: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: both - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-app-ios: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: ios - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test-app-android: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: android - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test-app-both: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: both - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - flutter-config-test-app-ios: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: flutter - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-build@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - platform: ios - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - flutter-installer@%s: {} - - flutter-analyze@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - flutter-test@%s: - inputs: - - project_location: $BITRISE_FLUTTER_PROJECT_LOCATION - - deploy-to-bitrise-io@%s: {} - ionic: - default-ionic-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: ionic - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - npm@%s: - inputs: - - command: install - - workdir: $IONIC_WORK_DIR - - generate-cordova-build-configuration@%s: {} - - ionic-archive@%s: - inputs: - - workdir: $IONIC_WORK_DIR - - platform: $IONIC_PLATFORM - - target: emulator - - deploy-to-bitrise-io@%s: {} - ios: - default-ios-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: ios - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - recreate-user-schemes@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - cocoapods-install@%s: {} - - xcode-test@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - recreate-user-schemes@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - cocoapods-install@%s: {} - - xcode-test@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - macos: - default-macos-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: macos - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - recreate-user-schemes@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - cocoapods-install@%s: {} - - xcode-test-mac@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - xcode-archive-mac@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - cache-pull@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - recreate-user-schemes@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - cocoapods-install@%s: {} - - xcode-test-mac@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - deploy-to-bitrise-io@%s: {} - - cache-push@%s: {} - other: - other-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: other - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - deploy-to-bitrise-io@%s: {} - react-native: - default-react-native-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: react-native - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - command: install - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - android-build@%s: - inputs: - - project_location: $PROJECT_LOCATION - - certificate-and-profile-installer@%s: {} - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - command: install - - npm@%s: - inputs: - - command: test - - deploy-to-bitrise-io@%s: {} - react-native-expo: - default-react-native-expo-expo-kit-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: react-native-expo - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - description: "## Configure Android part of the deploy workflow\n\nTo generate - a signed APK:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. - Add **Sign APK step right after Android Build step**\n1. Click on **Code Signing** - tab\n1. Find the **ANDROID KEYSTORE FILE** section\n1. Click or drop your file - on the upload file field\n1. Fill the displayed 3 input fields:\n1. **Keystore - password**\n1. **Keystore alias**\n1. **Private key password**\n1. Click on - **[Save metadata]** button\n\nThat's it! From now on, **Sign APK** step will - receive your uploaded files.\n\n## Configure iOS part of the deploy workflow\n\nTo - generate IPA:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. - Click on **Code Signing** tab\n1. Find the **PROVISIONING PROFILE** section\n1. - Click or drop your file on the upload file field\n1. Find the **CODE SIGNING - IDENTITY** section\n1. Click or drop your file on the upload file field\n1. - Click on **Workflows** tab\n1. Select deploy workflow\n1. Select **Xcode Archive - & Export for iOS** step\n1. Open **Force Build Settings** input group\n1. Specify - codesign settings\nSet **Force code signing with Development Team**, **Force - code signing with Code Signing Identity** \nand **Force code signing with Provisioning - Profile** inputs regarding to the uploaded codesigning files\n1. Specify manual - codesign style\nIf the codesigning files, are generated manually on the Apple - Developer Portal, \nyou need to explicitly specify to use manual coedsign settings - \ \n(as ejected rn projects have xcode managed codesigning turned on). \nTo - do so, add 'CODE_SIGN_STYLE=\"Manual\"' to 'Additional options for xcodebuild - call' input\n\n## To run this workflow\n\nIf you want to run this workflow manually:\n\n1. - Open the app's build list page\n2. Click on **[Start/Schedule a Build]** button\n3. - Select **deploy** in **Workflow** dropdown input\n4. Click **[Start Build]** - button\n\nOr if you need this workflow to be started by a GIT event:\n\n1. Click - on **Triggers** tab\n2. Setup your desired event (push/tag/pull) and select - **deploy** workflow\n3. Click on **[Done]** and then **[Save]** buttons\n\nThe - next change in your repository that matches any of your trigger map event will - start **deploy** workflow.\n" - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - workdir: $WORKDIR - - command: install - - expo-detach@%s: - inputs: - - project_path: $WORKDIR - - user_name: $EXPO_USERNAME - - password: $EXPO_PASSWORD - - run_publish: "yes" - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - android-build@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - certificate-and-profile-installer@%s: {} - - cocoapods-install@%s: {} - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - workdir: $WORKDIR - - command: install - - npm@%s: - inputs: - - workdir: $WORKDIR - - command: test - - deploy-to-bitrise-io@%s: {} - default-react-native-expo-plain-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: react-native-expo - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - description: "## Configure Android part of the deploy workflow\n\nTo generate - a signed APK:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. - Add **Sign APK step right after Android Build step**\n1. Click on **Code Signing** - tab\n1. Find the **ANDROID KEYSTORE FILE** section\n1. Click or drop your file - on the upload file field\n1. Fill the displayed 3 input fields:\n1. **Keystore - password**\n1. **Keystore alias**\n1. **Private key password**\n1. Click on - **[Save metadata]** button\n\nThat's it! From now on, **Sign APK** step will - receive your uploaded files.\n\n## Configure iOS part of the deploy workflow\n\nTo - generate IPA:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. - Click on **Code Signing** tab\n1. Find the **PROVISIONING PROFILE** section\n1. - Click or drop your file on the upload file field\n1. Find the **CODE SIGNING - IDENTITY** section\n1. Click or drop your file on the upload file field\n1. - Click on **Workflows** tab\n1. Select deploy workflow\n1. Select **Xcode Archive - & Export for iOS** step\n1. Open **Force Build Settings** input group\n1. Specify - codesign settings\nSet **Force code signing with Development Team**, **Force - code signing with Code Signing Identity** \nand **Force code signing with Provisioning - Profile** inputs regarding to the uploaded codesigning files\n1. Specify manual - codesign style\nIf the codesigning files, are generated manually on the Apple - Developer Portal, \nyou need to explicitly specify to use manual coedsign settings - \ \n(as ejected rn projects have xcode managed codesigning turned on). \nTo - do so, add 'CODE_SIGN_STYLE=\"Manual\"' to 'Additional options for xcodebuild - call' input\n\n## To run this workflow\n\nIf you want to run this workflow manually:\n\n1. - Open the app's build list page\n2. Click on **[Start/Schedule a Build]** button\n3. - Select **deploy** in **Workflow** dropdown input\n4. Click **[Start Build]** - button\n\nOr if you need this workflow to be started by a GIT event:\n\n1. Click - on **Triggers** tab\n2. Setup your desired event (push/tag/pull) and select - **deploy** workflow\n3. Click on **[Done]** and then **[Save]** buttons\n\nThe - next change in your repository that matches any of your trigger map event will - start **deploy** workflow.\n" - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - workdir: $WORKDIR - - command: install - - expo-detach@%s: - inputs: - - project_path: $WORKDIR - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - android-build@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - certificate-and-profile-installer@%s: {} - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - workdir: $WORKDIR - - command: install - - npm@%s: - inputs: - - workdir: $WORKDIR - - command: test - - deploy-to-bitrise-io@%s: {} - xamarin: - default-xamarin-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: xamarin - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - xamarin-user-management@%s: - run_if: .IsCI - - nuget-restore@%s: {} - - xamarin-components-restore@%s: {} - - xamarin-archive@%s: - inputs: - - xamarin_solution: $BITRISE_PROJECT_PATH - - xamarin_configuration: $BITRISE_XAMARIN_CONFIGURATION - - xamarin_platform: $BITRISE_XAMARIN_PLATFORM - - deploy-to-bitrise-io@%s: {} -`, customConfigVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_expo_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_expo_test.go deleted file mode 100644 index 7406f9dc..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_expo_test.go +++ /dev/null @@ -1,446 +0,0 @@ -package integration - -import ( - "fmt" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestReactNativeExpoWithExpoKit(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__reactnative_expo_with_expo_kit__") - require.NoError(t, err) - - t.Log("BitriseExpoKit") - { - sampleAppDir := filepath.Join(tmpDir, "BitriseExpoKit") - sampleAppURL := "https://github.com/bitrise-samples/BitriseExpoKit.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - - validateConfigExpectation(t, "BitriseExpoKit", strings.TrimSpace(bitriseExpoKitResultYML), strings.TrimSpace(result), bitriseExpoKitVersions...) - } -} - -func TestReactNativeExpo(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__reactnative_expo__") - require.NoError(t, err) - - t.Log("BitriseCRNA") - { - sampleAppDir := filepath.Join(tmpDir, "BitriseCRNA") - sampleAppURL := "https://github.com/bitrise-samples/BitriseCRNA.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - - validateConfigExpectation(t, "BitriseCRNA", strings.TrimSpace(bitriseCRNAResultYML), strings.TrimSpace(result), bitriseCRNAVersions...) - } -} - -var bitriseCRNAVersions = []interface{}{ - models.FormatVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.ExpoDetachVersion, - steps.InstallMissingAndroidToolsVersion, - steps.AndroidBuildVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, -} - -var bitriseCRNAResultYML = fmt.Sprintf(`options: - react-native-expo: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - ios/BitriseCRNA.xcodeproj: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - BitriseCRNA: - title: iOS Development team - env_key: BITRISE_IOS_DEVELOPMENT_TEAM - value_map: - _: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - config: react-native-expo-config - app-store: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - config: react-native-expo-config - development: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - config: react-native-expo-config - enterprise: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - config: react-native-expo-config -configs: - react-native-expo: - react-native-expo-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: react-native-expo - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - description: "## Configure Android part of the deploy workflow\n\nTo generate - a signed APK:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. - Add **Sign APK step right after Android Build step**\n1. Click on **Code Signing** - tab\n1. Find the **ANDROID KEYSTORE FILE** section\n1. Click or drop your file - on the upload file field\n1. Fill the displayed 3 input fields:\n1. **Keystore - password**\n1. **Keystore alias**\n1. **Private key password**\n1. Click on - **[Save metadata]** button\n\nThat's it! From now on, **Sign APK** step will - receive your uploaded files.\n\n## Configure iOS part of the deploy workflow\n\nTo - generate IPA:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. - Click on **Code Signing** tab\n1. Find the **PROVISIONING PROFILE** section\n1. - Click or drop your file on the upload file field\n1. Find the **CODE SIGNING - IDENTITY** section\n1. Click or drop your file on the upload file field\n1. - Click on **Workflows** tab\n1. Select deploy workflow\n1. Select **Xcode Archive - & Export for iOS** step\n1. Open **Force Build Settings** input group\n1. Specify - codesign settings\nSet **Force code signing with Development Team**, **Force - code signing with Code Signing Identity** \nand **Force code signing with Provisioning - Profile** inputs regarding to the uploaded codesigning files\n1. Specify manual - codesign style\nIf the codesigning files, are generated manually on the Apple - Developer Portal, \nyou need to explicitly specify to use manual coedsign settings - \ \n(as ejected rn projects have xcode managed codesigning turned on). \nTo - do so, add 'CODE_SIGN_STYLE=\"Manual\"' to 'Additional options for xcodebuild - call' input\n\n## To run this workflow\n\nIf you want to run this workflow manually:\n\n1. - Open the app's build list page\n2. Click on **[Start/Schedule a Build]** button\n3. - Select **deploy** in **Workflow** dropdown input\n4. Click **[Start Build]** - button\n\nOr if you need this workflow to be started by a GIT event:\n\n1. Click - on **Triggers** tab\n2. Setup your desired event (push/tag/pull) and select - **deploy** workflow\n3. Click on **[Done]** and then **[Save]** buttons\n\nThe - next change in your repository that matches any of your trigger map event will - start **deploy** workflow.\n" - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - command: install - - expo-detach@%s: - inputs: - - project_path: ./ - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - android-build@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - certificate-and-profile-installer@%s: {} - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - configuration: Release - - export_method: $BITRISE_EXPORT_METHOD - - force_team_id: $BITRISE_IOS_DEVELOPMENT_TEAM - - xcodebuild_options: -UseModernBuildSystem=NO - - deploy-to-bitrise-io@%s: {} -warnings: - react-native-expo: [] -`, bitriseCRNAVersions...) - -var bitriseExpoKitVersions = []interface{}{ - models.FormatVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.ExpoDetachVersion, - steps.InstallMissingAndroidToolsVersion, - steps.AndroidBuildVersion, - steps.CertificateAndProfileInstallerVersion, - steps.CocoapodsInstallVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.NpmVersion, - steps.DeployToBitriseIoVersion, -} - -var bitriseExpoKitResultYML = fmt.Sprintf(`options: - react-native-expo: - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - ios/bitriseexpokit.xcworkspace: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - bitriseexpokit: - title: iOS Development team - env_key: BITRISE_IOS_DEVELOPMENT_TEAM - value_map: - _: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - title: Expo username - env_key: EXPO_USERNAME - value_map: - _: - title: Expo password - env_key: EXPO_PASSWORD - value_map: - _: - config: react-native-expo-config - app-store: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - title: Expo username - env_key: EXPO_USERNAME - value_map: - _: - title: Expo password - env_key: EXPO_PASSWORD - value_map: - _: - config: react-native-expo-config - development: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - title: Expo username - env_key: EXPO_USERNAME - value_map: - _: - title: Expo password - env_key: EXPO_PASSWORD - value_map: - _: - config: react-native-expo-config - enterprise: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - ./android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - Release: - title: Expo username - env_key: EXPO_USERNAME - value_map: - _: - title: Expo password - env_key: EXPO_PASSWORD - value_map: - _: - config: react-native-expo-config -configs: - react-native-expo: - react-native-expo-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: react-native-expo - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - description: "## Configure Android part of the deploy workflow\n\nTo generate - a signed APK:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. - Add **Sign APK step right after Android Build step**\n1. Click on **Code Signing** - tab\n1. Find the **ANDROID KEYSTORE FILE** section\n1. Click or drop your file - on the upload file field\n1. Fill the displayed 3 input fields:\n1. **Keystore - password**\n1. **Keystore alias**\n1. **Private key password**\n1. Click on - **[Save metadata]** button\n\nThat's it! From now on, **Sign APK** step will - receive your uploaded files.\n\n## Configure iOS part of the deploy workflow\n\nTo - generate IPA:\n\n1. Open the **Workflow** tab of your project on Bitrise.io\n1. - Click on **Code Signing** tab\n1. Find the **PROVISIONING PROFILE** section\n1. - Click or drop your file on the upload file field\n1. Find the **CODE SIGNING - IDENTITY** section\n1. Click or drop your file on the upload file field\n1. - Click on **Workflows** tab\n1. Select deploy workflow\n1. Select **Xcode Archive - & Export for iOS** step\n1. Open **Force Build Settings** input group\n1. Specify - codesign settings\nSet **Force code signing with Development Team**, **Force - code signing with Code Signing Identity** \nand **Force code signing with Provisioning - Profile** inputs regarding to the uploaded codesigning files\n1. Specify manual - codesign style\nIf the codesigning files, are generated manually on the Apple - Developer Portal, \nyou need to explicitly specify to use manual coedsign settings - \ \n(as ejected rn projects have xcode managed codesigning turned on). \nTo - do so, add 'CODE_SIGN_STYLE=\"Manual\"' to 'Additional options for xcodebuild - call' input\n\n## To run this workflow\n\nIf you want to run this workflow manually:\n\n1. - Open the app's build list page\n2. Click on **[Start/Schedule a Build]** button\n3. - Select **deploy** in **Workflow** dropdown input\n4. Click **[Start Build]** - button\n\nOr if you need this workflow to be started by a GIT event:\n\n1. Click - on **Triggers** tab\n2. Setup your desired event (push/tag/pull) and select - **deploy** workflow\n3. Click on **[Done]** and then **[Save]** buttons\n\nThe - next change in your repository that matches any of your trigger map event will - start **deploy** workflow.\n" - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - command: install - - expo-detach@%s: - inputs: - - project_path: ./ - - user_name: $EXPO_USERNAME - - password: $EXPO_PASSWORD - - run_publish: "yes" - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - android-build@%s: - inputs: - - project_location: $PROJECT_LOCATION - - module: $MODULE - - variant: $VARIANT - - certificate-and-profile-installer@%s: {} - - cocoapods-install@%s: {} - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - configuration: Release - - export_method: $BITRISE_EXPORT_METHOD - - force_team_id: $BITRISE_IOS_DEVELOPMENT_TEAM - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - command: install - - npm@%s: - inputs: - - command: test - - deploy-to-bitrise-io@%s: {} -warnings: - react-native-expo: [] -`, bitriseExpoKitVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_test.go deleted file mode 100644 index e1ef86ed..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/reactnative_test.go +++ /dev/null @@ -1,299 +0,0 @@ -package integration - -import ( - "fmt" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestReactNative(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__reactnative__") - require.NoError(t, err) - - t.Log("sample-apps-react-native-ios-and-android") - { - sampleAppDir := filepath.Join(tmpDir, "sample-apps-react-native-ios-and-android") - sampleAppURL := "https://github.com/bitrise-samples/sample-apps-react-native-ios-and-android.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - - validateConfigExpectation(t, "sample-apps-react-native-ios-and-android", strings.TrimSpace(sampleAppsReactNativeIosAndAndroidResultYML), strings.TrimSpace(result), sampleAppsReactNativeIosAndAndroidVersions...) - } - - t.Log("sample-apps-react-native-subdir") - { - sampleAppDir := filepath.Join(tmpDir, "sample-apps-react-native-subdir") - sampleAppURL := "https://github.com/bitrise-samples/sample-apps-react-native-subdir.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - - validateConfigExpectation(t, "sample-apps-react-native-subdir", strings.TrimSpace(sampleAppsReactNativeSubdirResultYML), strings.TrimSpace(result), sampleAppsReactNativeSubdirVersions...) - } -} - -var sampleAppsReactNativeSubdirVersions = []interface{}{ - models.FormatVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.InstallMissingAndroidToolsVersion, - steps.AndroidBuildVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.NpmVersion, - steps.DeployToBitriseIoVersion, -} - -var sampleAppsReactNativeSubdirResultYML = fmt.Sprintf(`options: - react-native: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - project/android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - "": - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - project/ios/SampleAppsReactNativeAndroid.xcodeproj: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - SampleAppsReactNativeAndroid: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: react-native-android-ios-test-config - app-store: - config: react-native-android-ios-test-config - development: - config: react-native-android-ios-test-config - enterprise: - config: react-native-android-ios-test-config - SampleAppsReactNativeAndroid-tvOS: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: react-native-android-ios-test-config - app-store: - config: react-native-android-ios-test-config - development: - config: react-native-android-ios-test-config - enterprise: - config: react-native-android-ios-test-config -configs: - react-native: - react-native-android-ios-test-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: react-native - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - workdir: project - - command: install - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - android-build@%s: - inputs: - - project_location: $PROJECT_LOCATION - - certificate-and-profile-installer@%s: {} - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - workdir: project - - command: install - - npm@%s: - inputs: - - workdir: project - - command: test - - deploy-to-bitrise-io@%s: {} -warnings: - react-native: [] -`, sampleAppsReactNativeSubdirVersions...) - -var sampleAppsReactNativeIosAndAndroidVersions = []interface{}{ - models.FormatVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.InstallMissingAndroidToolsVersion, - steps.AndroidBuildVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XcodeArchiveVersion, - steps.DeployToBitriseIoVersion, - - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.NpmVersion, - steps.NpmVersion, - steps.DeployToBitriseIoVersion, -} - -var sampleAppsReactNativeIosAndAndroidResultYML = fmt.Sprintf(`options: - react-native: - title: The root directory of an Android project - env_key: PROJECT_LOCATION - value_map: - android: - title: Module - env_key: MODULE - value_map: - app: - title: Variant - env_key: VARIANT - value_map: - "": - title: Project (or Workspace) path - env_key: BITRISE_PROJECT_PATH - value_map: - ios/SampleAppsReactNativeAndroid.xcodeproj: - title: Scheme name - env_key: BITRISE_SCHEME - value_map: - SampleAppsReactNativeAndroid: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: react-native-android-ios-test-config - app-store: - config: react-native-android-ios-test-config - development: - config: react-native-android-ios-test-config - enterprise: - config: react-native-android-ios-test-config - SampleAppsReactNativeAndroid-tvOS: - title: ipa export method - env_key: BITRISE_EXPORT_METHOD - value_map: - ad-hoc: - config: react-native-android-ios-test-config - app-store: - config: react-native-android-ios-test-config - development: - config: react-native-android-ios-test-config - enterprise: - config: react-native-android-ios-test-config -configs: - react-native: - react-native-android-ios-test-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: react-native - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - deploy: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - command: install - - install-missing-android-tools@%s: - inputs: - - gradlew_path: $PROJECT_LOCATION/gradlew - - android-build@%s: - inputs: - - project_location: $PROJECT_LOCATION - - certificate-and-profile-installer@%s: {} - - xcode-archive@%s: - inputs: - - project_path: $BITRISE_PROJECT_PATH - - scheme: $BITRISE_SCHEME - - export_method: $BITRISE_EXPORT_METHOD - - configuration: Release - - deploy-to-bitrise-io@%s: {} - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - npm@%s: - inputs: - - command: install - - npm@%s: - inputs: - - command: test - - deploy-to-bitrise-io@%s: {} -warnings: - react-native: [] -`, sampleAppsReactNativeIosAndAndroidVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/xamarin_test.go b/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/xamarin_test.go deleted file mode 100644 index 44379ff2..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/_tests/integration/xamarin_test.go +++ /dev/null @@ -1,281 +0,0 @@ -package integration - -import ( - "fmt" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestXamarin(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__xamarin__") - require.NoError(t, err) - - t.Log("xamarin-sample-app") - { - sampleAppDir := filepath.Join(tmpDir, "xamarin-sample-app") - sampleAppURL := "https://github.com/bitrise-samples/xamarin-sample-app.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - require.Equal(t, strings.TrimSpace(xamarinSampleAppResultYML), strings.TrimSpace(result)) - } - - t.Log("sample-apps-xamarin-ios") - { - sampleAppDir := filepath.Join(tmpDir, "sample-apps-xamarin-ios") - sampleAppURL := "https://github.com/bitrise-io/sample-apps-xamarin-ios.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - require.Equal(t, strings.TrimSpace(sampleAppsXamarinIosResultYML), strings.TrimSpace(result)) - } - - t.Log("sample-apps-xamarin-android") - { - sampleAppDir := filepath.Join(tmpDir, "sample-apps-xamarin-android") - sampleAppURL := "https://github.com/bitrise-io/sample-apps-xamarin-android.git" - gitClone(t, sampleAppDir, sampleAppURL) - - cmd := command.New(binPath(), "--ci", "config", "--dir", sampleAppDir, "--output-dir", sampleAppDir) - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - scanResultPth := filepath.Join(sampleAppDir, "result.yml") - - result, err := fileutil.ReadStringFromFile(scanResultPth) - require.NoError(t, err) - require.Equal(t, strings.TrimSpace(sampleAppsXamarinAndroidResultYML), strings.TrimSpace(result)) - } -} - -var xamarinSampleAppVersions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.XamarinUserManagementVersion, - steps.NugetRestoreVersion, - steps.XamarinComponentsRestoreVersion, - steps.XamarinArchiveVersion, - steps.DeployToBitriseIoVersion, -} - -var xamarinSampleAppResultYML = fmt.Sprintf(`options: - xamarin: - title: Path to the Xamarin Solution file - env_key: BITRISE_PROJECT_PATH - value_map: - XamarinSampleApp.sln: - title: Xamarin solution configuration - env_key: BITRISE_XAMARIN_CONFIGURATION - value_map: - Debug: - title: Xamarin solution platform - env_key: BITRISE_XAMARIN_PLATFORM - value_map: - Any CPU: - config: xamarin-nuget-components-config - iPhone: - config: xamarin-nuget-components-config - iPhoneSimulator: - config: xamarin-nuget-components-config - Release: - title: Xamarin solution platform - env_key: BITRISE_XAMARIN_PLATFORM - value_map: - Any CPU: - config: xamarin-nuget-components-config - iPhone: - config: xamarin-nuget-components-config - iPhoneSimulator: - config: xamarin-nuget-components-config -configs: - xamarin: - xamarin-nuget-components-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: xamarin - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - xamarin-user-management@%s: - run_if: .IsCI - - nuget-restore@%s: {} - - xamarin-components-restore@%s: {} - - xamarin-archive@%s: - inputs: - - xamarin_solution: $BITRISE_PROJECT_PATH - - xamarin_configuration: $BITRISE_XAMARIN_CONFIGURATION - - xamarin_platform: $BITRISE_XAMARIN_PLATFORM - - deploy-to-bitrise-io@%s: {} -warnings: - xamarin: [] -`, xamarinSampleAppVersions...) - -var sampleAppsXamarinIosVersions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.NugetRestoreVersion, - steps.XamarinArchiveVersion, - steps.DeployToBitriseIoVersion, -} - -var sampleAppsXamarinIosResultYML = fmt.Sprintf(`options: - xamarin: - title: Path to the Xamarin Solution file - env_key: BITRISE_PROJECT_PATH - value_map: - CreditCardValidator.iOS.sln: - title: Xamarin solution configuration - env_key: BITRISE_XAMARIN_CONFIGURATION - value_map: - Debug: - title: Xamarin solution platform - env_key: BITRISE_XAMARIN_PLATFORM - value_map: - Any CPU: - config: xamarin-nuget-config - iPhone: - config: xamarin-nuget-config - iPhoneSimulator: - config: xamarin-nuget-config - Release: - title: Xamarin solution platform - env_key: BITRISE_XAMARIN_PLATFORM - value_map: - Any CPU: - config: xamarin-nuget-config - iPhone: - config: xamarin-nuget-config - iPhoneSimulator: - config: xamarin-nuget-config -configs: - xamarin: - xamarin-nuget-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: xamarin - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - nuget-restore@%s: {} - - xamarin-archive@%s: - inputs: - - xamarin_solution: $BITRISE_PROJECT_PATH - - xamarin_configuration: $BITRISE_XAMARIN_CONFIGURATION - - xamarin_platform: $BITRISE_XAMARIN_PLATFORM - - deploy-to-bitrise-io@%s: {} -warnings: - xamarin: [] -`, sampleAppsXamarinIosVersions...) - -var sampleAppsXamarinAndroidVersions = []interface{}{ - models.FormatVersion, - steps.ActivateSSHKeyVersion, - steps.GitCloneVersion, - steps.ScriptVersion, - steps.CertificateAndProfileInstallerVersion, - steps.NugetRestoreVersion, - steps.XamarinArchiveVersion, - steps.DeployToBitriseIoVersion, -} - -var sampleAppsXamarinAndroidResultYML = fmt.Sprintf(`options: - xamarin: - title: Path to the Xamarin Solution file - env_key: BITRISE_PROJECT_PATH - value_map: - CreditCardValidator.Droid.sln: - title: Xamarin solution configuration - env_key: BITRISE_XAMARIN_CONFIGURATION - value_map: - Debug: - title: Xamarin solution platform - env_key: BITRISE_XAMARIN_PLATFORM - value_map: - Any CPU: - config: xamarin-nuget-config - Release: - title: Xamarin solution platform - env_key: BITRISE_XAMARIN_PLATFORM - value_map: - Any CPU: - config: xamarin-nuget-config -configs: - xamarin: - xamarin-nuget-config: | - format_version: "%s" - default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - project_type: xamarin - trigger_map: - - push_branch: '*' - workflow: primary - - pull_request_source_branch: '*' - workflow: primary - workflows: - primary: - steps: - - activate-ssh-key@%s: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@%s: {} - - script@%s: - title: Do anything with Script step - - certificate-and-profile-installer@%s: {} - - nuget-restore@%s: {} - - xamarin-archive@%s: - inputs: - - xamarin_solution: $BITRISE_PROJECT_PATH - - xamarin_configuration: $BITRISE_XAMARIN_CONFIGURATION - - xamarin_platform: $BITRISE_XAMARIN_PLATFORM - - deploy-to-bitrise-io@%s: {} -warnings: - xamarin: [] -`, sampleAppsXamarinAndroidVersions...) diff --git a/vendor/github.com/bitrise-core/bitrise-init/bitrise.yml b/vendor/github.com/bitrise-core/bitrise-init/bitrise.yml deleted file mode 100644 index a3c84bcf..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/bitrise.yml +++ /dev/null @@ -1,155 +0,0 @@ -format_version: "5" -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - -workflows: - # ---------------------------------------------------------------- - # --- workflows for CI and testing - ci-docker: - steps: - - script: - inputs: - - content: |- - #!/bin/env bash - set -ex - docker-compose build - docker-compose run --rm app bitrise run ci - - ci: - after_run: - - go-test - - integration-test - - go-test: - steps: - - script: - title: Export go files to test - inputs: - - content: | - #!/usr/bin/env bash - set -ex - no_vendor_paths="$(go list ./... | grep -v vendor)" - envman add --key GOLIST_WITHOUT_VENDOR --value "$no_vendor_paths" - - script: - title: Err check - inputs: - - content: | - #!/usr/bin/env bash - set -ex - go get -u github.com/kisielk/errcheck - errcheck -asserts=true -blank=true $GOLIST_WITHOUT_VENDOR - - script: - title: Go lint - inputs: - - content: |- - #!/usr/bin/env bash - set -ex - go get -u github.com/golang/lint/golint - while read -r line; do - echo "-> Linting: $line" - golint_out="$(golint $line)" - if [[ "${golint_out}" != "" ]] ; then - echo "=> Golint issues found:" - echo "${golint_out}" - exit 1 - fi - done <<< "$GOLIST_WITHOUT_VENDOR" - - script: - title: Go test - inputs: - - content: go test ./... - - integration-test: - steps: - - script: - title: Go build - inputs: - - content: |- - #!/bin/bash - set -ex - - # build the new bitrise - current_dir=$(pwd) - current_bitrise_init=$current_dir/_tmp/ci-bin - go build -o $current_bitrise_init - - envman add --key CURRENT_BITRISE_INIT --value $current_bitrise_init - - script: - title: Run integration tests - inputs: - - content: |- - #!/bin/bash - echo "Running integration tests ..." - set -ex - - export INTEGRATION_TEST_BINARY_PATH="$CURRENT_BITRISE_INIT" - go test -v ./_tests/integration/... - - # ---------------------------------------------------------------- - # --- workflows for Utility - dep-update: - title: Do dependency update - steps: - - script: - title: Dependency update - inputs: - - content: |- - #!/bin/bash - set -ex - go get -u -v github.com/golang/dep/cmd/dep - dep ensure -v - dep ensure -v -update - - create-binaries: - title: Create binaries - description: | - Creates Linux and Darwin binaries - steps: - - script: - title: Create binaries - inputs: - - content: | - #!/bin/bash - set -e - set -x - - BIN_NAME="bitrise-init" - - echo - echo "Create final binaries" - echo " Build number: $BITRISE_BUILD_NUMBER" - - export ARCH=x86_64 - export GOARCH=amd64 - - # Create Darwin bin - export OS=Darwin - export GOOS=darwin - - DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" - echo " Create final Darwin binary at: $DEPLOY_PATH" - - version_package="github.com/bitrise-core/bitrise-init/version" - - go build \ - -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ - -o "$DEPLOY_PATH" - - envman add --key OSX_DEPLOY_PATH --value $DEPLOY_PATH - cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH - echo " Copy final Darwin binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" - - - # Create Linux binary - export OS=Linux - export GOOS=linux - - DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" - echo " Create final Linux binary at: $DEPLOY_PATH" - - go build \ - -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ - -o "$DEPLOY_PATH" - - envman add --key LINUX_DEPLOY_PATH --value $DEPLOY_PATH - cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH - echo " Copy final Linux binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" \ No newline at end of file diff --git a/vendor/github.com/bitrise-core/bitrise-init/cli/cli.go b/vendor/github.com/bitrise-core/bitrise-init/cli/cli.go deleted file mode 100644 index 18b19c9d..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/cli/cli.go +++ /dev/null @@ -1,72 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "path" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-core/bitrise-init/version" - "github.com/urfave/cli" -) - -// Run ... -func Run() { - // Parse cl - cli.VersionPrinter = func(c *cli.Context) { - fmt.Println(c.App.Version) - } - - app := cli.NewApp() - - app.Name = path.Base(os.Args[0]) - app.Usage = "Bitrise Init Tool" - app.Version = version.VERSION - app.Author = "" - app.Email = "" - - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "loglevel, l", - Usage: "Log level (options: debug, info, warn, error, fatal, panic).", - EnvVar: "LOGLEVEL", - }, - cli.BoolFlag{ - Name: "ci", - Usage: "If true it indicates that we're used by another tool so don't require any user input!", - EnvVar: "CI", - }, - } - - app.Before = func(c *cli.Context) error { - log.SetFormatter(&log.TextFormatter{ - FullTimestamp: true, - ForceColors: true, - TimestampFormat: "15:04:05", - }) - - // Log level - logLevelStr := c.String("loglevel") - if logLevelStr == "" { - logLevelStr = "info" - } - - level, err := log.ParseLevel(logLevelStr) - if err != nil { - return err - } - log.SetLevel(level) - - return nil - } - - app.Commands = []cli.Command{ - versionCommand, - configCommand, - manualConfigCommand, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/cli/config.go b/vendor/github.com/bitrise-core/bitrise-init/cli/config.go deleted file mode 100644 index 050d0a99..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/cli/config.go +++ /dev/null @@ -1,185 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "path" - "path/filepath" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/output" - "github.com/bitrise-core/bitrise-init/scanner" - "github.com/bitrise-io/go-utils/colorstring" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/urfave/cli" -) - -const ( - defaultScanResultDir = "_scan_result" -) - -var configCommand = cli.Command{ - Name: "config", - Usage: "Generates a bitrise config files based on your project.", - Action: func(c *cli.Context) error { - if err := initConfig(c); err != nil { - log.TErrorf(err.Error()) - os.Exit(1) - } - return nil - }, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "dir", - Usage: "Directory to scan.", - Value: "./", - }, - cli.StringFlag{ - Name: "output-dir", - Usage: "Directory to save scan results.", - Value: "./_scan_result", - }, - cli.StringFlag{ - Name: "format", - Usage: "Output format, options [json, yaml].", - Value: "yaml", - }, - }, -} - -func writeScanResult(scanResult models.ScanResultModel, outputDir string, format output.Format) (string, error) { - pth := path.Join(outputDir, "result") - return output.WriteToFile(scanResult, format, pth) -} - -func initConfig(c *cli.Context) error { - // Config - isCI := c.GlobalBool("ci") - searchDir := c.String("dir") - outputDir := c.String("output-dir") - formatStr := c.String("format") - - if isCI { - log.TInfof(colorstring.Yellow("CI mode")) - } - log.TInfof(colorstring.Yellowf("scan dir: %s", searchDir)) - log.TInfof(colorstring.Yellowf("output dir: %s", outputDir)) - log.TInfof(colorstring.Yellowf("output format: %s", formatStr)) - fmt.Println() - - currentDir, err := pathutil.AbsPath("./") - if err != nil { - return fmt.Errorf("Failed to expand path (%s), error: %s", outputDir, err) - } - - if searchDir == "" { - searchDir = currentDir - } - searchDir, err = pathutil.AbsPath(searchDir) - if err != nil { - return fmt.Errorf("Failed to expand path (%s), error: %s", outputDir, err) - } - - if outputDir == "" { - outputDir = filepath.Join(currentDir, defaultScanResultDir) - } - outputDir, err = pathutil.AbsPath(outputDir) - if err != nil { - return fmt.Errorf("Failed to expand path (%s), error: %s", outputDir, err) - } - if exist, err := pathutil.IsDirExists(outputDir); err != nil { - return err - } else if !exist { - if err := os.MkdirAll(outputDir, 0700); err != nil { - return fmt.Errorf("Failed to create (%s), error: %s", outputDir, err) - } - } - - if formatStr == "" { - formatStr = output.YAMLFormat.String() - } - format, err := output.ParseFormat(formatStr) - if err != nil { - return fmt.Errorf("Failed to parse format (%s), error: %s", formatStr, err) - } - if format != output.JSONFormat && format != output.YAMLFormat { - return fmt.Errorf("Not allowed output format (%s), options: [%s, %s]", format.String(), output.YAMLFormat.String(), output.JSONFormat.String()) - } - // --- - - scanResult := scanner.Config(searchDir) - - platforms := []string{} - for platform := range scanResult.ScannerToOptionRoot { - platforms = append(platforms, platform) - } - - if len(platforms) == 0 { - cmd := command.New("which", "tree") - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - if err != nil || out == "" { - log.TErrorf("tree not installed, can not list files") - } else { - fmt.Println() - cmd := command.NewWithStandardOuts("tree", ".", "-L", "3") - log.TPrintf("$ %s", cmd.PrintableCommandArgs()) - if err := cmd.Run(); err != nil { - log.TErrorf("Failed to list files in current directory, error: %s", err) - } - } - - log.TInfof("Saving outputs:") - scanResult.AddError("general", "No known platform detected") - - outputPth, err := writeScanResult(scanResult, outputDir, format) - if err != nil { - return fmt.Errorf("Failed to write output, error: %s", err) - } - - log.TPrintf("scan result: %s", outputPth) - return fmt.Errorf("No known platform detected") - } - - // Write output to files - if isCI { - log.TInfof("Saving outputs:") - - outputPth, err := writeScanResult(scanResult, outputDir, format) - if err != nil { - return fmt.Errorf("Failed to write output, error: %s", err) - } - - log.TPrintf(" scan result: %s", outputPth) - return nil - } - // --- - - // Select option - log.TInfof("Collecting inputs:") - - config, err := scanner.AskForConfig(scanResult) - if err != nil { - return err - } - - if exist, err := pathutil.IsDirExists(outputDir); err != nil { - return err - } else if !exist { - if err := os.MkdirAll(outputDir, 0700); err != nil { - return fmt.Errorf("Failed to create (%s), error: %s", outputDir, err) - } - } - - pth := path.Join(outputDir, "bitrise.yml") - outputPth, err := output.WriteToFile(config, format, pth) - if err != nil { - return fmt.Errorf("Failed to print result, error: %s", err) - } - log.TInfof(" bitrise.yml template: %s", outputPth) - fmt.Println() - // --- - - return nil -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/cli/manual_config.go b/vendor/github.com/bitrise-core/bitrise-init/cli/manual_config.go deleted file mode 100644 index d53c6648..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/cli/manual_config.go +++ /dev/null @@ -1,132 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "path" - "path/filepath" - - "github.com/bitrise-core/bitrise-init/output" - "github.com/bitrise-core/bitrise-init/scanner" - "github.com/bitrise-io/go-utils/colorstring" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/urfave/cli" -) - -const ( - defaultOutputDir = "_defaults" -) - -var manualConfigCommand = cli.Command{ - Name: "manual-config", - Usage: "Generates default bitrise config files.", - Action: func(c *cli.Context) error { - if err := initManualConfig(c); err != nil { - log.TErrorf(err.Error()) - os.Exit(1) - } - return nil - }, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "output-dir", - Usage: "Directory to save scan results.", - Value: "./_defaults", - }, - cli.StringFlag{ - Name: "format", - Usage: "Output format, options [json, yaml].", - Value: "yaml", - }, - }, -} - -func initManualConfig(c *cli.Context) error { - // Config - isCI := c.GlobalBool("ci") - outputDir := c.String("output-dir") - formatStr := c.String("format") - - if isCI { - log.TInfof(colorstring.Yellow("CI mode")) - } - log.TInfof(colorstring.Yellowf("output dir: %s", outputDir)) - log.TInfof(colorstring.Yellowf("output format: %s", formatStr)) - fmt.Println() - - currentDir, err := pathutil.AbsPath("./") - if err != nil { - return fmt.Errorf("Failed to get current directory, error: %s", err) - } - - if outputDir == "" { - outputDir = filepath.Join(currentDir, defaultOutputDir) - } - outputDir, err = pathutil.AbsPath(outputDir) - if err != nil { - return fmt.Errorf("Failed to expand path (%s), error: %s", outputDir, err) - } - if exist, err := pathutil.IsDirExists(outputDir); err != nil { - return err - } else if !exist { - if err := os.MkdirAll(outputDir, 0700); err != nil { - return fmt.Errorf("Failed to create (%s), error: %s", outputDir, err) - } - } - - if formatStr == "" { - formatStr = output.YAMLFormat.String() - } - format, err := output.ParseFormat(formatStr) - if err != nil { - return fmt.Errorf("Failed to parse format, err: %s", err) - } - if format != output.JSONFormat && format != output.YAMLFormat { - return fmt.Errorf("Not allowed output format (%v), options: [%s, %s]", format, output.YAMLFormat.String(), output.JSONFormat.String()) - } - // --- - - scanResult, err := scanner.ManualConfig() - if err != nil { - return err - } - - // Write output to files - if isCI { - log.TInfof(colorstring.Blue("Saving outputs:")) - - if err := os.MkdirAll(outputDir, 0700); err != nil { - return fmt.Errorf("Failed to create (%s), error: %s", outputDir, err) - } - - pth := path.Join(outputDir, "result") - outputPth, err := output.WriteToFile(scanResult, format, pth) - if err != nil { - return fmt.Errorf("Failed to print result, error: %s", err) - } - log.TInfof(" scan result: %s", colorstring.Blue(outputPth)) - - return nil - } - // --- - - // Select option - log.TInfof(colorstring.Blue("Collecting inputs:")) - - config, err := scanner.AskForConfig(scanResult) - if err != nil { - return err - } - - pth := path.Join(outputDir, "bitrise.yml") - outputPth, err := output.WriteToFile(config, format, pth) - if err != nil { - return fmt.Errorf("Failed to print result, error: %s", err) - } - log.TInfof(" bitrise.yml template: %s", colorstring.Blue(outputPth)) - fmt.Println() - // --- - - return nil -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/cli/version.go b/vendor/github.com/bitrise-core/bitrise-init/cli/version.go deleted file mode 100644 index 941b2935..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/cli/version.go +++ /dev/null @@ -1,79 +0,0 @@ -package cli - -import ( - "fmt" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-core/bitrise-init/output" - "github.com/bitrise-core/bitrise-init/version" - "github.com/urfave/cli" -) - -// VersionOutputModel ... -type VersionOutputModel struct { - Version string `json:"version" yaml:"version"` - BuildNumber string `json:"build_number" yaml:"build_number"` - Commit string `json:"commit" yaml:"commit"` -} - -var versionCommand = cli.Command{ - Name: "version", - Usage: "Prints the version", - Action: func(c *cli.Context) error { - if err := printVersion(c); err != nil { - log.Fatal(err) - } - return nil - }, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "format", - Usage: "Output format, options [raw, json, yaml].", - Value: "raw", - }, - cli.BoolFlag{ - Name: "full", - Usage: "Prints the build number as well.", - }, - }, -} - -func printVersion(c *cli.Context) error { - fullVersion := c.Bool("full") - formatStr := c.String("format") - - if formatStr == "" { - formatStr = output.YAMLFormat.String() - } - format, err := output.ParseFormat(formatStr) - if err != nil { - return fmt.Errorf("Failed to parse format, error: %s", err) - } - - versionOutput := VersionOutputModel{ - Version: version.VERSION, - } - - if fullVersion { - versionOutput.BuildNumber = version.BuildNumber - versionOutput.Commit = version.Commit - } - - var out interface{} - - if format == output.RawFormat { - if fullVersion { - out = fmt.Sprintf("version: %v\nbuild_number: %v\ncommit: %v\n", versionOutput.Version, versionOutput.BuildNumber, versionOutput.Commit) - } else { - out = fmt.Sprintf("%v\n", versionOutput.Version) - } - } else { - out = versionOutput - } - - if err := output.Print(out, format); err != nil { - return fmt.Errorf("Failed to print version, error: %s", err) - } - - return nil -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/docker-compose.yml b/vendor/github.com/bitrise-core/bitrise-init/docker-compose.yml deleted file mode 100644 index 4ce3712f..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/docker-compose.yml +++ /dev/null @@ -1,7 +0,0 @@ -app: - build: . - volumes: - - .:/bitrise/go/src/github.com/bitrise-core/bitrise-init - environment: - CI: "true" - BITRISE_SOURCE_DIR: /bitrise/go/src/github.com/bitrise-core/bitrise-init \ No newline at end of file diff --git a/vendor/github.com/bitrise-core/bitrise-init/gows.yml b/vendor/github.com/bitrise-core/bitrise-init/gows.yml deleted file mode 100644 index 4fd8e6f3..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/gows.yml +++ /dev/null @@ -1 +0,0 @@ -package_name: github.com/bitrise-core/bitrise-init diff --git a/vendor/github.com/bitrise-core/bitrise-init/main.go b/vendor/github.com/bitrise-core/bitrise-init/main.go deleted file mode 100644 index 53c81335..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/main.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import ( - "github.com/bitrise-core/bitrise-init/cli" - _ "github.com/bitrise-io/go-utils/command/git" -) - -func main() { - cli.Run() -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/models/model_test.go b/vendor/github.com/bitrise-core/bitrise-init/models/model_test.go deleted file mode 100644 index 2587b2a5..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/models/model_test.go +++ /dev/null @@ -1,390 +0,0 @@ -package models - -import ( - "fmt" - "testing" - - "encoding/json" - - "github.com/stretchr/testify/require" -) - -func TestNewOption(t *testing.T) { - actual := NewOption("Project (or Workspace) path", "BITRISE_PROJECT_PATH") - expected := &OptionNode{ - Title: "Project (or Workspace) path", - EnvKey: "BITRISE_PROJECT_PATH", - ChildOptionMap: map[string]*OptionNode{}, - Components: []string{}, - } - - require.Equal(t, expected, actual) -} - -func TestGetValues(t *testing.T) { - option := OptionNode{ - ChildOptionMap: map[string]*OptionNode{}, - } - option.ChildOptionMap["assembleAndroidTest"] = &OptionNode{} - option.ChildOptionMap["assembleDebug"] = &OptionNode{} - option.ChildOptionMap["assembleRelease"] = &OptionNode{} - - values := option.GetValues() - - expectedMap := map[string]bool{ - "assembleAndroidTest": false, - "assembleDebug": false, - "assembleRelease": false, - } - - for _, value := range values { - delete(expectedMap, value) - } - - require.Equal(t, 0, len(expectedMap)) -} - -func TestLastOptions(t *testing.T) { - // 1. level - opt0 := NewOption("OPT0", "OPT0_KEY") - - // 2. level - opt01 := NewOption("OPT01", "OPT01_KEY") // has no child - opt01.AddConfig("test", nil) - opt0.AddOption("value1", opt01) - - opt02 := NewOption("OPT02", "OPT02_KEY") - opt0.AddOption("value2", opt02) - - // 3. level - opt021 := NewOption("OPT021", "OPT021_KEY") - opt02.AddOption("value1", opt021) - - // 4. level - opt0211 := NewOption("OPT0211", "OPT0211_KEY") // has no child - opt021.AddOption("value1", opt0211) - - opt0212 := NewOption("OPT0212", "OPT0212_KEY") - opt021.AddOption("value2", opt0212) - - // 5. level - opt02121 := NewOption("OPT02121", "OPT02121_KEY") // has no child - opt0212.AddOption("value1", opt02121) - - lastOptions := opt0.LastChilds() - require.Equal(t, true, len(lastOptions) == 3, fmt.Sprintf("%d", len(lastOptions))) - - optionsMap := map[string]bool{} - for _, opt := range lastOptions { - optionsMap[opt.Title] = true - } - - require.Equal(t, true, optionsMap["OPT01"]) - require.Equal(t, true, optionsMap["OPT0211"]) - require.Equal(t, true, optionsMap["OPT02121"]) - - { - optionJSON := `{ - "title": "Project (or Workspace) path", - "env_key": "BITRISE_PROJECT_PATH", - "value_map": { - "BitriseTest.xcodeproj": { - "title": "Scheme name", - "env_key": "BITRISE_SCHEME", - "value_map": { - "BitriseTest": { - "config": "ios-test-config" - }, - "BitriseTest-tvOS": { - "config": "ios-test-config" - } - } - } - } -}` - - var option OptionNode - require.NoError(t, json.Unmarshal([]byte(optionJSON), &option)) - - lastOptions := option.LastChilds() - optionsMap := map[string]bool{} - for _, opt := range lastOptions { - optionsMap[opt.String()] = true - } - - require.Equal(t, true, optionsMap[`{ - "title": "Scheme name", - "env_key": "BITRISE_SCHEME", - "value_map": { - "BitriseTest": { - "config": "ios-test-config" - }, - "BitriseTest-tvOS": { - "config": "ios-test-config" - } - } -}`]) - } - - { - optionJSON := `{ - "title": "Gradlew file path", - "env_key": "GRADLEW_PATH", - "value_map": { - "$HOME/Develop/react/AwesomeProject/android/gradlew": { - "title": "Path to the gradle file to use", - "env_key": "GRADLE_BUILD_FILE_PATH", - "value_map": { - "$HOME/Develop/react/AwesomeProject/android/build.gradle": { - "config": "android-config" - } - } - } - } -}` - - var option OptionNode - require.NoError(t, json.Unmarshal([]byte(optionJSON), &option)) - - lastOptions := option.LastChilds() - optionsMap := map[string]bool{} - for _, opt := range lastOptions { - optionsMap[opt.String()] = true - } - - require.Equal(t, true, optionsMap[`{ - "title": "Path to the gradle file to use", - "env_key": "GRADLE_BUILD_FILE_PATH", - "value_map": { - "$HOME/Develop/react/AwesomeProject/android/build.gradle": { - "config": "android-config" - } - } -}`]) - } - - { - optionJSON := `{ - "title": "project_dir", - "env_key": "PROJECT_DIR", - "value_map": { - "$HOME/Develop/react/AwesomeProject": { - "title": "Gradlew file path", - "env_key": "GRADLEW_PATH", - "value_map": { - "$HOME/Develop/react/AwesomeProject/android/gradlew": { - "title": "Path to the gradle file to use", - "env_key": "GRADLE_BUILD_FILE_PATH", - "value_map": { - "$HOME/Develop/react/AwesomeProject/android/build.gradle": {} - } - } - } - } - } -}` - - var option OptionNode - require.NoError(t, json.Unmarshal([]byte(optionJSON), &option)) - - lastOptions := option.LastChilds() - optionsMap := map[string]bool{} - for _, opt := range lastOptions { - optionsMap[opt.String()] = true - } - - require.Equal(t, true, optionsMap[`{ - "title": "Path to the gradle file to use", - "env_key": "GRADLE_BUILD_FILE_PATH", - "value_map": { - "$HOME/Develop/react/AwesomeProject/android/build.gradle": {} - } -}`]) - } -} - -func TestCopy(t *testing.T) { - // 1. level - opt0 := NewOption("OPT0", "OPT0_KEY") - - // 2. level - opt01 := NewOption("OPT01", "OPT01_KEY") - opt01.AddOption("value01", nil) - - opt0.AddOption("value1", opt01) - - opt02 := NewConfigOption("name") - opt0.AddConfig("value2", opt02) - - // make a copy - opt0Copy := opt0.Copy() - - // Ensure copy is the same - require.Equal(t, opt0.Title, opt0Copy.Title) - require.Equal(t, opt0.EnvKey, opt0Copy.EnvKey) - - opt01Copy := opt0Copy.ChildOptionMap["value1"] - require.Equal(t, opt01.Title, opt01Copy.Title) - require.Equal(t, opt01.EnvKey, opt01Copy.EnvKey) - require.Equal(t, 1, len(opt01Copy.ChildOptionMap)) - _, ok := opt01Copy.ChildOptionMap["value01"] - require.Equal(t, true, ok) - require.Equal(t, "", opt01Copy.Config) - - opt02Copy := opt0Copy.ChildOptionMap["value2"] - require.Equal(t, opt02.Title, opt02Copy.Title) - require.Equal(t, opt02.EnvKey, opt02Copy.EnvKey) - require.Equal(t, 0, len(opt02Copy.ChildOptionMap)) - require.Equal(t, "name", opt02Copy.Config) - - // Ensure copy is a new object - opt0Copy.Title = "OPT0_COPY" - require.Equal(t, "OPT0", opt0.Title) - - opt01Copy.Title = "OPT01_COPY" - require.Equal(t, "OPT01", opt01.Title) - - opt02Copy.Config = "name_copy" - require.Equal(t, "name", opt02.Config) -} - -func TestComponents(t *testing.T) { - // 1. level - opt0 := NewOption("OPT0", "OPT0_KEY") - - // 2. level - opt01 := NewOption("OPT01", "OPT01_KEY") // has no child - opt0.AddOption("value1", opt01) - - opt02 := NewOption("OPT02", "OPT02_KEY") - opt0.AddOption("value2", opt02) - - // 3. level - opt021 := NewOption("OPT021", "OPT021_KEY") - opt02.AddOption("value1", opt021) - - // 4. level - opt0211 := NewOption("OPT0211", "OPT0211_KEY") // has no child - opt021.AddOption("value1", opt0211) - - opt0212 := NewOption("OPT0212", "OPT0212_KEY") - opt021.AddOption("value2", opt0212) - - // 5. level - opt02121 := NewOption("OPT02121", "OPT02121_KEY") // has no child - opt0212.AddOption("value1", opt02121) - - require.Equal(t, []string{}, opt0.Components) - require.Equal(t, []string{"value1"}, opt01.Components) - require.Equal(t, []string{"value2"}, opt02.Components) - require.Equal(t, []string{"value2", "value1"}, opt021.Components) - require.Equal(t, []string{"value2", "value1", "value1"}, opt0211.Components) - require.Equal(t, []string{"value2", "value1", "value2"}, opt0212.Components) - require.Equal(t, []string{"value2", "value1", "value2", "value1"}, opt02121.Components) -} - -func TestHead(t *testing.T) { - // 1. level - opt0 := NewOption("OPT0", "OPT0_KEY") - - // 2. level - opt01 := NewOption("OPT01", "OPT01_KEY") // has no child - opt0.AddOption("value1", opt01) - - opt02 := NewOption("OPT02", "OPT02_KEY") - opt0.AddOption("value2", opt02) - - // 3. level - opt021 := NewOption("OPT021", "OPT021_KEY") - opt02.AddOption("value1", opt021) - - require.Equal(t, (*OptionNode)(nil), opt0.Head) - require.Equal(t, opt0, opt01.Head) - require.Equal(t, opt0, opt02.Head) - require.Equal(t, opt0, opt021.Head) -} - -func TestParent(t *testing.T) { - // 1. level - opt0 := NewOption("OPT0", "OPT0_KEY") - - // 2. level - opt01 := NewOption("OPT01", "OPT01_KEY") // has no child - opt0.AddOption("value1", opt01) - - opt02 := NewOption("OPT02", "OPT02_KEY") - opt0.AddOption("value2", opt02) - - // 3. level - opt021 := NewOption("OPT021", "OPT021_KEY") - opt02.AddOption("value1", opt021) - - { - parent, underKey, ok := opt0.Parent() - require.Equal(t, (*OptionNode)(nil), parent) - require.Equal(t, "", underKey) - require.Equal(t, false, ok) - } - - { - parent, underKey, ok := opt01.Parent() - require.Equal(t, opt0, parent) - require.Equal(t, "value1", underKey) - require.Equal(t, true, ok) - } - - { - parent, underKey, ok := opt02.Parent() - require.Equal(t, opt0, parent) - require.Equal(t, "value2", underKey) - require.Equal(t, true, ok) - } - - { - parent, underKey, ok := opt021.Parent() - require.Equal(t, opt02, parent) - require.Equal(t, "value1", underKey) - require.Equal(t, true, ok) - } -} - -func TestRemoveConfigs(t *testing.T) { - optionJSON := `{ - "title": "Project (or Workspace) path", - "env_key": "BITRISE_PROJECT_PATH", - "value_map": { - "BitriseTest.xcodeproj": { - "title": "Scheme name", - "env_key": "BITRISE_SCHEME", - "value_map": { - "BitriseTest": { - "config": "ios-test-config" - }, - "BitriseTest-tvOS": { - "config": "ios-test-config" - } - } - } - } -}` - - var option OptionNode - require.NoError(t, json.Unmarshal([]byte(optionJSON), &option)) - - option.RemoveConfigs() - - require.Equal(t, `{ - "title": "Project (or Workspace) path", - "env_key": "BITRISE_PROJECT_PATH", - "value_map": { - "BitriseTest.xcodeproj": { - "title": "Scheme name", - "env_key": "BITRISE_SCHEME", - "value_map": { - "BitriseTest": {}, - "BitriseTest-tvOS": {} - } - } - } -}`, option.String()) -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/output/output.go b/vendor/github.com/bitrise-core/bitrise-init/output/output.go deleted file mode 100644 index 8edc8f52..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/output/output.go +++ /dev/null @@ -1,120 +0,0 @@ -package output - -import ( - "encoding/json" - "fmt" - "path/filepath" - "strings" - - "gopkg.in/yaml.v2" - - "github.com/bitrise-io/go-utils/fileutil" -) - -// Format ... -type Format uint8 - -const ( - // RawFormat ... - RawFormat Format = iota - // JSONFormat ... - JSONFormat - // YAMLFormat ... - YAMLFormat -) - -// ParseFormat ... -func ParseFormat(format string) (Format, error) { - switch strings.ToLower(format) { - case "raw": - return RawFormat, nil - case "json": - return JSONFormat, nil - case "yaml": - return YAMLFormat, nil - } - - var f Format - return f, fmt.Errorf("not a valid format: %s", format) -} - -// String ... -func (format Format) String() string { - switch format { - case RawFormat: - return "raw" - case JSONFormat: - return "json" - case YAMLFormat: - return "yaml" - } - - return "unknown" -} - -// WriteToFile ... -func WriteToFile(a interface{}, format Format, pth string) (string, error) { - str := "" - ext := "" - - switch format { - case RawFormat: - str = fmt.Sprint(a) - ext = ".txt" - case JSONFormat: - bytes, err := json.MarshalIndent(a, "", "\t") - if err != nil { - return "", err - } - str = string(bytes) - ext = ".json" - case YAMLFormat: - bytes, err := yaml.Marshal(a) - if err != nil { - return "", err - } - str = string(bytes) - ext = ".yml" - default: - return "", fmt.Errorf("not a valid format: %s", format) - } - - fileExt := filepath.Ext(pth) - if fileExt != "" { - pth = strings.TrimSuffix(pth, fileExt) - } - pth = pth + ext - - if err := fileutil.WriteStringToFile(pth, str); err != nil { - return "", err - } - - return pth, nil -} - -// Print ... -func Print(a interface{}, format Format) error { - str := "" - - switch format { - case RawFormat: - str = fmt.Sprint(a) - case JSONFormat: - bytes, err := json.MarshalIndent(a, "", "\t") - if err != nil { - return err - } - str = string(bytes) - case YAMLFormat: - bytes, err := yaml.Marshal(a) - if err != nil { - return err - } - str = string(bytes) - default: - return fmt.Errorf("not a valid format: %s", format) - } - - fmt.Println(str) - return nil -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/release_config.yml b/vendor/github.com/bitrise-core/bitrise-init/release_config.yml deleted file mode 100644 index 4a1f737d..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/release_config.yml +++ /dev/null @@ -1,13 +0,0 @@ -release: - development_branch: master - release_branch: master -changelog: - path: CHANGELOG.md - content_template: |- - {{range .ContentItems}}### {{.EndTaggedCommit.Tag}} ({{.EndTaggedCommit.Date.Format "2006 Jan 02"}}) - - {{range .Commits}}* [{{firstChars .Hash 7}}] {{.Message}} - {{end}} - {{end}} - header_template: '## Changelog (Current version: {{.Version}})' - footer_template: 'Updated: {{.CurrentDate.Format "2006 Jan 02"}}' diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanner/config.go b/vendor/github.com/bitrise-core/bitrise-init/scanner/config.go deleted file mode 100644 index 5fbe8c91..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanner/config.go +++ /dev/null @@ -1,231 +0,0 @@ -package scanner - -import ( - "fmt" - "os" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/scanners" - "github.com/bitrise-io/go-utils/colorstring" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/go-utils/sliceutil" -) - -const otherProjectType = "other" - -type status int - -const ( - // in case DetectPlatform() returned error, or false - notDetected status = iota - // in case DetectPlatform() returned true, but Options() or Config() returned an error - detectedWithErrors - // in case DetectPlatform() returned true, Options() and Config() returned no error - detected -) - -type scannerOutput struct { - status status - - // can always be set - // warnings returned by DetectPlatform(), Options() - warnings models.Warnings - - // set if scanResultStatus is scanResultDetectedWithErrors - // errors returned by Config() - errors models.Errors - - // set if scanResultStatus is scanResultDetected - options models.OptionNode - configs models.BitriseConfigMap - excludedScanners []string -} - -// Config ... -func Config(searchDir string) models.ScanResultModel { - result := models.ScanResultModel{} - - // - // Setup - currentDir, err := os.Getwd() - if err != nil { - result.AddError("general", fmt.Sprintf("Failed to expand current directory path, error: %s", err)) - return result - } - - if searchDir == "" { - searchDir = currentDir - } else { - absScerach, err := pathutil.AbsPath(searchDir) - if err != nil { - result.AddError("general", fmt.Sprintf("Failed to expand path (%s), error: %s", searchDir, err)) - return result - } - searchDir = absScerach - } - - if searchDir != currentDir { - if err := os.Chdir(searchDir); err != nil { - result.AddError("general", fmt.Sprintf("Failed to change dir, to (%s), error: %s", searchDir, err)) - return result - } - defer func() { - if err := os.Chdir(currentDir); err != nil { - log.TWarnf("Failed to change dir, to (%s), error: %s", searchDir, err) - } - }() - } - // --- - - // - // Scan - log.TInfof(colorstring.Blue("Running scanners:")) - fmt.Println() - - // Collect scanner outputs, by scanner name - scannerToOutput := map[string]scannerOutput{} - { - projectScannerToOutputs := runScanners(scanners.ProjectScanners, searchDir) - detectedProjectTypes := getDetectedScannerNames(projectScannerToOutputs) - log.Printf("Detected project types: %s", detectedProjectTypes) - fmt.Println() - - // Project types are needed by tool scanners, to create decision tree on which project type - // to actually use in bitrise.yml - if len(detectedProjectTypes) == 0 { - detectedProjectTypes = []string{otherProjectType} - } - for _, toolScanner := range scanners.AutomationToolScanners { - toolScanner.(scanners.AutomationToolScanner).SetDetectedProjectTypes(detectedProjectTypes) - } - - toolScannerToOutputs := runScanners(scanners.AutomationToolScanners, searchDir) - detectedAutomationToolScanners := getDetectedScannerNames(toolScannerToOutputs) - log.Printf("Detected automation tools: %s", detectedAutomationToolScanners) - fmt.Println() - - // Merge project and tool scanner outputs - scannerToOutput = toolScannerToOutputs - for scanner, scannerOutput := range projectScannerToOutputs { - scannerToOutput[scanner] = scannerOutput - } - } - - scannerToWarnings := map[string]models.Warnings{} - scannerToErrors := map[string]models.Errors{} - scannerToOptions := map[string]models.OptionNode{} - scannerToConfigMap := map[string]models.BitriseConfigMap{} - for scanner, scannerOutput := range scannerToOutput { - // Currently the tests except an empty warning list if no warnings - // are created in the not detect case. - if scannerOutput.status == notDetected && len(scannerOutput.warnings) > 0 || - scannerOutput.status != notDetected { - scannerToWarnings[scanner] = scannerOutput.warnings - } - if len(scannerOutput.errors) > 0 && - (scannerOutput.status == detected || scannerOutput.status == detectedWithErrors) { - scannerToErrors[scanner] = scannerOutput.errors - } - if len(scannerOutput.configs) > 0 && scannerOutput.status == detected { - scannerToOptions[scanner] = scannerOutput.options - scannerToConfigMap[scanner] = scannerOutput.configs - } - } - return models.ScanResultModel{ - ScannerToOptionRoot: scannerToOptions, - ScannerToBitriseConfigMap: scannerToConfigMap, - ScannerToWarnings: scannerToWarnings, - ScannerToErrors: scannerToErrors, - } -} - -func runScanners(scannerList []scanners.ScannerInterface, searchDir string) map[string]scannerOutput { - scannerOutputs := map[string]scannerOutput{} - var excludedScannerNames []string - for _, scanner := range scannerList { - log.TInfof("Scanner: %s", colorstring.Blue(scanner.Name())) - if sliceutil.IsStringInSlice(scanner.Name(), excludedScannerNames) { - log.TWarnf("scanner is marked as excluded, skipping...") - fmt.Println() - continue - } - - log.TPrintf("+------------------------------------------------------------------------------+") - log.TPrintf("| |") - scannerOutput := runScanner(scanner, searchDir) - log.TPrintf("| |") - log.TPrintf("+------------------------------------------------------------------------------+") - fmt.Println() - - scannerOutputs[scanner.Name()] = scannerOutput - excludedScannerNames = append(excludedScannerNames, scannerOutput.excludedScanners...) - } - return scannerOutputs -} - -// Collect output of a specific scanner -func runScanner(detector scanners.ScannerInterface, searchDir string) scannerOutput { - var detectorWarnings models.Warnings - var detectorErrors []string - - if isDetect, err := detector.DetectPlatform(searchDir); err != nil { - log.TErrorf("Scanner failed, error: %s", err) - return scannerOutput{ - status: notDetected, - warnings: models.Warnings{err.Error()}, - } - } else if !isDetect { - return scannerOutput{ - status: notDetected, - } - } - - options, projectWarnings, err := detector.Options() - detectorWarnings = append(detectorWarnings, projectWarnings...) - - if err != nil { - log.TErrorf("Analyzer failed, error: %s", err) - // Error returned as a warning - detectorWarnings = append(detectorWarnings, err.Error()) - return scannerOutput{ - status: detectedWithErrors, - warnings: detectorWarnings, - } - } - - // Generate configs - configs, err := detector.Configs() - if err != nil { - log.TErrorf("Failed to generate config, error: %s", err) - detectorErrors = append(detectorErrors, err.Error()) - return scannerOutput{ - status: detectedWithErrors, - warnings: detectorWarnings, - errors: detectorErrors, - } - } - - scannerExcludedScanners := detector.ExcludedScannerNames() - if len(scannerExcludedScanners) > 0 { - log.TWarnf("Scanner will exclude scanners: %v", scannerExcludedScanners) - } - - return scannerOutput{ - status: detected, - warnings: detectorWarnings, - errors: detectorErrors, - options: options, - configs: configs, - excludedScanners: scannerExcludedScanners, - } -} - -func getDetectedScannerNames(scannerOutputs map[string]scannerOutput) (names []string) { - for scanner, scannerOutput := range scannerOutputs { - if scannerOutput.status == detected { - names = append(names, scanner) - } - } - return -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanner/manual_config.go b/vendor/github.com/bitrise-core/bitrise-init/scanner/manual_config.go deleted file mode 100644 index 2c917ef1..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanner/manual_config.go +++ /dev/null @@ -1,38 +0,0 @@ -package scanner - -import ( - "fmt" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/scanners" -) - -// ManualConfig ... -func ManualConfig() (models.ScanResultModel, error) { - scannerList := append(scanners.ProjectScanners, scanners.AutomationToolScanners...) - scannerToOptionRoot := map[string]models.OptionNode{} - scannerToBitriseConfigMap := map[string]models.BitriseConfigMap{} - - for _, scanner := range scannerList { - option := scanner.DefaultOptions() - scannerToOptionRoot[scanner.Name()] = option - - configs, err := scanner.DefaultConfigs() - if err != nil { - return models.ScanResultModel{}, fmt.Errorf("Failed create default configs, error: %s", err) - } - scannerToBitriseConfigMap[scanner.Name()] = configs - } - - customConfig, err := scanners.CustomConfig() - if err != nil { - return models.ScanResultModel{}, fmt.Errorf("Failed create default custom configs, error: %s", err) - } - - scannerToBitriseConfigMap[scanners.CustomProjectType] = customConfig - - return models.ScanResultModel{ - ScannerToOptionRoot: scannerToOptionRoot, - ScannerToBitriseConfigMap: scannerToBitriseConfigMap, - }, nil -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanner/utils.go b/vendor/github.com/bitrise-core/bitrise-init/scanner/utils.go deleted file mode 100644 index f9187e60..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanner/utils.go +++ /dev/null @@ -1,151 +0,0 @@ -package scanner - -import ( - "errors" - "fmt" - - yaml "gopkg.in/yaml.v2" - - "github.com/bitrise-core/bitrise-init/models" - bitriseModels "github.com/bitrise-io/bitrise/models" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/goinp/goinp" -) - -func askForOptionValue(option models.OptionNode) (string, string, error) { - optionValues := option.GetValues() - - selectedValue := "" - if len(optionValues) == 1 { - if optionValues[0] == "_" { - // provide option value - question := fmt.Sprintf("Provide: %s", option.Title) - answer, err := goinp.AskForString(question) - if err != nil { - return "", "", err - } - - selectedValue = answer - } else { - // auto select the only one value - selectedValue = optionValues[0] - } - } else { - // select from values - question := fmt.Sprintf("Select: %s", option.Title) - answer, err := goinp.SelectFromStrings(question, optionValues) - if err != nil { - return "", "", err - } - - selectedValue = answer - } - - return option.EnvKey, selectedValue, nil -} - -// AskForOptions ... -func AskForOptions(options models.OptionNode) (string, []envmanModels.EnvironmentItemModel, error) { - configPth := "" - appEnvs := []envmanModels.EnvironmentItemModel{} - - var walkDepth func(models.OptionNode) error - walkDepth = func(opt models.OptionNode) error { - optionEnvKey, selectedValue, err := askForOptionValue(opt) - if err != nil { - return fmt.Errorf("Failed to ask for value, error: %s", err) - } - - if opt.Title == "" { - // last option selected, config got - configPth = selectedValue - return nil - } else if optionEnvKey != "" { - // env's value selected - appEnvs = append(appEnvs, envmanModels.EnvironmentItemModel{ - optionEnvKey: selectedValue, - }) - } - - var nestedOptions *models.OptionNode - if len(opt.ChildOptionMap) == 1 { - // auto select the next option - for _, childOption := range opt.ChildOptionMap { - nestedOptions = childOption - break - } - } else { - // go to the next option, based on the selected value - childOptions, found := opt.ChildOptionMap[selectedValue] - if !found { - return nil - } - nestedOptions = childOptions - } - - return walkDepth(*nestedOptions) - } - - if err := walkDepth(options); err != nil { - return "", []envmanModels.EnvironmentItemModel{}, err - } - - if configPth == "" { - return "", nil, errors.New("no config selected") - } - - return configPth, appEnvs, nil -} - -// AskForConfig ... -func AskForConfig(scanResult models.ScanResultModel) (bitriseModels.BitriseDataModel, error) { - - // - // Select platform - platforms := []string{} - for platform := range scanResult.ScannerToOptionRoot { - platforms = append(platforms, platform) - } - - platform := "" - if len(platforms) == 0 { - return bitriseModels.BitriseDataModel{}, errors.New("no platform detected") - } else if len(platforms) == 1 { - platform = platforms[0] - } else { - var err error - platform, err = goinp.SelectFromStrings("Select platform", platforms) - if err != nil { - return bitriseModels.BitriseDataModel{}, err - } - } - // --- - - // - // Select config - options, ok := scanResult.ScannerToOptionRoot[platform] - if !ok { - return bitriseModels.BitriseDataModel{}, fmt.Errorf("invalid platform selected: %s", platform) - } - - configPth, appEnvs, err := AskForOptions(options) - if err != nil { - return bitriseModels.BitriseDataModel{}, err - } - // -- - - // - // Build config - configMap := scanResult.ScannerToBitriseConfigMap[platform] - configStr := configMap[configPth] - - var config bitriseModels.BitriseDataModel - if err := yaml.Unmarshal([]byte(configStr), &config); err != nil { - return bitriseModels.BitriseDataModel{}, fmt.Errorf("failed to unmarshal config, error: %s", err) - } - - config.App.Environments = append(config.App.Environments, appEnvs...) - // --- - - return config, nil -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/android/android.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/android/android.go deleted file mode 100644 index 7559a2f0..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/android/android.go +++ /dev/null @@ -1,123 +0,0 @@ -package android - -import ( - "fmt" - "path/filepath" - - "gopkg.in/yaml.v2" - - "github.com/bitrise-core/bitrise-init/models" -) - -// Scanner ... -type Scanner struct { - SearchDir string - ProjectRoots []string - ExcludeTest bool -} - -// NewScanner ... -func NewScanner() *Scanner { - return &Scanner{} -} - -// Name ... -func (Scanner) Name() string { - return ScannerName -} - -// ExcludedScannerNames ... -func (*Scanner) ExcludedScannerNames() []string { - return nil -} - -// DetectPlatform ... -func (scanner *Scanner) DetectPlatform(searchDir string) (_ bool, err error) { - scanner.SearchDir = searchDir - - scanner.ProjectRoots, err = walkMultipleFiles(searchDir, "build.gradle", "settings.gradle") - if err != nil { - return false, fmt.Errorf("failed to search for build.gradle files, error: %s", err) - } - - return len(scanner.ProjectRoots) > 0, err -} - -// Options ... -func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { - projectLocationOption := models.NewOption(ProjectLocationInputTitle, ProjectLocationInputEnvKey) - warnings := models.Warnings{} - - for _, projectRoot := range scanner.ProjectRoots { - if err := checkGradlew(projectRoot); err != nil { - return models.OptionNode{}, warnings, err - } - - relProjectRoot, err := filepath.Rel(scanner.SearchDir, projectRoot) - if err != nil { - return models.OptionNode{}, warnings, err - } - - configOption := models.NewConfigOption(ConfigName) - moduleOption := models.NewOption(ModuleInputTitle, ModuleInputEnvKey) - variantOption := models.NewOption(VariantInputTitle, VariantInputEnvKey) - - projectLocationOption.AddOption(relProjectRoot, moduleOption) - moduleOption.AddOption("app", variantOption) - variantOption.AddConfig("", configOption) - } - - return *projectLocationOption, warnings, nil -} - -// DefaultOptions ... -func (scanner *Scanner) DefaultOptions() models.OptionNode { - projectLocationOption := models.NewOption(ProjectLocationInputTitle, ProjectLocationInputEnvKey) - moduleOption := models.NewOption(ModuleInputTitle, ModuleInputEnvKey) - variantOption := models.NewOption(VariantInputTitle, VariantInputEnvKey) - configOption := models.NewConfigOption(DefaultConfigName) - - projectLocationOption.AddOption("_", moduleOption) - moduleOption.AddOption("_", variantOption) - variantOption.AddConfig("", configOption) - - return *projectLocationOption -} - -// Configs ... -func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { - configBuilder := scanner.generateConfigBuilder() - - config, err := configBuilder.Generate(ScannerName) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(config) - if err != nil { - return models.BitriseConfigMap{}, err - } - - return models.BitriseConfigMap{ - ConfigName: string(data), - }, nil -} - -// DefaultConfigs ... -func (scanner *Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { - configBuilder := scanner.generateConfigBuilder() - - config, err := configBuilder.Generate(ScannerName) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(config) - if err != nil { - return models.BitriseConfigMap{}, err - } - - return models.BitriseConfigMap{ - DefaultConfigName: string(data), - }, nil -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/android/const.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/android/const.go deleted file mode 100644 index f5df5aee..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/android/const.go +++ /dev/null @@ -1,34 +0,0 @@ -package android - -const deployWorkflowDescription = `## How to get a signed APK - -This workflow contains the **Sign APK** step. To sign your APK all you have to do is to: - -1. Click on **Code Signing** tab -1. Find the **ANDROID KEYSTORE FILE** section -1. Click or drop your file on the upload file field -1. Fill the displayed 3 input fields: - 1. **Keystore password** - 1. **Keystore alias** - 1. **Private key password** -1. Click on **[Save metadata]** button - -That's it! From now on, **Sign APK** step will receive your uploaded files. - -## To run this workflow - -If you want to run this workflow manually: - -1. Open the app's build list page -2. Click on **[Start/Schedule a Build]** button -3. Select **deploy** in **Workflow** dropdown input -4. Click **[Start Build]** button - -Or if you need this workflow to be started by a GIT event: - -1. Click on **Triggers** tab -2. Setup your desired event (push/tag/pull) and select **deploy** workflow -3. Click on **[Done]** and then **[Save]** buttons - -The next change in your repository that matches any of your trigger map event will start **deploy** workflow. -` diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/android/utility.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/android/utility.go deleted file mode 100644 index 7f5ceb36..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/android/utility.go +++ /dev/null @@ -1,187 +0,0 @@ -package android - -import ( - "errors" - "os" - "path/filepath" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/steps" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/pathutil" -) - -// Constants ... -const ( - ScannerName = "android" - ConfigName = "android-config" - DefaultConfigName = "default-android-config" - - ProjectLocationInputKey = "project_location" - ProjectLocationInputEnvKey = "PROJECT_LOCATION" - ProjectLocationInputTitle = "The root directory of an Android project" - - ModuleBuildGradlePathInputKey = "build_gradle_path" - - VariantInputKey = "variant" - VariantInputEnvKey = "VARIANT" - VariantInputTitle = "Variant" - - ModuleInputKey = "module" - ModuleInputEnvKey = "MODULE" - ModuleInputTitle = "Module" - - GradlewPathInputKey = "gradlew_path" - GradlewPathInputEnvKey = "GRADLEW_PATH" - GradlewPathInputTitle = "Gradlew file path" -) - -func walk(src string, fn func(path string, info os.FileInfo) error) error { - return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if path == src { - return nil - } - return fn(path, info) - }) -} - -func checkFiles(path string, files ...string) (bool, error) { - for _, file := range files { - exists, err := pathutil.IsPathExists(filepath.Join(path, file)) - if err != nil { - return false, err - } - if !exists { - return false, nil - } - } - return true, nil -} - -func walkMultipleFiles(searchDir string, files ...string) (matches []string, err error) { - match, err := checkFiles(searchDir, files...) - if err != nil { - return nil, err - } - if match { - matches = append(matches, searchDir) - } - return matches, walk(searchDir, func(path string, info os.FileInfo) error { - if err != nil { - return err - } - if info.IsDir() { - match, err := checkFiles(path, files...) - if err != nil { - return err - } - if match { - matches = append(matches, path) - } - } - return nil - }) -} - -func checkGradlew(projectDir string) error { - gradlewPth := filepath.Join(projectDir, "gradlew") - exist, err := pathutil.IsPathExists(gradlewPth) - if err != nil { - return err - } - if !exist { - return errors.New(`No Gradle Wrapper (gradlew) found. -Using a Gradle Wrapper (gradlew) is required, as the wrapper is what makes sure -that the right Gradle version is installed and used for the build. More info/guide: https://docs.gradle.org/current/userguide/gradle_wrapper.html`) - } - return nil -} - -func (scanner *Scanner) generateConfigBuilder() models.ConfigBuilderModel { - configBuilder := models.NewDefaultConfigBuilder() - - projectLocationEnv, gradlewPath, moduleEnv, variantEnv := "$"+ProjectLocationInputEnvKey, "$"+ProjectLocationInputEnvKey+"/gradlew", "$"+ModuleInputEnvKey, "$"+VariantInputEnvKey - - //-- primary - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(true)...) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.InstallMissingAndroidToolsStepListItem( - envmanModels.EnvironmentItemModel{GradlewPathInputKey: gradlewPath}, - )) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.AndroidLintStepListItem( - envmanModels.EnvironmentItemModel{ - ProjectLocationInputKey: projectLocationEnv, - }, - envmanModels.EnvironmentItemModel{ - ModuleInputKey: moduleEnv, - }, - envmanModels.EnvironmentItemModel{ - VariantInputKey: variantEnv, - }, - )) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.AndroidUnitTestStepListItem( - envmanModels.EnvironmentItemModel{ - ProjectLocationInputKey: projectLocationEnv, - }, - envmanModels.EnvironmentItemModel{ - ModuleInputKey: moduleEnv, - }, - envmanModels.EnvironmentItemModel{ - VariantInputKey: variantEnv, - }, - )) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(true)...) - - //-- deploy - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(true)...) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.InstallMissingAndroidToolsStepListItem( - envmanModels.EnvironmentItemModel{GradlewPathInputKey: gradlewPath}, - )) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.ChangeAndroidVersionCodeAndVersionNameStepListItem( - envmanModels.EnvironmentItemModel{ModuleBuildGradlePathInputKey: filepath.Join(projectLocationEnv, moduleEnv, "build.gradle")}, - )) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidLintStepListItem( - envmanModels.EnvironmentItemModel{ - ProjectLocationInputKey: projectLocationEnv, - }, - envmanModels.EnvironmentItemModel{ - ModuleInputKey: moduleEnv, - }, - envmanModels.EnvironmentItemModel{ - VariantInputKey: variantEnv, - }, - )) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidUnitTestStepListItem( - envmanModels.EnvironmentItemModel{ - ProjectLocationInputKey: projectLocationEnv, - }, - envmanModels.EnvironmentItemModel{ - ModuleInputKey: moduleEnv, - }, - envmanModels.EnvironmentItemModel{ - VariantInputKey: variantEnv, - }, - )) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( - envmanModels.EnvironmentItemModel{ - ProjectLocationInputKey: projectLocationEnv, - }, - envmanModels.EnvironmentItemModel{ - ModuleInputKey: moduleEnv, - }, - envmanModels.EnvironmentItemModel{ - VariantInputKey: variantEnv, - }, - )) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.SignAPKStepListItem()) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(true)...) - - configBuilder.SetWorkflowDescriptionTo(models.DeployWorkflowID, deployWorkflowDescription) - - return *configBuilder -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova.go deleted file mode 100644 index 2816e491..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova.go +++ /dev/null @@ -1,405 +0,0 @@ -package cordova - -import ( - "fmt" - "path/filepath" - "strings" - - yaml "gopkg.in/yaml.v2" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/scanners/android" - "github.com/bitrise-core/bitrise-init/scanners/ios" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-core/bitrise-init/utility" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" -) - -// ScannerName ... -const ScannerName = "cordova" - -const ( - configName = "cordova-config" - defaultConfigName = "default-cordova-config" -) - -// Step Inputs -const ( - workDirInputKey = "workdir" - workDirInputTitle = "Directory of Cordova Config.xml" - workDirInputEnvKey = "CORDOVA_WORK_DIR" -) - -const ( - platformInputKey = "platform" - platformInputTitle = "Platform to use in cordova-cli commands" - platformInputEnvKey = "CORDOVA_PLATFORM" -) - -const ( - targetInputKey = "target" - targetEmulator = "emulator" -) - -//------------------ -// ScannerInterface -//------------------ - -// Scanner ... -type Scanner struct { - cordovaConfigPth string - relCordovaConfigDir string - searchDir string - hasKarmaJasmineTest bool - hasJasmineTest bool -} - -// NewScanner ... -func NewScanner() *Scanner { - return &Scanner{} -} - -// Name ... -func (Scanner) Name() string { - return ScannerName -} - -// DetectPlatform ... -func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { - fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) - if err != nil { - return false, fmt.Errorf("failed to search for files in (%s), error: %s", searchDir, err) - } - - // Search for config.xml file - log.TInfof("Searching for config.xml file") - - configXMLPth, err := FilterRootConfigXMLFile(fileList) - if err != nil { - return false, fmt.Errorf("failed to search for config.xml file, error: %s", err) - } - - log.TPrintf("config.xml: %s", configXMLPth) - - if configXMLPth == "" { - log.TPrintf("platform not detected") - return false, nil - } - - widget, err := ParseConfigXML(configXMLPth) - if err != nil { - log.TPrintf("can not parse config.xml as a Cordova widget, error: %s", err) - log.TPrintf("platform not detected") - return false, nil - } - - // ensure it is a cordova widget - if !strings.Contains(widget.XMLNSCDV, "cordova.apache.org") { - log.TPrintf("config.xml propert: xmlns:cdv does not contain cordova.apache.org") - log.TPrintf("platform not detected") - return false, nil - } - - // ensure it is not an ionic project - projectBaseDir := filepath.Dir(configXMLPth) - - if exist, err := pathutil.IsPathExists(filepath.Join(projectBaseDir, "ionic.project")); err != nil { - return false, fmt.Errorf("failed to check if project is an ionic project, error: %s", err) - } else if exist { - log.TPrintf("ionic.project file found seems to be an ionic project") - return false, nil - } - - if exist, err := pathutil.IsPathExists(filepath.Join(projectBaseDir, "ionic.config.json")); err != nil { - return false, fmt.Errorf("failed to check if project is an ionic project, error: %s", err) - } else if exist { - log.TPrintf("ionic.config.json file found seems to be an ionic project") - return false, nil - } - - log.TSuccessf("Platform detected") - - scanner.cordovaConfigPth = configXMLPth - scanner.searchDir = searchDir - - return true, nil -} - -// ExcludedScannerNames ... -func (*Scanner) ExcludedScannerNames() []string { - return []string{ - string(ios.XcodeProjectTypeIOS), - string(ios.XcodeProjectTypeMacOS), - android.ScannerName, - } -} - -// Options ... -func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { - warnings := models.Warnings{} - projectRootDir := filepath.Dir(scanner.cordovaConfigPth) - - packagesJSONPth := filepath.Join(projectRootDir, "package.json") - packages, err := utility.ParsePackagesJSON(packagesJSONPth) - if err != nil { - return models.OptionNode{}, warnings, err - } - - // Search for karma/jasmine tests - log.TPrintf("Searching for karma/jasmine test") - - karmaTestDetected := false - - karmaJasmineDependencyFound := false - for dependency := range packages.Dependencies { - if strings.Contains(dependency, "karma-jasmine") { - karmaJasmineDependencyFound = true - } - } - if !karmaJasmineDependencyFound { - for dependency := range packages.DevDependencies { - if strings.Contains(dependency, "karma-jasmine") { - karmaJasmineDependencyFound = true - } - } - } - log.TPrintf("karma-jasmine dependency found: %v", karmaJasmineDependencyFound) - - if karmaJasmineDependencyFound { - karmaConfigJSONPth := filepath.Join(projectRootDir, "karma.conf.js") - if exist, err := pathutil.IsPathExists(karmaConfigJSONPth); err != nil { - return models.OptionNode{}, warnings, err - } else if exist { - karmaTestDetected = true - } - } - log.TPrintf("karma.conf.js found: %v", karmaTestDetected) - - scanner.hasKarmaJasmineTest = karmaTestDetected - // --- - - // Search for jasmine tests - jasminTestDetected := false - - if !karmaTestDetected { - log.TPrintf("Searching for jasmine test") - - jasmineDependencyFound := false - for dependency := range packages.Dependencies { - if strings.Contains(dependency, "jasmine") { - jasmineDependencyFound = true - break - } - } - if !jasmineDependencyFound { - for dependency := range packages.DevDependencies { - if strings.Contains(dependency, "jasmine") { - jasmineDependencyFound = true - break - } - } - } - log.TPrintf("jasmine dependency found: %v", jasmineDependencyFound) - - if jasmineDependencyFound { - jasmineConfigJSONPth := filepath.Join(projectRootDir, "spec", "support", "jasmine.json") - if exist, err := pathutil.IsPathExists(jasmineConfigJSONPth); err != nil { - return models.OptionNode{}, warnings, err - } else if exist { - jasminTestDetected = true - } - } - - log.TPrintf("jasmine.json found: %v", jasminTestDetected) - - scanner.hasJasmineTest = jasminTestDetected - } - // --- - - // Get relative config.xml dir - cordovaConfigDir := filepath.Dir(scanner.cordovaConfigPth) - relCordovaConfigDir, err := utility.RelPath(scanner.searchDir, cordovaConfigDir) - if err != nil { - return models.OptionNode{}, warnings, fmt.Errorf("Failed to get relative config.xml dir path, error: %s", err) - } - if relCordovaConfigDir == "." { - // config.xml placed in the search dir, no need to change-dir in the workflows - relCordovaConfigDir = "" - } - scanner.relCordovaConfigDir = relCordovaConfigDir - // --- - - // Options - var rootOption *models.OptionNode - - platforms := []string{"ios", "android", "ios,android"} - - if relCordovaConfigDir != "" { - rootOption = models.NewOption(workDirInputTitle, workDirInputEnvKey) - - projectTypeOption := models.NewOption(platformInputTitle, platformInputEnvKey) - rootOption.AddOption(relCordovaConfigDir, projectTypeOption) - - for _, platform := range platforms { - configOption := models.NewConfigOption(configName) - projectTypeOption.AddConfig(platform, configOption) - } - } else { - rootOption = models.NewOption(platformInputTitle, platformInputEnvKey) - - for _, platform := range platforms { - configOption := models.NewConfigOption(configName) - rootOption.AddConfig(platform, configOption) - } - } - // --- - - return *rootOption, warnings, nil -} - -// DefaultOptions ... -func (*Scanner) DefaultOptions() models.OptionNode { - workDirOption := models.NewOption(workDirInputTitle, workDirInputEnvKey) - - projectTypeOption := models.NewOption(platformInputTitle, platformInputEnvKey) - workDirOption.AddOption("_", projectTypeOption) - - platforms := []string{ - "ios", - "android", - "ios,android", - } - for _, platform := range platforms { - configOption := models.NewConfigOption(defaultConfigName) - projectTypeOption.AddConfig(platform, configOption) - } - - return *workDirOption -} - -// Configs ... -func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { - configBuilder := models.NewDefaultConfigBuilder() - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - - workdirEnvList := []envmanModels.EnvironmentItemModel{} - if scanner.relCordovaConfigDir != "" { - workdirEnvList = append(workdirEnvList, envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}) - } - - if scanner.hasJasmineTest || scanner.hasKarmaJasmineTest { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - - // CI - if scanner.hasKarmaJasmineTest { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.KarmaJasmineTestRunnerStepListItem(workdirEnvList...)) - } else if scanner.hasJasmineTest { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.JasmineTestRunnerStepListItem(workdirEnvList...)) - } - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - // CD - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - - if scanner.hasKarmaJasmineTest { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.KarmaJasmineTestRunnerStepListItem(workdirEnvList...)) - } else if scanner.hasJasmineTest { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.JasmineTestRunnerStepListItem(workdirEnvList...)) - } - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.GenerateCordovaBuildConfigStepListItem()) - - cordovaArchiveEnvs := []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{platformInputKey: "$" + platformInputEnvKey}, - envmanModels.EnvironmentItemModel{targetInputKey: targetEmulator}, - } - if scanner.relCordovaConfigDir != "" { - cordovaArchiveEnvs = append(cordovaArchiveEnvs, envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}) - } - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CordovaArchiveStepListItem(cordovaArchiveEnvs...)) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) - - config, err := configBuilder.Generate(ScannerName) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(config) - if err != nil { - return models.BitriseConfigMap{}, err - } - - return models.BitriseConfigMap{ - configName: string(data), - }, nil - } - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.GenerateCordovaBuildConfigStepListItem()) - - cordovaArchiveEnvs := []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{platformInputKey: "$" + platformInputEnvKey}, - envmanModels.EnvironmentItemModel{targetInputKey: targetEmulator}, - } - if scanner.relCordovaConfigDir != "" { - cordovaArchiveEnvs = append(cordovaArchiveEnvs, envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}) - } - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CordovaArchiveStepListItem(cordovaArchiveEnvs...)) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - config, err := configBuilder.Generate(ScannerName) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(config) - if err != nil { - return models.BitriseConfigMap{}, err - } - - return models.BitriseConfigMap{ - configName: string(data), - }, nil -} - -// DefaultConfigs ... -func (*Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { - configBuilder := models.NewDefaultConfigBuilder() - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem( - envmanModels.EnvironmentItemModel{"command": "install"}, - envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey})) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.GenerateCordovaBuildConfigStepListItem()) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CordovaArchiveStepListItem( - envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}, - envmanModels.EnvironmentItemModel{platformInputKey: "$" + platformInputEnvKey}, - envmanModels.EnvironmentItemModel{targetInputKey: targetEmulator})) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - config, err := configBuilder.Generate(ScannerName) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(config) - if err != nil { - return models.BitriseConfigMap{}, err - } - - return models.BitriseConfigMap{ - defaultConfigName: string(data), - }, nil -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova_test.go deleted file mode 100644 index d756586b..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/cordova_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package cordova - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestParseConfigXMLContent(t *testing.T) { - widget, err := parseConfigXMLContent(testConfigXMLContent) - require.NoError(t, err) - require.Equal(t, "http://cordova.apache.org/ns/1.0", widget.XMLNSCDV) -} - -const testConfigXMLContent = ` - - CordovaOnBitrise - A sample Apache Cordova application that builds on Bitrise. - - - - - - - - - - - - - - - - - - -` diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/utility.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/utility.go deleted file mode 100644 index 700ff087..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/cordova/utility.go +++ /dev/null @@ -1,47 +0,0 @@ -package cordova - -import ( - "encoding/xml" - - "github.com/bitrise-core/bitrise-init/utility" - "github.com/bitrise-io/go-utils/fileutil" -) - -const configXMLBasePath = "config.xml" - -// WidgetModel ... -type WidgetModel struct { - XMLNSCDV string `xml:"xmlns cdv,attr"` -} - -func parseConfigXMLContent(content string) (WidgetModel, error) { - widget := WidgetModel{} - if err := xml.Unmarshal([]byte(content), &widget); err != nil { - return WidgetModel{}, err - } - return widget, nil -} - -// ParseConfigXML ... -func ParseConfigXML(pth string) (WidgetModel, error) { - content, err := fileutil.ReadStringFromFile(pth) - if err != nil { - return WidgetModel{}, err - } - return parseConfigXMLContent(content) -} - -// FilterRootConfigXMLFile ... -func FilterRootConfigXMLFile(fileList []string) (string, error) { - allowConfigXMLBaseFilter := utility.BaseFilter(configXMLBasePath, true) - configXMLs, err := utility.FilterPaths(fileList, allowConfigXMLBaseFilter) - if err != nil { - return "", err - } - - if len(configXMLs) == 0 { - return "", nil - } - - return configXMLs[0], nil -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane.go deleted file mode 100644 index ba8beddf..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane.go +++ /dev/null @@ -1,242 +0,0 @@ -package fastlane - -import ( - "fmt" - - "gopkg.in/yaml.v2" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-core/bitrise-init/toolscanner" - "github.com/bitrise-core/bitrise-init/utility" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/log" -) - -const scannerName = "fastlane" - -const ( - unknownProjectType = "other" - fastlaneWorkflowID = scannerName -) - -const ( - configName = "fastlane-config" - defaultConfigName = "default-fastlane-config" -) - -// Step Inputs -const ( - laneInputKey = "lane" - laneInputTitle = "Fastlane lane" - laneInputEnvKey = "FASTLANE_LANE" -) - -const ( - workDirInputKey = "work_dir" - workDirInputTitle = "Working directory" - workDirInputEnvKey = "FASTLANE_WORK_DIR" -) - -const ( - fastlaneXcodeListTimeoutEnvKey = "FASTLANE_XCODE_LIST_TIMEOUT" - fastlaneXcodeListTimeoutEnvValue = "120" -) - -//------------------ -// ScannerInterface -//------------------ - -// Scanner ... -type Scanner struct { - Fastfiles []string - projectTypes []string -} - -// NewScanner ... -func NewScanner() *Scanner { - return &Scanner{} -} - -// Name ... -func (Scanner) Name() string { - return scannerName -} - -// DetectPlatform ... -func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { - fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) - if err != nil { - return false, fmt.Errorf("failed to search for files in (%s), error: %s", searchDir, err) - } - - // Search for Fastfile - log.TInfof("Searching for Fastfiles") - - fastfiles, err := FilterFastfiles(fileList) - if err != nil { - return false, fmt.Errorf("failed to search for Fastfile in (%s), error: %s", searchDir, err) - } - - scanner.Fastfiles = fastfiles - - log.TPrintf("%d Fastfiles detected", len(fastfiles)) - for _, file := range fastfiles { - log.TPrintf("- %s", file) - } - - if len(fastfiles) == 0 { - log.TPrintf("platform not detected") - return false, nil - } - - log.TSuccessf("Platform detected") - - return true, nil -} - -// ExcludedScannerNames ... -func (*Scanner) ExcludedScannerNames() []string { - return []string{} -} - -// Options ... -func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { - warnings := models.Warnings{} - - isValidFastfileFound := false - - // Inspect Fastfiles - - workDirOption := models.NewOption(workDirInputTitle, workDirInputEnvKey) - - for _, fastfile := range scanner.Fastfiles { - log.TInfof("Inspecting Fastfile: %s", fastfile) - - workDir := WorkDir(fastfile) - log.TPrintf("fastlane work dir: %s", workDir) - - lanes, err := InspectFastfile(fastfile) - if err != nil { - log.TWarnf("Failed to inspect Fastfile, error: %s", err) - warnings = append(warnings, fmt.Sprintf("Failed to inspect Fastfile (%s), error: %s", fastfile, err)) - continue - } - - log.TPrintf("%d lanes found", len(lanes)) - - if len(lanes) == 0 { - log.TWarnf("No lanes found") - warnings = append(warnings, fmt.Sprintf("No lanes found for Fastfile: %s", fastfile)) - continue - } - - isValidFastfileFound = true - - laneOption := models.NewOption(laneInputTitle, laneInputEnvKey) - workDirOption.AddOption(workDir, laneOption) - - for _, lane := range lanes { - log.TPrintf("- %s", lane) - - configOption := models.NewConfigOption(configName) - laneOption.AddConfig(lane, configOption) - } - } - - if !isValidFastfileFound { - log.TErrorf("No valid Fastfile found") - warnings = append(warnings, "No valid Fastfile found") - return models.OptionNode{}, warnings, nil - } - - // Add project_type property option to decision tree - optionWithProjectType := toolscanner.AddProjectTypeToOptions(*workDirOption, scanner.projectTypes) - - return optionWithProjectType, warnings, nil -} - -// DefaultOptions ... -func (*Scanner) DefaultOptions() models.OptionNode { - workDirOption := models.NewOption(workDirInputTitle, workDirInputEnvKey) - - laneOption := models.NewOption(laneInputTitle, laneInputEnvKey) - workDirOption.AddOption("_", laneOption) - - configOption := models.NewConfigOption(defaultConfigName) - laneOption.AddConfig("_", configOption) - - return *workDirOption -} - -// Configs ... -func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { - configBuilder := models.NewDefaultConfigBuilder() - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.FastlaneStepListItem( - envmanModels.EnvironmentItemModel{laneInputKey: "$" + laneInputEnvKey}, - envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}, - )) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - // Fill in project type later, from the list of detected project types - config, err := configBuilder.Generate(unknownProjectType, - envmanModels.EnvironmentItemModel{ - fastlaneXcodeListTimeoutEnvKey: fastlaneXcodeListTimeoutEnvValue, - }) - if err != nil { - return models.BitriseConfigMap{}, err - } - - // Create list of possible configs with project types - nameToConfigModel := toolscanner.AddProjectTypeToConfig(configName, config, scanner.projectTypes) - - nameToConfigString := map[string]string{} - for configName, config := range nameToConfigModel { - data, err := yaml.Marshal(config) - if err != nil { - return models.BitriseConfigMap{}, err - } - nameToConfigString[configName] = string(data) - } - return nameToConfigString, nil -} - -// DefaultConfigs ... -func (*Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { - configBuilder := models.NewDefaultConfigBuilder() - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.FastlaneStepListItem( - envmanModels.EnvironmentItemModel{laneInputKey: "$" + laneInputEnvKey}, - envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}, - )) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - config, err := configBuilder.Generate(unknownProjectType, envmanModels.EnvironmentItemModel{fastlaneXcodeListTimeoutEnvKey: fastlaneXcodeListTimeoutEnvValue}) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(config) - if err != nil { - return models.BitriseConfigMap{}, err - } - - return models.BitriseConfigMap{ - defaultConfigName: string(data), - }, nil -} - -// AutomationToolScannerInterface - -// SetDetectedProjectTypes ... -func (scanner *Scanner) SetDetectedProjectTypes(detectedProjectTypes []string) { - scanner.projectTypes = detectedProjectTypes -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test.go deleted file mode 100644 index a6799719..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test.go +++ /dev/null @@ -1,149 +0,0 @@ -package fastlane - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestFilterFastFiles(t *testing.T) { - - t.Log(`Contains "Fastfile" files`) - { - fileList := []string{ - "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-android/fastlane/Fastfile", - "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-android/Fastfile", - "path/to/my/gradlew/file", - "path/to/my", - } - - files, err := FilterFastfiles(fileList) - require.NoError(t, err) - require.Equal(t, 2, len(files)) - - // Also sorts "Fastfile" files by path components length - require.Equal(t, "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-android/Fastfile", files[0]) - require.Equal(t, "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-android/fastlane/Fastfile", files[1]) - } - - t.Log(`Do not contains "Fastfile" file`) - { - fileList := []string{ - "path/to/my/gradlew/build.", - "path/to/my/gradle", - } - - files, err := FilterFastfiles(fileList) - require.NoError(t, err) - require.Equal(t, 0, len(files)) - } -} - -func TestInspectFastFileContent(t *testing.T) { - lines := []string{ - " test ", - " lane ", - ":xcode", - - " lane :xcode do", - "lane :deploy do", - " lane :unit_tests do |params|", - - " private_lane :post_to_slack do |options|", - " private_lane :verify_xcode_version do", - } - content := strings.Join(lines, "\n") - - expectedLanes := []string{ - "xcode", - "deploy", - "unit_tests", - } - - lanes, err := inspectFastfileContent(content) - require.NoError(t, err) - require.Equal(t, expectedLanes, lanes) - - t.Log("ios test") - { - lanes, err := inspectFastfileContent(iosTesFastfileContent) - require.NoError(t, err) - - expectedLanes := []string{ - "ios test", - } - - require.Equal(t, expectedLanes, lanes) - } - - t.Log("experimental ios test") - { - lanes, err := inspectFastfileContent(complexIosTestFastFileContent) - require.NoError(t, err) - - expectedLanes := []string{ - "ios analyze", - "ios testAndPushBeta", - "ios submitAndPushToMaster", - "ios verifyTestPlatforms", - "ios verify", - "ios bumpPatch", - "ios bumpMinor", - "ios bumpMajor", - "ios bump", - "ios bumpAndTagBeta", - "ios bumpAndTagRelease", - "ios default_changelog", - "ios beta", - "ios store", - "ios dev", - } - - require.Equal(t, expectedLanes, lanes) - } -} - -func TestFastlaneWorkDir(t *testing.T) { - t.Log("Fastfile's dir, if Fastfile is NOT in fastlane dir") - { - expected := "." - actual := WorkDir("Fastfile") - require.Equal(t, expected, actual) - } - - t.Log("fastlane dir's parent, if Fastfile is in fastlane dir") - { - expected := "." - actual := WorkDir("fastlane/Fastfile") - require.Equal(t, expected, actual) - } - - t.Log("Fastfile's dir, if Fastfile is NOT in fastlane dir") - { - expected := "test" - actual := WorkDir("test/Fastfile") - require.Equal(t, expected, actual) - } - - t.Log("fastlane dir's parent, if Fastfile is in fastlane dir") - { - expected := "test" - actual := WorkDir("test/fastlane/Fastfile") - require.Equal(t, expected, actual) - } - - t.Log("Fastfile's dir, if Fastfile is NOT in fastlane dir") - { - expected := "my/app/test" - actual := WorkDir("my/app/test/Fastfile") - require.Equal(t, expected, actual) - } - - t.Log("fastlane dir's parent, if Fastfile is in fastlane dir") - { - expected := "my/app/test" - actual := WorkDir("my/app/test/fastlane/Fastfile") - require.Equal(t, expected, actual) - } -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test_file_contents.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test_file_contents.go deleted file mode 100644 index 5b9115b1..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/fastlane_test_file_contents.go +++ /dev/null @@ -1,310 +0,0 @@ -package fastlane - -const iosTesFastfileContent = `fastlane_version "1.49.0" - -default_platform :ios - - -platform :ios do - before_all do - # ENV["SLACK_URL"] = "https://hooks.slack.com/services/..." - - end - - desc "Runs all tests, archives app" - lane :test do - - match( - username: ENV['FASTLANE_USERNAME'], - app_identifier: "io.bitrise.BitriseFastlaneSample", - readonly: true, - type: "appstore" - ) - - scan( - scheme: "BitriseFastlaneSample", - destination: "name=iPhone 5s,OS=latest", - output_directory: ENV['BITRISE_DEPLOY_DIR'] - ) - - gym( - scheme: "BitriseFastlaneSample", - configuration: "Release", - output_directory: ENV['BITRISE_DEPLOY_DIR'], - output_name: "BitriseFastlaneSample.ipa", - use_legacy_build_api: "true" - ) - - crashlytics( - crashlytics_path: "./Crashlytics.framework", - api_token: ENV['CRASHLYTICS_API_TOKEN'], - build_secret: ENV['CRASHLYTICS_BUILD_SECRET'], - ipa_path: "#{ENV['BITRISE_DEPLOY_DIR']}/BitriseFastlaneSample.ipa" - ) - - snapshot - end - - after_all do |lane| - - end - - error do |lane, exception| - - end -end` - -const complexIosTestFastFileContent = `default_platform :ios - -platform :ios do - before_all do - # Set project for commit_version_bump, which seems to get confused by projects in other folders - ENV['FL_BUILD_NUMBER_PROJECT'] = "Wikipedia.xcodeproj" - ensure_git_status_clean unless ENV['FL_NO_ENSURE_CLEAN'] - end - - desc "Runs linting (and eventually static analysis)" - lane :analyze do - xcodebuild( - workspace: "Wikipedia.xcworkspace", - scheme: "Wikipedia", - configuration: "Debug", - sdk: 'iphonesimulator', - destination: 'platform=iOS Simulator,OS=9.3,name=iPhone 6', - analyze: true - ) - end - - desc "Runs tests, version, tag, and push to the beta branch" - lane :testAndPushBeta do - verifyTestPlatforms - bumpAndTagBeta - badge(dark: true, shield: get_badge_version_string, shield_no_resize: true) - beta - end - - desc "Runs tests, version, tag, and push to the beta branch" - lane :submitAndPushToMaster do - bumpAndTagRelease - store - end - - desc "Runs tests on the primary platforms and configurations" - lane :verifyTestPlatforms do - verify( - sim_os: 8.4, - scheme: "WikipediaRTL" - ) - verify( - sim_os: 8.4 - ) - verify( - scheme: "WikipediaRTL" - ) - verify( - junit: true - ) - end - - desc "Runs unit tests, optionally generating a JUnit report." - lane :verify do |options| - scheme = options[:scheme] || 'Wikipedia' - sim_os = options[:sim_os] || '9.3' - destination = "platform=iOS Simulator,name=iPhone 6,OS=#{sim_os}" - opts = { - :scheme => scheme, - :workspace => 'Wikipedia.xcworkspace', - :configuration => 'Debug', - :destination => destination, - :buildlog_path => './build', - :output_directory => './build/reports', - :output_style => 'basic', - :code_coverage => true, - :xcargs => "TRAVIS=#{ENV["TRAVIS"]}" - } - opts[:output_types] = options[:junit] ? "junit" : "" - scan(opts) - end - - desc "Increment the app version patch" - lane :bumpPatch do - increment_version_number( - bump_type: "patch" - ) - end - - desc "Increment the app version minor" - lane :bumpMinor do - increment_version_number( - bump_type: "minor" - ) - end - - desc "Increment the app version major" - lane :bumpMajor do - increment_version_number( - bump_type: "major" - ) - end - - desc "Increment the app's build number without committing the changes. Returns a string of the new, bumped version." - lane :bump do |options| - opt_build_num = options[:build_number] || ENV['BUILD_NUMBER'] - if opt_build_num then - increment_build_number(build_number: opt_build_num.to_i) - else - increment_build_number(build_number: get_build_number) - end - get_version_number - end - - desc "Increment the app's beta build number, add a tag, and push to the beta branch." - lane :bumpAndTagBeta do |options| - sh "git fetch" - sh "git checkout develop" - sh "git pull" - sh "git checkout beta" - sh "git merge develop" - - increment_build_number - - new_version = get_version_number - commit_version_bump - push_to_git_remote( - local_branch: 'beta', # optional, aliased by 'branch', default: 'master' - remote_branch: 'beta', # optional, default is set to local_branch - ) - - tag_name = "betas/#{new_version}" - add_git_tag(tag: tag_name) - sh "git push origin --tags" - - end - - desc "Increment the app's build number, add a tag, and push to the master branch." - lane :bumpAndTagRelease do |options| - sh "git fetch" - sh "git checkout release" - sh "git pull" - sh "git checkout master" - sh "git merge release" - - increment_build_number(build_number: get_release_build_number) - - new_version = get_version_number - commit_version_bump - push_to_git_remote( - local_branch: 'master', # optional, aliased by 'branch', default: 'master' - remote_branch: 'master', # optional, default is set to local_branch - ) - - tag_name = "releases/#{new_version}" - add_git_tag(tag: tag_name) - sh "git push origin --tags" - - end - - - desc "Returns a default changelog." - lane :default_changelog do - changelog = changelog_from_git_commits( - between: [ENV['GIT_PREVIOUS_SUCCESSFUL_COMMIT'] || "HEAD^^^^^", "HEAD"], - pretty: "- %s" - ) - # HAX: strip emoji from changelog - changelog = changelog.sub(/[\u{1F300}-\u{1F6FF}]/, '') - Actions.lane_context[SharedValues::FL_CHANGELOG] = changelog - puts changelog - changelog - end - - desc "Submit a new **Wikipedia Beta** build to Apple TestFlight for internal testing." - lane :beta do - - sigh( - adhoc: false, - force: true - ) - - gym( - configuration: "Beta", - scheme: "Wikipedia Beta" - ) - - # changelog = default_changelog - - hockey_beta_id = ENV["HOCKEY_BETA"] - raise "Must specifiy HockeyApp public identifier." unless hockey_beta_id.length > 0 - - hockey( - public_identifier: hockey_beta_id, - # notes: changelog, - notify: '0', # Means do not notify - status: '1', # Means do not make available for download - ) - - pilot( - skip_submission: false, - distribute_external: false - ) - - end - - desc "Submit a new App Store release candidate Apple TestFlight for internal testing." - lane :store do - sigh( - adhoc: false, - force: true - ) - - gym( - configuration: "Release", - scheme: "Wikipedia" - ) - - hockey_prod_id = ENV["HOCKEY_PRODUCTION"] - raise "Must specifiy HockeyApp public identifier." unless hockey_prod_id.length > 0 - - hockey( - public_identifier: hockey_prod_id, - notify: '0', # Means do not notify - status: '1', # Means do not make available for download - ) - - pilot( - skip_submission: false, - distribute_external: false - ) - - end - - - desc "Upload a developer build to Hockey." - lane :dev do - sigh( - adhoc: true, - # Fastlane has issues forcing AdHoc profiles - force: false - ) - - # force iTunes file sharing to be enabled (normally disabled for release builds) - ENV['WMF_FORCE_ITUNES_FILE_SHARING'] = '1' - # force debug menu to be shown - ENV['WMF_FORCE_DEBUG_MENU'] = '1' - - gym( - configuration: "AdHoc", - scheme: "Wikipedia AdHoc", - # both of these flags are required for ad hoc - export_method: 'ad-hoc', - use_legacy_build_api: true - ) - - hockey( - notes: default_changelog, - notify: '2', # Notify all testers - status: '2', # Make available for download - release_type: '2' # 'alpha' release type - ) - end -end` diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/utility.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/utility.go deleted file mode 100644 index 74f11d7d..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/fastlane/utility.go +++ /dev/null @@ -1,104 +0,0 @@ -package fastlane - -import ( - "bufio" - "path/filepath" - "regexp" - "strings" - - "github.com/bitrise-core/bitrise-init/utility" - "github.com/bitrise-io/go-utils/fileutil" -) - -const ( - fastfileBasePath = "Fastfile" -) - -// FilterFastfiles ... -func FilterFastfiles(fileList []string) ([]string, error) { - allowFastfileBaseFilter := utility.BaseFilter(fastfileBasePath, true) - fastfiles, err := utility.FilterPaths(fileList, allowFastfileBaseFilter) - if err != nil { - return []string{}, err - } - - return utility.SortPathsByComponents(fastfiles) -} - -func inspectFastfileContent(content string) ([]string, error) { - commonLanes := []string{} - laneMap := map[string][]string{} - - // platform :ios do ... - platformSectionStartRegexp := regexp.MustCompile(`platform\s+:(?P.*)\s+do`) - platformSectionEndPattern := "end" - platform := "" - - // lane :test_and_snapshot do - laneRegexp := regexp.MustCompile(`^[\s]*lane\s+:(?P.*)\s+do`) - - reader := strings.NewReader(content) - scanner := bufio.NewScanner(reader) - for scanner.Scan() { - line := strings.TrimRight(scanner.Text(), " ") - - if platform != "" && line == platformSectionEndPattern { - platform = "" - continue - } - - if platform == "" { - if match := platformSectionStartRegexp.FindStringSubmatch(line); len(match) == 2 { - platform = match[1] - continue - } - } - - if match := laneRegexp.FindStringSubmatch(line); len(match) == 2 { - lane := match[1] - - if platform != "" { - lanes, found := laneMap[platform] - if !found { - lanes = []string{} - } - lanes = append(lanes, lane) - laneMap[platform] = lanes - } else { - commonLanes = append(commonLanes, lane) - } - } - } - if err := scanner.Err(); err != nil { - return []string{}, err - } - - lanes := commonLanes - for platform, platformLanes := range laneMap { - for _, lane := range platformLanes { - lanes = append(lanes, platform+" "+lane) - } - } - - return lanes, nil -} - -// InspectFastfile ... -func InspectFastfile(fastFile string) ([]string, error) { - content, err := fileutil.ReadStringFromFile(fastFile) - if err != nil { - return []string{}, err - } - - return inspectFastfileContent(content) -} - -// WorkDir ... -func WorkDir(fastfilePth string) string { - dirPth := filepath.Dir(fastfilePth) - dirName := filepath.Base(dirPth) - if dirName == "fastlane" { - return filepath.Dir(dirPth) - } - return dirPth -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/flutter/flutter.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/flutter/flutter.go deleted file mode 100644 index 462c4b9e..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/flutter/flutter.go +++ /dev/null @@ -1,471 +0,0 @@ -package flutter - -import ( - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/bitrise-io/go-utils/pathutil" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/scanners/android" - "github.com/bitrise-core/bitrise-init/scanners/ios" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-core/bitrise-init/utility" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-tools/xcode-project/xcworkspace" - yaml "gopkg.in/yaml.v2" -) - -const ( - scannerName = "flutter" - configName = "flutter-config" - projectLocationInputKey = "project_location" - platformInputKey = "platform" - defaultIOSConfiguration = "Release" - projectLocationInputEnvKey = "BITRISE_FLUTTER_PROJECT_LOCATION" - projectLocationInputTitle = "Project Location" - projectTypeInputEnvKey = "BITRISE_FLUTTER_PROJECT_TYPE" - projectTypeInputTitle = "Project Type" - testsInputTitle = "Run tests found in the project" - platformInputTitle = "Platform" -) - -var ( - projectTypes = []string{ - "app", - "plugin", - "package", - } - platforms = []string{ - "none", - "android", - "ios", - "both", - } -) - -//------------------ -// ScannerInterface -//------------------ - -// Scanner ... -type Scanner struct { - projects []project -} - -type project struct { - path string - xcodeProjectPaths map[string][]string - hasTest bool - hasIosProject bool - hasAndroidProject bool -} - -type pubspec struct { - Name string `yaml:"name"` -} - -// NewScanner ... -func NewScanner() *Scanner { - return &Scanner{} -} - -// Name ... -func (Scanner) Name() string { - return scannerName -} - -func findProjectLocations(searchDir string) ([]string, error) { - fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) - if err != nil { - return nil, err - } - - filters := []utility.FilterFunc{ - utility.BaseFilter("pubspec.yaml", true), - } - - paths, err := utility.FilterPaths(fileList, filters...) - if err != nil { - return nil, err - } - - for i, path := range paths { - paths[i] = filepath.Dir(path) - } - - return paths, nil -} - -func findWorkspaceLocations(projectLocation string) ([]string, error) { - fileList, err := utility.ListPathInDirSortedByComponents(projectLocation, true) - if err != nil { - return nil, err - } - - for i, file := range fileList { - fileList[i] = filepath.Join(projectLocation, file) - } - - filters := []utility.FilterFunc{ - ios.AllowXCWorkspaceExtFilter, - ios.AllowIsDirectoryFilter, - ios.ForbidEmbeddedWorkspaceRegexpFilter, - ios.ForbidGitDirComponentFilter, - ios.ForbidPodsDirComponentFilter, - ios.ForbidCarthageDirComponentFilter, - ios.ForbidFramworkComponentWithExtensionFilter, - ios.ForbidCordovaLibDirComponentFilter, - ios.ForbidNodeModulesComponentFilter, - } - - return utility.FilterPaths(fileList, filters...) -} - -// DetectPlatform ... -func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { - log.TInfof("Search for project(s)") - projectLocations, err := findProjectLocations(searchDir) - if err != nil { - return false, err - } - - log.TPrintf("Paths containing pubspec.yaml(%d):", len(projectLocations)) - for _, p := range projectLocations { - log.TPrintf("- %s", p) - } - log.TPrintf("") - - log.TInfof("Fetching pubspec.yaml files") -projects: - for _, projectLocation := range projectLocations { - var proj project - - pubspecPath := filepath.Join(projectLocation, "pubspec.yaml") - pubspecFile, err := os.Open(pubspecPath) - if err != nil { - log.TErrorf("Failed to open pubspec.yaml file at: %s, error: %s", pubspecPath, err) - return false, err - } - - var ps pubspec - if err := yaml.NewDecoder(pubspecFile).Decode(&ps); err != nil { - log.TErrorf("Failed to decode yaml pubspec.yaml file at: %s, error: %s", pubspecPath, err) - return false, err - } - - testsDirPath := filepath.Join(projectLocation, "test") - if exists, err := pathutil.IsDirExists(testsDirPath); err == nil && exists { - if files, err := ioutil.ReadDir(testsDirPath); err == nil && len(files) > 0 { - for _, file := range files { - if strings.HasSuffix(file.Name(), "_test.dart") { - proj.hasTest = true - break - } - } - } - } - - iosProjPath := filepath.Join(projectLocation, "ios", "Runner.xcworkspace") - if exists, err := pathutil.IsPathExists(iosProjPath); err == nil && exists { - proj.hasIosProject = true - } - - androidProjPath := filepath.Join(projectLocation, "android", "build.gradle") - if exists, err := pathutil.IsPathExists(androidProjPath); err == nil && exists { - proj.hasAndroidProject = true - } - - log.TPrintf("- Project name: %s", ps.Name) - log.TPrintf(" Path: %s", projectLocation) - log.TPrintf(" HasTest: %t", proj.hasTest) - log.TPrintf(" HasAndroidProject: %t", proj.hasAndroidProject) - log.TPrintf(" HasIosProject: %t", proj.hasIosProject) - - proj.path = projectLocation - - if proj.hasIosProject { - if workspaceLocations, err := findWorkspaceLocations(filepath.Join(projectLocation, "ios")); err != nil { - log.TWarnf("Failed to check path at: %s, error: %s", filepath.Join(projectLocation, "ios"), err) - } else { - log.TPrintf(" XCWorkspaces(%d):", len(workspaceLocations)) - - for _, workspaceLocation := range workspaceLocations { - log.TPrintf(" Path: %s", workspaceLocation) - ws, err := xcworkspace.Open(workspaceLocation) - if err != nil { - continue projects - } - schemeMap, err := ws.Schemes() - if err != nil { - continue projects - } - - proj.xcodeProjectPaths = map[string][]string{} - - for _, schemes := range schemeMap { - if len(schemes) > 0 { - log.TPrintf(" Schemes(%d):", len(schemes)) - } - for _, scheme := range schemes { - log.TPrintf(" - %s", scheme.Name) - proj.xcodeProjectPaths[workspaceLocation] = append(proj.xcodeProjectPaths[workspaceLocation], scheme.Name) - } - } - } - } - } - - scanner.projects = append(scanner.projects, proj) - } - - if len(scanner.projects) == 0 { - return false, nil - } - - return true, nil -} - -// ExcludedScannerNames ... -func (Scanner) ExcludedScannerNames() []string { - return []string{ - string(ios.XcodeProjectTypeIOS), - android.ScannerName, - } -} - -// Options ... -func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { - flutterProjectLocationOption := models.NewOption(projectLocationInputTitle, projectLocationInputEnvKey) - - for _, project := range scanner.projects { - if project.hasTest { - flutterProjectHasTestOption := models.NewOption(testsInputTitle, "") - flutterProjectLocationOption.AddOption(project.path, flutterProjectHasTestOption) - - for _, v := range []string{"yes", "no"} { - cfg := configName - if v == "yes" { - cfg += "-test" - } - - if project.hasIosProject || project.hasAndroidProject { - if project.hasIosProject { - projectPathOption := models.NewOption(ios.ProjectPathInputTitle, ios.ProjectPathInputEnvKey) - flutterProjectHasTestOption.AddOption(v, projectPathOption) - - for xcodeWorkspacePath, schemes := range project.xcodeProjectPaths { - schemeOption := models.NewOption(ios.SchemeInputTitle, ios.SchemeInputEnvKey) - projectPathOption.AddOption(xcodeWorkspacePath, schemeOption) - - for _, scheme := range schemes { - exportMethodOption := models.NewOption(ios.IosExportMethodInputTitle, ios.ExportMethodInputEnvKey) - schemeOption.AddOption(scheme, exportMethodOption) - - for _, exportMethod := range ios.IosExportMethods { - configOption := models.NewConfigOption(cfg + "-app-" + getBuildablePlatform(project.hasAndroidProject, project.hasIosProject)) - exportMethodOption.AddConfig(exportMethod, configOption) - } - } - } - } else { - configOption := models.NewConfigOption(cfg + "-app-" + getBuildablePlatform(project.hasAndroidProject, project.hasIosProject)) - flutterProjectHasTestOption.AddOption(v, configOption) - } - } else { - configOption := models.NewConfigOption(cfg) - flutterProjectHasTestOption.AddOption(v, configOption) - } - } - } else { - cfg := configName - - if project.hasIosProject || project.hasAndroidProject { - if project.hasIosProject { - projectPathOption := models.NewOption(ios.ProjectPathInputTitle, ios.ProjectPathInputEnvKey) - flutterProjectLocationOption.AddOption(project.path, projectPathOption) - - for xcodeWorkspacePath, schemes := range project.xcodeProjectPaths { - schemeOption := models.NewOption(ios.SchemeInputTitle, ios.SchemeInputEnvKey) - projectPathOption.AddOption(xcodeWorkspacePath, schemeOption) - - for _, scheme := range schemes { - exportMethodOption := models.NewOption(ios.IosExportMethodInputTitle, ios.ExportMethodInputEnvKey) - schemeOption.AddOption(scheme, exportMethodOption) - - for _, exportMethod := range ios.IosExportMethods { - configOption := models.NewConfigOption(cfg + "-app-" + getBuildablePlatform(project.hasAndroidProject, project.hasIosProject)) - exportMethodOption.AddConfig(exportMethod, configOption) - } - } - } - } else { - configOption := models.NewConfigOption(cfg + "-app-" + getBuildablePlatform(project.hasAndroidProject, project.hasIosProject)) - flutterProjectLocationOption.AddOption(project.path, configOption) - } - } else { - configOption := models.NewConfigOption(cfg) - flutterProjectLocationOption.AddOption(project.path, configOption) - } - } - } - - return *flutterProjectLocationOption, nil, nil -} - -func getBuildablePlatform(hasAndroidProject, hasIosProject bool) string { - switch { - case hasAndroidProject && !hasIosProject: - return "android" - case !hasAndroidProject && hasIosProject: - return "ios" - default: - return "both" - } -} - -// DefaultOptions ... -func (Scanner) DefaultOptions() models.OptionNode { - flutterProjectLocationOption := models.NewOption(projectLocationInputTitle, projectLocationInputEnvKey) - - flutterProjectHasTestOption := models.NewOption(testsInputTitle, "") - flutterProjectLocationOption.AddOption("_", flutterProjectHasTestOption) - - for _, v := range []string{"yes", "no"} { - cfg := configName - if v == "yes" { - cfg += "-test" - } - flutterPlatformOption := models.NewOption(platformInputTitle, "") - flutterProjectHasTestOption.AddOption(v, flutterPlatformOption) - - for _, platform := range platforms { - if platform != "none" { - if platform != "android" { - projectPathOption := models.NewOption(ios.ProjectPathInputTitle, ios.ProjectPathInputEnvKey) - flutterPlatformOption.AddOption(platform, projectPathOption) - - schemeOption := models.NewOption(ios.SchemeInputTitle, ios.SchemeInputEnvKey) - projectPathOption.AddOption("_", schemeOption) - - exportMethodOption := models.NewOption(ios.IosExportMethodInputTitle, ios.ExportMethodInputEnvKey) - schemeOption.AddOption("_", exportMethodOption) - - for _, exportMethod := range ios.IosExportMethods { - configOption := models.NewConfigOption(cfg + "-app-" + platform) - exportMethodOption.AddConfig(exportMethod, configOption) - } - } else { - configOption := models.NewConfigOption(cfg + "-app-" + platform) - flutterPlatformOption.AddConfig(platform, configOption) - } - } else { - configOption := models.NewConfigOption(cfg) - flutterPlatformOption.AddConfig(platform, configOption) - } - } - } - - return *flutterProjectLocationOption -} - -// Configs ... -func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { - return scanner.DefaultConfigs() -} - -// DefaultConfigs ... -func (scanner Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { - configs := models.BitriseConfigMap{} - - for _, variant := range []struct { - configID string - test bool - deploy bool - platform string - }{ - {test: false, deploy: false, configID: configName}, - {test: true, deploy: false, configID: configName + "-test"}, - {test: false, deploy: true, platform: "both", configID: configName + "-app-both"}, - {test: true, deploy: true, platform: "both", configID: configName + "-test-app-both"}, - {test: false, deploy: true, platform: "android", configID: configName + "-app-android"}, - {test: true, deploy: true, platform: "android", configID: configName + "-test-app-android"}, - {test: false, deploy: true, platform: "ios", configID: configName + "-app-ios"}, - {test: true, deploy: true, platform: "ios", configID: configName + "-test-app-ios"}, - } { - configBuilder := models.NewDefaultConfigBuilder() - - // primary - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.FlutterInstallStepListItem()) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.FlutterAnalyzeStepListItem( - envmanModels.EnvironmentItemModel{projectLocationInputKey: "$" + projectLocationInputEnvKey}, - )) - - if variant.test { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.FlutterTestStepListItem( - envmanModels.EnvironmentItemModel{projectLocationInputKey: "$" + projectLocationInputEnvKey}, - )) - } - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - // deploy - - if variant.deploy { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) - - if variant.platform != "android" { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - } - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.FlutterInstallStepListItem()) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.FlutterAnalyzeStepListItem( - envmanModels.EnvironmentItemModel{projectLocationInputKey: "$" + projectLocationInputEnvKey}, - )) - - if variant.test { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.FlutterTestStepListItem( - envmanModels.EnvironmentItemModel{projectLocationInputKey: "$" + projectLocationInputEnvKey}, - )) - } - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.FlutterBuildStepListItem( - envmanModels.EnvironmentItemModel{projectLocationInputKey: "$" + projectLocationInputEnvKey}, - envmanModels.EnvironmentItemModel{platformInputKey: variant.platform}, - )) - - if variant.platform != "android" { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem( - envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: defaultIOSConfiguration}, - )) - } - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) - } - - config, err := configBuilder.Generate(scannerName) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(config) - if err != nil { - return models.BitriseConfigMap{}, err - } - - configs[variant.configID] = string(data) - } - - return configs, nil -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ionic/ionic.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ionic/ionic.go deleted file mode 100644 index a1e5a0c1..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/ionic/ionic.go +++ /dev/null @@ -1,406 +0,0 @@ -package ionic - -import ( - "fmt" - "path/filepath" - "strings" - - yaml "gopkg.in/yaml.v2" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/scanners/android" - "github.com/bitrise-core/bitrise-init/scanners/cordova" - "github.com/bitrise-core/bitrise-init/scanners/ios" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-core/bitrise-init/utility" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" -) - -const scannerName = "ionic" - -const ( - configName = "ionic-config" - defaultConfigName = "default-ionic-config" -) - -// Step Inputs -const ( - workDirInputKey = "workdir" - workDirInputTitle = "Directory of Ionic Config.xml" - workDirInputEnvKey = "IONIC_WORK_DIR" -) - -const ( - platformInputKey = "platform" - platformInputTitle = "Platform to use in ionic-cli commands" - platformInputEnvKey = "IONIC_PLATFORM" -) - -const ( - targetInputKey = "target" - targetEmulator = "emulator" -) - -//------------------ -// ScannerInterface -//------------------ - -// Scanner ... -type Scanner struct { - cordovaConfigPth string - relCordovaConfigDir string - searchDir string - hasKarmaJasmineTest bool - hasJasmineTest bool -} - -// NewScanner ... -func NewScanner() *Scanner { - return &Scanner{} -} - -// Name ... -func (Scanner) Name() string { - return scannerName -} - -// DetectPlatform ... -func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { - fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) - if err != nil { - return false, fmt.Errorf("failed to search for files in (%s), error: %s", searchDir, err) - } - - // Search for config.xml file - log.TInfof("Searching for config.xml file") - - configXMLPth, err := cordova.FilterRootConfigXMLFile(fileList) - if err != nil { - return false, fmt.Errorf("failed to search for config.xml file, error: %s", err) - } - - log.TPrintf("config.xml: %s", configXMLPth) - - if configXMLPth == "" { - log.TPrintf("platform not detected") - return false, nil - } - - widget, err := cordova.ParseConfigXML(configXMLPth) - if err != nil { - log.TPrintf("can not parse config.xml as a Cordova widget, error: %s", err) - log.TPrintf("platform not detected") - return false, nil - } - - // ensure it is a cordova widget - if !strings.Contains(widget.XMLNSCDV, "cordova.apache.org") { - log.TPrintf("config.xml propert: xmlns:cdv does not contain cordova.apache.org") - log.TPrintf("platform not detected") - return false, nil - } - - // ensure it is an ionic project - projectBaseDir := filepath.Dir(configXMLPth) - - ionicProjectExist, err := pathutil.IsPathExists(filepath.Join(projectBaseDir, "ionic.project")) - if err != nil { - return false, fmt.Errorf("failed to check if project is an ionic project, error: %s", err) - } - - ionicConfigExist, err := pathutil.IsPathExists(filepath.Join(projectBaseDir, "ionic.config.json")) - if err != nil { - return false, fmt.Errorf("failed to check if project is an ionic project, error: %s", err) - } - - if !ionicProjectExist && !ionicConfigExist { - log.Printf("no ionic.project file nor ionic.config.json found, seems to be a cordova project") - return false, nil - } - - log.TSuccessf("Platform detected") - - scanner.cordovaConfigPth = configXMLPth - scanner.searchDir = searchDir - - return true, nil -} - -// ExcludedScannerNames ... -func (Scanner) ExcludedScannerNames() []string { - return []string{ - string(ios.XcodeProjectTypeIOS), - string(ios.XcodeProjectTypeMacOS), - cordova.ScannerName, - android.ScannerName, - } -} - -// Options ... -func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { - warnings := models.Warnings{} - projectRootDir := filepath.Dir(scanner.cordovaConfigPth) - - packagesJSONPth := filepath.Join(projectRootDir, "package.json") - packages, err := utility.ParsePackagesJSON(packagesJSONPth) - if err != nil { - return models.OptionNode{}, warnings, err - } - - // Search for karma/jasmine tests - log.TPrintf("Searching for karma/jasmine test") - - karmaTestDetected := false - - karmaJasmineDependencyFound := false - for dependency := range packages.Dependencies { - if strings.Contains(dependency, "karma-jasmine") { - karmaJasmineDependencyFound = true - } - } - if !karmaJasmineDependencyFound { - for dependency := range packages.DevDependencies { - if strings.Contains(dependency, "karma-jasmine") { - karmaJasmineDependencyFound = true - } - } - } - log.TPrintf("karma-jasmine dependency found: %v", karmaJasmineDependencyFound) - - if karmaJasmineDependencyFound { - karmaConfigJSONPth := filepath.Join(projectRootDir, "karma.conf.js") - if exist, err := pathutil.IsPathExists(karmaConfigJSONPth); err != nil { - return models.OptionNode{}, warnings, err - } else if exist { - karmaTestDetected = true - } - } - log.TPrintf("karma.conf.js found: %v", karmaTestDetected) - - scanner.hasKarmaJasmineTest = karmaTestDetected - // --- - - // Search for jasmine tests - jasminTestDetected := false - - if !karmaTestDetected { - log.TPrintf("Searching for jasmine test") - - jasmineDependencyFound := false - for dependency := range packages.Dependencies { - if strings.Contains(dependency, "jasmine") { - jasmineDependencyFound = true - break - } - } - if !jasmineDependencyFound { - for dependency := range packages.DevDependencies { - if strings.Contains(dependency, "jasmine") { - jasmineDependencyFound = true - break - } - } - } - log.TPrintf("jasmine dependency found: %v", jasmineDependencyFound) - - if jasmineDependencyFound { - jasmineConfigJSONPth := filepath.Join(projectRootDir, "spec", "support", "jasmine.json") - if exist, err := pathutil.IsPathExists(jasmineConfigJSONPth); err != nil { - return models.OptionNode{}, warnings, err - } else if exist { - jasminTestDetected = true - } - } - - log.TPrintf("jasmine.json found: %v", jasminTestDetected) - - scanner.hasJasmineTest = jasminTestDetected - } - // --- - - // Get relative config.xml dir - cordovaConfigDir := filepath.Dir(scanner.cordovaConfigPth) - relCordovaConfigDir, err := utility.RelPath(scanner.searchDir, cordovaConfigDir) - if err != nil { - return models.OptionNode{}, warnings, fmt.Errorf("Failed to get relative config.xml dir path, error: %s", err) - } - if relCordovaConfigDir == "." { - // config.xml placed in the search dir, no need to change-dir in the workflows - relCordovaConfigDir = "" - } - scanner.relCordovaConfigDir = relCordovaConfigDir - // --- - - // Options - var rootOption *models.OptionNode - - platforms := []string{"ios", "android", "ios,android"} - - if relCordovaConfigDir != "" { - rootOption = models.NewOption(workDirInputTitle, workDirInputEnvKey) - - projectTypeOption := models.NewOption(platformInputTitle, platformInputEnvKey) - rootOption.AddOption(relCordovaConfigDir, projectTypeOption) - - for _, platform := range platforms { - configOption := models.NewConfigOption(configName) - projectTypeOption.AddConfig(platform, configOption) - } - } else { - rootOption = models.NewOption(platformInputTitle, platformInputEnvKey) - - for _, platform := range platforms { - configOption := models.NewConfigOption(configName) - rootOption.AddConfig(platform, configOption) - } - } - // --- - - return *rootOption, warnings, nil -} - -// DefaultOptions ... -func (Scanner) DefaultOptions() models.OptionNode { - workDirOption := models.NewOption(workDirInputTitle, workDirInputEnvKey) - - projectTypeOption := models.NewOption(platformInputTitle, platformInputEnvKey) - workDirOption.AddOption("_", projectTypeOption) - - platforms := []string{ - "ios", - "android", - "ios,android", - } - for _, platform := range platforms { - configOption := models.NewConfigOption(defaultConfigName) - projectTypeOption.AddConfig(platform, configOption) - } - - return *workDirOption -} - -// Configs ... -func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { - configBuilder := models.NewDefaultConfigBuilder() - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - - workdirEnvList := []envmanModels.EnvironmentItemModel{} - if scanner.relCordovaConfigDir != "" { - workdirEnvList = append(workdirEnvList, envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}) - } - - if scanner.hasJasmineTest || scanner.hasKarmaJasmineTest { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - - // CI - if scanner.hasKarmaJasmineTest { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.KarmaJasmineTestRunnerStepListItem(workdirEnvList...)) - } else if scanner.hasJasmineTest { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.JasmineTestRunnerStepListItem(workdirEnvList...)) - } - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - // CD - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - - if scanner.hasKarmaJasmineTest { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.KarmaJasmineTestRunnerStepListItem(workdirEnvList...)) - } else if scanner.hasJasmineTest { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.JasmineTestRunnerStepListItem(workdirEnvList...)) - } - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.GenerateCordovaBuildConfigStepListItem()) - - ionicArchiveEnvs := []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{platformInputKey: "$" + platformInputEnvKey}, - envmanModels.EnvironmentItemModel{targetInputKey: targetEmulator}, - } - if scanner.relCordovaConfigDir != "" { - ionicArchiveEnvs = append(ionicArchiveEnvs, envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}) - } - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.IonicArchiveStepListItem(ionicArchiveEnvs...)) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) - - config, err := configBuilder.Generate(scannerName) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(config) - if err != nil { - return models.BitriseConfigMap{}, err - } - - return models.BitriseConfigMap{ - configName: string(data), - }, nil - } - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.GenerateCordovaBuildConfigStepListItem()) - - ionicArchiveEnvs := []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{platformInputKey: "$" + platformInputEnvKey}, - envmanModels.EnvironmentItemModel{targetInputKey: targetEmulator}, - } - if scanner.relCordovaConfigDir != "" { - ionicArchiveEnvs = append(ionicArchiveEnvs, envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}) - } - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.IonicArchiveStepListItem(ionicArchiveEnvs...)) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - config, err := configBuilder.Generate(scannerName) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(config) - if err != nil { - return models.BitriseConfigMap{}, err - } - - return models.BitriseConfigMap{ - configName: string(data), - }, nil -} - -// DefaultConfigs ... -func (Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { - configBuilder := models.NewDefaultConfigBuilder() - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem( - envmanModels.EnvironmentItemModel{"command": "install"}, - envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey})) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.GenerateCordovaBuildConfigStepListItem()) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.IonicArchiveStepListItem( - envmanModels.EnvironmentItemModel{workDirInputKey: "$" + workDirInputEnvKey}, - envmanModels.EnvironmentItemModel{platformInputKey: "$" + platformInputEnvKey}, - envmanModels.EnvironmentItemModel{targetInputKey: targetEmulator})) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - config, err := configBuilder.Generate(scannerName) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(config) - if err != nil { - return models.BitriseConfigMap{}, err - } - - return models.BitriseConfigMap{ - defaultConfigName: string(data), - }, nil -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/gemutil_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/gemutil_test.go deleted file mode 100644 index a01294c7..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/gemutil_test.go +++ /dev/null @@ -1,210 +0,0 @@ -package ios - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestGemVersionFromGemfileLockContent(t *testing.T) { - version := GemVersionFromGemfileLockContent("fastlane", gemfileLockContent) - require.Equal(t, "2.13.0", version) -} - -const gemfileLockContent = `GIT - remote: git://xyz.git - revision: xyz - branch: patch-1 - specs: - fastlane-xyz (1.0.2) -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (2.3.5) - activesupport (4.2.7.1) - i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - addressable (2.5.0) - public_suffix (~> 2.0, >= 2.0.2) - babosa (1.0.2) - claide (1.0.1) - cocoapods (1.1.1) - activesupport (>= 4.0.2, < 5) - claide (>= 1.0.1, < 2.0) - cocoapods-core (= 1.1.1) - cocoapods-deintegrate (>= 1.0.1, < 2.0) - cocoapods-downloader (>= 1.1.2, < 2.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.1.1, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored (~> 1.2) - escape (~> 0.0.4) - fourflusher (~> 2.0.1) - gh_inspector (~> 1.0) - molinillo (~> 0.5.1) - nap (~> 1.0) - xcodeproj (>= 1.3.3, < 2.0) - cocoapods-core (1.1.1) - activesupport (>= 4.0.2, < 5) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - cocoapods-deintegrate (1.0.1) - cocoapods-downloader (1.1.3) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) - cocoapods-trunk (1.1.2) - nap (>= 0.8, < 2.0) - netrc (= 0.7.8) - cocoapods-try (1.1.0) - colored (1.2) - commander (4.4.3) - highline (~> 1.7.2) - domain_name (0.5.20161129) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.2.0) - escape (0.0.4) - excon (0.54.0) - faraday (0.11.0) - multipart-post (>= 1.2, < 3) - faraday-cookie_jar (0.0.6) - faraday (>= 0.7.4) - http-cookie (~> 1.0.0) - faraday_middleware (0.11.0.1) - faraday (>= 0.7.4, < 1.0) - fastimage (2.0.1) - addressable (~> 2) - fastlane (2.13.0) - activesupport (< 5) - addressable (>= 2.3, < 3.0.0) - babosa (>= 1.0.2, < 2.0.0) - bundler (>= 1.12.0, < 2.0.0) - colored - commander (>= 4.4.0, < 5.0.0) - dotenv (>= 2.1.1, < 3.0.0) - excon (>= 0.45.0, < 1.0.0) - faraday (~> 0.9) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 0.9) - fastimage (>= 1.6) - gh_inspector (>= 1.0.1, < 2.0.0) - google-api-client (~> 0.9.2) - highline (>= 1.7.2, < 2.0.0) - json (< 3.0.0) - mini_magick (~> 4.5.1) - multi_json - multi_xml (~> 0.5) - multipart-post (~> 2.0.0) - plist (>= 3.1.0, < 4.0.0) - rubyzip (>= 1.1.0, < 2.0.0) - security (= 0.1.3) - slack-notifier (>= 1.3, < 2.0.0) - terminal-notifier (>= 1.6.2, < 2.0.0) - terminal-table (>= 1.4.5, < 2.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 0.20, < 2.0.0) - xcpretty (>= 0.2.4, < 1.0.0) - xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-tpa (1.1.0) - fourflusher (2.0.1) - fuzzy_match (2.0.4) - gh_inspector (1.0.3) - google-api-client (0.9.26) - addressable (~> 2.3) - googleauth (~> 0.5) - httpclient (~> 2.7) - hurley (~> 0.1) - memoist (~> 0.11) - mime-types (>= 1.6) - representable (~> 2.3.0) - retriable (~> 2.0) - googleauth (0.5.1) - faraday (~> 0.9) - jwt (~> 1.4) - logging (~> 2.0) - memoist (~> 0.12) - multi_json (~> 1.11) - os (~> 0.9) - signet (~> 0.7) - highline (1.7.8) - http-cookie (1.0.3) - domain_name (~> 0.5) - httpclient (2.8.3) - hurley (0.2) - i18n (0.8.0) - json (1.8.6) - jwt (1.5.6) - little-plugger (1.1.4) - logging (2.1.0) - little-plugger (~> 1.1) - multi_json (~> 1.10) - memoist (0.15.0) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mini_magick (4.5.1) - minitest (5.10.1) - molinillo (0.5.5) - multi_json (1.12.1) - multi_xml (0.6.0) - multipart-post (2.0.0) - nanaimo (0.2.3) - nap (1.1.0) - netrc (0.7.8) - os (0.9.6) - plist (3.2.0) - public_suffix (2.0.5) - representable (2.3.0) - uber (~> 0.0.7) - retriable (2.1.0) - rouge (1.11.1) - rubyzip (1.2.0) - security (0.1.3) - signet (0.7.3) - addressable (~> 2.3) - faraday (~> 0.9) - jwt (~> 1.5) - multi_json (~> 1.10) - slack-notifier (1.5.1) - terminal-notifier (1.7.1) - terminal-table (1.7.3) - unicode-display_width (~> 1.1.1) - thread_safe (0.3.5) - tzinfo (1.2.2) - thread_safe (~> 0.1) - uber (0.0.15) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.2) - unicode-display_width (1.1.3) - word_wrap (1.0.0) - xcode-install (2.1.1) - claide (>= 0.9.1, < 1.1.0) - fastlane (>= 2.1.0, < 3.0.0) - xcodeproj (1.4.2) - CFPropertyList (~> 2.3.3) - activesupport (>= 3) - claide (>= 1.0.1, < 2.0) - colored (~> 1.2) - nanaimo (~> 0.2.3) - xcpretty (0.2.4) -> 1.8) - xcpretty-travis-formatter (0.0.4) - xcpretty (~> 0.2, >= 0.0.7) -PLATFORMS - ruby -DEPENDENCIES - cocoapods (~> 1.1.0) - dotenv (~> 2.0) - fastlane (~> 2.0) - fastlane-plugin-prepare_build_resources! - fastlane-plugin-tpa (~> 1.1.0) - xcode-install -BUNDLED WITH - 1.13.6` diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/podfile_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/podfile_test.go deleted file mode 100644 index ce9a3a60..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/podfile_test.go +++ /dev/null @@ -1,609 +0,0 @@ -package ios - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-core/bitrise-init/utility" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-tools/go-xcode/xcodeproj" - "github.com/stretchr/testify/require" -) - -func TestAllowPodfileBaseFilter(t *testing.T) { - t.Log("abs path") - { - absPaths := []string{ - "/Users/bitrise/Test.txt", - "/Users/bitrise/.git/Podfile", - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Podfile", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Podfile", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Podfile", - } - - expectedPaths := []string{ - "/Users/bitrise/.git/Podfile", - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Podfile", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Podfile", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Podfile", - } - - actualPaths, err := utility.FilterPaths(absPaths, AllowPodfileBaseFilter) - require.NoError(t, err) - require.Equal(t, expectedPaths, actualPaths) - } - - t.Log("rel path") - { - relPaths := []string{ - ".", - "Test.txt", - ".git/Podfile", - "sample-apps-ios-cocoapods/Pods/Podfile", - "ios-no-shared-schemes/Carthage/Checkouts/Result/Podfile", - "ios-no-shared-schemes/test.framework/Checkouts/Result/Podfile", - } - - expectedPaths := []string{ - ".git/Podfile", - "sample-apps-ios-cocoapods/Pods/Podfile", - "ios-no-shared-schemes/Carthage/Checkouts/Result/Podfile", - "ios-no-shared-schemes/test.framework/Checkouts/Result/Podfile", - } - - actualPaths, err := utility.FilterPaths(relPaths, AllowPodfileBaseFilter) - require.NoError(t, err) - require.Equal(t, expectedPaths, actualPaths) - } -} - -func TestGetTargetDefinitionProjectMap(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__utility_test__") - require.NoError(t, err) - defer func() { - require.NoError(t, os.RemoveAll(tmpDir)) - }() - - t.Log("xcodeproj defined") - { - tmpDir = filepath.Join(tmpDir, "xcodeproj_defined") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -project 'MyXcodeProject' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - expectedTargetDefinition := map[string]string{ - "Pods": "MyXcodeProject.xcodeproj", - } - actualTargetDefinition, err := getTargetDefinitionProjectMap(podfilePth, "") - require.NoError(t, err) - require.Equal(t, expectedTargetDefinition, actualTargetDefinition) - } - - t.Log("xcodeproj NOT defined") - { - tmpDir = filepath.Join(tmpDir, "xcodeproj_not_defined") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - expectedTargetDefinition := map[string]string{} - actualTargetDefinition, err := getTargetDefinitionProjectMap(podfilePth, "") - require.NoError(t, err) - require.Equal(t, expectedTargetDefinition, actualTargetDefinition) - } - - t.Log("cocoapods 0.38.0") - { - tmpDir = filepath.Join(tmpDir, "xcodeproj_not_defined") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' - -# pod 'Functional.m', '~> 1.0' - -# Add Kiwi as an exclusive dependency for the Test target -target :SampleAppWithCocoapodsTests, :exclusive => true do - pod 'Kiwi' -end - -# post_install do |installer_representation| -# installer_representation.project.targets.each do |target| -# target.build_configurations.each do |config| -# config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO' -# end -# end -# end` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - expectedTargetDefinition := map[string]string{} - actualTargetDefinition, err := getTargetDefinitionProjectMap(podfilePth, "0.38.0") - require.NoError(t, err) - require.Equal(t, expectedTargetDefinition, actualTargetDefinition) - } -} - -func TestGetUserDefinedProjectRelavtivePath(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__utility_test__") - require.NoError(t, err) - defer func() { - require.NoError(t, os.RemoveAll(tmpDir)) - }() - - t.Log("xcodeproj defined") - { - tmpDir = filepath.Join(tmpDir, "xcodeproj_defined") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -project 'MyXcodeProject' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - expectedProject := "MyXcodeProject.xcodeproj" - actualProject, err := getUserDefinedProjectRelavtivePath(podfilePth, "") - require.NoError(t, err) - require.Equal(t, expectedProject, actualProject) - } - - t.Log("xcodeproj NOT defined") - { - tmpDir = filepath.Join(tmpDir, "xcodeproj_not_defined") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - expectedProject := "" - actualProject, err := getUserDefinedProjectRelavtivePath(podfilePth, "") - require.NoError(t, err) - require.Equal(t, expectedProject, actualProject) - } -} - -func TestGetUserDefinedWorkspaceRelativePath(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__utility_test__") - require.NoError(t, err) - defer func() { - require.NoError(t, os.RemoveAll(tmpDir)) - }() - - t.Log("workspace defined") - { - tmpDir = filepath.Join(tmpDir, "workspace_defined") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -workspace 'MyWorkspace' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - expectedWorkspace := "MyWorkspace.xcworkspace" - actualWorkspace, err := getUserDefinedWorkspaceRelativePath(podfilePth, "") - require.NoError(t, err) - require.Equal(t, expectedWorkspace, actualWorkspace) - } - - t.Log("workspace NOT defined") - { - tmpDir = filepath.Join(tmpDir, "workspace_not_defined") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - expectedWorkspace := "" - actualWorkspace, err := getUserDefinedWorkspaceRelativePath(podfilePth, "") - require.NoError(t, err) - require.Equal(t, expectedWorkspace, actualWorkspace) - } -} - -func TestGetWorkspaceProjectMap(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__utility_test__") - require.NoError(t, err) - defer func() { - require.NoError(t, os.RemoveAll(tmpDir)) - }() - - t.Log("0 project in Podfile's dir") - { - tmpDir = filepath.Join(tmpDir, "no_project") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{}) - require.Error(t, err) - require.Equal(t, 0, len(workspaceProjectMap)) - - require.NoError(t, os.RemoveAll(tmpDir)) - } - - t.Log("1 project in Podfile's dir") - { - tmpDir = filepath.Join(tmpDir, "one_project") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - project := "" - projectPth := filepath.Join(tmpDir, "project.xcodeproj") - require.NoError(t, fileutil.WriteStringToFile(projectPth, project)) - - workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{projectPth}) - require.NoError(t, err) - require.Equal(t, 1, len(workspaceProjectMap)) - - for workspace, project := range workspaceProjectMap { - workspaceBasename := filepath.Base(workspace) - workspaceName := strings.TrimSuffix(workspaceBasename, ".xcworkspace") - - projectBasename := filepath.Base(project) - projectName := strings.TrimSuffix(projectBasename, ".xcodeproj") - - require.Equal(t, "project", workspaceName, fmt.Sprintf("%v", workspaceProjectMap)) - require.Equal(t, "project", projectName, fmt.Sprintf("%v", workspaceProjectMap)) - } - - require.NoError(t, os.RemoveAll(tmpDir)) - } - - t.Log("Multiple project in Podfile's dir") - { - tmpDir = filepath.Join(tmpDir, "multiple_project") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - project1 := "" - project1Pth := filepath.Join(tmpDir, "project1.xcodeproj") - require.NoError(t, fileutil.WriteStringToFile(project1Pth, project1)) - - project2 := "" - project2Pth := filepath.Join(tmpDir, "project2.xcodeproj") - require.NoError(t, fileutil.WriteStringToFile(project2Pth, project2)) - - workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{project1Pth, project2Pth}) - require.Error(t, err) - require.Equal(t, 0, len(workspaceProjectMap)) - - require.NoError(t, os.RemoveAll(tmpDir)) - } - - t.Log("0 project in Podfile's dir + project defined in Podfile") - { - tmpDir = filepath.Join(tmpDir, "no_project_project_defined") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -project 'MyXcodeProject' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{}) - require.Error(t, err) - require.Equal(t, 0, len(workspaceProjectMap)) - - require.NoError(t, os.RemoveAll(tmpDir)) - } - - t.Log("1 project in Podfile's dir + project defined in Podfile") - { - tmpDir = filepath.Join(tmpDir, "one_project_project_defined") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -project 'project' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - project := "" - projectPth := filepath.Join(tmpDir, "project.xcodeproj") - require.NoError(t, fileutil.WriteStringToFile(projectPth, project)) - - workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{projectPth}) - require.NoError(t, err) - require.Equal(t, 1, len(workspaceProjectMap)) - - for workspace, project := range workspaceProjectMap { - workspaceBasename := filepath.Base(workspace) - workspaceName := strings.TrimSuffix(workspaceBasename, ".xcworkspace") - - projectBasename := filepath.Base(project) - projectName := strings.TrimSuffix(projectBasename, ".xcodeproj") - - require.Equal(t, "project", workspaceName, fmt.Sprintf("%v", workspaceProjectMap)) - require.Equal(t, "project", projectName, fmt.Sprintf("%v", workspaceProjectMap)) - } - - require.NoError(t, os.RemoveAll(tmpDir)) - } - - t.Log("Multiple project in Podfile's dir + project defined in Podfile") - { - tmpDir = filepath.Join(tmpDir, "multiple_project") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -project 'project1' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - project1 := "" - project1Pth := filepath.Join(tmpDir, "project1.xcodeproj") - require.NoError(t, fileutil.WriteStringToFile(project1Pth, project1)) - - project2 := "" - project2Pth := filepath.Join(tmpDir, "project2.xcodeproj") - require.NoError(t, fileutil.WriteStringToFile(project2Pth, project2)) - - workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{project1Pth, project2Pth}) - require.NoError(t, err) - require.Equal(t, 1, len(workspaceProjectMap)) - - for workspace, project := range workspaceProjectMap { - workspaceBasename := filepath.Base(workspace) - workspaceName := strings.TrimSuffix(workspaceBasename, ".xcworkspace") - - projectBasename := filepath.Base(project) - projectName := strings.TrimSuffix(projectBasename, ".xcodeproj") - - require.Equal(t, "project1", workspaceName, fmt.Sprintf("%v", workspaceProjectMap)) - require.Equal(t, "project1", projectName, fmt.Sprintf("%v", workspaceProjectMap)) - } - - require.NoError(t, os.RemoveAll(tmpDir)) - } - - t.Log("1 project in Podfile's dir + workspace defined in Podfile") - { - tmpDir = filepath.Join(tmpDir, "one_project") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -workspace 'MyWorkspace' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - project := "" - projectPth := filepath.Join(tmpDir, "project.xcodeproj") - require.NoError(t, fileutil.WriteStringToFile(projectPth, project)) - - workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{projectPth}) - require.NoError(t, err) - require.Equal(t, 1, len(workspaceProjectMap)) - - for workspace, project := range workspaceProjectMap { - workspaceBasename := filepath.Base(workspace) - workspaceName := strings.TrimSuffix(workspaceBasename, ".xcworkspace") - - projectBasename := filepath.Base(project) - projectName := strings.TrimSuffix(projectBasename, ".xcodeproj") - - require.Equal(t, "MyWorkspace", workspaceName, fmt.Sprintf("%v", workspaceProjectMap)) - require.Equal(t, "project", projectName, fmt.Sprintf("%v", workspaceProjectMap)) - } - - require.NoError(t, os.RemoveAll(tmpDir)) - } - - t.Log("Multiple project in Podfile's dir + workspace defined in Podfile") - { - tmpDir = filepath.Join(tmpDir, "multiple_project") - require.NoError(t, os.MkdirAll(tmpDir, 0777)) - - podfile := `platform :ios, '9.0' -project 'project1' -workspace 'MyWorkspace' -pod 'Alamofire', '~> 3.4' -` - podfilePth := filepath.Join(tmpDir, "Podfile") - require.NoError(t, fileutil.WriteStringToFile(podfilePth, podfile)) - - project1 := "" - project1Pth := filepath.Join(tmpDir, "project1.xcodeproj") - require.NoError(t, fileutil.WriteStringToFile(project1Pth, project1)) - - project2 := "" - project2Pth := filepath.Join(tmpDir, "project2.xcodeproj") - require.NoError(t, fileutil.WriteStringToFile(project2Pth, project2)) - - workspaceProjectMap, err := GetWorkspaceProjectMap(podfilePth, []string{project1Pth, project2Pth}) - require.NoError(t, err) - require.Equal(t, 1, len(workspaceProjectMap)) - - for workspace, project := range workspaceProjectMap { - workspaceBasename := filepath.Base(workspace) - workspaceName := strings.TrimSuffix(workspaceBasename, ".xcworkspace") - - projectBasename := filepath.Base(project) - projectName := strings.TrimSuffix(projectBasename, ".xcodeproj") - - require.Equal(t, "MyWorkspace", workspaceName, fmt.Sprintf("%v", workspaceProjectMap)) - require.Equal(t, "project1", projectName, fmt.Sprintf("%v", workspaceProjectMap)) - } - - require.NoError(t, os.RemoveAll(tmpDir)) - } -} - -func TestMergePodWorkspaceProjectMap(t *testing.T) { - t.Log("workspace is in the repository") - { - podWorkspaceMap := map[string]string{ - "MyWorkspace.xcworkspace": "MyXcodeProject.xcodeproj", - } - - standaloneProjects := []xcodeproj.ProjectModel{} - expectedStandaloneProjects := []xcodeproj.ProjectModel{} - - workspaces := []xcodeproj.WorkspaceModel{ - xcodeproj.WorkspaceModel{ - Pth: "MyWorkspace.xcworkspace", - Name: "MyWorkspace", - Projects: []xcodeproj.ProjectModel{ - xcodeproj.ProjectModel{ - Pth: "MyXcodeProject.xcodeproj", - }, - }, - }, - } - expectedWorkspaces := []xcodeproj.WorkspaceModel{ - xcodeproj.WorkspaceModel{ - Pth: "MyWorkspace.xcworkspace", - Name: "MyWorkspace", - Projects: []xcodeproj.ProjectModel{ - xcodeproj.ProjectModel{ - Pth: "MyXcodeProject.xcodeproj", - }, - }, - IsPodWorkspace: true, - }, - } - - mergedStandaloneProjects, mergedWorkspaces, err := MergePodWorkspaceProjectMap(podWorkspaceMap, standaloneProjects, workspaces) - require.NoError(t, err) - require.Equal(t, expectedStandaloneProjects, mergedStandaloneProjects) - require.Equal(t, expectedWorkspaces, mergedWorkspaces) - } - - t.Log("workspace is in the repository, but project not attached - ERROR") - { - podWorkspaceMap := map[string]string{ - "MyWorkspace.xcworkspace": "MyXcodeProject.xcodeproj", - } - - standaloneProjects := []xcodeproj.ProjectModel{} - - workspaces := []xcodeproj.WorkspaceModel{ - xcodeproj.WorkspaceModel{ - Pth: "MyWorkspace.xcworkspace", - Name: "MyWorkspace", - }, - } - - mergedStandaloneProjects, mergedWorkspaces, err := MergePodWorkspaceProjectMap(podWorkspaceMap, standaloneProjects, workspaces) - require.Error(t, err) - require.Equal(t, []xcodeproj.ProjectModel{}, mergedStandaloneProjects) - require.Equal(t, []xcodeproj.WorkspaceModel{}, mergedWorkspaces) - } - - t.Log("workspace is in the repository, but project is marged as standalon - ERROR") - { - podWorkspaceMap := map[string]string{ - "MyWorkspace.xcworkspace": "MyXcodeProject.xcodeproj", - } - - standaloneProjects := []xcodeproj.ProjectModel{ - xcodeproj.ProjectModel{ - Pth: "MyXcodeProject.xcodeproj", - }, - } - - workspaces := []xcodeproj.WorkspaceModel{ - xcodeproj.WorkspaceModel{ - Pth: "MyWorkspace.xcworkspace", - Name: "MyWorkspace", - }, - } - - mergedStandaloneProjects, mergedWorkspaces, err := MergePodWorkspaceProjectMap(podWorkspaceMap, standaloneProjects, workspaces) - require.Error(t, err) - require.Equal(t, []xcodeproj.ProjectModel{}, mergedStandaloneProjects) - require.Equal(t, []xcodeproj.WorkspaceModel{}, mergedWorkspaces) - } - - t.Log("workspace is gitignored") - { - podWorkspaceMap := map[string]string{ - "MyWorkspace.xcworkspace": "MyXcodeProject.xcodeproj", - } - - standaloneProjects := []xcodeproj.ProjectModel{ - xcodeproj.ProjectModel{ - Pth: "MyXcodeProject.xcodeproj", - }, - } - expectedStandaloneProjects := []xcodeproj.ProjectModel{} - - workspaces := []xcodeproj.WorkspaceModel{} - expectedWorkspaces := []xcodeproj.WorkspaceModel{ - xcodeproj.WorkspaceModel{ - Pth: "MyWorkspace.xcworkspace", - Name: "MyWorkspace", - Projects: []xcodeproj.ProjectModel{ - xcodeproj.ProjectModel{ - Pth: "MyXcodeProject.xcodeproj", - }, - }, - IsPodWorkspace: true, - }, - } - - mergedStandaloneProjects, mergedWorkspaces, err := MergePodWorkspaceProjectMap(podWorkspaceMap, standaloneProjects, workspaces) - require.NoError(t, err) - require.Equal(t, expectedStandaloneProjects, mergedStandaloneProjects) - require.Equal(t, expectedWorkspaces, mergedWorkspaces) - } - - t.Log("workspace is gitignored, but standalon project missing - ERROR") - { - podWorkspaceMap := map[string]string{ - "MyWorkspace.xcworkspace": "MyXcodeProject.xcodeproj", - } - - standaloneProjects := []xcodeproj.ProjectModel{} - - workspaces := []xcodeproj.WorkspaceModel{} - - mergedStandaloneProjects, mergedWorkspaces, err := MergePodWorkspaceProjectMap(podWorkspaceMap, standaloneProjects, workspaces) - require.Error(t, err) - require.Equal(t, []xcodeproj.ProjectModel{}, mergedStandaloneProjects) - require.Equal(t, []xcodeproj.WorkspaceModel{}, mergedWorkspaces) - } -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/rubyscript_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/rubyscript_test.go deleted file mode 100644 index c0121d57..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/rubyscript_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package ios - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestRunRubyScriptForOutput(t *testing.T) { - gemfileContent := `source 'https://rubygems.org' -gem 'json' -` - - rubyScriptContent := `require 'json' - -puts "#{{ :test_key => 'test_value' }.to_json}" -` - - expectedOut := "{\"test_key\":\"test_value\"}" - actualOut, err := runRubyScriptForOutput(rubyScriptContent, gemfileContent, "", []string{}) - require.NoError(t, err) - require.Equal(t, expectedOut, actualOut) -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/utility_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/utility_test.go deleted file mode 100644 index f293b241..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/utility_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package ios - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestNewConfigDescriptor(t *testing.T) { - descriptor := NewConfigDescriptor(false, "", false, true) - require.Equal(t, false, descriptor.HasPodfile) - require.Equal(t, false, descriptor.HasTest) - require.Equal(t, true, descriptor.MissingSharedSchemes) - require.Equal(t, "", descriptor.CarthageCommand) -} - -func TestConfigName(t *testing.T) { - { - descriptor := NewConfigDescriptor(false, "", false, false) - require.Equal(t, "ios-config", descriptor.ConfigName(XcodeProjectTypeIOS)) - } - - { - descriptor := NewConfigDescriptor(true, "", false, false) - require.Equal(t, "ios-pod-config", descriptor.ConfigName(XcodeProjectTypeIOS)) - } - - { - descriptor := NewConfigDescriptor(false, "bootsrap", false, false) - require.Equal(t, "ios-carthage-config", descriptor.ConfigName(XcodeProjectTypeIOS)) - } - - { - descriptor := NewConfigDescriptor(false, "", true, false) - require.Equal(t, "ios-test-config", descriptor.ConfigName(XcodeProjectTypeIOS)) - } - - { - descriptor := NewConfigDescriptor(false, "", false, true) - require.Equal(t, "ios-missing-shared-schemes-config", descriptor.ConfigName(XcodeProjectTypeIOS)) - } - - { - descriptor := NewConfigDescriptor(true, "bootstrap", false, false) - require.Equal(t, "ios-pod-carthage-config", descriptor.ConfigName(XcodeProjectTypeIOS)) - } - - { - descriptor := NewConfigDescriptor(true, "bootstrap", true, false) - require.Equal(t, "ios-pod-carthage-test-config", descriptor.ConfigName(XcodeProjectTypeIOS)) - } - - { - descriptor := NewConfigDescriptor(true, "bootstrap", true, true) - require.Equal(t, "ios-pod-carthage-test-missing-shared-schemes-config", descriptor.ConfigName(XcodeProjectTypeIOS)) - } -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj_test.go deleted file mode 100644 index 47c7bf7c..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/ios/xcodeproj_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package ios - -import ( - "os" - "path/filepath" - "testing" - - "github.com/bitrise-core/bitrise-init/utility" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestAllowXcodeProjExtFilter(t *testing.T) { - paths := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", - } - expectedFiltered := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", - } - actualFiltered, err := utility.FilterPaths(paths, AllowXcodeProjExtFilter) - require.NoError(t, err) - require.Equal(t, expectedFiltered, actualFiltered) -} - -func TestAllowXCWorkspaceExtFilter(t *testing.T) { - paths := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", - } - expectedFiltered := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - } - actualFiltered, err := utility.FilterPaths(paths, AllowXCWorkspaceExtFilter) - require.NoError(t, err) - require.Equal(t, expectedFiltered, actualFiltered) -} - -func TestForbidEmbeddedWorkspaceRegexpFilter(t *testing.T) { - paths := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", - } - expectedFiltered := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", - } - actualFiltered, err := utility.FilterPaths(paths, ForbidEmbeddedWorkspaceRegexpFilter) - require.NoError(t, err) - require.Equal(t, expectedFiltered, actualFiltered) -} - -func TestForbidGitDirComponentFilter(t *testing.T) { - paths := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", - } - expectedFiltered := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", - } - actualFiltered, err := utility.FilterPaths(paths, ForbidGitDirComponentFilter) - require.NoError(t, err) - require.Equal(t, expectedFiltered, actualFiltered) -} - -func TestForbidPodsDirComponentFilter(t *testing.T) { - paths := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", - } - expectedFiltered := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", - } - actualFiltered, err := utility.FilterPaths(paths, ForbidPodsDirComponentFilter) - require.NoError(t, err) - require.Equal(t, expectedFiltered, actualFiltered) -} - -func TestForbidCarthageDirComponentFilter(t *testing.T) { - paths := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", - } - expectedFiltered := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", - } - actualFiltered, err := utility.FilterPaths(paths, ForbidCarthageDirComponentFilter) - require.NoError(t, err) - require.Equal(t, expectedFiltered, actualFiltered) -} - -func TestForbidFramworkComponentWithExtensionFilter(t *testing.T) { - paths := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/test.framework/Checkouts/Result/Result.xcodeproj", - } - expectedFiltered := []string{ - "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/.git/SampleAppWithCocoapods.xcodeproj/project.xcworkspace", - "/Users/bitrise/sample-apps-ios-cocoapods/Pods/Pods.xcodeproj", - "/Users/bitrise/ios-no-shared-schemes/Carthage/Checkouts/Result/Result.xcodeproj", - } - actualFiltered, err := utility.FilterPaths(paths, ForbidFramworkComponentWithExtensionFilter) - require.NoError(t, err) - require.Equal(t, expectedFiltered, actualFiltered) -} - -func TestAllowIphoneosSDKFilter(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__xcodeproj_test__") - require.NoError(t, err) - defer func() { - require.NoError(t, os.RemoveAll(tmpDir)) - }() - - iphoneosProject := filepath.Join(tmpDir, "iphoneos.xcodeproj") - require.NoError(t, os.MkdirAll(iphoneosProject, 0777)) - - iphoneosPbxprojPth := filepath.Join(iphoneosProject, "project.pbxproj") - require.NoError(t, fileutil.WriteStringToFile(iphoneosPbxprojPth, testIOSPbxprojContent)) - - macosxProject := filepath.Join(tmpDir, "macosx.xcodeproj") - require.NoError(t, os.MkdirAll(macosxProject, 0777)) - - macosxPbxprojPth := filepath.Join(macosxProject, "project.pbxproj") - require.NoError(t, fileutil.WriteStringToFile(macosxPbxprojPth, testMacOSPbxprojContent)) - - t.Log("iphoneos sdk") - { - paths := []string{ - iphoneosProject, - macosxProject, - } - expectedFiltered := []string{ - iphoneosProject, - } - actualFiltered, err := utility.FilterPaths(paths, AllowIphoneosSDKFilter) - require.NoError(t, err) - require.Equal(t, expectedFiltered, actualFiltered) - } - - t.Log("macosx sdk") - { - paths := []string{ - iphoneosProject, - macosxProject, - } - expectedFiltered := []string{ - macosxProject, - } - actualFiltered, err := utility.FilterPaths(paths, AllowMacosxSDKFilter) - require.NoError(t, err) - require.Equal(t, expectedFiltered, actualFiltered) - } -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/macos/macos.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/macos/macos.go deleted file mode 100644 index c252b677..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/macos/macos.go +++ /dev/null @@ -1,70 +0,0 @@ -package macos - -import ( - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/scanners/ios" -) - -//------------------ -// ScannerInterface -//------------------ - -// Scanner ... -type Scanner struct { - searchDir string - configDescriptors []ios.ConfigDescriptor -} - -// NewScanner ... -func NewScanner() *Scanner { - return &Scanner{} -} - -// Name ... -func (Scanner) Name() string { - return string(ios.XcodeProjectTypeMacOS) -} - -// DetectPlatform ... -func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { - scanner.searchDir = searchDir - - detected, err := ios.Detect(ios.XcodeProjectTypeMacOS, searchDir) - if err != nil { - return false, err - } - - return detected, nil -} - -// ExcludedScannerNames ... -func (Scanner) ExcludedScannerNames() []string { - return []string{} -} - -// Options ... -func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { - options, configDescriptors, warnings, err := ios.GenerateOptions(ios.XcodeProjectTypeMacOS, scanner.searchDir) - if err != nil { - return models.OptionNode{}, warnings, err - } - - scanner.configDescriptors = configDescriptors - - return options, warnings, nil -} - -// DefaultOptions ... -func (Scanner) DefaultOptions() models.OptionNode { - return ios.GenerateDefaultOptions(ios.XcodeProjectTypeMacOS) -} - -// Configs ... -func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { - return ios.GenerateConfig(ios.XcodeProjectTypeMacOS, scanner.configDescriptors, true) -} - -// DefaultConfigs ... -func (Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { - return ios.GenerateDefaultConfig(ios.XcodeProjectTypeMacOS, true) -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative-expo/reactnative-expo.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative-expo/reactnative-expo.go deleted file mode 100644 index f6ef7576..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative-expo/reactnative-expo.go +++ /dev/null @@ -1,785 +0,0 @@ -package expo - -import ( - "bufio" - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/scanners/android" - "github.com/bitrise-core/bitrise-init/scanners/ios" - "github.com/bitrise-core/bitrise-init/scanners/reactnative" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-core/bitrise-init/utility" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-tools/xcode-project/serialized" - yaml "gopkg.in/yaml.v2" -) - -const ( - configName = "react-native-expo-config" - defaultConfigName = "default-" + configName -) - -const deployWorkflowDescription = `## Configure Android part of the deploy workflow - -To generate a signed APK: - -1. Open the **Workflow** tab of your project on Bitrise.io -1. Add **Sign APK step right after Android Build step** -1. Click on **Code Signing** tab -1. Find the **ANDROID KEYSTORE FILE** section -1. Click or drop your file on the upload file field -1. Fill the displayed 3 input fields: -1. **Keystore password** -1. **Keystore alias** -1. **Private key password** -1. Click on **[Save metadata]** button - -That's it! From now on, **Sign APK** step will receive your uploaded files. - -## Configure iOS part of the deploy workflow - -To generate IPA: - -1. Open the **Workflow** tab of your project on Bitrise.io -1. Click on **Code Signing** tab -1. Find the **PROVISIONING PROFILE** section -1. Click or drop your file on the upload file field -1. Find the **CODE SIGNING IDENTITY** section -1. Click or drop your file on the upload file field -1. Click on **Workflows** tab -1. Select deploy workflow -1. Select **Xcode Archive & Export for iOS** step -1. Open **Force Build Settings** input group -1. Specify codesign settings -Set **Force code signing with Development Team**, **Force code signing with Code Signing Identity** -and **Force code signing with Provisioning Profile** inputs regarding to the uploaded codesigning files -1. Specify manual codesign style -If the codesigning files, are generated manually on the Apple Developer Portal, -you need to explicitly specify to use manual coedsign settings -(as ejected rn projects have xcode managed codesigning turned on). -To do so, add 'CODE_SIGN_STYLE="Manual"' to 'Additional options for xcodebuild call' input - -## To run this workflow - -If you want to run this workflow manually: - -1. Open the app's build list page -2. Click on **[Start/Schedule a Build]** button -3. Select **deploy** in **Workflow** dropdown input -4. Click **[Start Build]** button - -Or if you need this workflow to be started by a GIT event: - -1. Click on **Triggers** tab -2. Setup your desired event (push/tag/pull) and select **deploy** workflow -3. Click on **[Done]** and then **[Save]** buttons - -The next change in your repository that matches any of your trigger map event will start **deploy** workflow. -` - -// Name ... -const Name = "react-native-expo" - -// Scanner ... -type Scanner struct { - searchDir string - packageJSONPth string - usesExpoKit bool -} - -// NewScanner ... -func NewScanner() *Scanner { - return &Scanner{} -} - -// Name ... -func (Scanner) Name() string { - return Name -} - -// DetectPlatform ... -func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { - scanner.searchDir = searchDir - - log.TInfof("Collect package.json files") - - packageJSONPths, err := reactnative.CollectPackageJSONFiles(searchDir) - if err != nil { - return false, err - } - - if len(packageJSONPths) == 0 { - return false, nil - } - - log.TPrintf("%d package.json file detected", len(packageJSONPths)) - for _, pth := range packageJSONPths { - log.TPrintf("- %s", pth) - } - log.TPrintf("") - - log.TInfof("Filter package.json files with expo dependency") - - relevantPackageJSONPths := []string{} - for _, packageJSONPth := range packageJSONPths { - packages, err := utility.ParsePackagesJSON(packageJSONPth) - if err != nil { - log.Warnf("Failed to parse package json file: %s, skipping...", packageJSONPth) - continue - } - - _, found := packages.Dependencies["expo"] - if !found { - continue - } - - // app.json file is a required part of react native projects and it exists next to the root package.json file - appJSONPth := filepath.Join(filepath.Dir(packageJSONPth), "app.json") - if exist, err := pathutil.IsPathExists(appJSONPth); err != nil { - log.Warnf("Failed to check if app.json file exist at: %s, skipping package json file: %s, error: %s", appJSONPth, packageJSONPth, err) - continue - } else if !exist { - log.Warnf("No app.json file exist at: %s, skipping package json file: %s", appJSONPth, packageJSONPth) - continue - } - - relevantPackageJSONPths = append(relevantPackageJSONPths, packageJSONPth) - } - - log.TPrintf("%d package.json file detected with expo dependency", len(relevantPackageJSONPths)) - for _, pth := range relevantPackageJSONPths { - log.TPrintf("- %s", pth) - } - log.TPrintf("") - - if len(relevantPackageJSONPths) == 0 { - return false, nil - } else if len(relevantPackageJSONPths) > 1 { - log.TWarnf("Multiple package.json file found, using: %s\n", relevantPackageJSONPths[0]) - } - - scanner.packageJSONPth = relevantPackageJSONPths[0] - return true, nil -} - -func appJSONIssue(appJSONPth, reason, explanation string) string { - return fmt.Sprintf("app.json file (%s) %s\n%s", appJSONPth, reason, explanation) -} - -// Options ... -func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { - warnings := models.Warnings{} - - // we need to know if the project uses the Expo Kit, - // since its usage differentiates the eject process and the config options - usesExpoKit := false - - fileList, err := utility.ListPathInDirSortedByComponents(scanner.searchDir, true) - if err != nil { - return models.OptionNode{}, warnings, err - } - - filters := []utility.FilterFunc{ - utility.ExtensionFilter(".js", true), - utility.ComponentFilter("node_modules", false), - } - sourceFiles, err := utility.FilterPaths(fileList, filters...) - if err != nil { - return models.OptionNode{}, warnings, err - } - - re := regexp.MustCompile(`import .* from 'expo'`) - -SourceFileLoop: - for _, sourceFile := range sourceFiles { - f, err := os.Open(sourceFile) - if err != nil { - return models.OptionNode{}, warnings, err - } - defer func() { - if cerr := f.Close(); cerr != nil { - log.Warnf("Failed to close: %s, error: %s", f.Name(), err) - } - }() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - if match := re.FindString(scanner.Text()); match != "" { - usesExpoKit = true - break SourceFileLoop - } - } - if err := scanner.Err(); err != nil { - return models.OptionNode{}, warnings, err - } - } - - scanner.usesExpoKit = usesExpoKit - log.TPrintf("Uses ExpoKit: %v", usesExpoKit) - - // ensure app.json contains the required information (for non interactive eject) - // and predict the ejected project name - var projectName string - - rootDir := filepath.Dir(scanner.packageJSONPth) - appJSONPth := filepath.Join(rootDir, "app.json") - appJSON, err := fileutil.ReadStringFromFile(appJSONPth) - if err != nil { - return models.OptionNode{}, warnings, err - } - var app serialized.Object - if err := json.Unmarshal([]byte(appJSON), &app); err != nil { - return models.OptionNode{}, warnings, err - } - - if usesExpoKit { - // if the project uses Expo Kit app.json needs to contain expo/ios/bundleIdentifier and expo/android/package entries - // to be able to eject in non interactive mode - errorMessage := `If the project uses Expo Kit the app.json file needs to contain: -- expo/name -- expo/ios/bundleIdentifier -- expo/android/package -entries.` - - expoObj, err := app.Object("expo") - if err != nil { - return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing expo entry", errorMessage)) - } - projectName, err = expoObj.String("name") - if err != nil || projectName == "" { - return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing or empty expo/name entry", errorMessage)) - } - - iosObj, err := expoObj.Object("ios") - if err != nil { - return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing expo/ios entry", errorMessage)) - } - bundleID, err := iosObj.String("bundleIdentifier") - if err != nil || bundleID == "" { - return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing or empty expo/ios/bundleIdentifier entry", errorMessage)) - } - - androidObj, err := expoObj.Object("android") - if err != nil { - return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing expo/android entry", errorMessage)) - } - packageName, err := androidObj.String("package") - if err != nil || packageName == "" { - return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing or empty expo/android/package entry", errorMessage)) - } - } else { - // if the project does not use Expo Kit app.json needs to contain name and displayName entries - // to be able to eject in non interactive mode - errorMessage := `The app.json file needs to contain: -- name -- displayName -entries.` - - projectName, err = app.String("name") - if err != nil || projectName == "" { - return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing or empty name entry", errorMessage)) - } - displayName, err := app.String("displayName") - if err != nil || displayName == "" { - return models.OptionNode{}, warnings, errors.New(appJSONIssue(appJSONPth, "missing or empty displayName entry", errorMessage)) - } - } - - log.TPrintf("Project name: %v", projectName) - - // ios options - projectPathOption := models.NewOption(ios.ProjectPathInputTitle, ios.ProjectPathInputEnvKey) - schemeOption := models.NewOption(ios.SchemeInputTitle, ios.SchemeInputEnvKey) - - if usesExpoKit { - projectName = strings.ToLower(regexp.MustCompile(`(?i:[^a-z0-9_\-])`).ReplaceAllString(projectName, "-")) - projectPathOption.AddOption(filepath.Join("./", "ios", projectName+".xcworkspace"), schemeOption) - } else { - projectPathOption.AddOption(filepath.Join("./", "ios", projectName+".xcodeproj"), schemeOption) - } - - developmentTeamOption := models.NewOption("iOS Development team", "BITRISE_IOS_DEVELOPMENT_TEAM") - schemeOption.AddOption(projectName, developmentTeamOption) - - exportMethodOption := models.NewOption(ios.IosExportMethodInputTitle, ios.ExportMethodInputEnvKey) - developmentTeamOption.AddOption("_", exportMethodOption) - - // android options - packageJSONDir := filepath.Dir(scanner.packageJSONPth) - relPackageJSONDir, err := utility.RelPath(scanner.searchDir, packageJSONDir) - if err != nil { - return models.OptionNode{}, warnings, fmt.Errorf("Failed to get relative package.json dir path, error: %s", err) - } - if relPackageJSONDir == "." { - // package.json placed in the search dir, no need to change-dir in the workflows - relPackageJSONDir = "" - } - - var moduleOption *models.OptionNode - if relPackageJSONDir == "" { - projectLocationOption := models.NewOption(android.ProjectLocationInputTitle, android.ProjectLocationInputEnvKey) - for _, exportMethod := range ios.IosExportMethods { - exportMethodOption.AddOption(exportMethod, projectLocationOption) - } - - moduleOption = models.NewOption(android.ModuleInputTitle, android.ModuleInputEnvKey) - projectLocationOption.AddOption("./android", moduleOption) - } else { - workDirOption := models.NewOption("Project root directory (the directory of the project app.json/package.json file)", "WORKDIR") - for _, exportMethod := range ios.IosExportMethods { - exportMethodOption.AddOption(exportMethod, workDirOption) - } - - projectLocationOption := models.NewOption(android.ProjectLocationInputTitle, android.ProjectLocationInputEnvKey) - workDirOption.AddOption(relPackageJSONDir, projectLocationOption) - - moduleOption = models.NewOption(android.ModuleInputTitle, android.ModuleInputEnvKey) - projectLocationOption.AddOption(filepath.Join(relPackageJSONDir, "android"), moduleOption) - } - - buildVariantOption := models.NewOption(android.VariantInputTitle, android.VariantInputEnvKey) - moduleOption.AddOption("app", buildVariantOption) - - // expo options - if scanner.usesExpoKit { - userNameOption := models.NewOption("Expo username", "EXPO_USERNAME") - buildVariantOption.AddOption("Release", userNameOption) - - passwordOption := models.NewOption("Expo password", "EXPO_PASSWORD") - userNameOption.AddOption("_", passwordOption) - - configOption := models.NewConfigOption(configName) - passwordOption.AddConfig("_", configOption) - } else { - configOption := models.NewConfigOption(configName) - buildVariantOption.AddConfig("Release", configOption) - } - - return *projectPathOption, warnings, nil -} - -// Configs ... -func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { - configMap := models.BitriseConfigMap{} - - // determine workdir - packageJSONDir := filepath.Dir(scanner.packageJSONPth) - relPackageJSONDir, err := utility.RelPath(scanner.searchDir, packageJSONDir) - if err != nil { - return models.BitriseConfigMap{}, fmt.Errorf("Failed to get relative package.json dir path, error: %s", err) - } - if relPackageJSONDir == "." { - // package.json placed in the search dir, no need to change-dir in the workflows - relPackageJSONDir = "" - } - log.TPrintf("Working directory: %v", relPackageJSONDir) - - workdirEnvList := []envmanModels.EnvironmentItemModel{} - if relPackageJSONDir != "" { - workdirEnvList = append(workdirEnvList, envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: relPackageJSONDir}) - } - - // determine dependency manager step - hasYarnLockFile := false - if exist, err := pathutil.IsPathExists(filepath.Join(relPackageJSONDir, "yarn.lock")); err != nil { - log.Warnf("Failed to check if yarn.lock file exists in the workdir: %s", err) - log.TPrintf("Dependency manager: npm") - } else if exist { - log.TPrintf("Dependency manager: yarn") - hasYarnLockFile = true - } else { - log.TPrintf("Dependency manager: npm") - } - - // find test script in package.json file - b, err := fileutil.ReadBytesFromFile(scanner.packageJSONPth) - if err != nil { - return models.BitriseConfigMap{}, err - } - var packageJSON serialized.Object - if err := json.Unmarshal([]byte(b), &packageJSON); err != nil { - return models.BitriseConfigMap{}, err - } - - hasTest := false - if scripts, err := packageJSON.Object("scripts"); err == nil { - if _, err := scripts.String("test"); err == nil { - hasTest = true - } - } - log.TPrintf("test script found in package.json: %v", hasTest) - - if !hasTest { - // if the project has no test script defined, - // we can only provide deploy like workflow, - // so that is going to be the primary workflow - - configBuilder := models.NewDefaultConfigBuilder() - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - - if hasYarnLockFile { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.YarnStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - } else { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - } - - projectDir := relPackageJSONDir - if relPackageJSONDir == "" { - projectDir = "./" - } - if scanner.usesExpoKit { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.ExpoDetachStepListItem( - envmanModels.EnvironmentItemModel{"project_path": projectDir}, - envmanModels.EnvironmentItemModel{"user_name": "$EXPO_USERNAME"}, - envmanModels.EnvironmentItemModel{"password": "$EXPO_PASSWORD"}, - envmanModels.EnvironmentItemModel{"run_publish": "yes"}, - )) - } else { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.ExpoDetachStepListItem( - envmanModels.EnvironmentItemModel{"project_path": projectDir}, - )) - } - - // android build - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.InstallMissingAndroidToolsStepListItem( - envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, - )) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.AndroidBuildStepListItem( - envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: "$" + android.ProjectLocationInputEnvKey}, - envmanModels.EnvironmentItemModel{android.ModuleInputKey: "$" + android.ModuleInputEnvKey}, - envmanModels.EnvironmentItemModel{android.VariantInputKey: "$" + android.VariantInputEnvKey}, - )) - - // ios build - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - - if scanner.usesExpoKit { - // in case of expo kit rn project expo eject generates an ios project with Podfile - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CocoapodsInstallStepListItem()) - } - - xcodeArchiveInputs := []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, - envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, - envmanModels.EnvironmentItemModel{"force_team_id": "$BITRISE_IOS_DEVELOPMENT_TEAM"}, - } - if !scanner.usesExpoKit { - // in case of plain rn project new xcode build system needs to be turned off - xcodeArchiveInputs = append(xcodeArchiveInputs, envmanModels.EnvironmentItemModel{"xcodebuild_options": "-UseModernBuildSystem=NO"}) - } - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XcodeArchiveStepListItem(xcodeArchiveInputs...)) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - configBuilder.SetWorkflowDescriptionTo(models.PrimaryWorkflowID, deployWorkflowDescription) - - bitriseDataModel, err := configBuilder.Generate(Name) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(bitriseDataModel) - if err != nil { - return models.BitriseConfigMap{}, err - } - - configMap[configName] = string(data) - - return configMap, nil - } - - // primary workflow - configBuilder := models.NewDefaultConfigBuilder() - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - if hasYarnLockFile { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.YarnStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.YarnStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "test"})...)) - } else { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "test"})...)) - } - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - // deploy workflow - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) - if hasYarnLockFile { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.YarnStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - } else { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - } - - projectDir := relPackageJSONDir - if relPackageJSONDir == "" { - projectDir = "./" - } - if scanner.usesExpoKit { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.ExpoDetachStepListItem( - envmanModels.EnvironmentItemModel{"project_path": projectDir}, - envmanModels.EnvironmentItemModel{"user_name": "$EXPO_USERNAME"}, - envmanModels.EnvironmentItemModel{"password": "$EXPO_PASSWORD"}, - envmanModels.EnvironmentItemModel{"run_publish": "yes"}, - )) - } else { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.ExpoDetachStepListItem( - envmanModels.EnvironmentItemModel{"project_path": projectDir}, - )) - } - - // android build - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.InstallMissingAndroidToolsStepListItem( - envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, - )) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( - envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: "$" + android.ProjectLocationInputEnvKey}, - envmanModels.EnvironmentItemModel{android.ModuleInputKey: "$" + android.ModuleInputEnvKey}, - envmanModels.EnvironmentItemModel{android.VariantInputKey: "$" + android.VariantInputEnvKey}, - )) - - // ios build - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - - if scanner.usesExpoKit { - // in case of expo kit rn project expo eject generates an ios project with Podfile - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CocoapodsInstallStepListItem()) - } - - xcodeArchiveInputs := []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, - envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, - envmanModels.EnvironmentItemModel{"force_team_id": "$BITRISE_IOS_DEVELOPMENT_TEAM"}, - } - if !scanner.usesExpoKit { - xcodeArchiveInputs = append(xcodeArchiveInputs, envmanModels.EnvironmentItemModel{"xcodebuild_options": "-UseModernBuildSystem=NO"}) - } - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem(xcodeArchiveInputs...)) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) - configBuilder.SetWorkflowDescriptionTo(models.DeployWorkflowID, deployWorkflowDescription) - - bitriseDataModel, err := configBuilder.Generate(Name) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(bitriseDataModel) - if err != nil { - return models.BitriseConfigMap{}, err - } - - configMap[configName] = string(data) - - return configMap, nil -} - -// DefaultOptions ... -func (Scanner) DefaultOptions() models.OptionNode { - expoKitOption := models.NewOption("Project uses Expo Kit (any js file imports expo dependency)?", "USES_EXPO_KIT") - - // with Expo Kit - { - // ios options - workspacePathOption := models.NewOption("The iOS workspace path generated ny the 'expo eject' process", ios.ProjectPathInputEnvKey) - expoKitOption.AddOption("yes", workspacePathOption) - - schemeOption := models.NewOption("The iOS scheme name generated by the 'expo eject' process", ios.SchemeInputEnvKey) - workspacePathOption.AddOption("_", schemeOption) - - exportMethodOption := models.NewOption(ios.IosExportMethodInputTitle, ios.ExportMethodInputEnvKey) - schemeOption.AddOption("_", exportMethodOption) - - // android options - workDirOption := models.NewOption("Project root directory (the directory of the project app.json/package.json file)", "WORKDIR") - for _, exportMethod := range ios.IosExportMethods { - exportMethodOption.AddOption(exportMethod, workDirOption) - } - - projectLocationOption := models.NewOption(android.ProjectLocationInputTitle, android.ProjectLocationInputEnvKey) - workDirOption.AddOption("_", projectLocationOption) - - moduleOption := models.NewOption(android.ModuleInputTitle, android.ModuleInputEnvKey) - projectLocationOption.AddOption("./android", moduleOption) - - buildVariantOption := models.NewOption(android.VariantInputTitle, android.VariantInputEnvKey) - moduleOption.AddOption("app", buildVariantOption) - - // Expo CLI options - userNameOption := models.NewOption("Expo username", "EXPO_USERNAME") - buildVariantOption.AddOption("Release", userNameOption) - - passwordOption := models.NewOption("Expo password", "EXPO_PASSWORD") - userNameOption.AddOption("_", passwordOption) - - configOption := models.NewConfigOption("react-native-expo-expo-kit-default-config") - passwordOption.AddConfig("_", configOption) - } - - // without Expo Kit - { - // ios options - projectPathOption := models.NewOption("The iOS project path generated ny the 'expo eject' process", ios.ProjectPathInputEnvKey) - expoKitOption.AddOption("no", projectPathOption) - - schemeOption := models.NewOption("The iOS scheme name generated by the 'expo eject' process", ios.SchemeInputEnvKey) - projectPathOption.AddOption("_", schemeOption) - - exportMethodOption := models.NewOption(ios.IosExportMethodInputTitle, ios.ExportMethodInputEnvKey) - schemeOption.AddOption("_", exportMethodOption) - - // android options - workDirOption := models.NewOption("Project root directory (the directory of the project app.json/package.json file)", "WORKDIR") - for _, exportMethod := range ios.IosExportMethods { - exportMethodOption.AddOption(exportMethod, workDirOption) - } - - projectLocationOption := models.NewOption(android.ProjectLocationInputTitle, android.ProjectLocationInputEnvKey) - workDirOption.AddOption("_", projectLocationOption) - - moduleOption := models.NewOption(android.ModuleInputTitle, android.ModuleInputEnvKey) - projectLocationOption.AddOption("./android", moduleOption) - - buildVariantOption := models.NewOption(android.VariantInputTitle, android.VariantInputEnvKey) - moduleOption.AddOption("app", buildVariantOption) - - configOption := models.NewConfigOption("react-native-expo-plain-default-config") - buildVariantOption.AddConfig("Release", configOption) - } - - return *expoKitOption -} - -// DefaultConfigs ... -func (Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { - configMap := models.BitriseConfigMap{} - - // with Expo Kit - { - // primary workflow - configBuilder := models.NewDefaultConfigBuilder() - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: "$WORKDIR"}, envmanModels.EnvironmentItemModel{"command": "install"})) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: "$WORKDIR"}, envmanModels.EnvironmentItemModel{"command": "test"})) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - // deploy workflow - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: "$WORKDIR"}, envmanModels.EnvironmentItemModel{"command": "install"})) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.ExpoDetachStepListItem( - envmanModels.EnvironmentItemModel{"project_path": "$WORKDIR"}, - envmanModels.EnvironmentItemModel{"user_name": "$EXPO_USERNAME"}, - envmanModels.EnvironmentItemModel{"password": "$EXPO_PASSWORD"}, - envmanModels.EnvironmentItemModel{"run_publish": "yes"}, - )) - - // android build - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.InstallMissingAndroidToolsStepListItem( - envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, - )) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( - envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: "$" + android.ProjectLocationInputEnvKey}, - envmanModels.EnvironmentItemModel{android.ModuleInputKey: "$" + android.ModuleInputEnvKey}, - envmanModels.EnvironmentItemModel{android.VariantInputKey: "$" + android.VariantInputEnvKey}, - )) - - // ios build - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CocoapodsInstallStepListItem()) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem( - envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, - )) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) - configBuilder.SetWorkflowDescriptionTo(models.DeployWorkflowID, deployWorkflowDescription) - - bitriseDataModel, err := configBuilder.Generate(Name) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(bitriseDataModel) - if err != nil { - return models.BitriseConfigMap{}, err - } - - configMap["default-react-native-expo-expo-kit-config"] = string(data) - } - - { - // primary workflow - configBuilder := models.NewDefaultConfigBuilder() - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: "$WORKDIR"}, envmanModels.EnvironmentItemModel{"command": "install"})) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: "$WORKDIR"}, envmanModels.EnvironmentItemModel{"command": "test"})) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - // deploy workflow - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{reactnative.WorkDirInputKey: "$WORKDIR"}, envmanModels.EnvironmentItemModel{"command": "install"})) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.ExpoDetachStepListItem(envmanModels.EnvironmentItemModel{"project_path": "$WORKDIR"})) - - // android build - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.InstallMissingAndroidToolsStepListItem( - envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, - )) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( - envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: "$" + android.ProjectLocationInputEnvKey}, - envmanModels.EnvironmentItemModel{android.ModuleInputKey: "$" + android.ModuleInputEnvKey}, - envmanModels.EnvironmentItemModel{android.VariantInputKey: "$" + android.VariantInputEnvKey}, - )) - - // ios build - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem( - envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, - )) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) - configBuilder.SetWorkflowDescriptionTo(models.DeployWorkflowID, deployWorkflowDescription) - - bitriseDataModel, err := configBuilder.Generate(Name) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(bitriseDataModel) - if err != nil { - return models.BitriseConfigMap{}, err - } - - configMap["default-react-native-expo-plain-config"] = string(data) - } - - return configMap, nil -} - -// ExcludedScannerNames ... -func (Scanner) ExcludedScannerNames() []string { - return []string{ - reactnative.Name, - string(ios.XcodeProjectTypeIOS), - string(ios.XcodeProjectTypeMacOS), - android.ScannerName, - } -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/reactnative.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/reactnative.go deleted file mode 100644 index 39ccc14a..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/reactnative.go +++ /dev/null @@ -1,501 +0,0 @@ -package reactnative - -import ( - "errors" - "fmt" - "path/filepath" - - "gopkg.in/yaml.v2" - - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/scanners/android" - "github.com/bitrise-core/bitrise-init/scanners/ios" - "github.com/bitrise-core/bitrise-init/steps" - "github.com/bitrise-core/bitrise-init/utility" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" -) - -// Name ... -const Name = "react-native" - -const ( - // WorkDirInputKey ... - WorkDirInputKey = "workdir" -) - -// Scanner ... -type Scanner struct { - searchDir string - iosScanner *ios.Scanner - androidScanner *android.Scanner - hasNPMTest bool - packageJSONPth string -} - -// NewScanner ... -func NewScanner() *Scanner { - return &Scanner{} -} - -// Name ... -func (Scanner) Name() string { - return Name -} - -// DetectPlatform ... -func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { - scanner.searchDir = searchDir - - log.TInfof("Collect package.json files") - - packageJSONPths, err := CollectPackageJSONFiles(searchDir) - if err != nil { - return false, err - } - - log.TPrintf("%d package.json file detected", len(packageJSONPths)) - - log.TInfof("Filter relevant package.json files") - - relevantPackageJSONPths := []string{} - iosScanner := ios.NewScanner() - androidScanner := android.NewScanner() - for _, packageJSONPth := range packageJSONPths { - log.TPrintf("checking: %s", packageJSONPth) - - projectDir := filepath.Dir(packageJSONPth) - - iosProjectDetected := false - iosDir := filepath.Join(projectDir, "ios") - if exist, err := pathutil.IsDirExists(iosDir); err != nil { - return false, err - } else if exist { - if detected, err := iosScanner.DetectPlatform(scanner.searchDir); err != nil { - return false, err - } else if detected { - iosProjectDetected = true - } - } - - androidProjectDetected := false - androidDir := filepath.Join(projectDir, "android") - if exist, err := pathutil.IsDirExists(androidDir); err != nil { - return false, err - } else if exist { - if detected, err := androidScanner.DetectPlatform(scanner.searchDir); err != nil { - return false, err - } else if detected { - androidProjectDetected = true - } - } - - if iosProjectDetected || androidProjectDetected { - relevantPackageJSONPths = append(relevantPackageJSONPths, packageJSONPth) - } else { - log.TWarnf("no ios nor android project found, skipping package.json file") - } - } - - if len(relevantPackageJSONPths) == 0 { - return false, nil - } - - scanner.packageJSONPth = relevantPackageJSONPths[0] - - return true, nil -} - -// Options ... -func (scanner *Scanner) Options() (models.OptionNode, models.Warnings, error) { - warnings := models.Warnings{} - - var rootOption models.OptionNode - - // react options - packages, err := utility.ParsePackagesJSON(scanner.packageJSONPth) - if err != nil { - return models.OptionNode{}, warnings, err - } - - hasNPMTest := false - if _, found := packages.Scripts["test"]; found { - hasNPMTest = true - scanner.hasNPMTest = true - } - - projectDir := filepath.Dir(scanner.packageJSONPth) - - // android options - var androidOptions *models.OptionNode - androidDir := filepath.Join(projectDir, "android") - if exist, err := pathutil.IsDirExists(androidDir); err != nil { - return models.OptionNode{}, warnings, err - } else if exist { - androidScanner := android.NewScanner() - - if detected, err := androidScanner.DetectPlatform(scanner.searchDir); err != nil { - return models.OptionNode{}, warnings, err - } else if detected { - // only the first match we need - androidScanner.ExcludeTest = true - androidScanner.ProjectRoots = []string{androidScanner.ProjectRoots[0]} - - npmCmd := command.New("npm", "install") - npmCmd.SetDir(projectDir) - if out, err := npmCmd.RunAndReturnTrimmedCombinedOutput(); err != nil { - return models.OptionNode{}, warnings, fmt.Errorf("failed to npm install react-native in: %s\noutput: %s\nerror: %s", projectDir, out, err) - } - - options, warns, err := androidScanner.Options() - warnings = append(warnings, warns...) - if err != nil { - return models.OptionNode{}, warnings, err - } - - androidOptions = &options - scanner.androidScanner = androidScanner - } - } - - // ios options - var iosOptions *models.OptionNode - iosDir := filepath.Join(projectDir, "ios") - if exist, err := pathutil.IsDirExists(iosDir); err != nil { - return models.OptionNode{}, warnings, err - } else if exist { - iosScanner := ios.NewScanner() - - if detected, err := iosScanner.DetectPlatform(scanner.searchDir); err != nil { - return models.OptionNode{}, warnings, err - } else if detected { - options, warns, err := iosScanner.Options() - warnings = append(warnings, warns...) - if err != nil { - return models.OptionNode{}, warnings, err - } - - iosOptions = &options - scanner.iosScanner = iosScanner - } - } - - if androidOptions == nil && iosOptions == nil { - return models.OptionNode{}, warnings, errors.New("no ios nor android project detected") - } - // --- - - if androidOptions != nil { - if iosOptions == nil { - // we only found an android project - // we need to update the config names - lastChilds := androidOptions.LastChilds() - for _, child := range lastChilds { - for _, child := range child.ChildOptionMap { - if child.Config == "" { - return models.OptionNode{}, warnings, fmt.Errorf("no config for option: %s", child.String()) - } - - configName := configName(true, false, hasNPMTest) - child.Config = configName - } - } - } else { - // we have both ios and android projects - // we need to remove the android option's config names, - // since ios options will hold them - androidOptions.RemoveConfigs() - } - - rootOption = *androidOptions - } - - if iosOptions != nil { - lastChilds := iosOptions.LastChilds() - for _, child := range lastChilds { - for _, child := range child.ChildOptionMap { - if child.Config == "" { - return models.OptionNode{}, warnings, fmt.Errorf("no config for option: %s", child.String()) - } - - configName := configName(scanner.androidScanner != nil, true, hasNPMTest) - child.Config = configName - } - } - - if androidOptions == nil { - // we only found an ios project - rootOption = *iosOptions - } else { - // we have both ios and android projects - // we attach ios options to the android options - rootOption.AttachToLastChilds(iosOptions) - } - - } - - return rootOption, warnings, nil -} - -// DefaultOptions ... -func (Scanner) DefaultOptions() models.OptionNode { - androidOptions := (&android.Scanner{ExcludeTest: true}).DefaultOptions() - androidOptions.RemoveConfigs() - - iosOptions := (&ios.Scanner{}).DefaultOptions() - for _, child := range iosOptions.LastChilds() { - for _, child := range child.ChildOptionMap { - child.Config = defaultConfigName() - } - } - - androidOptions.AttachToLastChilds(&iosOptions) - - return androidOptions -} - -// Configs ... -func (scanner *Scanner) Configs() (models.BitriseConfigMap, error) { - configMap := models.BitriseConfigMap{} - - packageJSONDir := filepath.Dir(scanner.packageJSONPth) - relPackageJSONDir, err := utility.RelPath(scanner.searchDir, packageJSONDir) - if err != nil { - return models.BitriseConfigMap{}, fmt.Errorf("Failed to get relative config.xml dir path, error: %s", err) - } - if relPackageJSONDir == "." { - // config.xml placed in the search dir, no need to change-dir in the workflows - relPackageJSONDir = "" - } - - workdirEnvList := []envmanModels.EnvironmentItemModel{} - if relPackageJSONDir != "" { - workdirEnvList = append(workdirEnvList, envmanModels.EnvironmentItemModel{WorkDirInputKey: relPackageJSONDir}) - } - - if scanner.hasNPMTest { - configBuilder := models.NewDefaultConfigBuilder() - - // ci - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "test"})...)) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - // cd - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - - // android cd - if scanner.androidScanner != nil { - projectLocationEnv := "$" + android.ProjectLocationInputEnvKey - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.InstallMissingAndroidToolsStepListItem( - envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, - )) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( - envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: projectLocationEnv}, - )) - } - - // ios cd - if scanner.iosScanner != nil { - for _, descriptor := range scanner.iosScanner.ConfigDescriptors { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - - if descriptor.MissingSharedSchemes { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.RecreateUserSchemesStepListItem( - envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, - )) - } - - if descriptor.HasPodfile { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CocoapodsInstallStepListItem()) - } - - if descriptor.CarthageCommand != "" { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CarthageStepListItem( - envmanModels.EnvironmentItemModel{ios.CarthageCommandInputKey: descriptor.CarthageCommand}, - )) - } - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem( - envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, - )) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) - - bitriseDataModel, err := configBuilder.Generate(Name) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(bitriseDataModel) - if err != nil { - return models.BitriseConfigMap{}, err - } - - configName := configName(scanner.androidScanner != nil, true, true) - configMap[configName] = string(data) - } - } else { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) - - bitriseDataModel, err := configBuilder.Generate(Name) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(bitriseDataModel) - if err != nil { - return models.BitriseConfigMap{}, err - } - - configName := configName(scanner.androidScanner != nil, false, true) - configMap[configName] = string(data) - } - } else { - configBuilder := models.NewDefaultConfigBuilder() - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(append(workdirEnvList, envmanModels.EnvironmentItemModel{"command": "install"})...)) - - if scanner.androidScanner != nil { - projectLocationEnv := "$" + android.ProjectLocationInputEnvKey - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.InstallMissingAndroidToolsStepListItem( - envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, - )) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( - envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: projectLocationEnv}, - )) - } - - if scanner.iosScanner != nil { - for _, descriptor := range scanner.iosScanner.ConfigDescriptors { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - - if descriptor.MissingSharedSchemes { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.RecreateUserSchemesStepListItem( - envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, - )) - } - - if descriptor.HasPodfile { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CocoapodsInstallStepListItem()) - } - - if descriptor.CarthageCommand != "" { - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.CarthageStepListItem( - envmanModels.EnvironmentItemModel{ios.CarthageCommandInputKey: descriptor.CarthageCommand}, - )) - } - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.XcodeArchiveStepListItem( - envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, - )) - - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - bitriseDataModel, err := configBuilder.Generate(Name) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(bitriseDataModel) - if err != nil { - return models.BitriseConfigMap{}, err - } - - configName := configName(scanner.androidScanner != nil, true, false) - configMap[configName] = string(data) - } - } else { - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) - - bitriseDataModel, err := configBuilder.Generate(Name) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(bitriseDataModel) - if err != nil { - return models.BitriseConfigMap{}, err - } - - configName := configName(scanner.androidScanner != nil, false, false) - configMap[configName] = string(data) - } - } - - return configMap, nil -} - -// DefaultConfigs ... -func (Scanner) DefaultConfigs() (models.BitriseConfigMap, error) { - configBuilder := models.NewDefaultConfigBuilder() - - // ci - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{"command": "install"})) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{"command": "test"})) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - // cd - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepList(false)...) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.NpmStepListItem(envmanModels.EnvironmentItemModel{"command": "install"})) - - // android - projectLocationEnv := "$" + android.ProjectLocationInputEnvKey - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.InstallMissingAndroidToolsStepListItem( - envmanModels.EnvironmentItemModel{android.GradlewPathInputKey: "$" + android.ProjectLocationInputEnvKey + "/gradlew"}, - )) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( - envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: projectLocationEnv}, - )) - - // ios - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CertificateAndProfileInstallerStepListItem()) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.XcodeArchiveStepListItem( - envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.SchemeInputKey: "$" + ios.SchemeInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ExportMethodInputKey: "$" + ios.ExportMethodInputEnvKey}, - envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, - )) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) - - bitriseDataModel, err := configBuilder.Generate(Name) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(bitriseDataModel) - if err != nil { - return models.BitriseConfigMap{}, err - } - - configName := defaultConfigName() - configMap := models.BitriseConfigMap{ - configName: string(data), - } - return configMap, nil -} - -// ExcludedScannerNames ... -func (Scanner) ExcludedScannerNames() []string { - return []string{ - string(ios.XcodeProjectTypeIOS), - string(ios.XcodeProjectTypeMacOS), - android.ScannerName, - } -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/utility.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/utility.go deleted file mode 100644 index df357158..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/reactnative/utility.go +++ /dev/null @@ -1,56 +0,0 @@ -package reactnative - -import ( - "github.com/bitrise-core/bitrise-init/utility" -) - -// CollectPackageJSONFiles - Collects package.json files, with react-native dependency -func CollectPackageJSONFiles(searchDir string) ([]string, error) { - fileList, err := utility.ListPathInDirSortedByComponents(searchDir, true) - if err != nil { - return nil, err - } - - filters := []utility.FilterFunc{ - utility.BaseFilter("package.json", true), - utility.ComponentFilter("node_modules", false), - } - packageFileList, err := utility.FilterPaths(fileList, filters...) - if err != nil { - return nil, err - } - - relevantPackageFileList := []string{} - for _, packageFile := range packageFileList { - packages, err := utility.ParsePackagesJSON(packageFile) - if err != nil { - return nil, err - } - - _, found := packages.Dependencies["react-native"] - if found { - relevantPackageFileList = append(relevantPackageFileList, packageFile) - } - } - - return relevantPackageFileList, nil -} - -func configName(hasAndroidProject, hasIosProject, hasNPMTest bool) string { - name := "react-native" - if hasAndroidProject { - name += "-android" - } - if hasIosProject { - name += "-ios" - } - if hasNPMTest { - name += "-test" - } - name += "-config" - return name -} - -func defaultConfigName() string { - return "default-react-native-config" -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/scanners.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/scanners.go deleted file mode 100644 index 922bb8c8..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/scanners.go +++ /dev/null @@ -1,115 +0,0 @@ -package scanners - -import ( - "github.com/bitrise-core/bitrise-init/models" - "github.com/bitrise-core/bitrise-init/scanners/android" - "github.com/bitrise-core/bitrise-init/scanners/cordova" - "github.com/bitrise-core/bitrise-init/scanners/fastlane" - "github.com/bitrise-core/bitrise-init/scanners/flutter" - "github.com/bitrise-core/bitrise-init/scanners/ionic" - "github.com/bitrise-core/bitrise-init/scanners/ios" - "github.com/bitrise-core/bitrise-init/scanners/macos" - "github.com/bitrise-core/bitrise-init/scanners/reactnative" - expo "github.com/bitrise-core/bitrise-init/scanners/reactnative-expo" - "github.com/bitrise-core/bitrise-init/scanners/xamarin" - "github.com/bitrise-core/bitrise-init/steps" - "gopkg.in/yaml.v2" -) - -// ScannerInterface ... -type ScannerInterface interface { - // The name of the scanner is used for logging and - // to store the scanner outputs, like warnings, options and configs. - // The outputs are stored in a map[NAME]OUTPUT, like: warningMap[ios]warnings, optionsMap[android]options, configMap[xamarin]configs, ..., - // this means, that the SCANNER NAME HAS TO BE UNIQUE. - // Returns: - // - the name of the scanner - Name() string - - // Should implement as minimal logic as possible to determine if searchDir contains the - in question - platform or not. - // Inouts: - // - searchDir: the directory where the project to scan exists. - // Returns: - // - platform detected - // - error if (if any) - DetectPlatform(string) (bool, error) - - // ExcludedScannerNames is used to mark, which scanners should be excluded, if the current scanner detects platform. - ExcludedScannerNames() []string - - // OptionNode is the model, an n-ary tree, used to store the available configuration combintaions. - // It defines an option decision tree whose every branch maps to a bitrise configuration. - // Each branch should define a complete and valid options to build the final bitrise config model. - // Every leaf node has to be the key of the workflow (in the BitriseConfigMap), which will be fulfilled with the selected options. - // Returns: - // - OptionNode - // - Warnings (if any) - // - error if (if any) - Options() (models.OptionNode, models.Warnings, error) - - // Returns: - // - default options for the platform. - DefaultOptions() models.OptionNode - - // BitriseConfigMap's each element is a bitrise config template which will be fulfilled with the user selected options. - // Every config's key should be the last option one of the OptionNode branches. - // Returns: - // - platform BitriseConfigMap - Configs() (models.BitriseConfigMap, error) - - // Returns: - // - platform default BitriseConfigMap - DefaultConfigs() (models.BitriseConfigMap, error) -} - -// AutomationToolScanner contains additional methods (relative to ScannerInterface) -// implemented by an AutomationToolScanner -type AutomationToolScanner interface { - // Set the project types detected - SetDetectedProjectTypes(projectTypes []string) -} - -// ProjectScanners ... -var ProjectScanners = []ScannerInterface{ - expo.NewScanner(), - reactnative.NewScanner(), - flutter.NewScanner(), - ionic.NewScanner(), - cordova.NewScanner(), - ios.NewScanner(), - macos.NewScanner(), - android.NewScanner(), - xamarin.NewScanner(), -} - -// AutomationToolScanners contains active automation tool scanners -var AutomationToolScanners = []ScannerInterface{ - fastlane.NewScanner(), -} - -// CustomProjectType ... -const CustomProjectType = "other" - -// CustomConfigName ... -const CustomConfigName = "other-config" - -// CustomConfig ... -func CustomConfig() (models.BitriseConfigMap, error) { - configBuilder := models.NewDefaultConfigBuilder() - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepList(false)...) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepList(false)...) - - config, err := configBuilder.Generate(CustomProjectType) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(config) - if err != nil { - return models.BitriseConfigMap{}, err - } - - return models.BitriseConfigMap{ - CustomConfigName: string(data), - }, nil -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/xamarin_test.go b/vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/xamarin_test.go deleted file mode 100644 index c95973d7..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/scanners/xamarin/xamarin_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package xamarin - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestFilterSolutionFiles(t *testing.T) { - t.Log(`Contains solution files`) - { - fileList := []string{ - "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-xamarin-ios/CreditCardValidator.iOS.sln", - "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-android/sln", - "path/to/my/gradlew/file", - "path/to/my", - } - - files, err := FilterSolutionFiles(fileList) - require.NoError(t, err) - require.Equal(t, 1, len(files)) - - // Also sorts solution files by path components length - require.Equal(t, "/Users/bitrise/Develop/bitrise/sample-apps/sample-apps-xamarin-ios/CreditCardValidator.iOS.sln", files[0]) - } - - t.Log(`Do not contains solution file`) - { - fileList := []string{ - "path/to/my/gradlew/build.", - "path/to/my/gradle", - } - - files, err := FilterSolutionFiles(fileList) - require.NoError(t, err) - require.Equal(t, 0, len(files)) - } -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner.go b/vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner.go deleted file mode 100644 index 9ab360b4..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner.go +++ /dev/null @@ -1,56 +0,0 @@ -package toolscanner - -import ( - "github.com/bitrise-core/bitrise-init/models" - bitriseModels "github.com/bitrise-io/bitrise/models" -) - -// ProjectTypeEnvKey is the name of the enviroment variable used to substitute the project type for -// automation tool scanner's config -const ( - ProjectTypeUserTitle = "Project type" - // The key is used in the options decision tree model. - // If empty, it will not be inserted into the bitrise.yml - ProjectTypeEnvKey = "" -) - -// AddProjectTypeToConfig returns the config filled in with every detected project type, that could be selected -func AddProjectTypeToConfig(configName string, config bitriseModels.BitriseDataModel, detectedProjectTypes []string) map[string]bitriseModels.BitriseDataModel { - configMapWithProjecTypes := map[string]bitriseModels.BitriseDataModel{} - for _, projectType := range detectedProjectTypes { - configWithProjectType := config - configWithProjectType.ProjectType = projectType - configMapWithProjecTypes[appendProjectTypeToConfigName(configName, projectType)] = configWithProjectType - } - return configMapWithProjecTypes -} - -// AddProjectTypeToOptions adds a project type question to automation tool scanners's option tree -func AddProjectTypeToOptions(scannerOptionTree models.OptionNode, detectedProjectTypes []string) models.OptionNode { - optionsTreeWithProjectTypeRoot := models.NewOption(ProjectTypeUserTitle, ProjectTypeEnvKey) - for _, projectType := range detectedProjectTypes { - optionsTreeWithProjectTypeRoot.AddOption(projectType, - appendProjectTypeToConfig(scannerOptionTree, projectType)) - } - return *optionsTreeWithProjectTypeRoot -} - -func appendProjectTypeToConfigName(configName string, projectType string) string { - return configName + "_" + projectType -} - -func appendProjectTypeToConfig(options models.OptionNode, projectType string) *models.OptionNode { - var appendToConfigNames func(*models.OptionNode) - appendToConfigNames = func(node *models.OptionNode) { - if (*node).IsConfigOption() || (*node).ChildOptionMap == nil { - (*node).Config = appendProjectTypeToConfigName((*node).Config, projectType) - return - } - for _, child := range (*node).ChildOptionMap { - appendToConfigNames(child) - } - } - optionsWithProjectType := options.Copy() - appendToConfigNames(optionsWithProjectType) - return optionsWithProjectType -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner_test.go b/vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner_test.go deleted file mode 100644 index 6c6b2f7e..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/toolscanner/toolscanner_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package toolscanner - -import ( - "reflect" - "testing" - - "github.com/bitrise-core/bitrise-init/models" - bitriseModels "github.com/bitrise-io/bitrise/models" - "github.com/google/go-cmp/cmp" -) - -func TestAddProjectTypeToOptions(t *testing.T) { - const detectedProjectType = "ios" - type args struct { - scannerOptionTree models.OptionNode - detectedProjectTypes []string - } - tests := []struct { - name string - args args - want models.OptionNode - }{ - { - name: "1 project type", - args: args{ - scannerOptionTree: models.OptionNode{ - Title: "Working directory", - EnvKey: "FASTLANE_WORK_DIR", - ChildOptionMap: map[string]*models.OptionNode{ - "BitriseFastlaneSample": &models.OptionNode{ - Title: "Fastlane lane", - EnvKey: "FASTLANE_LANE", - ChildOptionMap: map[string]*models.OptionNode{ - "ios test": &models.OptionNode{ - Config: "fastlane-config", - }, - }, - }, - }, - }, - detectedProjectTypes: []string{detectedProjectType}, - }, - want: models.OptionNode{ - Title: "Project type", - EnvKey: ProjectTypeEnvKey, - ChildOptionMap: map[string]*models.OptionNode{ - detectedProjectType: &models.OptionNode{ - Title: "Working directory", - EnvKey: "FASTLANE_WORK_DIR", - ChildOptionMap: map[string]*models.OptionNode{ - "BitriseFastlaneSample": &models.OptionNode{ - Title: "Fastlane lane", - EnvKey: "FASTLANE_LANE", - ChildOptionMap: map[string]*models.OptionNode{ - "ios test": &models.OptionNode{ - Config: "fastlane-config" + "_" + detectedProjectType, - }}}}}, - }, - }, - }, - { - name: "2 project types", - args: args{ - scannerOptionTree: models.OptionNode{ - Title: "Working directory", - EnvKey: "FASTLANE_WORK_DIR", - ChildOptionMap: map[string]*models.OptionNode{ - "BitriseFastlaneSample": &models.OptionNode{ - Title: "Fastlane lane", - EnvKey: "FASTLANE_LANE", - ChildOptionMap: map[string]*models.OptionNode{ - "ios test": &models.OptionNode{ - Config: "fastlane-config", - }, - }, - }, - }, - }, - detectedProjectTypes: []string{"ios", "android"}, - }, - want: models.OptionNode{ - Title: "Project type", - EnvKey: ProjectTypeEnvKey, - ChildOptionMap: map[string]*models.OptionNode{ - "ios": &models.OptionNode{ - Title: "Working directory", - EnvKey: "FASTLANE_WORK_DIR", - ChildOptionMap: map[string]*models.OptionNode{ - "BitriseFastlaneSample": &models.OptionNode{ - Title: "Fastlane lane", - EnvKey: "FASTLANE_LANE", - ChildOptionMap: map[string]*models.OptionNode{ - "ios test": &models.OptionNode{ - Config: "fastlane-config" + "_" + "ios", - }, - }, - }, - }, - }, - "android": &models.OptionNode{ - Title: "Working directory", - EnvKey: "FASTLANE_WORK_DIR", - ChildOptionMap: map[string]*models.OptionNode{ - "BitriseFastlaneSample": &models.OptionNode{ - Title: "Fastlane lane", - EnvKey: "FASTLANE_LANE", - ChildOptionMap: map[string]*models.OptionNode{ - "ios test": &models.OptionNode{ - Config: "fastlane-config" + "_" + "android", - }, - }, - }, - }, - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := AddProjectTypeToOptions(tt.args.scannerOptionTree, tt.args.detectedProjectTypes); !reflect.DeepEqual(got.String(), tt.want.String()) { - t.Errorf("AddProjectTypeToOptions() = %v, want %v", got, tt.want) - t.Errorf("%s", cmp.Diff(got, tt.want)) - } - }) - } -} - -func TestAddProjectTypeToConfig(t *testing.T) { - const title = "abcd" - type args struct { - configName string - config bitriseModels.BitriseDataModel - detectedProjectTypes []string - } - tests := []struct { - name string - args args - want map[string]bitriseModels.BitriseDataModel - }{ - { - name: "2 project types", - args: args{ - configName: "fastlane-config", - config: bitriseModels.BitriseDataModel{ - Title: title, - ProjectType: "other", - }, - detectedProjectTypes: []string{"ios", "android"}, - }, - want: map[string]bitriseModels.BitriseDataModel{ - "fastlane-config_ios": bitriseModels.BitriseDataModel{ - Title: title, - ProjectType: "ios", - }, - "fastlane-config_android": bitriseModels.BitriseDataModel{ - Title: title, - ProjectType: "android", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := AddProjectTypeToConfig(tt.args.configName, tt.args.config, tt.args.detectedProjectTypes); !reflect.DeepEqual(got, tt.want) { - t.Errorf("AddProjectTypeToConfig() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/utility/sortable_path_test.go b/vendor/github.com/bitrise-core/bitrise-init/utility/sortable_path_test.go deleted file mode 100644 index 26311f15..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/utility/sortable_path_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package utility - -import ( - "os" - "strings" - "testing" - - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestNewSortablePath(t *testing.T) { - t.Log("rel path") - { - expectedAbsPth, err := pathutil.AbsPath("test") - require.NoError(t, err) - - expectedComponents := strings.Split(expectedAbsPth, string(os.PathSeparator)) - fixedExpectedComponents := []string{} - for _, c := range expectedComponents { - if c != "" { - fixedExpectedComponents = append(fixedExpectedComponents, c) - } - } - - sortable, err := NewSortablePath("test") - require.NoError(t, err) - require.Equal(t, "test", sortable.Pth) - require.Equal(t, expectedAbsPth, sortable.AbsPth) - require.Equal(t, fixedExpectedComponents, sortable.Components) - } - - t.Log("rel path") - { - expectedAbsPth, err := pathutil.AbsPath("./test") - require.NoError(t, err) - - expectedComponents := strings.Split(expectedAbsPth, string(os.PathSeparator)) - fixedExpectedComponents := []string{} - for _, c := range expectedComponents { - if c != "" { - fixedExpectedComponents = append(fixedExpectedComponents, c) - } - } - - sortable, err := NewSortablePath("./test") - require.NoError(t, err) - require.Equal(t, "./test", sortable.Pth) - require.Equal(t, expectedAbsPth, sortable.AbsPth) - require.Equal(t, fixedExpectedComponents, sortable.Components) - } - - t.Log("abs path") - { - expectedAbsPth := "/Users/bitrise/test" - expectedComponents := []string{"Users", "bitrise", "test"} - - sortable, err := NewSortablePath("/Users/bitrise/test") - require.NoError(t, err) - require.Equal(t, "/Users/bitrise/test", sortable.Pth) - require.Equal(t, expectedAbsPth, sortable.AbsPth) - require.Equal(t, expectedComponents, sortable.Components) - } -} - -func TestSortPathsByComponents(t *testing.T) { - t.Log("abs paths") - { - paths := []string{ - "/Users/bitrise/test/test/test", - "/Users/bitrise/test/test", - "/Users/vagrant", - "/Users/bitrise", - } - - expectedSorted := []string{ - "/Users/bitrise", - "/Users/vagrant", - "/Users/bitrise/test/test", - "/Users/bitrise/test/test/test", - } - actualSorted, err := SortPathsByComponents(paths) - require.NoError(t, err) - require.Equal(t, expectedSorted, actualSorted) - } - - t.Log("rel paths") - { - paths := []string{ - "bitrise/test/test/test", - "bitrise/test/test", - "vagrant", - "bitrise", - } - - expectedSorted := []string{ - "bitrise", - "vagrant", - "bitrise/test/test", - "bitrise/test/test/test", - } - actualSorted, err := SortPathsByComponents(paths) - require.NoError(t, err) - require.Equal(t, expectedSorted, actualSorted) - } -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/utility/utility_test.go b/vendor/github.com/bitrise-core/bitrise-init/utility/utility_test.go deleted file mode 100644 index 196f36d6..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/utility/utility_test.go +++ /dev/null @@ -1,306 +0,0 @@ -package utility - -import ( - "os" - "strings" - "testing" - - "path/filepath" - - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestListPathInDirSortedByComponents(t *testing.T) { - t.Log() - { - files, err := ListPathInDirSortedByComponents("./", true) - require.NoError(t, err) - require.NotEqual(t, 0, len(files)) - } - - t.Log() - { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__lis_path_test__") - require.NoError(t, err) - defer func() { - require.NoError(t, os.RemoveAll(tmpDir)) - }() - - pths := []string{ - filepath.Join(tmpDir, "testdir/testfile"), - filepath.Join(tmpDir, "testdir/testdir/testfile"), - } - - for _, pth := range pths { - dir := filepath.Dir(pth) - require.NoError(t, os.MkdirAll(dir, 0700)) - - require.NoError(t, fileutil.WriteStringToFile(pth, "test")) - } - - expected := []string{ - ".", - "testdir", - "testdir/testdir", - "testdir/testfile", - "testdir/testdir/testfile", - } - - files, err := ListPathInDirSortedByComponents(tmpDir, true) - require.NoError(t, err) - require.Equal(t, expected, files) - } - - t.Log() - { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__lis_path_test1__") - require.NoError(t, err) - defer func() { - require.NoError(t, os.RemoveAll(tmpDir)) - }() - - pths := []string{ - filepath.Join(tmpDir, "testdir/testfile"), - filepath.Join(tmpDir, "testdir/testdir/testfile"), - } - - for _, pth := range pths { - dir := filepath.Dir(pth) - require.NoError(t, os.MkdirAll(dir, 0700)) - - require.NoError(t, fileutil.WriteStringToFile(pth, "test")) - } - - expected := []string{ - tmpDir, - filepath.Join(tmpDir, "testdir"), - filepath.Join(tmpDir, "testdir/testdir"), - filepath.Join(tmpDir, "testdir/testfile"), - filepath.Join(tmpDir, "testdir/testdir/testfile"), - } - - files, err := ListPathInDirSortedByComponents(tmpDir, false) - require.NoError(t, err) - require.Equal(t, expected, files) - } -} - -func TestFilterPaths(t *testing.T) { - t.Log("without any filter") - { - paths := []string{ - "/Users/bitrise/test", - "/Users/vagrant/test", - } - filtered, err := FilterPaths(paths) - require.NoError(t, err) - require.Equal(t, paths, filtered) - } - - t.Log("with filter") - { - paths := []string{ - "/Users/bitrise/test", - "/Users/vagrant/test", - } - filter := func(pth string) (bool, error) { - return strings.Contains(pth, "vagrant"), nil - } - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"/Users/vagrant/test"}, filtered) - } -} - -func TestBaseFilter(t *testing.T) { - t.Log("allow") - { - paths := []string{ - "path/to/my/gradlew", - "path/to/my/gradlew/file", - } - filter := BaseFilter("gradlew", true) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"path/to/my/gradlew"}, filtered) - } - - t.Log("forbid") - { - paths := []string{ - "path/to/my/gradlew", - "path/to/my/gradlew/file", - } - filter := BaseFilter("gradlew", false) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"path/to/my/gradlew/file"}, filtered) - } -} - -func TestExtensionFilter(t *testing.T) { - t.Log("allow") - { - paths := []string{ - "path/to/my/project.xcodeproj", - "path/to/my/project.xcworkspace", - } - filter := ExtensionFilter(".xcodeproj", true) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"path/to/my/project.xcodeproj"}, filtered) - } - - t.Log("forbid") - { - paths := []string{ - "path/to/my/project.xcodeproj", - "path/to/my/project.xcworkspace", - } - filter := ExtensionFilter(".xcodeproj", false) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"path/to/my/project.xcworkspace"}, filtered) - } -} - -func TestRegexpFilter(t *testing.T) { - t.Log("allow") - { - paths := []string{ - "path/to/my/project.xcodeproj", - "path/to/my/project.xcworkspace", - } - filter := RegexpFilter(".*.xcodeproj", true) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"path/to/my/project.xcodeproj"}, filtered) - } - - t.Log("forbid") - { - paths := []string{ - "path/to/my/project.xcodeproj", - "path/to/my/project.xcworkspace", - } - filter := RegexpFilter(".*.xcodeproj", false) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"path/to/my/project.xcworkspace"}, filtered) - } -} - -func TestComponentFilter(t *testing.T) { - t.Log("allow") - { - paths := []string{ - "/Users/bitrise/test", - "/Users/vagrant/test", - } - filter := ComponentFilter("bitrise", true) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"/Users/bitrise/test"}, filtered) - } - - t.Log("forbid") - { - paths := []string{ - "/Users/bitrise/test", - "/Users/vagrant/test", - } - filter := ComponentFilter("bitrise", false) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"/Users/vagrant/test"}, filtered) - } -} - -func TestComponentWithExtensionFilter(t *testing.T) { - t.Log("allow") - { - paths := []string{ - "/Users/bitrise.framework/test", - "/Users/vagrant/test", - } - filter := ComponentWithExtensionFilter(".framework", true) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"/Users/bitrise.framework/test"}, filtered) - } - - t.Log("forbid") - { - paths := []string{ - "/Users/bitrise.framework/test", - "/Users/vagrant/test", - } - filter := ComponentWithExtensionFilter(".framework", false) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"/Users/vagrant/test"}, filtered) - } -} - -func TestIsDirectoryFilter(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__bitrise-init__") - require.NoError(t, err) - defer func() { - require.NoError(t, os.RemoveAll(tmpDir)) - }() - - tmpFile := filepath.Join(tmpDir, "file.txt") - require.NoError(t, fileutil.WriteStringToFile(tmpFile, "")) - - t.Log("allow") - { - paths := []string{ - tmpDir, - tmpFile, - } - filter := IsDirectoryFilter(true) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{tmpDir}, filtered) - } - - t.Log("forbid") - { - paths := []string{ - tmpDir, - tmpFile, - } - filter := IsDirectoryFilter(false) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{tmpFile}, filtered) - } -} - -func TestInDirectoryFilter(t *testing.T) { - t.Log("allow") - { - paths := []string{ - "/Users/bitrise/test", - "/Users/vagrant/test", - } - filter := InDirectoryFilter("/Users/bitrise", true) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"/Users/bitrise/test"}, filtered) - } - - t.Log("forbid") - { - paths := []string{ - "/Users/bitrise/test", - "/Users/vagrant/test", - } - filter := InDirectoryFilter("/Users/bitrise", false) - filtered, err := FilterPaths(paths, filter) - require.NoError(t, err) - require.Equal(t, []string{"/Users/vagrant/test"}, filtered) - } -} diff --git a/vendor/github.com/bitrise-core/bitrise-init/version/build.go b/vendor/github.com/bitrise-core/bitrise-init/version/build.go deleted file mode 100644 index 06c70c10..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/version/build.go +++ /dev/null @@ -1,7 +0,0 @@ -package version - -// BuildNumber ... -var BuildNumber = "" - -// Commit ... -var Commit = "" diff --git a/vendor/github.com/bitrise-core/bitrise-init/version/version.go b/vendor/github.com/bitrise-core/bitrise-init/version/version.go deleted file mode 100644 index 6f4c87e0..00000000 --- a/vendor/github.com/bitrise-core/bitrise-init/version/version.go +++ /dev/null @@ -1,4 +0,0 @@ -package version - -// VERSION ... -const VERSION = "1.8.0" diff --git a/vendor/github.com/bitrise-io/bitrise/.codeclimate.yml b/vendor/github.com/bitrise-io/bitrise/.codeclimate.yml deleted file mode 100644 index 1e4d2d7f..00000000 --- a/vendor/github.com/bitrise-io/bitrise/.codeclimate.yml +++ /dev/null @@ -1,41 +0,0 @@ -# This is a sample .codeclimate.yml configured for Engine analysis on Code -# Climate Platform. For an overview of the Code Climate Platform, see here: -# http://docs.codeclimate.com/article/300-the-codeclimate-platform - -# Under the engines key, you can configure which engines will analyze your repo. -# Each key is an engine name. For each value, you need to specify enabled: true -# to enable the engine as well as any other engines-specific configuration. - -# For more details, see here: -# http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform - -# For a list of all available engines, see here: -# http://docs.codeclimate.com/article/296-engines-available-engines - -engines: -# to turn on an engine, add it here and set enabled to `true` -# to turn off an engine, set enabled to `false` or remove it - golint: - enabled: true - gofmt: - enabled: true - -# Engines can analyze files and report issues on them, but you can separately -# decide which files will receive ratings based on those issues. This is -# specified by path patterns under the ratings key. - -# For more details see here: -# http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform - -# Note: If the ratings key is not specified, this will result in a 0.0 GPA on your dashboard. - -ratings: - paths: - - "**.go" - -# You can globally exclude files from being analyzed by any engine using the -# exclude_paths key. - -#exclude_paths: -#- spec/**/* -#- vendor/**/* diff --git a/vendor/github.com/bitrise-io/bitrise/.gitignore b/vendor/github.com/bitrise-io/bitrise/.gitignore deleted file mode 100644 index bf64a57a..00000000 --- a/vendor/github.com/bitrise-io/bitrise/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -_temp/ -_tmp/ -.bitrise -.bitrise.secrets.yml -_bin -.envstore.yml -_FAKE_HOME/ -.gows.user.yml -.DS_Store diff --git a/vendor/github.com/bitrise-io/bitrise/CHANGELOG.md b/vendor/github.com/bitrise-io/bitrise/CHANGELOG.md deleted file mode 100644 index 300eb2de..00000000 --- a/vendor/github.com/bitrise-io/bitrise/CHANGELOG.md +++ /dev/null @@ -1,3155 +0,0 @@ -## Changelog (Current version: 1.8.0) - ------------------ - -## 1.8.0 (2017 Aug 07) - -### Release Notes - -__`bitrise plugin update` command's log fix__ - -From now on `bitrise plugin update` command will print which plugin is under update. - -__`bitrise run WORKFLOW` command's log update__ - -`bitrise run WORKFLOW` command prints the workflow stack to better understand which workflows will run in what order. - -``` -Running workflows: BEFORE_WORKFLOW_1 -> BEFORE_WORKFLOW_2 --> WORKFLOW --> AFTER_WORKFLOW_1 --> AFTER_WORKFLOW_2 -``` -__Bitrise Tools update__ - -- min envman version: [1.1.6](https://github.com/bitrise-io/envman/releases/tag/1.1.6) -- min stepman version: [0.9.33](https://github.com/bitrise-io/stepman/releases/tag/0.9.33) - -__Bitrise Plugins update__ - -- default init plugin version: [0.9.7](https://github.com/bitrise-core/bitrise-plugins-init/releases/tag/0.9.7) -- default workflow-editor plugin version: [1.0.13](https://github.com/bitrise-io/bitrise-workflow-editor/releases/tag/1.0.13) -- default analytics plugin version: [0.9.10](https://github.com/bitrise-core/bitrise-plugins-analytics/releases/tag/0.9.10) - -__Bitrise Model's version bumped to 4__ - -Meta field (`meta`) added to `EnvironmentItemOptionsModel`, this property of the environment options is used to define extra options without creating a new [envman](https://github.com/bitrise-io/envman) release. - -The __bitrise-cli__ does not use `meta` field directly, but other tools can use this property to expand the environment options. - -For example the `bitrise.io` website will use the `meta` field to define if secret environment variables should be used in pull request triggered builds or not. - -``` -.bitrise.secrets.yml - -envs: -- MY_SECRET_ENV: secret value - opts: - meta: - is_expose: true -``` - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.8.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.7.0 -> 1.8.0 - -* [83309a7] Krisztian Godrei - prepare for 1.8.0 (2017 Aug 07) -* [a79a5ab] Krisztian Godrei - bitrise plugin analytics updated to 0.9.10 (2017 Aug 07) -* [7cc355d] Krisztian Godrei - bump model version to 4 (2017 Aug 07) -* [97d835c] Krisztian Godrei - prepare for 1.8.0 (2017 Aug 07) -* [9412a8c] Krisztián Gödrei - bitrise tools and plugins version update (#530) (2017 Aug 07) -* [b982443] Krisztián Gödrei - godeps-update (#529) (2017 Aug 07) -* [313384d] Zsolt - CLI workflow prints (#507) (2017 Aug 07) -* [7998a20] Krisztián Gödrei - print plugin name in update command (#528) (2017 Aug 07) - - -## 1.7.0 (2017 Jul 10) - -### Release Notes - -__empty workflow id validation__ - -From now on bitrise-cli will fail if the bitrise configuration (bitrise.yml) contains workflow with empty workflow id. - -``` -format_version: "3" -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - -workflows: - "": - steps: - - script: -``` - -__git step's default branch is master__ - -If you use a step from its git source without specifying the branch to use: - -``` -format_version: "3" -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - -workflows: - primary: - steps: - - git::https://github.com/bitrise-io/steps-script.git: -``` - -bitrise will activate and use the step repo's master branch. - -__support for cross-device file moving__ - -In previous cli versions, if a user home directory was on different device from the os temporary directory, you received the following error message during the bitrise setup process: `invalid cross-device link`. This version uses a cross-device compatible file moving function. - -__progress indicator__ - -bitrise plugin install and update commands take some time to finish as bitrise-cli git clones the plugin source repository and then downloads and installs the plugin's compiled binary. We added a loading indicator to these commands. - -``` -Checking Bitrise Plugins... -Default plugin (analytics) NOT found, installing... -git clone plugin source ⣯ -Downloading plugin binary ⡿ -``` - -__dependency updates:__ - - - min envman version: [1.1.5](https://github.com/bitrise-io/envman/releases/tag/1.1.5) - - min stepman version: [0.9.32](https://github.com/bitrise-io/stepman/releases/tag/0.9.32) - - default init plugin version: [0.9.6](https://github.com/bitrise-core/bitrise-plugins-init/releases/tag/0.9.6) - - default step plugin version: [0.9.4](https://github.com/bitrise-core/bitrise-plugins-step/releases/tag/0.9.4) - - default workflow-editor plugin version: [1.0.11](https://github.com/bitrise-io/bitrise-workflow-editor/releases/tag/1.0.11) - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.7.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.6.2 -> 1.7.0 - -* [ba092dd] Krisztian Godrei - prepare for 1.7.0 (2017 Jul 10) -* [37cad84] Krisztián Gödrei - stepman, envman and default plugins version update (#527) (2017 Jul 10) -* [2cb8861] Krisztián Gödrei - godeps update (#526) (2017 Jul 10) -* [45e9fec] Krisztián Gödrei - Progress (#524) (2017 Jul 05) -* [a9b29d8] Krisztián Gödrei - code style updates (#525) (2017 Jul 05) -* [8f00883] Karol Wrótniak - Added support for cross-device file moving, fixes #518 (#523) (2017 Jul 05) -* [03d1b5d] Krisztián Gödrei - merged (#522) (2017 Jul 04) -* [6a53b4a] Krisztián Gödrei - git steps default branch is master (#520) (2017 Jul 04) -* [afd7aa2] Krisztián Gödrei - fail if workflow id is empty (#519) (2017 Jul 03) - - -## 1.6.2 (2017 Jun 12) - -### Release Notes - -__plugin info command__ - -bitrise-cli got a new command: `bitrise plugin info` - -Prints infos about the specified installed bitrise plugin. You use the command's `--format` flag to specify the output format (valid options: `raw`, `json`). - -The command prints the following infos: - -``` -Name: PLUGIN_NAME -Version: PLUGIN_VERSION -Source: PLUGIN_SOURCE -Definition: PLUGIN_DEFINITION -``` - -__plugin list command__ - -`bitrise plugin list` command prints infos about the installed plugins, the command got a new flag: `--format`, which you can use to specify the output's format (valid options: `raw`, `json`). - -The command prints the same infos about the plugins as the new `bitrise plugin info` command. - -__plugin update command__ - -In previous versions specifying the plugin's name, to update, was required. From now, if you do not specify which plugin to update `bitrise plugin update` command will update every installed bitrise plugin. - -__plugin update command fix__ - -From now `bitrise plugin update` prepares the new plugin version as a sandbox and once everything is downloaded to install the plugin, the cli just copies it to the plugins directory (`$HOME/.bitrise/plugins`). - -__export command fix__ - -From now `bitrise export` command will print the command's help, if required arguments/flags were not provided. - -__Bitrise temporary directory__ - -This bitrise-cli version creates an exports a temporary directory: `BITRISE_TMP_DIR` (if it is not already set). This directory is dedicated to store temporary files, during the bitrise-cli commands. - -__go toolkit__ - -go version bump from 1.8.1 to 1.8.3 - -__Dependency updates:__ - - - min envman version: 1.1.4 - - min stepman version: 0.9.31 - - default init plugin version: 0.9.4 - - default step plugin version: 0.9.3 - - default workflow-editor plugin version: 1.0.9 - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.6.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.6.1 -> 1.6.2 - -* [d6ba2d7] Krisztian Godrei - prepare for 1.6.2 (2017 Jun 12) -* [573f411] Krisztián Gödrei - bitrise deps and tools update (#515) (2017 Jun 12) -* [006e187] Krisztián Gödrei - godeps update (#514) (2017 Jun 12) -* [69be428] Krisztián Gödrei - Plugin update (#513) (2017 Jun 12) -* [0fc8020] Krisztián Gödrei - plugin info cmd, plugin list cmd update, plugin review (#512) (2017 Jun 12) -* [f25a2ee] Krisztián Gödrei - BITRISE_TMP_DIR & tests (#511) (2017 Jun 09) -* [0b36108] Krisztián Gödrei - plugin update fix (#510) (2017 Jun 09) -* [7b9e900] Zsolt - Bitrise export fix (#508) (2017 Jun 08) -* [cdc9d51] Viktor Benei - go toolkit - go version bump from 1.8.1 to 1.8.3 (#506) (2017 Jun 08) - - -## 1.6.1 (2017 May 10) - -### Release Notes - -* FIX regression: previous version (1.6.0) of bitrise-cli thrown and error when bitrise was on setting up the help template: failed to get current version map. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.6.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.6.0 -> 1.6.1 - -* [bf6da15] Krisztian Godrei - prepare for 1.6.1 (2017 May 10) -* [8441798] Krisztián Gödrei - default plugin updates (#503) (2017 May 10) -* [30c383f] Krisztián Gödrei - plugin list fix (#502) (2017 May 10) -* [3ec4e0d] Krisztián Gödrei - StepRunResultsModel’s Error field fix (#501) (2017 May 10) -* [bde77b8] Krisztián Gödrei - define plugin env keys as exported const (#500) (2017 May 10) - - -## 1.6.0 (2017 May 09) - -### Release Notes - -__1. Install local plugins:__ - -From this bitrise-cli version you can test your local plugin directly through the CLI, by installing it: - -`bitrise plugin install PATH/TO/MY/LOCAL/PLUGIN` - -_NOTE: You can specify your plugin's source as a command argument, no need to specify it with --src flag, however using the flag is still supported._ - -__2. Step Output Aliases__ - -You can specify the output's alias, by setting value to the desired alias key and the cli will export the output with the given alias. - -It is as simple as : - -``` -... -workflows: - primary: - steps: - - gradle-runner: - outputs: - - BITRISE_APK_PATH: ALIAS_APK_PATH -... -``` - -_The generated apk path will be available under `ALIAS_APK_PATH` key, instead of the default `BITRISE_APK_PATH` key._ - -_Note: if alias specified the output will be exported only with the alias, so the value will NOT be available with the original environment key._ - -__3. bitrise-cli got a new default plugin: `step`__ - -Bitrise Plugin to interact with steps, list them, retrieve information, or create your own! - -Want to create your own step? Just run `bitrise :step create` and you can create the perfect Step in no time! - -__4. default plugin updates:__ - -- init 0.9.1 -- workflow-editor 0.9.9 -- analytics 0.9.8 - -__5. Step development guidline updates, read more in [docs](https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md).__ - -__6. bitrise.yml format specification updates, read more in [docs](https://github.com/bitrise-io/bitrise/blob/master/_docs/bitrise-yml-format-spec.md).__ - -__7. Go toolkit's mininum go version bumped to: 1.8.1__ - -__8. Format version bumped to: 3__ - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.6.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.5.6 -> 1.6.0 - -* [3064a4b] Krisztian Godrei - prepare for 1.6.0 (2017 May 09) -* [5a2d97a] Krisztián Gödrei - default plugin version updates (#498) (2017 May 09) -* [ab1565d] Viktor Benei - proper plugin available message (#499) (2017 May 09) -* [c4039d0] Krisztián Gödrei - send bitrise format version to plugins (#497) (2017 May 09) -* [87fa22f] Krisztián Gödrei - alias fix (#496) (2017 May 09) -* [6e8307f] Viktor Benei - output alias test (#494) (2017 May 09) -* [fc69c58] Krisztián Gödrei - format version bumped to 3 (#495) (2017 May 09) -* [095c9a8] Krisztián Gödrei - step output alias (#493) (2017 May 09) -* [6fe235d] Viktor Benei - Step and env var spec enhancement (#492) (2017 May 08) -* [bd4d909] Viktor Benei - README: replace Slack with discuss link (#491) (2017 May 08) -* [f48ecec] Krisztián Gödrei - install local plugins (#490) (2017 May 08) -* [a3be4d7] Krisztián Gödrei - Step id naming convention (#489) (2017 May 02) -* [a22c2a9] Krisztian Godrei - type tag names update (2017 May 02) -* [ad15062] Krisztián Gödrei - Step grouping (#488) (2017 May 02) -* [d264edf] Viktor Benei - Go for toolkit version bump, from 1.8 to 1.8.1 (#486) (2017 Apr 28) -* [07a7827] Karol Wrótniak - Added version naming convention advice (#487) (2017 Apr 28) -* [2882891] Krisztián Gödrei - Release a new version description (#485) (2017 Apr 25) - - -## 1.5.6 (2017 Apr 11) - -### Release Notes - -* Switch Bitrise Data Model's (bitrise.yml) `format_version` to one component version number. -* Added `project_type` property to Bitrise Data Model - defines your source project's type. -* `bitrise.yml` format [specification](https://github.com/bitrise-io/bitrise/blob/master/_docs/bitrise-yml-format-spec.md) update. -* Dependency updates: - - - minimum [stepman](https://github.com/bitrise-io/stepman) version: [0.9.30](https://github.com/bitrise-io/stepman/releases/tag/0.9.30) - - default [workflow-editor](https://github.com/bitrise-io/bitrise-workflow-editor) version: [0.9.8](https://github.com/bitrise-io/bitrise-workflow-editor/releases/tag/0.9.8) - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.6/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.5.5 -> 1.5.6 - -* [4920e46] Krisztian Godrei - prepare for 1.5.6 (2017 Apr 11) -* [08589f2] Krisztián Gödrei - bump min stepman to 0.9.30, bump min wf editor to 0.9.8 (#484) (2017 Apr 11) -* [bc97566] Krisztián Gödrei - switch format version to one component version number, docs (#483) (2017 Apr 11) -* [2c0d842] Krisztián Gödrei - godeps update (#482) (2017 Apr 10) -* [2fa6ef4] Krisztián Gödrei - Update CHANGELOG.md (2017 Mar 14) - - -## 1.5.5 (2017 Mar 13) - -### Release Notes - -* __Silent setup__: bitrise will do a setup (_if was not performed for the current version_) before any plugin run. - -* From now the `bitrise --help` command output will include __PLUGINS help section__ as well: - -``` -NAME: bitrise - Bitrise Automations Workflow Runner - -USAGE: bitrise [OPTIONS] COMMAND/PLUGIN [arg...] - -VERSION: 1.5.5 - -GLOBAL OPTIONS: - --loglevel value, -l value Log level (options: debug, info, warn, error, fatal, panic). [$LOGLEVEL] - --debug If true it enabled DEBUG mode. If no separate Log Level is specified this will also set the loglevel to debug. [$DEBUG] - --ci If true it indicates that we're used by another tool so don't require any user input! [$CI] - --pr If true bitrise runs in pull request mode. - --help, -h show help - --version, -v print the version - -COMMANDS: - init Init bitrise config. - setup Setup the current host. Install every required tool to run Workflows. - version Prints the version - validate Validates a specified bitrise config. - run Runs a specified Workflow. - trigger-check Prints out which workflow will triggered by specified pattern. - trigger Triggers a specified Workflow. - export Export the bitrise configuration. - normalize Normalize the bitrise configuration. - step-list List of available steps. - step-info Provides information (step ID, last version, given version) about specified step. - workflows List of available workflows in config. - share Publish your step. - plugin Plugin handling. - stepman Runs a stepman command. - envman Runs an envman command. - help Shows a list of commands or help for one command - -PLUGINS: - :analytics Submitting anonymized usage information. - :init Initialize bitrise __config (bitrise.yml)__ and __secrets (.bitrise.secrets.yml)__ based on your project. - :workflow-editor Bitrise Workflow Editor. - -COMMAND HELP: bitrise COMMAND --help/-h -``` - -* `bitrise validate` command fixes: - - - minimal bitrise.yml should contain a `format_version` property - - `no bitrise.yml found` error message fix - -* Dependency updates: - - - minimum go version updated from 1.7.4 to 1.8 - - minimum stepman version: 0.9.29 - - default workflow-editor version: 0.9.6 - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.5/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.5.4 -> 1.5.5 - -* [cb8e402] Krisztian Godrei - prepare for v1.5.5 (2017 Mar 13) -* [e6dc915] Krisztián Gödrei - min workflow-editor: 0.9.6, min stepman: 0.9.29 (#479) (2017 Mar 13) -* [be45cd1] Krisztián Gödrei - godeps update (#478) (2017 Mar 13) -* [2315cd8] Krisztián Gödrei - Silent setup (#477) (2017 Mar 13) -* [332bea3] Krisztián Gödrei - Validate fix (#476) (2017 Mar 13) -* [8e98109] Krisztián Gödrei - not bitrise.yml found error message fix (#475) (2017 Feb 28) -* [3adda49] Viktor Benei - Go toolkit - go version upgrade from 1.7.5 to 1.8 (#474) (2017 Feb 23) -* [ce11f40] Viktor Benei - Go toolkit - min go version update from 1.7.4 to 1.7.5 (#472) (2017 Feb 20) -* [4939b98] Tamas Papik - Include plugins list on the help pages (#473) (2017 Feb 20) - - -## 1.5.4 (2017 Feb 14) - -### Release Notes - -* To allow bitrise-cli, to use [stepman](https://github.com/bitrise-io/stepman)'s new features we updated the required minimal stepman version to [0.9.28](https://github.com/bitrise-io/stepman/releases/tag/0.9.28). - -The new stepman version adds support for local and git setps in `step-info` command. This update will allow the [offline workflow-editor](https://github.com/bitrise-io/bitrise-workflow-editor) to handle every type of steps, even it is a git or local step. - -* This version of bitrise-cli ships with the [offline workflow-editor](https://github.com/bitrise-io/bitrise-workflow-editor) as a default plugin. - -This means, once you update your bitrise-cli to 1.5.4, it will install the workflow editor for you, during the setup process. To run the editor, just call: - -`bitrise :workflow-editor` - -* bitrise-cli checks, if there is a new version of any installed plugin, from now it will print a command for you, which you can use to update a plugin. Do not miss any updates! - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.4/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.5.3 -> 1.5.4 - -* [dd61696] Krisztian Godrei - prepare for 1.5.4 (2017 Feb 14) -* [be02cbb] Krisztian Godrei - Merge branch 'master' of github.com:bitrise-io/bitrise (2017 Feb 14) -* [2b1c1a4] Krisztián Gödrei - create-release wf update, switch workflow log fix, min stepman version bumped to 0.9.28, workflow-editor default plugin (#471) (2017 Feb 14) -* [8810152] trapacska - New plugin warning extended with instructions (#470) (2017 Feb 14) -* [9b39206] trapacska - New plugin warning extended with instructions (#470) (2017 Feb 14) -* [20af75e] Krisztián Gödrei - Stepman update (#469) (2017 Feb 14) - - -## 1.5.3 (2017 Jan 26) - -### Release Notes - -* use envman & stepman throught bitrise-cli - -bitrise-cli manages his envman and stepman dependencies internally, but you may want to use stepman or envman direct. -From now you can use `bitrise envman` to access envman commands or `bitrise stepman` to stepman's. - -* `bitrise validate` now warns you if your trigger item would trigger utility workflow (_utility workflow's workflow id starts with underscore (`_`) character_) - -* stepman min version bumped to: [0.9.27](https://github.com/bitrise-io/stepman/releases/tag/0.9.27) - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.3/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.5.2 -> 1.5.3 - -* [5e48e55] Krisztian Godrei - prepare for 1.5.3 (2017 Jan 26) -* [f528276] Krisztián Gödrei - prepare for 1.5.3, min stepman version bumped to 0.9.27 (#468) (2017 Jan 26) -* [74cc0f6] Krisztián Gödrei - logrus instead of log package (#467) (2017 Jan 24) -* [576ed57] Krisztián Gödrei - trigger utility workflow (#466) (2017 Jan 24) -* [8f832e7] Krisztián Gödrei - use envman & stepman throught bitrise-cli (#464) (2017 Jan 24) -* [d45c6e6] Krisztián Gödrei - godeps update (#465) (2017 Jan 24) - - -## 1.5.2 (2017 Jan 10) - -### Release Notes - -* envman min version bumped to [1.1.3](https://github.com/bitrise-io/envman/releases/tag/1.1.3) -* expanded trigger map validation: - - - validate whether workflow (defined in trigger map item) exists - - validate whether duplicate patterns with same type exists - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.5.1 -> 1.5.2 - -* [23ecca2] Krisztian Godrei - prepare for 1.5.2 (2017 Jan 10) -* [d9e9898] Krisztián Gödrei - deps update (#463) (2017 Jan 10) -* [70514a8] Krisztián Gödrei - Bitrise yml validation (#462) (2017 Jan 10) - - -## 1.5.1 (2016 Dec 14) - -### Release Notes - -* `stepman` min version bumped to [0.9.26](https://github.com/bitrise-io/stepman/releases/tag/0.9.26) - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.5.0 -> 1.5.1 - -* [dc2fd02] Krisztian Godrei - version fix (2016 Dec 14) -* [27c566f] Krisztian Godrei - prepare for 1.5.1 (2016 Dec 14) -* [ecdf381] Krisztián Gödrei - stepman version bump to 0.9.26 (#461) (2016 Dec 14) - - -## 1.5.0 (2016 Dec 13) - -### Release Notes - -* init command moved to a separate [plugin](https://github.com/bitrise-core/bitrise-plugins-init), this means you can initialize a new bitrise config by running `bitrise :init`, (previous `bitrise init` command also exists, but it calls the plugin). - - The new init plugin uses the [core](https://github.com/bitrise-core/bitrise-init) of the [Project Scanner step](https://github.com/bitrise-steplib/steps-project-scanner), which used by the [btrise.io](https://www.bitrise.io) website to add new app. - - You can create a project type based init by running: `bitrise :init` or create a 'custom' configuration by calling `bitrise :init --minimal`. - -* bitrise now prints available step update, even if step does not fail -* bitrise-cli docs are expanded with __bitrise.yml format specification / reference__ -* improvements on available workflows log -* fixed `validate` command - - the validate command fails if bitrise config or bitrise secrets is empty - - fixed exit status if validate fails - - integration tests - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.5.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.4.5 -> 1.5.0 - -* [a20102d] Krisztian Godrei - prepare for 1.5.0 (2016 Dec 13) -* [8315903] Krisztián Gödrei - init (#460) (2016 Dec 13) -* [5b38532] Krisztián Gödrei - Validate fix (#459) (2016 Dec 13) -* [5fbb00c] Krisztián Gödrei - remove timeout (#458) (2016 Dec 12) -* [6a7dc40] Krisztián Gödrei - version bump to 1.5.0, format version bump to 1.4.0 (#457) (2016 Dec 08) -* [50e3241] Viktor Benei - don't print timestamp for workflow list (#455) (2016 Dec 07) -* [d41571a] Viktor Benei - Go 1.7.4 (#452) (2016 Dec 07) -* [ab98213] Viktor Benei - Update bitrise-yml-format-spec.md (2016 Dec 06) -* [4b440cc] Viktor Benei - Feature/docs property ref docs (#456) (2016 Dec 06) -* [21708e5] Krisztián Gödrei - version bump to 1.4.6-pre (#450) (2016 Nov 29) -* [89dc8db] Krisztián Gödrei - Godeps update (#449) (2016 Nov 29) -* [a0e962c] Krisztián Gödrei - Init (#447) (2016 Nov 29) -* [22e93de] Krisztián Gödrei - Step timeout (#445) (2016 Nov 29) -* [d6d19a3] Krisztián Gödrei - go-toolkit step template test (#446) (2016 Nov 29) -* [14d74d0] Krisztián Gödrei - print update available if any (#448) (2016 Nov 29) -* [0a6e522] Krisztián Gödrei - godeps update (#444) (2016 Nov 24) - - -## 1.4.5 (2016 Nov 10) - -### Release Notes - -* __FIX__ regression: previous version (1.4.4) of bitrise-cli thrown and error when `bitrise steup` was called on Linux: `unsupported platform`. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.4.5/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.4.4 -> 1.4.5 - -* [742cd01] Krisztian Godrei - prepare for 1.4.5 (2016 Nov 10) -* [bfc376a] Krisztián Gödrei - linux install fix (#440) (2016 Nov 10) - - -## 1.4.4 (2016 Nov 08) - -### Release Notes - -* apt get package install check fix: previous apt-get package install check (`dpkg -l PACKAGE`) was returning with exist code: `0`, even if the package is not fully installed. This version of `bitrise-cli` uses `dpkg -s PACKAGE` command to check if package is installed or not. -* `bitrise version --full` command now prints the __Go__ and __OS__ version, which was used to build the bitrise-cli binary. -* `bitrise plugin` command group now get a new command: `update`. -This command can be used to update bitrise plugins, like: `bitrise plugin update analytics`. -* retry step dependency install, if it fails, for improved reliability. -* envman minimum version updated to: [1.1.2](https://github.com/bitrise-io/envman/releases/tag/1.1.2) -* used analytics plugin version updated to: [0.9.6](https://github.com/bitrise-core/bitrise-plugins-analytics/releases/tag/0.9.6) - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.4.4/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.4.3 -> 1.4.4 - -* [7ad576b] Krisztian Godrei - workflow refactors (2016 Nov 08) -* [cce35e6] Krisztián Gödrei - godeps update, test update (#439) (2016 Nov 08) -* [a5c0329] Krisztián Gödrei - retry if step dependency install failed (#438) (2016 Nov 08) -* [7a78c50] Krisztián Gödrei - envman min version bumped to: 1.1.2, analytics min version bumped to: 0.9.6, bitrise.yml update (#437) (2016 Nov 08) -* [65ca4b3] Krisztián Gödrei - Plugin update (#436) (2016 Nov 08) -* [607f20d] Krisztián Gödrei - print go and os version in version command (#435) (2016 Nov 04) -* [e11bc96] Viktor Benei - apt get package installed check fix (#434) (2016 Nov 02) - - -## 1.4.3 (2016 Oct 24) - -### Release Notes - -#### __Removed emojis__ from step and build run result logs. - -- Success step run's icon changed from: ✅ to: `✓` -- Failed step run's icon changed from: 🚫 to: `x` -- Skipped by fail step run's icon changed from: ⚠️ to: `!` -- Skipped by run_if expression step run's icon changed from: ➡ to: `-` - -#### Go version bumped for toolkit to 1.7.3 -#### Fixed `panic: runtime error: makeslice: len out of range` issue, when printing long running step's runtime in step and build run result logs. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.4.3/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.4.2 -> 1.4.3 - -* [46c2607] Krisztián Gödrei - prepare for 1.4.3 (#432) (2016 Oct 24) -* [200e397] Viktor Benei - version 1.4.3-pre (#430) (2016 Oct 21) -* [e8510f3] Krisztián Gödrei - long step run time (#429) (2016 Oct 20) -* [c7f900e] Viktor Benei - bumped Go version for toolkit to 1.7.3 (#428) (2016 Oct 20) -* [9978d5c] Viktor Benei - Feature/remove log emojis (#427) (2016 Oct 20) -* [807f3c8] Krisztián Gödrei - Update CHANGELOG.md (2016 Oct 14) - - -## 1.4.2 (2016 Oct 14) - -### Release Notes - -* stepman min version update to: [0.9.25](https://github.com/bitrise-io/stepman/releases/tag/0.9.25): - -`stepman share` command fix: in version 0.9.24 stepman created a branch - for sharing a new step - with name: `STEP_ID` and later tried to push the steplib changes on branch: `STEP_ID-STEP_VERSION`, which branch does not exist. -This release contains a quick fix for stepman sharing, the final share branch layout is: `STEP_ID-STEP_VERSION` - -* `format_version` updated to: `1.3.1` (fix: forgot to bump in 1.4.1) - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.4.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.4.1 -> 1.4.2 - -* [fb62066] Krisztian Godrei - prepare for 1.4.2 (2016 Oct 14) -* [9d46c63] Krisztián Gödrei - stepman min version: 0.9.25 (#425) (2016 Oct 14) -* [b250db7] Krisztián Gödrei - Update CHANGELOG.md (2016 Oct 11) -* [a29e80a] Krisztián Gödrei - Update CHANGELOG.md (2016 Oct 11) - - -## 1.4.1 (2016 Oct 11) - -### Release Notes - -#### Tag trigger event handling - -The new trigger map is completed with tag event support. - -``` -- tag: TAG_PATTERN - workflow: WORKFLOW_NAME -``` - -example: - -``` -- tag: *.*.* - workflow: deploy -``` - -#### bitrise-cli global flag fix - -Fixed _Pull Request Mode_ and _CI Mode_ global flag (`--pr` and `--ci`) handling. -_Pull Request Mode_ and _CI Mode_ global flags are available in `run`, `trigger` and `trigger-check` commands. - -From now `bitrise --pr COMMAND` will run in _Pull Request Mode_, whatever is set in environemnts or in secrets, -`bitrise --pr=false COMMAND` will __NOT__ run in _Pull Request Mode_, whatever is set in environemnts or in secrets. - -similar `bitrise --ci COMMAND` will perform the command in _CI Mode_, whatever is set in environemnts or in secrets and -`bitrise --ci=false COMMAND` will __NOT__ run in _CI Mode_, whatever is set in environemnts or in secrets. - -#### output envstore cleanup - -In previous versions of `bitrise-cli` the output envstore (which is a container for the step output environments) -was not cleared after processing its content. This led bitrise-cli to duplicate every output environment, which was generated by a step, after every next step run. - -#### bash toolkit entry file support - -Before this release bash-toolkit step's entry file was the hardcoded `step.sh`, from now these steps can specify the entry file path in the `step.yml`. - -example: - -``` -... -toolkit: - bash: - entry_file: step_entry.sh -... -``` - -#### dependency updates - -`stepman` min version updated to: [0.9.24](https://github.com/bitrise-io/stepman/releases/tag/0.9.24), `analytics plugin` version updated to [0.9.5](https://github.com/bitrise-core/bitrise-plugins-analytics/releases/tag/0.9.5). - -#### minor fixes - -Updated messages with default values at dependency installation. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.4.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.4.0 -> 1.4.1 - -* [49b3b81] Krisztian Godrei - prepare for 1.4.1 (2016 Oct 11) -* [866b031] Krisztian Godrei - bitrise.yml updates (2016 Oct 11) -* [42fa26a] Krisztián Gödrei - stepman version: 0.9.24, analitics version: 0.9.5 (#423) (2016 Oct 11) -* [03337ca] Krisztián Gödrei - entry file test (#422) (2016 Oct 11) -* [8f38591] Krisztián Gödrei - envstore test (#421) (2016 Oct 07) -* [850e87e] Krisztián Gödrei - global flag handling fix (#420) (2016 Oct 06) -* [b9e6d7a] Krisztián Gödrei - Tag event (#419) (2016 Oct 04) -* [9e077f9] Viktor Benei - just a minor dep install text change/clarification (#418) (2016 Sep 27) -* [143a90e] Viktor Benei - Feature/dep install prompt fix (#415) (2016 Sep 24) -* [ff1ec60] Viktor Benei - .DS_Store gitignore (2016 Sep 24) -* [524fb8f] Viktor Benei - base for integration tests in go (#412) (2016 Sep 19) -* [f4fba50] Viktor Benei - Feature/minor rev (#413) (2016 Sep 19) -* [2dffff4] Viktor Benei - deps update (#411) (2016 Sep 18) -* [6ede212] Viktor Benei - minor scoping revision (#410) (2016 Sep 18) - - -## 1.4.0 (2016 Sep 13) - -### Release Notes - -#### New trigger map - -bitrise contains a new trigger map syntax, to allow specify more specific and felxible trigger events, full proposal is available on [github](https://github.com/bitrise-io/bitrise.io/issues/40). - -_Keep in mind:_ -__Every single trigger event should contain at minimum one condition.__ -__Every single trigger event conditions are evaluated with AND condition.__ - -__code push:__ - -``` -- push_branch: BRANCH_NAME - workflow: WORKFLOW_ID_TO_RUN -``` - -__pull request:__ - -``` -- pull_request_source_branch: SOURCE_BRANCH_NAME - pull_request_target_branch: TARGET_BRANCH_NAME - workflow: WORKFLOW_ID_TO_RUN -``` - -exmple: - -``` -trigger_map: -- push_branch: release* - workflow: deploy -- push_branch: master - workflow: primary -- pull_request_target_branch: develop - workflow: test -``` - -_New trigger map handling is fully compatible with the old syntax, following conversion is applied:_ - -``` -Old syntax: New Syntax: - -trigger_map: trigger_map: -- pattern: * -> - push_branch: * - workflow: primary workflow: primary -``` - -``` -Old syntax: New Syntax: - -trigger_map: trigger_map: -- push_branch: * -> - push_branch: * - is_pull_request_allowed: true workflow: primary - workflow: primary - pull_request_source_branch: * - workflow: primary -``` - -#### Toolkit support (_BETA_) - -_Toolkit support is still in beta and details of it migth change in upcoming cli releases._ - -Currently available toolkits: `bash` and `go`. - -__bash toolkit__ realize the way of current step handling, -e.g.: every step needs to have a `step.sh` in the step's directory as an entry point for the step. - -When bitrise executes the step, it call calls `bash step.sh`. - -In case of __go toolkit__, you need to specify the package name, and the toolkit takes care about: - -* moving the go step into a prepared GOPATH inside of the .bitrise directory -* building the step project -* chaching the binary of given version of step - -When bitrise executes the step, it calls the step's binary. - -_Using the toolkit can provide performance benefits, as it does automatic binary caching - -which means that a given version of the step will only be compiled the first time, -subsequent execution of the same version will use the compiled binary of the step!_ - -_Toolkit also takes care of its own dependencies. -For example go toolkit requires installed go, -so toolkit checks if desired version of go is installed on the system, -if not it installs it for itself (inside the .bitrise directory), -but does not touch the system installed version._ - -Check out `slack` step for living example of go toolkit usage: [slack v2.2.0](https://github.com/bitrise-io/steps-slack-message/releases/tag/2.2.0) - -#### Step dependency handling revision - -* fixed check whether dependency is installed or not -* dependecy models got new property: `bin_name` - -_bin_name is the binary's name, if it doesn't match the package's name. -E.g. in case of "AWS CLI" the package is `awscli` and the binary is `aws`. -If bin_name is empty name will be used as bin_name too._ - -#### Other changes: - -* Every __networking__ function of bitrise cli uses __retry logic__ and prints progress indicator. -* bitrise run now prints _Running workflow: WORKFLOW_ID_, for the workflow started running - and prints _Switching to workflow: WORKFLOW_ID_ when running before and after workflows. -* bitrise configuration (bitrise.yml) __format version__ updated to __1.4.0__ -* __stepman__ version update to [0.9.23](https://github.com/bitrise-io/stepman/releases/tag/0.9.23) -* __envman__ version update to [1.1.1](https://github.com/bitrise-io/envman/releases/tag/1.1.1) - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.4.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.3.7 -> 1.4.0 - -* [e229c64] Krisztián Gödrei - min envman version: 1.1.1, min stepman version: 0.9.23 (#407) (2016 Sep 13) -* [1e3cbb9] Krisztián Gödrei - godeps update (#406) (2016 Sep 13) -* [d7bf595] Krisztián Gödrei - New trigger (#402) (2016 Sep 13) -* [80719e3] Viktor Benei - Step deps handling revision (#405) (2016 Sep 12) -* [51f55dc] Viktor Benei - bitrise run now prints the workflow it was started with (#403) (2016 Sep 12) -* [f07a254] Viktor Benei - model version 1.3.0 (#404) (2016 Sep 10) -* [c4320c6] Viktor Benei - Feature/toolkit bootstrap revision (#401) (2016 Sep 09) -* [751dd74] Viktor Benei - Feature/toolkit enforcement revision (#400) (2016 Sep 09) -* [2b2505a] Viktor Benei - Feature/go toolkit beta revs (#399) (2016 Sep 08) -* [fc43c43] Viktor Benei - v1.4.0 - version number prep (#398) (2016 Sep 08) -* [1dd93e4] Viktor Benei - [WIP] Feature/toolkit go (#385) (2016 Sep 08) -* [35ea8d2] Viktor Benei - setup / dependency install : error passing fix (#397) (2016 Sep 07) -* [d7ced31] Viktor Benei - Feature/deps update (#396) (2016 Sep 06) -* [db7f786] Viktor Benei - tools install & download separation (#395) (2016 Sep 05) -* [40277a4] Viktor Benei - fix in tests, to make `go test ./...` work after a clean checkout (e.g. in `docker`) (#394) (2016 Sep 05) -* [5ecb521] Viktor Benei - dependencies (tools & plugins install) : with progress & retry (#393) (2016 Sep 05) -* [eb57eb6] Viktor Benei - Feature/readme and docker revision (#392) (2016 Sep 05) -* [e130bba] Viktor Benei - typo fixes (#391) (2016 Sep 05) -* [452dced] Viktor Benei - deps update (#390) (2016 Sep 05) -* [40f28c1] Viktor Benei - step URL note if git:: step clone fails (#389) (2016 Sep 01) -* [d01ea23] Viktor Benei - deps update (#386) (2016 Aug 23) - - -## 1.3.7 (2016 Aug 09) - -### Release Notes - -* From now you can specify __workflow id to run__ with `--workflow` flag for `bitrise run` command. - Example: `bitrise run --workflow WORKFLOW_ID`. - _In previous versions you were able to specify workflow id to run as a command argument (`bitrise run WORKFLOW_ID`); this method is still supported._ - -* Similar to run command's new `--workflow` flag, `trigger` and `trigger-check` commands also received new flags for specifying the __trigger pattern__: `--pattern`. - Example: `bitrise trigger --pattern PATTERN`. - _In previous versions you were able to specify the pattern as a command argument (`bitrise trigger PATTERN`); this method is still supported._ - -* __json parameters__: every workflow run related commands (`run`, `trigger`, `trigger-check`) now have new inputs: - - - `--json-params` - - `--json-params-base64`. - - You can use `--json-params` to specify __every available command flag__ in a single json struct. This json struct should be a string-string map, where every key is the command flag's name, and the value should be the flag's value. - - For example: - `bitrise run --config bitrise.yml --workflow primary` - - Equivalent with json-params: - `bitrise run --json-params '{"config":"bitrise.yml", "workflow":"primary"}'` - - To see the command's available flags, call `bitrise COMMAND -h`. - - If you want to avoid character escaping side effects while running the `bitrise` cli, you can base64 encode --json-params value and pass to bitrise cli using the `--json-params-base64` flag. - -* feature/internal tools handling revision: __the `envman` and `stepman` (used by `bitrise`) tools install path moved from `/usl/local/bin` to `$HOME/.bitrise/tools`__ to make sure bitrise cli uses the desired tool version. - -* stepman min version updated to 0.9.22 - -* deprecated action signature fix - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.7/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.3.6 -> 1.3.7 - -* [890307c] Krisztián Gödrei - prepare for 1.3.7 (2016 Aug 09) -* [5be9c1d] Krisztián Gödrei - Json params prepare for new trigger map (#380) (2016 Aug 08) -* [d91f6ac] Krisztián Gödrei - remove unnecessary init path from run (#379) (2016 Aug 03) -* [c2187b3] Krisztián Gödrei - Json params (#378) (2016 Aug 03) -* [187382f] Krisztián Gödrei - deprecated action signature fix (#377) (2016 Aug 01) -* [45ed0d0] Viktor Benei - Feature/internal tools handling revision (#374) (2016 Jul 26) - - -## 1.3.6 (2016 Jul 12) - -### Release Notes - -* stepman dependency update to 0.9.21 -* build run result log now prints "Not provided" if missing source_code_url / support_url -* step-development-guideline.md update -* typo fix - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.6/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.3.5 -> 1.3.6 - -* [65406ce] Krisztián Gödrei - prepare for 1.3.6 (2016 Jul 12) -* [3509ca9] Krisztián Gödrei - stepman dependency update to 0.9.21 (#371) (2016 Jul 12) -* [8c245dd] Krisztián Gödrei - godeps update (#370) (2016 Jul 12) -* [2a5d92d] Viktor Benei - Update README.md (2016 Jul 02) -* [acb42e7] Krisztián Gödrei - Merge pull request #367 from godrei/godep_update (2016 Jun 30) -* [3e8bb97] Krisztián Gödrei - errcheck fix (2016 Jun 29) -* [baf812d] Krisztián Gödrei - colorfunc update, bitrise.yml updates (2016 Jun 29) -* [836d298] Krisztián Gödrei - godep update (2016 Jun 29) -* [e36c9c6] Viktor Benei - Update step-development-guideline.md (2016 Jun 28) -* [f5f639a] Krisztián Gödrei - Merge pull request #365 from godrei/error_footer (2016 Jun 23) -* [d6d847e] Viktor Benei - Merge pull request #363 from viktorbenei/master (2016 Jun 23) -* [c4cd641] Viktor Benei - Merge pull request #364 from bitrise-io/viktorbenei-patch-1 (2016 Jun 23) -* [bdce9bb] Krisztián Gödrei - test updates (2016 Jun 22) -* [97bbbae] Krisztián Gödrei - chardiff = 0 test (2016 Jun 22) -* [8ff6f12] Krisztián Gödrei - print "Not provided" if missing source_code_url / support_url (2016 Jun 22) -* [691049c] Viktor Benei - typo fix (2016 Jun 20) -* [b8d6738] Viktor Benei - gows init & go-utils/pathutil fix (2016 Jun 16) - - -## 1.3.5 (2016 Jun 07) - -### Release Notes - -* From now on `bitrise setup` (without any flag) is the equivalent of the previous `bitrise setup --minimal` call (e.g.: it omits `brew doctor` call, which fails if brew or Xcode is outdated). You can achieve the old *full* setup behaviour (e.g.: which includes `brew doctor`) by calling `bitrise setup --full`. -* Logging improvements. -* New `run_if` template [examples](https://github.com/bitrise-io/bitrise/blob/master/_examples/experimentals/templates/bitrise.yml) -* A fix for installing bitrise plugins from local paths (e.g. during plugin development) - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.5/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.3.4 -> 1.3.5 - -* [6e15ca5] Krisztián Gödrei - Merge pull request #361 from godrei/setup_review (2016 Jun 03) -* [433cd40] Krisztián Gödrei - log full setup (2016 Jun 03) -* [b7ed487] Krisztián Gödrei - setup fix for local plugins (2016 Jun 03) -* [a3e3fdc] Krisztián Gödrei - bitrise ci workflow name refactors (2016 Jun 03) -* [f9a91b8] Viktor Benei - Merge pull request #360 from godrei/template_examples (2016 May 26) -* [8501df7] Krisztián Gödrei - run_if template examples (2016 May 26) -* [9119289] Krisztián Gödrei - Merge pull request #359 from godrei/config_fix (2016 May 25) -* [f0f378c] Krisztián Gödrei - log config error (2016 May 25) -* [fd067e8] Krisztián Gödrei - Merge pull request #358 from godrei/setup (2016 May 11) -* [ba22d81] Krisztián Gödrei - minimal setup by default (2016 May 11) - - -## 1.3.4 (2016 May 10) - -### Release Notes - -* Removed exist status error from failed step's log: - -``` -ERRO[13:14:02] Step (tmp) failed, error: (exit status 1) -``` - -* Now bitrise `trigger map` will be validated before use. The validation makes sure there is no trigger map item with empty pattern or workflow id. -* Minor fixes and improvements - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.4/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.3.3 -> 1.3.4 - -* [62c0033] Krisztián Gödrei - godep update (2016 May 10) -* [d352a3c] Krisztián Gödrei - prepare for release (2016 May 10) -* [6b6b63f] Krisztián Gödrei - Merge pull request #355 from godrei/failed_step_log_fix (2016 May 10) -* [5354284] Krisztián Gödrei - removed exist status error from failed step's log (2016 May 10) -* [45c2106] Krisztián Gödrei - Merge pull request #354 from godrei/exit_review (2016 May 10) -* [1946f40] Krisztián Gödrei - trigger map empty test fix (2016 May 09) -* [7e9ec69] Krisztián Gödrei - empty pattern/wf id integration tests (2016 May 09) -* [cbcee15] Krisztián Gödrei - exit review (2016 May 09) - - -## 1.3.3 (2016 Apr 27) - -### Release Notes - -* __FIX__ regression since `1.2.x`: `bitrise trigger [PATTERN]` did not handled PR mode correctly, if PR mode was set in bitrise secrets. `is_pull_request_allowed: false` was not correctly handled in the `trigger_map` if the PR mode indication was declared in the bitrise secrets. This version fixes the PR mode handling when running `bitrise trigger [PATTERN]` and also includes unit and integration tests for it. -* Now `bitrise trigger-check [PATTERN]` also checks for PR envs in secrets. It uses the same functionality to determine which workflow id to select as `bitrise trigger [PATTERN]` does. -* __FIX__ regression: `bitrise trigger [PATTERN]` once again allows to trigger *utility workflows* as well. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.3/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.3.2 -> 1.3.3 - -* [2c97445] Krisztián Gödrei - Merge pull request #349 from godrei/trigger_fix (2016 Apr 27) -* [ee247e1] Krisztián Gödrei - fixed bitrise trigger (2016 Apr 27) -* [256526a] Krisztián Gödrei - Merge pull request #348 from godrei/trigger_fix (2016 Apr 26) -* [aeb9db5] Krisztián Gödrei - fatal instead of error (2016 Apr 26) -* [a347f6e] Krisztián Gödrei - expand cli context (2016 Apr 26) -* [8522bd3] Krisztián Gödrei - Merge pull request #347 from godrei/master (2016 Apr 20) -* [4967f16] Krisztián Gödrei - PR fix (2016 Apr 20) -* [ca9d760] Krisztián Gödrei - changelog update (2016 Apr 20) -* [9856b9c] Krisztián Gödrei - changelog (2016 Apr 20) - - -## 1.3.2 (2016 Apr 20) - -### Release Notes - -* __FIX__: although the previous version (1.3.1) fixed the exit code issue for `bitrise run`, the exit code was still not the right one in case of `bitrise trigger`. This version fixes the issue for bitrise trigger too, as well as we unified the handling codes of `run` and `trigger` as much as possible. Additionally, we now have integration tests (testing the exit codes) for both `bitrise run` and `bitrise trigger`. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.3.1 -> 1.3.2 - -* [5207fc1] Krisztián Gödrei - Merge pull request #346 from godrei/trigger_fix (2016 Apr 20) -* [f248c47] Krisztián Gödrei - PR fix (2016 Apr 20) -* [be73f82] Krisztián Gödrei - PR fix (2016 Apr 20) -* [19b2861] Krisztián Gödrei - common run (2016 Apr 20) -* [9a83792] Krisztián Gödrei - integration tests moved to bitrise-integration.yml (2016 Apr 20) -* [aa9364e] Krisztián Gödrei - allow pull request at trigger tests (2016 Apr 20) -* [dda344d] Krisztián Gödrei - unit tests (2016 Apr 20) -* [5982017] Viktor Benei - Merge pull request #345 from godrei/tests (2016 Apr 20) -* [b88c9e4] Krisztián Gödrei - test updates (2016 Apr 19) -* [56787eb] Krisztián Gödrei - Merge pull request #344 from godrei/master (2016 Apr 19) -* [82e0746] Krisztián Gödrei - Changelog (2016 Apr 19) - - -## 1.3.1 (2016 Apr 19) - -### Release Notes - -* __FIX__: We discovered a critical issue in the CLI v1.3.0. Version 1.3.0 of the CLI does not return the expected exit code after `bitrise run [WORKFLOW-ID]` if the `run` fails. It always returns exit code 0 if the configuration was correct and the workflow was executed, even if a step failed during `run`. This version fixes the exit code issue. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.3.0 -> 1.3.1 - -* [1255aa7] Krisztián Gödrei - Merge pull request #343 from godrei/master (2016 Apr 19) -* [16a587f] Krisztián Gödrei - code cleaning (2016 Apr 18) -* [ea1349f] Krisztián Gödrei - Merge pull request #342 from godrei/run_exit_code (2016 Apr 18) -* [9be9913] Krisztián Gödrei - cleanup (2016 Apr 18) -* [fd09faf] Krisztián Gödrei - exit code fix (2016 Apr 18) -* [48d609c] Krisztián Gödrei - exit code test (2016 Apr 18) -* [33065b5] Viktor Benei - Merge pull request #341 from godrei/test_updates (2016 Apr 15) -* [d56ec9e] Krisztián Gödrei - typo fix (2016 Apr 15) -* [eadf1bd] Krisztián Gödrei - test updates (2016 Apr 15) - - -## 1.3.0 (2016 Apr 12) - -### Release Notes - -* __BREAKING__ : Now you can delete/reset environment variables by setting the value to empty string (""). - Previously an empty value (e.g. `- an_input: ""`) was just ignored, - now it actually sets the value to an empty value. -* __NEW__ : Plugins ("beta"), to extend the `bitrise` functionality without modifying the "core" - * Install plugin: `bitrise plugin install [PLUGIN_NAME]` - * Delete plugin: `bitrise plugin delete [PLUGIN_NAME]` - * List installed plugins: `bitrise plugin list` - * Run plugin: `bitrise :[PLUGIN_NAME]` - * bitrise cli now installs default plugins at `bitrise setup`. -* __NEW__ docs & tutorials: - * Step Development Guideline: `_docs/step-development-guideline.md` - * React Native Tutorial: `_examples/tutorials/react-native` -* Step Template revision: - * Generic format update - * Using `change-workdir` instead of a custom script - * Added a `share-this-step` workflow for quick & easy step sharing -* New `--format=json` & `--format=yml` output modes (beta, only a few command supports this flag right now) - * Added a new `version` command which now supports the `--format=json` -* README.md updates - * Tooling and `--format=json` - * Share your own Step section -* `bitrise workflows`, `bitrise step-info [STEP_ID]`, `bitrise step-list` cmd output improvements -* `bitrise validate` cmd updates: - * workflow id validation - * check for duplicated inputs -* bitrise output log improvements - * Now build log contains deprecation infos about deprecated steps -* typo fixes -* Requires new `envman` (`1.1.0`) and `stepman` (`0.9.18`) versions - it'll - auto-install these at first run if the required new versions are not found. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.3.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.2.4 -> 1.3.0 - -* [5181b50] Krisztián Gödrei - Merge pull request #339 from godrei/cache_dir_env (2016 Apr 11) -* [ce86551] godrei - cache dir env (2016 Apr 11) -* [da1edb6] Krisztián Gödrei - Merge pull request #337 from godrei/plugin_update (2016 Apr 11) -* [3f28676] godrei - plugin update fix & analytics 0.9.4 (2016 Apr 11) -* [590015d] Krisztián Gödrei - Merge pull request #336 from godrei/version_cmd (2016 Apr 07) -* [3c2bfe8] godrei - cleanup (2016 Apr 07) -* [d784f98] godrei - include commit in full version (2016 Apr 07) -* [f33738b] godrei - outputFormat moved to output package (2016 Apr 07) -* [e868e78] Krisztián Gödrei - Merge pull request #334 from bitrise-io/update-react-example (2016 Apr 07) -* [534119c] Krisztián Gödrei - Merge pull request #335 from godrei/build_number (2016 Apr 06) -* [bc2bbe6] godrei - move binaries to deploy dir (2016 Apr 06) -* [da00fb2] godrei - PR fix (2016 Apr 06) -* [b999737] Agnes Vasarhelyi - Update bitrise.yml (2016 Apr 06) -* [6feb388] godrei - build number (2016 Apr 06) -* [0bc68e7] vasarhelyia - Remove local path (2016 Apr 06) -* [19cde67] vasarhelyia - Use dedicated steps (2016 Apr 06) -* [a02d148] Krisztián Gödrei - Merge pull request #333 from godrei/master (2016 Apr 06) -* [e0e98ec] godrei - release notes (2016 Apr 06) -* [8d4e86a] godrei - v1.3.0 (2016 Apr 06) -* [471b2ab] godrei - bitrise.yml typo fix (2016 Apr 06) -* [19e845a] Krisztián Gödrei - Merge pull request #332 from godrei/prepare_for_relelase (2016 Apr 06) -* [e5187ac] godrei - removed old changelogs (2016 Apr 06) -* [77ee955] godrei - prepare for release (2016 Apr 06) -* [f34df79] Krisztián Gödrei - Merge pull request #331 from godrei/feature/default_plugins (2016 Apr 06) -* [68c93fa] godrei - default analytics plugin min version update (2016 Apr 06) -* [e29d733] godrei - log installed plugin (2016 Apr 05) -* [d453d8b] godrei - default plugins (2016 Apr 05) -* [2b398f1] Krisztián Gödrei - Merge pull request #330 from godrei/duplicated_inputs (2016 Apr 05) -* [5d8bc29] godrei - test fixes (2016 Apr 05) -* [5d142cb] godrei - check for duplicated inputs (2016 Apr 05) -* [e0402d0] Krisztián Gödrei - Merge pull request #329 from godrei/godep-update (2016 Apr 05) -* [fb51075] godrei - godep update (2016 Apr 05) -* [4d78370] Krisztián Gödrei - Merge pull request #328 from godrei/separate_packages (2016 Apr 05) -* [8ff9a6e] Krisztián Gödrei - Merge pull request #327 from godrei/ci_updates (2016 Apr 05) -* [1e57cb7] godrei - cleanup (2016 Apr 05) -* [ace4e2b] godrei - separate bitrise packages (2016 Apr 05) -* [2ec5090] godrei - bitrise.yml updates (2016 Apr 04) -* [4c2ca4a] Viktor Benei - Merge pull request #326 from godrei/deprecate_wildcard_workflow_id (2016 Apr 01) -* [5477a8e] godrei - PR fix (2016 Apr 01) -* [722cc71] godrei - [b4475fe] [204cd7c] typo fix [b672304] workflow id validation (2016 Apr 01) -* [dbc2e95] Krisztián Gödrei - Merge pull request #325 from godrei/global_step_info (2016 Mar 18) -* [95448a1] Krisztián Gödrei - deprecate infos (2016 Mar 18) -* [85ac8ce] Krisztián Gödrei - Merge pull request #324 from godrei/skip_if_empty (2016 Mar 18) -* [866d86e] Krisztián Gödrei - instal bitrise tool in _prepare_and_setup workflow (2016 Mar 18) -* [797a42a] Krisztián Gödrei - bitrise.yml updates (2016 Mar 18) -* [463e9ae] Krisztián Gödrei - plugin update for new envman version, release configs, bitrise.yml updates (2016 Mar 18) -* [f74bee9] Krisztián Gödrei - removed local reference in create_changelog workflow & skip_if_empty unit test new environment variable (skip_if_empty) handling (2016 Mar 18) -* [a92c659] Krisztián Gödrei - Merge pull request #323 from godrei/test_fix (2016 Mar 18) -* [384ee68] Krisztián Gödrei - plugin version check fix (2016 Mar 17) -* [ed5b5ca] Krisztián Gödrei - use bitrise-core test repos (2016 Mar 17) -* [8542528] Krisztián Gödrei - removed download test (2016 Mar 17) -* [18a1ac9] Viktor Benei - Merge pull request #321 from godrei/events (2016 Mar 17) -* [c8f88ba] Krisztián Gödrei - envman test fix, typo, error log fix (2016 Mar 16) -* [b86b4c2] Viktor Benei - Merge pull request #322 from anas10/patch-1 (2016 Mar 16) -* [64e9f20] Anas AIT ALI - Update README.md (2016 Mar 16) -* [9c4fe9b] Krisztián Gödrei - fixed TestExpandEnvs (2016 Mar 11) -* [e024a42] Krisztián Gödrei - check for updatest, before using the plugin, but only if not CI mode (2016 Mar 11) -* [922e0e8] Krisztián Gödrei - install binary by platforms (2016 Mar 05) -* [2e397fc] Krisztián Gödrei - create plugin data dir at install, check for plugin new version fix (2016 Mar 05) -* [1e61ab7] Krisztián Gödrei - log fixes, run_test update (2016 Mar 05) -* [8f6a350] Krisztián Gödrei - create plugin data dir (2016 Mar 03) -* [242b493] Krisztián Gödrei - trigger event DidFinishRun dont print any logs after workflow summary (2016 Mar 03) -* [814a2b9] Viktor Benei - Merge pull request #320 from viktorbenei/master (2016 Mar 03) -* [49f4234] Viktor Benei - experimental/upload-download-bitrise-yml : updated for Bitrise CLI 1.3 & made it better for quick fixing (download, fix, upload) (2016 Mar 03) -* [a901456] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2016 Mar 03) -* [bba88bd] Viktor Benei - Dockerfile: use go 1.6 (2016 Mar 03) -* [90d32ed] Viktor Benei - yml format update for new Bitrise CLI compatibility (2016 Mar 03) -* [7dad162] Krisztián Gödrei - Merge pull request #319 from godrei/plugin (2016 Mar 01) -* [844ea97] Krisztián Gödrei - NewEnvJSONList instead of CreateFromJSON (2016 Mar 01) -* [53cef9e] Krisztián Gödrei - test updates (2016 Mar 01) -* [8329195] Krisztián Gödrei - version fix (2016 Mar 01) -* [55e6df5] Krisztián Gödrei - plugin requirement's required min version is required, minor fixes (2016 Mar 01) -* [9cc7d95] Krisztián Gödrei - version package instead of hard coded version (2016 Mar 01) -* [3cc0bf9] Krisztián Gödrei - base plugin handling (2016 Mar 01) -* [3acccd3] Viktor Benei - Merge pull request #318 from tomgilder/patch-1 (2016 Feb 28) -* [dc423d8] Tom Gilder - Fix spelling mistake (2016 Feb 28) -* [6eed6a7] Viktor Benei - script content fix (multiline) (2016 Feb 22) -* [59696c6] Viktor Benei - Merge pull request #317 from dag-io/master (2016 Feb 17) -* [52322cf] Damien Gavard - Fix typo (2016 Feb 17) -* [de931be] Viktor Benei - Merge pull request #314 from bitrise-io/update-install-guide (2016 Feb 09) -* [6220657] vasarhelyia - Update install info (2016 Feb 09) -* [c452548] Viktor Benei - Merge pull request #313 from bitrise-io/update-react-native-example (2016 Feb 07) -* [0ccbac7] vasarhelyia - Update workflow name (2016 Feb 06) -* [ae202b1] vasarhelyia - Add sample app yml (2016 Feb 06) -* [3ecbd13] Viktor Benei - Merge pull request #312 from bitrise-io/slack-channel-badge (2016 Feb 04) -* [7c16467] vasarhelyia - Add Slack channel badge (2016 Feb 04) -* [3b64e94] Viktor Benei - Merge pull request #311 from birmacher/typo (2016 Jan 26) -* [2e589c7] birmacher - typo fix (2016 Jan 26) -* [32a52d5] Viktor Benei - Merge pull request #310 from viktorbenei/master (2015 Dec 22) -* [96037ac] Viktor Benei - godeps-update fix (2015 Dec 22) -* [1703e25] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Dec 22) -* [d5d2a66] Viktor Benei - godeps-update (2015 Dec 22) -* [956bee4] Viktor Benei - bumped required envman (to 1.1.0) & stepman (to 0.9.18) versions (2015 Dec 22) -* [60bd807] Viktor Benei - README: intro one-liner text revision (2015 Dec 17) -* [d94054e] Viktor Benei - Merge pull request #309 from viktorbenei/master (2015 Dec 17) -* [b808e12] Viktor Benei - LOG : if config (bitrise.yml) is not valid include the path of the file (2015 Dec 17) -* [35614c0] Viktor Benei - LOG : if local step info print fails it'll print the path of the YML in the logs (2015 Dec 17) -* [b4ba81c] Viktor Benei - FIX : typo: "cofing" -> "config" & "faild" -> "failed" (2015 Dec 17) -* [f98a3da] Viktor Benei - Merge pull request #306 from godrei/changelog (2015 Dec 16) -* [866127d] Krisztián Gödrei - create_changelog workflow for automatic changelog generation based on commits from last tag on master (2015 Dec 16) -* [631a097] Viktor Benei - point highlights in Development Guideline (2015 Dec 16) -* [a47606f] Viktor Benei - Development Guideline section revision in README (2015 Dec 16) -* [469cc5f] Viktor Benei - Merge pull request #305 from godrei/format_version (2015 Dec 15) -* [625dc38] Krisztián Gödrei - format version (2015 Dec 15) -* [98d74a5] Viktor Benei - Merge pull request #304 from godrei/godeps-update (2015 Dec 15) -* [92be446] Krisztián Gödrei - godeps update (2015 Dec 15) -* [a715ba7] Viktor Benei - Merge pull request #303 from godrei/plugin_compatibility (2015 Dec 15) -* [0a246c2] Krisztián Gödrei - godeps update (2015 Dec 14) -* [c630110] Krisztián Gödrei - plugin fixes (2015 Dec 14) -* [5f14af7] Viktor Benei - Merge pull request #302 from viktorbenei/master (2015 Dec 12) -* [3a31596] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Dec 12) -* [a5243bc] Viktor Benei - Merge pull request #301 from godrei/plugin (2015 Dec 12) -* [86573cb] Krisztián Gödrei - PR fix (2015 Dec 12) -* [79dc0d4] Krisztián Gödrei - PR fix (2015 Dec 12) -* [645b2bf] Krisztián Gödrei - PR fixes (2015 Dec 12) -* [d062952] Krisztián Gödrei - plugin install, delete, list (2015 Dec 12) -* [a4a3511] Viktor Benei - godeps-update (2015 Dec 12) -* [90653cb] Viktor Benei - 1.3.0-pre (2015 Dec 12) -* [e7a6dfa] Krisztián Gödrei - base plugin handling (2015 Dec 12) -* [50c8c83] Viktor Benei - Merge pull request #300 from godrei/workflows (2015 Dec 08) -* [324b08e] Krisztián Gödrei - improvements (2015 Dec 08) -* [03668e9] Viktor Benei - Merge pull request #299 from viktorbenei/master (2015 Dec 07) -* [9db86b4] Viktor Benei - typo in bitrise.yml workflow description (2015 Dec 07) -* [a2c051a] Viktor Benei - Merge pull request #297 from godrei/step_info_fix (2015 Dec 07) -* [17ca336] Viktor Benei - Merge pull request #298 from godrei/workflows_fix (2015 Dec 07) -* [7da0a6b] Krisztián Gödrei - yellow no summary/description (2015 Dec 07) -* [0a5f69f] Krisztián Gödrei - step-info, step-list fixes (2015 Dec 07) -* [b8b385f] Viktor Benei - Merge pull request #296 from godrei/delete_envs (2015 Dec 07) -* [74cdd7a] Krisztián Gödrei - change log fix (2015 Dec 07) -* [1e2978a] Krisztián Gödrei - delete env + test (2015 Dec 07) -* [7ddbd10] Viktor Benei - Merge pull request #295 from viktorbenei/master (2015 Dec 07) -* [a87bf87] Viktor Benei - godeps-update (2015 Dec 07) -* [a34bec8] Viktor Benei - Merge pull request #294 from godrei/workflow_list (2015 Dec 07) -* [780de41] Krisztián Gödrei - workflow list (2015 Dec 07) -* [19547ac] Viktor Benei - Update step-development-guideline.md (2015 Dec 05) -* [e7e18f9] Viktor Benei - Do not use submodules, or require any other resource, downloaded on-demand (2015 Dec 05) -* [1d61661] Viktor Benei - clarification (2015 Dec 05) -* [3b3adb0] Viktor Benei - Update README.md (2015 Dec 05) -* [8873c57] Viktor Benei - Create step-development-guideline.md (2015 Dec 05) -* [01e3f36] Viktor Benei - Share your own Step section (2015 Nov 19) -* [cfb5e5b] Viktor Benei - Merge pull request #293 from viktorbenei/master (2015 Nov 09) -* [a451eb3] Viktor Benei - readme : tooling and `--format=json` (2015 Nov 09) -* [acd8356] Viktor Benei - godeps update (2015 Nov 09) -* [9865ba1] Viktor Benei - Merge pull request #292 from viktorbenei/master (2015 Nov 09) -* [cfb4319] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Nov 09) -* [0c024c9] Viktor Benei - `yml` option added/enabled for Output Format (2015 Nov 09) -* [4b64cb7] Viktor Benei - Merge pull request #291 from viktorbenei/master (2015 Nov 07) -* [9ea7723] Viktor Benei - test fix (2015 Nov 07) -* [69b5b9a] Viktor Benei - new packages : configs and output - to help with the new `--format=json` output mode ; added a new `version` command which now supports the `--format=json` flag (2015 Nov 07) -* [0404f7c] Viktor Benei - Merge pull request #290 from viktorbenei/master (2015 Nov 06) -* [2b2999b] Viktor Benei - step template revision : generic format update, using `change-workdir` instead of a custom script, and added a `share-this-step` workflow for quick & easy step sharing (2015 Nov 06) -* [a7ac606] Viktor Benei - Create .gitignore (2015 Nov 04) -* [00a0ab3] Viktor Benei - bitrise.io/cli (2015 Nov 04) -* [921d903] Viktor Benei - Update README.md (2015 Nov 04) - - -## 1.2.4 (2015 Nov 02) - -### Release Notes - -* __envman__ updated to `1.0.0`, which also includes a new ENV size limit feature. You can read more about the release at: https://github.com/bitrise-io/envman/releases/tag/1.0.0 - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.2.4/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.2.3 -> 1.2.4 - -* [044a87c] Viktor Benei - Merge pull request #288 from viktorbenei/master (2015 Nov 02) -* [9d0ba2d] Viktor Benei - 1.2.4 changelog (2015 Oct 31) -* [7e1a93a] Viktor Benei - Merge pull request #287 from viktorbenei/master (2015 Oct 31) -* [8e642cb] Viktor Benei - CI fix (2015 Oct 31) -* [ce1903e] Viktor Benei - 1.2.4 (2015 Oct 31) -* [57c0f18] Viktor Benei - Merge pull request #286 from viktorbenei/master (2015 Oct 31) -* [69849d1] Viktor Benei - just one more.. (2015 Oct 31) -* [4d05b0f] Viktor Benei - hopefully last CI fix :) (2015 Oct 31) -* [87f05be] Viktor Benei - one more CI workflow fix (2015 Oct 31) -* [f496b5b] Viktor Benei - bitrise.yml fix (2015 Oct 31) -* [3c44e47] Viktor Benei - godeps-update (2015 Oct 31) -* [733ae3d] Viktor Benei - envman : 1.0.0 (2015 Oct 31) -* [1537968] Viktor Benei - bitrise.yml test_and_install fix (2015 Oct 31) - - -## 1.2.3 (2015 Oct 19) - -### Release Notes - -* __FIX__ : `bitrise share create` had a parameter issue, calling `stepman share create` with wrong `--stepid` param. Fixed. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.2.3/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.2.2 -> 1.2.3 - -* [89c8ebf] Viktor Benei - Merge pull request #285 from viktorbenei/master (2015 Oct 19) -* [337cf23] Viktor Benei - golint : removed `set -e` (2015 Oct 19) -* [3e89237] Viktor Benei - changelog & godeps-update (2015 Oct 19) -* [4c09ffe] Viktor Benei - Merge pull request #284 from viktorbenei/master (2015 Oct 19) -* [a9e3f59] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Oct 19) -* [fd68787] Viktor Benei - 1.2.3 (2015 Oct 19) -* [8f65724] Viktor Benei - Merge pull request #283 from viktorbenei/master (2015 Oct 19) -* [3424a7c] Viktor Benei - next version changelog (2015 Oct 13) -* [aa15018] Viktor Benei - Merge pull request #282 from gkiki90/bitrise_share_fix (2015 Oct 12) -* [cd0ff8a] Krisztian Godrei - share fix (2015 Oct 12) - - -## 1.2.2 (2015 Oct 12) - -### Release Notes - -* __Fixed__ step log, at build failed mode (at step log footer section Issue tracker and Source row trimming fixed). -* __Fixed__ `bitrise validate` if called with `--format=json` : in case the validation failed it printed two JSON responses instead of just one. Fixed. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.2.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.2.1 -> 1.2.2 - -* [c0abd8c] Viktor Benei - Merge pull request #281 from viktorbenei/master (2015 Oct 12) -* [8d36ef3] Viktor Benei - 1.2.2 (2015 Oct 12) -* [7e06232] Viktor Benei - Merge pull request #280 from viktorbenei/1.2.2-pre (2015 Oct 09) -* [c65bf52] Viktor Benei - 1.2.2-pre (2015 Oct 09) -* [1ef1098] Viktor Benei - Merge pull request #279 from gkiki90/typo (2015 Oct 09) -* [08e6e24] Krisztian Godrei - log fix (2015 Oct 09) -* [74e697d] Krisztian Godrei - typo (2015 Oct 09) -* [44d2b40] Viktor Benei - Merge pull request #278 from gkiki90/validate_fix (2015 Oct 09) -* [e3b0d1c] Krisztian Godrei - validate fix (2015 Oct 09) -* [fec3772] Viktor Benei - Merge pull request #277 from gkiki90/trimming_issue_fix (2015 Oct 07) -* [5ca49d0] Krisztian Godrei - changelog (2015 Oct 07) -* [fe34675] Krisztian Godrei - fixed step log footer trimming issue (2015 Oct 07) - - -## 1.2.1 (2015 Oct 07) - -### Release Notes - -* __FIX__ : `trigger_map` handling in Pull Request mode: if the pattern does match an item which has `is_pull_request_allowed=false` it won't fail now, it'll just skip the item and the next one will be tried. -* __new command__ : `bitrise share`, to share your step through `bitrise` (this is just a wrapper around `stepman share`, does exactly the same, but hopefully it's a bit more convenient if you never used `stepman` directly before). -* __new flag__ : similar to the `--ci` flag the Pull Request mode can now be allowed by calling any command with `bitrise --pr [command]`. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.2.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.2.0 -> 1.2.1 - -* [7e3aa83] Viktor Benei - Merge pull request #276 from viktorbenei/master (2015 Oct 07) -* [366b459] Viktor Benei - v1.2.1 (2015 Oct 07) -* [dd1037d] Viktor Benei - Merge pull request #275 from gkiki90/changelog (2015 Oct 06) -* [24004c1] Krisztian Godrei - changelog (2015 Oct 06) -* [cdd4662] Viktor Benei - removed Gitter (2015 Oct 06) -* [62026fd] Viktor Benei - Merge pull request #274 from gkiki90/share (2015 Oct 05) -* [2648f37] Krisztian Godrei - share (2015 Oct 05) -* [b074d00] Viktor Benei - Merge pull request #273 from gkiki90/PR_mode (2015 Oct 05) -* [d7e28db] Krisztian Godrei - PR fix (2015 Oct 05) -* [0136c97] Viktor Benei - Merge pull request #272 from gkiki90/PR_mode (2015 Oct 05) -* [f539e6f] Viktor Benei - Merge pull request #271 from viktorbenei/master (2015 Oct 05) -* [f3be793] Krisztian Godrei - test fixes (2015 Oct 05) -* [aeffd47] Krisztian Godrei - ci iml fix, pr mode fix (2015 Oct 05) -* [2d5296f] Krisztian Godrei - removed unnecessary descriptions (2015 Oct 05) -* [3309132] Krisztian Godrei - Merge branch 'ci_fix' into PR_mode (2015 Oct 05) -* [bfb6b27] Krisztian Godrei - typo fix, pr mode trigger fix (2015 Oct 05) -* [20e98bf] Krisztian Goedrei - ci fix (2015 Oct 05) -* [75a188f] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Oct 03) -* [94c9187] Viktor Benei - install envman & stepman with `curl -fL` as it's the new recommended way (2015 Oct 02) - - -## 1.2.0 (2015 Oct 02) - -### Release Notes - -* __BREAKING__ / __FIX__ : If `bitrise trigger` is called with a trigger pattern that doesn't match any expression in `trigger_map` the workflow with the same name as the trigger pattern name **will no longer be selected**. This feature proved to have more issues than pros. -* __DEPRECATION__ : the previous `dependencies` property is now deprecated. From now on, dependencies should be declared in the `deps` property which has a new syntax, grouped by dependency managers. The syntax will be extended in the near future but in a backward compatible way. - Supported dependency managers: `brew`, `apt_get` and `check_only` (for checking dependencies which can't be installed automatically, ex the `Xcode.app`). - Example: - - ``` - - step: - deps: - brew: - - name: cmake - - name: git - - name: node - apt_get: - - name: cmake - check_only: - - name: xcode - ``` -* Improved validate command output. -* __BREAKING__ : if you don't specify the version of a step `bitrise` will now try to update the local Step Lib cache before using the Step. Previously the latest version **available in the local cache** was used, but this caused more confusion. The local cache is still used in this case if the Step Lib can't be updated, it'll still work in case of a network issue. -* __BREAKING__ : From `bitrise step-info` the `--id` flag was removed, the first cli param used as step id, no need to write the `--id` flag anymore. Example: `bitrise step-info script` instead of `bitrise step-info --id script`. -* __IMPORTANT__ : `format_version` bumped to `1.1.0`, which means that the `bitrise.yml` generate with this `bitrise` version won't be compatible with previous `bitrise` versions. Previous `bitrise.yml`s of course still work with this new `bitrise` version. -* Now you can use all your environment variables (secrets, app, workflow envs and step outputs) in `run_if` fields and in step inputs. -* `bitrise step-info` got a new option `--step-yml` flag, which allows printing step info from the specified `step.yml` directly (useful for local Step development). -* Step inputs got a new field: `IsTemplate` / `is_template`. This field indicates whether the value contains template expressions which should be evaluated before using the value, just like in case of `is_expand`. The template expression have to be written in Go's template language, and can use the same properties as `run_if` templates can. Example: - - ``` - - script: - title: Template example - inputs: - - content: |- - {{if .IsCI}} - echo "CI mode" - {{else}} - echo "not CI mode" - {{end}} - opts: - is_template: true - ``` -* Improved environment and input value and options casting: - * Now you can use `"NO"`, `"No"`, `"YES"`, `"Yes"`, `true`, `false`, `"true"`, `"false"` in every place `bitrise` expects a bool value (ex: `is_expand`). - * Every field where `bitrise` expects a string in now casted into a string. This means that you can now use `true` and `false` instead of `"true"` and `"false"` in `value_options`. Same is true for the input and environments value itself, so you can now write `true` instead of `"true"` and it'll still be casted to string. -* Pull Request and CI mode handling extension: related flag environment variables can now be defined in `secrets` / `inventory` as well. -* `bitrise` now prints if it runs in "Pull Request mode", just like it did for "CI" mode before. -* Step info logging got a complete revision, to make it more helpful, especially in case the Step fails. It now included the Step's issue tracker and source code URL infos in the log directly. -* __FIX__ : `--log-level` handling fix, the previous version had issues if the log level was set to `debug`. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.2.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.1.2 -> 1.2.0 - -* [9c785d1] Viktor Benei - Merge pull request #270 from viktorbenei/master (2015 Oct 02) -* [67ffc05] Viktor Benei - changelog for 1.2.0 (2015 Oct 02) -* [81dad75] Viktor Benei - v1.2.0 (2015 Oct 02) -* [b0ae673] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Oct 02) -* [59f7cee] Viktor Benei - updated next-version changelog (2015 Oct 02) -* [260a2f4] Viktor Benei - Merge pull request #269 from viktorbenei/master (2015 Oct 02) -* [56ac8b8] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Oct 02) -* [e33aaae] Viktor Benei - Merge pull request #268 from gkiki90/fake_home_fix (2015 Oct 02) -* [f328843] Krisztian Goedrei - PR fix (2015 Oct 02) -* [e80e2a3] Viktor Benei - Merge pull request #267 from gkiki90/fake_home_fix (2015 Oct 02) -* [5e6c187] Viktor Benei - Merge pull request #266 from gkiki90/dep_logs (2015 Oct 02) -* [e65ee13] Krisztian Goedrei - fake home fix (2015 Oct 02) -* [43b31c2] Viktor Benei - `DefaultIsTemplate` typo fix (2015 Oct 02) -* [be50cfe] Viktor Benei - godeps-update (2015 Oct 02) -* [99e7c63] Viktor Benei - required envman and stepman version bumps (2015 Oct 02) -* [306459d] Krisztian Goedrei - check only log (2015 Oct 02) -* [2b13244] Krisztian Goedrei - dep logs (2015 Oct 02) -* [3997ca2] Viktor Benei - Merge pull request #265 from gkiki90/envman_init_fix (2015 Oct 02) -* [4ad7773] Krisztian Goedrei - no internet connection (2015 Oct 02) -* [42d326c] Krisztian Goedrei - step info version fix (2015 Oct 02) -* [591e145] Krisztian Goedrei - fail test (2015 Oct 02) -* [1a8f411] Krisztian Goedrei - fixes (2015 Oct 02) -* [3c74d22] Krisztian Goedrei - fixes (2015 Oct 02) -* [7185c74] Krisztian Goedrei - fixed envman init (2015 Oct 02) -* [f9766e9] Viktor Benei - Merge pull request #264 from viktorbenei/master (2015 Oct 02) -* [49bc8d2] Viktor Benei - base codeclimate config (2015 Oct 02) -* [fe3f460] Viktor Benei - Merge pull request #263 from gkiki90/changelog (2015 Oct 01) -* [08176a8] Krisztian Goedrei - changelog (2015 Oct 01) -* [b14bf8b] Viktor Benei - Merge pull request #261 from gkiki90/latest (2015 Oct 01) -* [87fd6f8] Krisztian Goedrei - godeps (2015 Oct 01) -* [40782a1] Krisztian Goedrei - godep, fixes (2015 Oct 01) -* [4abf72e] Krisztian Goedrei - godep save (2015 Oct 01) -* [8ea8560] Krisztian Goedrei - merge (2015 Oct 01) -* [72f0f75] Viktor Benei - Merge pull request #262 from gkiki90/PR_mode (2015 Oct 01) -* [59976e4] Krisztian Goedrei - fixes (2015 Oct 01) -* [381835f] Krisztian Goedrei - PR & CI mode fix (2015 Oct 01) -* [49b128f] Viktor Benei - Merge pull request #260 from gkiki90/trigger_fix (2015 Sep 29) -* [d4ed963] Krisztian Goedrei - test fix (2015 Sep 28) -* [4b410a8] Krisztian Goedrei - start (2015 Sep 28) -* [8226ad3] Viktor Benei - Merge pull request #258 from gkiki90/is_template (2015 Sep 28) -* [f8ff04b] Krisztian Goedrei - envlist as template input (2015 Sep 27) -* [3d5a371] Viktor Benei - Merge pull request #257 from gkiki90/is_template (2015 Sep 25) -* [ca6f87e] Krisztian Goedrei - template run test (2015 Sep 25) -* [3fd9382] Krisztian Goedrei - template handling, godep (2015 Sep 25) -* [1a462bd] Krisztian Goedrei - template in tests (2015 Sep 25) -* [e394774] Krisztian Goedrei - IsTemplate in model methods (2015 Sep 25) -* [98b68d6] Krisztian Goedrei - require in test (2015 Sep 25) -* [e11fc8a] Viktor Benei - Merge pull request #256 from viktorbenei/master (2015 Sep 24) -* [1b4337c] Viktor Benei - `_tmp` added to .gitignore (2015 Sep 24) -* [53b798a] Viktor Benei - step template README update (2015 Sep 24) -* [15bf241] Viktor Benei - updated `_step_template` (2015 Sep 24) -* [f787c38] Viktor Benei - Merge pull request #255 from viktorbenei/master (2015 Sep 24) -* [b037300] Viktor Benei - format_version bumped in bitrise.yml (2015 Sep 24) -* [5f83c5c] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 24) -* [4d3fcd9] Viktor Benei - v1.1.3-pre (2015 Sep 24) -* [fda85d8] Viktor Benei - Merge pull request #254 from gkiki90/deps (2015 Sep 24) -* [885bbf0] Krisztian Goedrei - docker installs sudo, dependencies bitrise yml for linux and osx (2015 Sep 24) -* [23d41df] Krisztian Goedrei - godeps (2015 Sep 24) -* [7cb5b1f] Krisztian Goedrei - new deps (2015 Sep 24) -* [ef2dc12] Krisztian Goedrei - new deps in progress (2015 Sep 23) -* [24e45a4] Viktor Benei - Merge pull request #253 from gkiki90/triggered_workflow (2015 Sep 22) -* [3ba8dbc] Krisztian Goedrei - trigger check, output format fixes (2015 Sep 22) -* [6000035] Viktor Benei - create release with docker-compose & trigger patterns for release operations (2015 Sep 22) -* [f8a47fd] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 22) -* [7646937] Viktor Benei - updated _tests/brew_publish.yml with more info/description (2015 Sep 22) -* [5c8f602] Viktor Benei - Merge pull request #251 from gkiki90/template (2015 Sep 22) -* [4d60973] Krisztian Goedrei - fix (2015 Sep 22) -* [ccbdf33] Viktor Benei - Merge pull request #252 from gkiki90/step-info (2015 Sep 22) -* [5e6fb13] Krisztian Goedrei - local step info (2015 Sep 22) -* [993d240] Krisztian Goedrei - fix (2015 Sep 22) -* [6aa0e29] Krisztian Goedrei - template fixes (2015 Sep 22) -* [38732b7] Krisztian Goedrei - new envman version (2015 Sep 22) -* [dbe314e] Krisztian Goedrei - template (2015 Sep 22) -* [3bdecde] Viktor Benei - Merge pull request #250 from viktorbenei/master (2015 Sep 22) -* [1918bbb] Viktor Benei - deps.go comment (2015 Sep 22) -* [0af2247] Viktor Benei - Godeps update, with a new `deps.go` to include other packages required only for running the `go test`s (2015 Sep 22) -* [7002f92] Viktor Benei - Merge pull request #249 from gkiki90/step_list_fix (2015 Sep 21) -* [bb52a08] Viktor Benei - Merge pull request #248 from gkiki90/ci_fix (2015 Sep 21) -* [ef055b8] Krisztian Goedrei - step-list fix (2015 Sep 21) -* [7784c07] Krisztian Goedrei - ci fix (2015 Sep 21) -* [362f972] Viktor Benei - Merge pull request #242 from gkiki90/validation_fix (2015 Sep 21) -* [3aa743f] Viktor Benei - Merge pull request #244 from gkiki90/step_info_fix (2015 Sep 21) -* [27a7dc0] Viktor Benei - Merge pull request #245 from gkiki90/ci_fix (2015 Sep 21) -* [e890448] Viktor Benei - Merge pull request #247 from gkiki90/typo_fix (2015 Sep 21) -* [97cccd9] Krisztian Goedrei - typo (2015 Sep 21) -* [817edc7] Krisztian Goedrei - step_info fix (2015 Sep 21) -* [42af9e3] Krisztian Goedrei - bitrise.yml updates (2015 Sep 21) - - -## 1.1.2 (2015 Sep 21) - -### Release Notes - -* __FIX__ : Step outputs are now exposed (available for subsequent steps) even if the Step fails. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.1.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.1.1 -> 1.1.2 - -* [ca8f796] Viktor Benei - Merge pull request #243 from viktorbenei/master (2015 Sep 21) -* [c4b00ad] Krisztian Goedrei - in progress (2015 Sep 21) -* [b1a6a4c] Viktor Benei - v1.1.2 (2015 Sep 21) -* [829391a] Krisztian Goedrei - ci fix start (2015 Sep 21) -* [f44e4b9] Krisztian Goedrei - validation fix (2015 Sep 21) -* [5e49994] Krisztian Goedrei - validation fix (2015 Sep 21) -* [9478b7b] Viktor Benei - Merge pull request #241 from gkiki90/output_env_list_fix (2015 Sep 21) -* [26d0d1d] Krisztian Goedrei - test fixes (2015 Sep 21) -* [571cdfe] Krisztian Goedrei - step output fix (2015 Sep 21) -* [1795cb1] Krisztian Goedrei - validation exit codes (2015 Sep 21) - - -## 1.1.1 (2015 Sep 18) - -### Release Notes - -* __FIX__ : If `$BITRISE_SOURCE_DIR` is defined in an environment with an empty value `bitrise` now skips the definition. Practically this means that if you have an empty `BITRISE_SOURCE_DIR` item in your Workflow or App Environment but you define a real value in your `.bitrise.secrets.yml` `bitrise` will now use the (real) value defined in `.bitrise.secrets.yml`, instead of going with the empty value defined in the Workflow environments. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.1.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.1.0 -> 1.1.1 - -* [35c9a74] Viktor Benei - Merge pull request #239 from viktorbenei/master (2015 Sep 18) -* [50e09fa] Viktor Benei - v1.1.1 (2015 Sep 18) -* [e364fb3] Viktor Benei - Merge pull request #238 from viktorbenei/master (2015 Sep 18) -* [4db999d] Viktor Benei - `BITRISE_SOURCE_DIR` handling fix: skip empty values (2015 Sep 18) -* [956abad] Viktor Benei - slack ENVs revision (2015 Sep 18) - - -## 1.1.0 (2015 Sep 18) - -### Release Notes - -* BITRISE build result log improvements: - * step run summary contains step version, and update note, if new version available - * build run summary step sections contains step version, and update note, if new version available -* __BREAKING/FIX__ : `bitrise trigger` will **NOT** select any workflow in Pull Request mode if the pattern does not match any of the `trigger_map` definition. -* unified `config` and `inventory` flag handling: you can specify paths with `--config` and `--inventory`, and base64 encoded direct input with `--config-base64` and `--inventory-base64`. Can be used by tools, to skip the need to write into files. -* __FIX/BREAKING__ : environment handling order : App Envs can now overwrite the values defined in inventory/secrets (in the last version the secrets/inventory could overwrite the App Envs). -* `validate` command accepts `--format` flag: `--format=[json/raw]` (default is `raw`) -* new command: `step-list` (lis of available steps in Step Lib) `bitrise step-list` -* new command: `step-info` (infos about defined step) `bitrise step-info --id script --version 0.9.0` -* revision of `normalize`, to generate a better list/shorter output list -* __NEW__ : `$BITRISE_SOURCE_DIR` now updated per step, and can be changed by the steps. `$BITRISE_SOURCE_DIR` can be use for defining a new working directory. Example: if you want to create CI workflow for your Go project you have to switch your working directory to the proper one, located inside the `$GOPATH` (this is a Go requirement). You can find an example below. This feature is still a bit in "experimental" stage, and we might add new capabilities in the future. Right now, if you want to re-define the `$BITRISE_SOURCE_DIR` you have to set an **absolute** path, no expansion will be performed on the specified value! So, you should **NOT** store a reference like `$GOPATH/src/your/project/path` as it's value, but the actual, absolute path! - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.1.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 1.0.0 -> 1.1.0 - -* [3a37cb9] Viktor Benei - Merge pull request #237 from viktorbenei/master (2015 Sep 18) -* [6abe52a] Viktor Benei - v1.1.0 - changelog (2015 Sep 18) -* [8658bb0] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 18) -* [d955103] Viktor Benei - version 1.1.0 (2015 Sep 18) -* [f1493ab] Viktor Benei - run.go : param name revision, for clarity (2015 Sep 18) -* [6f6b358] Viktor Benei - Next version: note about BITRISE_SOURCE_DIR (2015 Sep 18) -* [a13dda7] Viktor Benei - Dockerfile : pre-install required tools (2015 Sep 18) -* [3b75762] Viktor Benei - Merge pull request #235 from gkiki90/breaking_source_dir (2015 Sep 18) -* [1a42c32] Krisztian Goedrei - test fix (2015 Sep 18) -* [c37cf93] Krisztian Goedrei - code cleaning (2015 Sep 18) -* [93db802] Krisztian Goedrei - removed expand (2015 Sep 18) -* [8ddef2d] Krisztian Goedrei - tmp path (2015 Sep 18) -* [3f74b81] Krisztian Goedrei - source dir updated per step (2015 Sep 18) -* [828a59d] Viktor Benei - next version changelog (2015 Sep 18) -* [cec4252] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 18) -* [34e87da] Viktor Benei - Merge pull request #234 from gkiki90/ci_breaking_change (2015 Sep 18) -* [2f118ae] Krisztian Goedrei - comments moved to description (2015 Sep 18) -* [90936c3] Krisztian Goedrei - new yml (2015 Sep 17) -* [0b72e80] Krisztian Goedrei - godeps-update, min stepman version (2015 Sep 17) -* [571d9f0] Krisztian Goedrei - bitrise.yml, stepman update (2015 Sep 17) -* [9689dd6] Krisztian Goedrei - fix (2015 Sep 17) -* [e192671] Krisztian Goedrei - godeps-update (2015 Sep 17) -* [7ee4938] Krisztian Goedrei - no message (2015 Sep 17) -* [736067e] Krisztian Goedrei - godeps-update (2015 Sep 17) -* [36a2307] Krisztian Goedrei - godeps-update (2015 Sep 17) -* [f2a1a9a] Krisztian Goedrei - godeps-update (2015 Sep 17) -* [6ae1c20] Krisztian Goedrei - change log (2015 Sep 17) -* [1e30001] Krisztian Goedrei - validation formats (2015 Sep 17) -* [b5fc3c6] Krisztian Goedrei - step-info, step-list (2015 Sep 17) -* [44aec85] Krisztian Goedrei - test fix (2015 Sep 17) -* [56f271a] Krisztian Goedrei - code style (2015 Sep 17) -* [bddd467] Krisztian Goedrei - env order fix, test (2015 Sep 17) -* [716ad8a] Krisztian Goedrei - in progress (2015 Sep 17) -* [fa1dd91] Viktor Benei - Merge pull request #229 from gkiki90/step_version (2015 Sep 16) -* [46c762f] Viktor Benei - Merge pull request #230 from bazscsa/master (2015 Sep 16) -* [67f7e0b] Krisztian Goedrei - PR fix (2015 Sep 16) -* [d65f7d8] Tamás Bazsonyi - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Sep 16) -* [ffb84f7] Tamás Bazsonyi - Updated lesson links (2015 Sep 16) -* [8fa1397] Tamás Bazsonyi - Lesson 5 and lesson 6 update (2015 Sep 16) -* [8efcf05] Krisztian Goedrei - PR fix (2015 Sep 16) -* [4fbdbc7] Viktor Benei - changelog template: curl call "fix" (2015 Sep 16) -* [1a9c807] Krisztian Goedrei - step version logs (2015 Sep 16) -* [e823193] Krisztian Goedrei - version print (2015 Sep 16) -* [cc37c11] Krisztian Goedrei - print fix (2015 Sep 16) -* [5592174] Krisztian Goedrei - start using stepinfo model for print (2015 Sep 16) -* [831fd6f] Krisztian Goedrei - step info model (2015 Sep 16) -* [1845bd5] Tamás Bazsonyi - Lesson 5 WF (2015 Sep 16) -* [0830d33] Viktor Benei - Merge pull request #226 from gkiki90/validate_config (2015 Sep 15) -* [56e8540] Krisztian Goedrei - test fix (2015 Sep 15) -* [a4d0547] Viktor Benei - Merge pull request #228 from gkiki90/trigger_fix (2015 Sep 15) -* [c8a26eb] Krisztian Goedrei - ci fix (2015 Sep 15) -* [ce5b886] Tamás Bazsonyi - Added trigger lesson (2015 Sep 15) -* [93a8e98] Krisztian Goedrei - trigger fix (2015 Sep 15) -* [50cd432] Viktor Benei - Merge pull request #227 from bazscsa/master (2015 Sep 15) -* [9a1da09] Tamás Bazsonyi - links in new line (2015 Sep 15) -* [e6c3fbb] Tamás Bazsonyi - updated links (2015 Sep 15) -* [5e11086] Tamás Bazsonyi - lesson 1 links (2015 Sep 15) -* [030d233] Tamás Bazsonyi - lessons links (2015 Sep 15) -* [dac981f] Tamás Bazsonyi - updated yml (2015 Sep 15) -* [5a8d115] Tamás Bazsonyi - removed readme (2015 Sep 15) -* [21e0df6] Tamás Bazsonyi - removed yml (2015 Sep 15) -* [32d98d3] Tamás Bazsonyi - Added Lessons (2015 Sep 15) -* [ca37f08] Tamás Bazsonyi - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Sep 15) -* [da08152] Viktor Benei - Merge pull request #225 from gkiki90/abc (2015 Sep 14) -* [1b4795d] Krisztian Goedrei - validate inventory (2015 Sep 14) -* [4388a16] Krisztian Goedrei - ci fix (2015 Sep 14) -* [037a0f6] Krisztian Goedrei - ci fix (2015 Sep 14) -* [4c5ce86] Krisztian Goedrei - bitrise.yml fix, test fix (2015 Sep 14) -* [028dc4d] Krisztian Goedrei - fixes (2015 Sep 12) -* [d14a781] Krisztian Goedrei - PR fix (2015 Sep 12) -* [e8fdff8] Krisztian Goedrei - sort (2015 Sep 12) -* [ecefc8a] Tamás Bazsonyi - paths updated (2015 Sep 12) -* [9840a57] Tamás Bazsonyi - path (2015 Sep 12) -* [d9552fa] Tamás Bazsonyi - Lessons README (2015 Sep 12) -* [772bf3e] Viktor Benei - Merge pull request #223 from gkiki90/custom_step (2015 Sep 11) -* [9a0b9b1] Viktor Benei - Merge pull request #224 from viktorbenei/master (2015 Sep 11) -* [168d496] Krisztian Goedrei - normalize fix (2015 Sep 11) -* [90245bd] Viktor Benei - start of v1.0.1 (2015 Sep 11) - - -## 1.0.0 (2015 Sep 11) - -### Release Notes - -* __Linux support__ : first official Linux release. No dependency manager support is available for Linux yet, but everything else should work the same as on OS X. -* Improved `bitrise init`, with better guides, `trigger_map` and more! -* Total runtime summary at the end of a build. -* Lots of internal code revision, improved `bitrise normalize`. -* __New command__ : `bitrise validate` to quick-check your `bitrise.yml`. -* Configurations (`bitrise.yml` and `.bitrise.secrets.yml`) can now be specified in `base64` format as well - useful for tools. -* __DEPRECATED__ : the old `--path` flag is now deprecated, in favor of `--config`, which has it's `base64` format (`--config-base64`) -* Logs now include the `step`'s version if it's referenced from a Step Collection. Prints the version even if no version constraint is defined (mainly for debug purposes). -* __NEW__ : sets `BITRISE_SOURCE_DIR` (to current dir) and `BITRISE_DEPLOY_DIR` (to a temp dir) environments if the env is not defined -* Only do a `stepman update` once for a collection if can't find a specified step (version). -* __FIX__ : Custom steps (where the collection is `_`) don't crash anymore because of missing required fields. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.0.0/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 0.9.11 -> 1.0.0 - -* [c2c7c04] Viktor Benei - Merge pull request #222 from viktorbenei/master (2015 Sep 11) -* [7ee03c8] Viktor Benei - updated 1.0.0 changelog (2015 Sep 11) -* [a4a1ad4] Viktor Benei - Merge pull request #221 from gkiki90/custom_step (2015 Sep 11) -* [2381bb4] Krisztian Goedrei - fix (2015 Sep 11) -* [bf3f9e2] Krisztian Goedrei - custom step defaults (2015 Sep 11) -* [53270bb] Tamás Bazsonyi - Workflows (2015 Sep 10) -* [164877f] Tamás Bazsonyi - Added step yml (2015 Sep 10) -* [0152403] Viktor Benei - Merge pull request #220 from gkiki90/pointers (2015 Sep 10) -* [3471df4] Krisztian Goedrei - pointer fixes (2015 Sep 10) -* [ead8159] Tamás Bazsonyi - removed <> (2015 Sep 10) -* [5ac8f5a] Tamás Bazsonyi - corrected format (2015 Sep 10) -* [88c0d5d] Tamás Bazsonyi - Added lesson 1 (2015 Sep 10) -* [5739093] Viktor Benei - Merge pull request #219 from gkiki90/deploy_dir (2015 Sep 10) -* [c52bd9a] Krisztian Goedrei - PR fix (2015 Sep 10) -* [8410daf] Krisztian Goedrei - PR fix (2015 Sep 10) -* [ba2f6b8] Krisztian Goedrei - deploy dir (2015 Sep 10) -* [4e71a04] Viktor Benei - Merge pull request #218 from viktorbenei/master (2015 Sep 09) -* [58b739d] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 09) -* [464f0e7] Viktor Benei - Merge pull request #217 from gkiki90/bitrise_src_dir (2015 Sep 09) -* [3912ffd] Krisztian Goedrei - BITRISE_SOURCE_DIR handling & test (2015 Sep 09) -* [ab20912] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 09) -* [118c543] Viktor Benei - BITRISE_PROJECT_TITLE renamed in `init` to BITRISE_APP_TITLE - to match the bitrise.io one (2015 Sep 09) -* [8869945] Viktor Benei - Merge pull request #216 from gkiki90/print_fix (2015 Sep 09) -* [f449dfa] Krisztian Goedrei - print tests (2015 Sep 09) -* [210ef4f] Tamás Bazsonyi - added initial readmes (2015 Sep 08) -* [7452e53] Tamás Bazsonyi - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 08) -* [97bdeaa] Tamás Bazsonyi - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 08) -* [8920f8a] Viktor Benei - Merge pull request #215 from viktorbenei/master (2015 Sep 08) -* [5b9b36c] Viktor Benei - godeps-update (2015 Sep 08) -* [9f034b9] Viktor Benei - step log version printing fix - trimming version string. Mainly affects the steps which are not used from a steplib (2015 Sep 08) -* [caac385] Viktor Benei - Merge pull request #213 from gkiki90/step_version_log_fix (2015 Sep 08) -* [cb9d4d2] Krisztian Goedrei - fix (2015 Sep 08) -* [cddeda9] Krisztian Goedrei - test (2015 Sep 08) -* [28673aa] Viktor Benei - Merge pull request #212 from viktorbenei/master (2015 Sep 08) -* [19da3cc] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 08) -* [a621c28] Viktor Benei - Merge pull request #211 from gkiki90/trigger_fix (2015 Sep 08) -* [2b81fda] Viktor Benei - godeps-update (2015 Sep 08) -* [2a4e93b] Viktor Benei - required stepman version bump (2015 Sep 08) -* [d3e9f76] Viktor Benei - full godeps-update (2015 Sep 08) -* [9b14e35] Krisztian Goedrei - PR fix (2015 Sep 08) -* [eabab55] Krisztian Goedrei - fix (2015 Sep 08) -* [bfde226] Viktor Benei - Merge pull request #210 from viktorbenei/master (2015 Sep 08) -* [d20d08a] Viktor Benei - godeps-update : CopyDir fix & stepman model property order change (2015 Sep 08) -* [ebcba2b] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 08) -* [d0763cb] Viktor Benei - _test/bitrise.yml step title fix (2015 Sep 08) -* [a378ab9] Viktor Benei - Merge pull request #209 from viktorbenei/master (2015 Sep 07) -* [ec5c61c] Viktor Benei - base trigger_map added to bitrise.yml, for CI (2015 Sep 07) -* [7773df7] Viktor Benei - step version printing note added to changelog (2015 Sep 07) -* [909ef5b] Viktor Benei - changelog - version fix (1.0.0) (2015 Sep 07) -* [ab1cefd] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 07) -* [dddf2e5] Viktor Benei - bit more explanation for setup --minimal in CI (2015 Sep 07) -* [f836cfa] Viktor Benei - Merge pull request #208 from viktorbenei/master (2015 Sep 07) -* [8f62ca5] Viktor Benei - minimal refactoring for CI (2015 Sep 07) -* [6fb55a4] Viktor Benei - tmp build fix for CI (2015 Sep 07) -* [359b8af] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 07) -* [44db09f] Viktor Benei - run a minimal setup at start of CI (2015 Sep 07) -* [8f42f56] Viktor Benei - Merge pull request #206 from gkiki90/step_version (2015 Sep 07) -* [017f05c] Krisztian Goedrei - PR fix (+2 squashed commits) Squashed commits: [9e44a47] fix [6ca52f8] step version (2015 Sep 07) -* [c525a42] Viktor Benei - Merge pull request #207 from viktorbenei/master (2015 Sep 07) -* [978ca1d] Viktor Benei - Linux ready release configuration; v1.0.0 changelog (2015 Sep 07) -* [ea9a59f] Viktor Benei - skip `brew` dependencies if platform is Linux (2015 Sep 07) -* [318119f] Viktor Benei - step-template update (2015 Sep 07) -* [9e55f89] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 07) -* [2017941] Viktor Benei - Merge pull request #205 from gkiki90/step_template (2015 Sep 07) -* [3d83f59] Viktor Benei - `bitrise init` now embeds the models.Version instead of a fixed 1.0.0; init now uses the new config 'title, summary, description' instead of YML comments (2015 Sep 07) -* [35d17af] Krisztian Goedrei - readme (2015 Sep 07) -* [f308373] Viktor Benei - Title, Summary, Description added to AppModel (main config model) & reordered the three, to be in this order in every model. (2015 Sep 07) -* [8a63a15] Viktor Benei - Merge pull request #202 from gkiki90/normalize_fix (2015 Sep 07) -* [8ed3179] Viktor Benei - Merge pull request #201 from gkiki90/step_template (2015 Sep 07) -* [bdd942b] Viktor Benei - Merge pull request #204 from gkiki90/util_test (2015 Sep 07) -* [6ebe2c2] Krisztian Goedrei - PR fix (2015 Sep 07) -* [b7d53d1] Viktor Benei - Merge pull request #203 from gkiki90/total_runtime (2015 Sep 07) -* [76fafc1] Krisztian Goedrei - PR fix (2015 Sep 07) -* [e4a39eb] Krisztian Goedrei - test (2015 Sep 07) -* [2ef20a8] Krisztian Goedrei - slice tests (2015 Sep 07) -* [eb7ffe7] Krisztian Goedrei - total runtime (2015 Sep 06) -* [4f1705b] Krisztian Goedrei - normalize fix (2015 Sep 05) -* [364df10] Krisztian Goedrei - missing fields (2015 Sep 05) -* [e2c2652] Viktor Benei - Merge pull request #200 from viktorbenei/master (2015 Sep 05) -* [31a7be0] Viktor Benei - updated Dockerfile & bitrise.yml for building `bitrise` in Docker, using the `bitrise.yml` (2015 Sep 04) -* [397838a] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 04) -* [40c3996] Viktor Benei - Merge pull request #199 from gkiki90/normalize_fix (2015 Sep 04) -* [432e3da] Viktor Benei - Merge pull request #198 from gkiki90/base64 (2015 Sep 04) -* [f22ff6b] Krisztian Goedrei - fix (2015 Sep 04) -* [9082415] Krisztian Goedrei - PR fix (2015 Sep 04) -* [d6ec4a1] Krisztian Goedrei - fix (2015 Sep 04) -* [2046c36] Viktor Benei - upload&download bitrise.yml : ensure-clean-git & create backup (2015 Sep 04) -* [e7cd7c5] Viktor Benei - experimental : upload & download bitrise.yml to/from bitrise.io (2015 Sep 04) -* [e9fe439] Krisztian Goedrei - PR fix (2015 Sep 04) -* [d4b9359] Krisztian Goedrei - PR fix (2015 Sep 04) -* [f9bd982] Krisztian Goedrei - test (2015 Sep 04) -* [937de7d] Krisztian Goedrei - base64 (2015 Sep 04) -* [3b6b4e6] Viktor Benei - Merge pull request #197 from viktorbenei/master (2015 Sep 04) -* [aba4989] Viktor Benei - bitrise.yml cleanup & format version update (2015 Sep 04) -* [2e42496] Viktor Benei - more thorough template expression simple "true/false" tests (2015 Sep 04) -* [7b472fa] Viktor Benei - Merge pull request #195 from viktorbenei/feature/trigger-map-in-init (2015 Sep 03) -* [c70451f] Viktor Benei - just a little bit more test for the `init` content (2015 Sep 03) -* [ea4c0af] Viktor Benei - godeps-update + a fix for recursive `godep save` (2015 Sep 03) -* [a43074b] Viktor Benei - annotated, and formatted `bitrise.yml` after init, with `trigger_map`, test, and a bit of info about `bitrise trigger` (2015 Sep 03) -* [128ead0] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Sep 03) -* [631bf0f] Viktor Benei - Merge pull request #194 from gkiki90/validate_config (2015 Sep 03) -* [90ade1d] Krisztian Goedrei - removed alias (2015 Sep 03) -* [1da63ba] Krisztian Goedrei - validate (2015 Sep 03) -* [b9f9593] Viktor Benei - base Linux setup/support (2015 Sep 03) -* [cf0a83c] Viktor Benei - Merge pull request #192 from gkiki90/normalize_fix (2015 Sep 03) -* [c06de4e] Krisztian Goedrei - PR fix (2015 Sep 03) -* [990f72d] Krisztian Goedrei - code cleaning (2015 Sep 03) -* [876c89c] Krisztian Goedrei - PR fix (2015 Sep 03) -* [77a3797] Viktor Benei - Merge pull request #193 from viktorbenei/master (2015 Sep 03) -* [c566b29] Krisztian Goedrei - pointer fix (2015 Sep 03) -* [96bfe66] Krisztian Goedrei - normalize fix (2015 Sep 03) -* [30e60d7] Viktor Benei - test workflow extended with a fail test & trigger_map (2015 Sep 02) -* [78b2c2a] Viktor Benei - Merge pull request #191 from viktorbenei/master (2015 Sep 02) -* [db3f4df] Viktor Benei - fail if `golint` finds any issue (2015 Sep 02) -* [d5ddee7] Viktor Benei - extended _tests/bitrise.yml (2015 Sep 02) -* [ddc8666] Viktor Benei - Merge pull request #190 from viktorbenei/master (2015 Aug 31) -* [58da26f] Viktor Benei - updated bitrise-cli install (2015 Aug 31) -* [22b32bc] Viktor Benei - start of v0.9.12 (2015 Aug 31) - - -## 0.9.11 (2015 Aug 31) - -### Release Notes - -* __NEW__ : `bitrise.yml` can now be exported into JSON (with `bitrise export`), and `.json` configuration is also acceptable now for a `bitrise run`. -* __NEW__ / __BREAKING__ : workflow names which start with an underscore (ex: _my_wf) are now treated as "utility" workflow, which can only be triggered by another workflow (as a `before_run` or `after_run` workflow). These "utility" workflows will only be listed by a `bitrise run` call as another section (utility workflows), to provide a better way to organize workflows which are not intended to be called directly. -* __FIX__ : Input environments handling fix: Step inputs are now isolated, one step's input won't affect another's with the same environment key -* __NEW__ : The workflow which was triggered by `bitrise run WORKFLOW-NAME` is now available as an environment variable - * `BITRISE_TRIGGERED_WORKFLOW_ID` : contains the ID of the workflow - * `BITRISE_TRIGGERED_WORKFLOW_TITLE` : contains the `title` of the workflow, if specified -* __NEW__ : `BITRISE_STEP_FORMATTED_OUTPUT_FILE_PATH` is now also defined, as a temporary file path. -* __NEW__ : `bitrise normalize` command, to help you "normalize" your `bitrise.yml`. -* __NEW__ : `trigger_map` definition and `bitrise trigger` action : with this you can map expressions to workflows. A common use case for this is to map branch names (ex: `feature/xyz`) to workflows, simply by defining the mapping in the `bitrise.yml`. -* Log format revision, to make it more obvious where a Step starts and ends, and at the end of the build it provides a much improved summary. -* A new "StepLib" source type (`_`), to provide compatibility with Steps which don't have an up-to-date `step.yml` in the Step's repository. Effectively the same as `git::http://step/url.git@version`, but it won't check for a `step.yml` at all - which means that every information have to be included in the `bitrise.yml`. -* Every configuration level (environments, step, step inputs, ...) which had at least a `title` or a `description` or `summary` now has all three: `title`, `summary` and `description`. -* Other internal revisions and minor fixes, and __lots__ of test added. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.11/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 0.9.10 -> 0.9.11 - -* [217e649] Viktor Benei - Merge pull request #189 from viktorbenei/master (2015 Aug 31) -* [53a2eb6] Viktor Benei - bitrise.yml revision : updated `test` handling (2015 Aug 31) -* [46490e7] Viktor Benei - changelog addition (2015 Aug 31) -* [c5ea28f] Viktor Benei - godeps-update (2015 Aug 31) -* [f5bd2ed] Viktor Benei - Merge pull request #188 from gkiki90/model_version (2015 Aug 31) -* [cb62ebb] Krisztian Goedrei - models version (2015 Aug 31) -* [badbdf4] Krisztian Goedrei - model version (2015 Aug 31) -* [870309c] Viktor Benei - Merge pull request #187 from gkiki90/published_at_fix (2015 Aug 31) -* [6244d9f] Krisztian Goedrei - PR fix (2015 Aug 31) -* [8493427] Krisztian Goedrei - PR fix (2015 Aug 31) -* [2bab801] Krisztian Goedrei - godeps-update (2015 Aug 31) -* [3b13c39] Krisztian Goedrei - published_at type fix (2015 Aug 31) -* [742321a] Viktor Benei - Merge pull request #186 from gkiki90/1_0_0_models (2015 Aug 31) -* [f10a1bb] Krisztian Goedrei - merge (2015 Aug 31) -* [dd63058] Krisztian Goedrei - # This is a combination of 2 commits. # The first commit's message is: (2015 Aug 31) -* [fae8b05] Viktor Benei - Merge pull request #185 from gkiki90/last_step_fix (2015 Aug 31) -* [8555f5d] Krisztian Goedrei - PR fix (2015 Aug 31) -* [64f78a9] Krisztian Goedrei - trigger workflow & last step fix (2015 Aug 31) -* [c2c8d94] Viktor Benei - Merge pull request #183 from gkiki90/run_summary (2015 Aug 28) -* [e2c65bf] Krisztian Goedrei - removed log (2015 Aug 28) -* [6197e11] Krisztian Goedrei - print run summary (2015 Aug 28) -* [5766950] Tamás Bazsonyi - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Aug 27) -* [1195f37] Viktor Benei - Merge pull request #182 from gkiki90/ci (2015 Aug 27) -* [0506d05] Krisztian Goedrei - ci fix (2015 Aug 27) -* [38dbd44] Krisztian Goedrei - ci (2015 Aug 27) -* [f54b01c] Viktor Benei - Merge pull request #181 from gkiki90/master (2015 Aug 27) -* [bf0cf88] Krisztian Goedrei - bypass checking for a TTY before outputting colors (2015 Aug 27) -* [9bac0a9] Viktor Benei - Merge pull request #180 from viktorbenei/master (2015 Aug 26) -* [d526963] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 26) -* [b85fbc5] Viktor Benei - changelog update (2015 Aug 26) -* [a129cbb] Viktor Benei - Merge pull request #179 from viktorbenei/master (2015 Aug 26) -* [6ad9602] Viktor Benei - step run summary log box revision & a bit longer fail-test in bitrise.yml (2015 Aug 26) -* [25b0e67] Viktor Benei - Merge pull request #178 from gkiki90/remove_defaults (2015 Aug 26) -* [a9a811e] Krisztian Goedrei - code style, tests (2015 Aug 26) -* [a1839c5] Krisztian Goedrei - remove defaults, fill step outputs (2015 Aug 26) -* [f10ada6] Viktor Benei - Merge pull request #177 from gkiki90/init_fix (2015 Aug 26) -* [93db1a8] Tamás Bazsonyi - Added local app install sample (2015 Aug 25) -* [44571e7] Krisztian Goedrei - init fix (2015 Aug 25) -* [90a9d83] Viktor Benei - Merge pull request #176 from viktorbenei/master (2015 Aug 24) -* [dc7f135] Viktor Benei - Merge pull request #175 from bazscsa/master (2015 Aug 24) -* [a8cad55] Viktor Benei - doRun_test major revision: most of the tests now use `runWorkflowWithConfiguration` to run the test, which is much closer to how a full `bitrise run` happens (2015 Aug 24) -* [69562d7] Tamás Bazsonyi - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Aug 24) -* [9eb00ae] Tamás Bazsonyi - Some rephrasing (2015 Aug 24) -* [589c1b3] Tamás Bazsonyi - Listified the Documentation overview (2015 Aug 24) -* [3cacdfd] Tamás Bazsonyi - Added _docs to README.md (2015 Aug 24) -* [84ce3ef] Tamás Bazsonyi - Added README.md to the docs folder (2015 Aug 24) -* [9b83f6e] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 24) -* [7c0af85] Viktor Benei - output env test & some logging text fix (workflow 'ID' instead of 'title') (2015 Aug 24) -* [39b1b12] Viktor Benei - Merge pull request #174 from gkiki90/separate_run (2015 Aug 24) -* [a5798c2] Krisztian Goedrei - separated run (2015 Aug 24) -* [e6536c5] Viktor Benei - removed unnecessary 'unload' from react-native example (2015 Aug 24) -* [c802db5] Viktor Benei - changelog v0.9.11 (2015 Aug 24) -* [f995a49] Viktor Benei - Merge pull request #172 from gkiki90/run_old_steps (2015 Aug 24) -* [eea413e] Viktor Benei - Merge pull request #173 from viktorbenei/master (2015 Aug 24) -* [090005b] Krisztian Goedrei - typo (2015 Aug 24) -* [250b197] Viktor Benei - AppendEnvironmentSlice replaced with the built in "append" method (2015 Aug 24) -* [433a48a] Viktor Benei - godeps-update & envman and stepman min version bump (2015 Aug 24) -* [4b6aeb5] Krisztian Goedrei - code style (2015 Aug 24) -* [231a151] Krisztian Goedrei - code style (2015 Aug 24) -* [9349b1d] Krisztian Goedrei - code style (2015 Aug 24) -* [5a5be3d] Krisztian Goedrei - run old steps (2015 Aug 24) -* [757f4f1] Viktor Benei - Merge pull request #171 from gkiki90/title_summary_desc (2015 Aug 19) -* [989ff21] Krisztian Goedrei - title, summary, description (2015 Aug 19) -* [c4da2c3] Krisztian Goedrei - Merge branch 'master' of github.com:bitrise-io/bitrise-cli (2015 Aug 19) -* [237eea0] Viktor Benei - Merge pull request #170 from gkiki90/step_working_dir (2015 Aug 19) -* [378a1c6] Krisztian Goedrei - Merge branch 'step_working_dir' (2015 Aug 19) -* [41c3069] Krisztian Goedrei - step working dir (2015 Aug 19) -* [a799972] Viktor Benei - Merge pull request #169 from viktorbenei/master (2015 Aug 18) -* [6670815] Viktor Benei - Slack examples version update: from 2.0.0 to 2.1.0 (2015 Aug 18) -* [d5faf3d] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 18) -* [91c9185] Viktor Benei - added RunIf ENV template tests (2015 Aug 18) -* [46446f2] Viktor Benei - Merge pull request #168 from gkiki90/go-utils (2015 Aug 18) -* [6cc2f51] Viktor Benei - Merge pull request #167 from gkiki90/workflow_title (2015 Aug 18) -* [8ef0281] Krisztian Goedrei - missing go-utils methods (2015 Aug 18) -* [3c8f325] Krisztian Goedrei - workflow title (2015 Aug 18) -* [77f14e4] Viktor Benei - Merge pull request #166 from gkiki90/step_input_fix (2015 Aug 18) -* [fc62a83] Krisztian Goedrei - environment handling (2015 Aug 18) -* [4272084] Viktor Benei - Merge pull request #165 from viktorbenei/master (2015 Aug 17) -* [17ef411] Viktor Benei - minor text change (2015 Aug 17) -* [df5ba72] Viktor Benei - dependency manager "OK" message - unified (2015 Aug 17) -* [f947c2e] Viktor Benei - Merge pull request #164 from viktorbenei/master (2015 Aug 17) -* [c4da8b7] Viktor Benei - slack step update to the new v2.0.0 version (2015 Aug 17) -* [5e1a2c2] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 17) -* [4845209] Viktor Benei - added "dependencies" to step-template (2015 Aug 17) -* [bf9533e] Viktor Benei - Merge pull request #163 from bazscsa/master (2015 Aug 17) -* [229c201] Tamás Bazsonyi - Added brew update (2015 Aug 17) -* [1674a3e] Tamás Bazsonyi - Some grammar corrections (2015 Aug 17) -* [278d29f] Tamás Bazsonyi - revisions (2015 Aug 17) -* [c037187] Tamás Bazsonyi - Added Share Guide (2015 Aug 17) -* [22ae699] Tamás Bazsonyi - Added React Native (2015 Aug 17) -* [17a7b1c] Tamás Bazsonyi - Added CLI share guide (2015 Aug 17) -* [796e8af] Tamás Bazsonyi - Added CLI introduction (2015 Aug 17) -* [02a0de9] Tamás Bazsonyi - How to guide (2015 Aug 17) -* [f73a40b] Viktor Benei - Merge pull request #162 from viktorbenei/feature/export_command (2015 Aug 17) -* [03eba50] Viktor Benei - godep-update for a required envman model fix (2015 Aug 17) -* [a00fd3e] Viktor Benei - export command : export a bitrise config file in either YAML or JSON format, with optional pretty printed JSON (2015 Aug 17) -* [9e52a55] Viktor Benei - Merge pull request #161 from viktorbenei/feature/predefined_envs (2015 Aug 17) -* [f633487] Viktor Benei - set predefined ENVs, so far only one: BITRISE_STEP_FORMATTED_OUTPUT_FILE_PATH (2015 Aug 17) -* [686b5a5] Viktor Benei - Update README.md (2015 Aug 14) -* [c02a212] Viktor Benei - Merge pull request #160 from viktorbenei/master (2015 Aug 14) -* [43d7c05] Viktor Benei - README revision (2015 Aug 14) -* [2fffdb7] Viktor Benei - Merge pull request #159 from viktorbenei/master (2015 Aug 14) -* [8039c00] Viktor Benei - removed the now obsolete reference to `brew update` in setup's `--minimal` flag (2015 Aug 14) -* [775a146] Viktor Benei - Merge pull request #158 from viktorbenei/master (2015 Aug 14) -* [6d396da] Viktor Benei - switch to bitrise 0.9.10 for CI (2015 Aug 14) -* [77da03c] Viktor Benei - start of v0.9.11 (2015 Aug 14) -* [0d2e718] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 14) - - -## 0.9.10 (2015 Aug 14) - -### Release Notes - -* Improved `setup` : it has a new `--minimal` flag to skip more advanced setup checks, like the `brew doctor` call. -* Removed `brew update` completely from the `setup`. -* Step dependencies: before installing a dependency with `brew` bitrise now specifically asks for permission to do so, except in `--ci` mode. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.10/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 0.9.9 -> 0.9.10 - -* [59bd349] Viktor Benei - Merge pull request #157 from viktorbenei/master (2015 Aug 14) -* [37f36fb] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 14) -* [08f1d6a] Viktor Benei - v0.9.10 changelog (2015 Aug 14) -* [59ffe92] Viktor Benei - Merge pull request #156 from viktorbenei/master (2015 Aug 14) -* [ba886cb] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 14) -* [18f2bfd] Viktor Benei - Merge pull request #155 from viktorbenei/master (2015 Aug 14) -* [29abf82] Viktor Benei - godeps-update (2015 Aug 14) -* [6f2fa64] Viktor Benei - stepman min version bump (2015 Aug 14) -* [4cdde7f] Viktor Benei - Merge pull request #154 from viktorbenei/master (2015 Aug 14) -* [3745167] Viktor Benei - _step_template and it's CI test moved from the stepman project to this repo (2015 Aug 14) -* [64f6f4a] Viktor Benei - Merge pull request #153 from viktorbenei/master (2015 Aug 14) -* [a8ba919] Viktor Benei - prepare for bitrise setup --minimal (2015 Aug 14) -* [a940056] Viktor Benei - Merge pull request #152 from viktorbenei/master (2015 Aug 14) -* [7f0c2e4] Viktor Benei - install bitrise cli for ci script (2015 Aug 14) -* [99b365b] Viktor Benei - every `brew doctor` issue counts - use the `--minimal` flag to skip `brew doctor` (2015 Aug 14) -* [2663282] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 14) -* [9dc16b3] Viktor Benei - don't do `brew update` in setup (2015 Aug 14) -* [c1113d4] Viktor Benei - Merge pull request #151 from gkiki90/dependency_fix (2015 Aug 14) -* [7932300] Krisztian Goedrei - dependency fixes (2015 Aug 14) -* [a6f7827] Viktor Benei - Merge pull request #150 from viktorbenei/master (2015 Aug 14) -* [f3a91dd] Viktor Benei - minimum envman version bump (2015 Aug 14) -* [f2e4caf] Viktor Benei - Merge pull request #149 from viktorbenei/master (2015 Aug 14) -* [0bb4e1d] Viktor Benei - minimal setup mode : skips brew update and brew doctor (2015 Aug 14) -* [6ee580f] Viktor Benei - Merge pull request #148 from viktorbenei/master (2015 Aug 14) -* [da000b2] Viktor Benei - godeps-update (2015 Aug 14) -* [b840881] Viktor Benei - Merge pull request #146 from gkiki90/master (2015 Aug 13) -* [f75b685] Viktor Benei - Merge pull request #147 from viktorbenei/master (2015 Aug 13) -* [6fb4c64] Viktor Benei - stepman dependency bump (2015 Aug 13) -* [63c6059] Krisztian Goedrei - cli fixes (2015 Aug 13) -* [fed4916] Krisztian Goedrei - godep-update (2015 Aug 13) -* [148f29a] Krisztian Goedrei - go-util update (2015 Aug 13) -* [b8de012] Viktor Benei - Merge pull request #145 from gkiki90/workflow_fixes (2015 Aug 13) -* [cb7e3c5] Viktor Benei - Merge pull request #144 from viktorbenei/master (2015 Aug 13) -* [6689c24] Viktor Benei - start of v0.9.10 (2015 Aug 13) - - -## 0.9.9 (2015 Aug 13) - -### Release Notes - -* `bitrise setup` revision : better `brew` checking (calls `brew update` and `brew doctor` too) but no direct Command Line Tools checking. - * The previous solution was incompatible with OS X Mountain Lion and earlier versions, this version solves this incompatibility. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.9/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 0.9.8 -> 0.9.9 - -* [a832bd5] Viktor Benei - Merge pull request #143 from viktorbenei/master (2015 Aug 13) -* [22b4e71] Viktor Benei - 0.9.9 changelog (2015 Aug 13) -* [2ae0825] Viktor Benei - Merge pull request #142 from viktorbenei/master (2015 Aug 13) -* [fc44dc0] Viktor Benei - Xcode CLT is not an explicit dependency anymore, only brew; but brew check extended with brew update and brew doctor (2015 Aug 13) -* [8bd8098] Viktor Benei - Merge pull request #141 from mistydemeo/xcode-select (2015 Aug 13) -* [6dd0fe9] Misty De Meo - Dependencies: fix xcode-select argument (2015 Aug 12) -* [2f72f83] Viktor Benei - Merge pull request #140 from viktorbenei/master (2015 Aug 12) -* [3cefbbb] Viktor Benei - start of v0.9.9 (2015 Aug 12) -* [662012a] Viktor Benei - changelog (2015 Aug 12) - - -## 0.9.8 (2015 Aug 12) - -### Release Notes - -* __BREAKING__ : `step.yml` shared in Step Libraries / Step Collections now have to include a `commit` (hash) property inside the `source` property, for better version validation (version tag have to match this commit hash)! - * You should switch to the new, final default StepLib, hosted on GitHub, which contains these commit hashes and works with stepman 0.9.8! URL: https://github.com/bitrise-io/bitrise-steplib - * We'll soon (in about 1 day) start to accept Step contributions to this new StepLib! - * You should replace the previous `https://bitbucket.org/bitrise-team/bitrise-new-steps-spec` `default_step_lib_source` and every other reference to this old (now deprecated) StepLib, and **replace it** with `https://github.com/bitrise-io/bitrise-steplib.git`! -* __BUGFIX__ : the `$STEPLIB_BUILD_STATUS` and `$BITRISE_BUILD_STATUS` environments were not set correctly in the previous version for a couple of multi-workflow setups. -* __NEW__ : `bitrise init` now automatically adds `.bitrise*` to the `.gitignore` file in the current folder, to prevent accidentally sharing your `.bitrise.secrets.yml` or other bitrise generated temporary files/folders. -* __NEW__ : built in commands to `share` a new step into a StepLib - through `stepman`. -* __NEW__ : `run_if` expressions can now use the new `.IsPR` check, to declare whether a given step should run in case of a Pull Request build. -* __NEW__ : Step dependencies : `Xcode` can now be specified as a dependency for steps. Unfortunately it can't be installed automatically, but you'll get proper error message about the missing full Xcode in this case, rather than a generic error message during running the step. -* __NEW__ : bitrise now checks the `format_version` of the `bitrise.yml` file and doesn't run it if it was created for a newer version. -* You no longer have to call `setup` after the installation or upgrade of `bitrise`, it'll automatically check whether `setup` was called (and succeeded) when you call `run`. -* Bitrise now creates it's temporary working cache dir in a System temp folder, instead of spamming the current directory with a `.bitrise` folder at every `bitrise run`. -* Improved `bitrise run` logs. -* LOTS of code revision - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.8/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 0.9.7 -> 0.9.8 - -* [e03719c] Viktor Benei - Merge pull request #139 from viktorbenei/master (2015 Aug 12) -* [f1e0a95] Viktor Benei - minimum envman and stepman version bump (2015 Aug 12) -* [f88e7da] Krisztian Goedrei - workflow fixes (2015 Aug 12) -* [e819d32] Viktor Benei - Merge pull request #138 from viktorbenei/master (2015 Aug 12) -* [84d15d2] Viktor Benei - godeps-update (2015 Aug 12) -* [9e72f25] Viktor Benei - Merge pull request #137 from viktorbenei/feature/debug-env (2015 Aug 12) -* [015089d] Viktor Benei - bit of ENV revision in general, and a new "--debug" flag (or DEBUG=true ENV) to run in Debug Mode (2015 Aug 12) -* [91c1b5c] Viktor Benei - Merge pull request #136 from viktorbenei/feature/pr-env (2015 Aug 12) -* [4fe4dba] Viktor Benei - PULL_REQUEST_ID env related template-expressions: .IsPR can now be used as Run-If expression (2015 Aug 12) -* [73e69cf] Viktor Benei - Merge pull request #135 from viktorbenei/feature/xcode-dependency (2015 Aug 11) -* [a91e67f] Viktor Benei - special "try check" dependency type, which can't be installed - a special one is 'xcode', which has it's own error msg (2015 Aug 11) -* [b33450e] Viktor Benei - Merge pull request #134 from gkiki90/master (2015 Aug 11) -* [d1742dd] Krisztian Goedrei - step title check (2015 Aug 11) -* [76a7d7a] Viktor Benei - typo fix (2015 Aug 11) -* [f8be2a4] Viktor Benei - Merge pull request #133 from gkiki90/go-utils (2015 Aug 11) -* [f98128d] Krisztian Goedrei - updated to go-utils (2015 Aug 11) -* [1e14821] Viktor Benei - Merge pull request #132 from viktorbenei/feature/check-workflow-version (2015 Aug 11) -* [faafea3] Viktor Benei - bitrise.yml format version check (2015 Aug 11) -* [55848a3] Krisztian Goedrei - RemoveFile instead of RemoveDir (2015 Aug 11) -* [946033e] Viktor Benei - Merge pull request #130 from gkiki90/working_directory (2015 Aug 11) -* [83aabab] Krisztian Goedrei - os temp workdir (2015 Aug 11) -* [8a5c48b] Viktor Benei - Merge pull request #127 from gkiki90/define_configs_as_string (2015 Aug 11) -* [e51c416] Krisztian Goedrei - removed test.yml (2015 Aug 11) -* [a36c4ab] Krisztian Goedrei - tests (2015 Aug 11) -* [7ab56cd] Viktor Benei - Merge pull request #128 from viktorbenei/master (2015 Aug 11) -* [fee03a8] Viktor Benei - godep-update (2015 Aug 11) -* [babb0c7] Viktor Benei - go-utils migration (2015 Aug 11) -* [6c3778d] Viktor Benei - Merge pull request #126 from gkiki90/refactor (2015 Aug 11) -* [d9d329a] Krisztian Goedrei - failed not important -> skippable (2015 Aug 11) -* [ec81f42] Viktor Benei - Merge pull request #125 from gkiki90/build_failed_fix (2015 Aug 10) -* [1f4b840] Krisztian Goedrei - build status env test fix (+3 squashed commits) Squashed commits: [3d3392c] build status [e77352e] build status env tests [9d9f504] var to const (+3 squashed commits) Squashed commits: [a60406a] godep-update [0f48b5f] run tests [8223985] do run step status fix (2015 Aug 10) -* [7df8b74] Viktor Benei - Merge pull request #123 from viktorbenei/feature/print-version-under-ascii-header (2015 Aug 10) -* [f21571f] Viktor Benei - print the version number under the ASCII header (2015 Aug 10) -* [d9ceb9e] Viktor Benei - Merge pull request #124 from gkiki90/build_failed_fix (2015 Aug 10) -* [9d9f504] Krisztian Goedrei - var to const (+3 squashed commits) Squashed commits: [a60406a] godep-update [0f48b5f] run tests [8223985] do run step status fix (2015 Aug 10) -* [ce2c9f1] Viktor Benei - Merge pull request #122 from viktorbenei/master (2015 Aug 09) -* [6cc9dbd] Viktor Benei - separate slack from-name for CI OK and error (2015 Aug 09) -* [c2cc3e7] Viktor Benei - Merge pull request #120 from viktorbenei/feature/init_add_items_to_gitignore (2015 Aug 09) -* [fed202a] Viktor Benei - Merge pull request #121 from viktorbenei/feature/build_status_env_fix (2015 Aug 09) -* [44fa69f] Viktor Benei - PR fix (2015 Aug 09) -* [e691054] Viktor Benei - fixed (2015 Aug 09) -* [cd81303] Viktor Benei - doInit : append the '.bitrise*' pattern to the .gitignore file in the current dir + example workflow renamed to 'test' (2015 Aug 09) -* [bde1c0a] Viktor Benei - Merge pull request #119 from viktorbenei/feature/include_build_url_in_ci_test_slack_msg (2015 Aug 09) -* [b6048b8] Viktor Benei - Merge pull request #118 from viktorbenei/master (2015 Aug 09) -* [7dfd358] Viktor Benei - Build URL added to CI slack msgs (2015 Aug 09) -* [ea67acf] Viktor Benei - include branch name in CI msg (2015 Aug 09) -* [1806749] Viktor Benei - Merge pull request #117 from viktorbenei/master (2015 Aug 09) -* [8663044] Viktor Benei - test added back + error text typo (2015 Aug 09) -* [8184360] Viktor Benei - bitrise.yml updated - 'ci' workflow added (2015 Aug 09) -* [4e9ff9c] Viktor Benei - color strings updated - white color was removed because it was invisible on white background (2015 Aug 09) -* [bee0e0f] Viktor Benei - Merge pull request #116 from viktorbenei/master (2015 Aug 09) -* [5a21b33] Viktor Benei - moved a couple of things into the new go-utils repo (2015 Aug 08) -* [2f52692] Viktor Benei - colorstring package moved to go-utils repo (2015 Aug 08) -* [4cc0d05] Viktor Benei - Merge pull request #115 from viktorbenei/master (2015 Aug 08) -* [f2bb4a6] Viktor Benei - godeps update (2015 Aug 08) -* [f2dd7ed] Viktor Benei - godeps update (2015 Aug 08) -* [bfa49f2] Viktor Benei - timestamp ten step revision / ref fix (2015 Aug 08) -* [d9985ad] Viktor Benei - added '.git' to the end of the default step lib source (https://github.com/bitrise-io/bitrise-steplib.git), for clarity (2015 Aug 08) -* [74dedb1] Viktor Benei - Merge pull request #114 from gkiki90/master (2015 Aug 08) -* [d4c4449] Krisztian Goedrei - Merge branch 'master' of github.com:bitrise-io/bitrise-cli (2015 Aug 08) -* [576d095] Viktor Benei - Merge pull request #113 from viktorbenei/master (2015 Aug 08) -* [0cd5690] Krisztian Goedrei - godep-update (2015 Aug 08) -* [f3ba246] Viktor Benei - godeps update (+1 squashed commit) Squashed commits: [e9ed7e8] util fix (2015 Aug 08) -* [7f3dfee] Krisztian Goedrei - run print fix (2015 Aug 08) -* [151f879] Viktor Benei - Merge pull request #112 from viktorbenei/master (2015 Aug 08) -* [e4f2052] Viktor Benei - print fix (2015 Aug 08) -* [cb5ecf8] Viktor Benei - Merge pull request #111 from gkiki90/master (2015 Aug 08) -* [0c048a4] Krisztian Goedrei - math fix (2015 Aug 08) -* [9e9fefb] Viktor Benei - Merge pull request #110 from viktorbenei/master (2015 Aug 08) -* [c877ca8] Viktor Benei - godep update (2015 Aug 08) -* [95cf4e5] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 08) -* [b2b3c48] Viktor Benei - Merge pull request #109 from gkiki90/master (2015 Aug 08) -* [a4d1e07] Krisztian Goedrei - godep update (2015 Aug 08) -* [1bf5e85] Krisztian Goedrei - stepman migration fix (2015 Aug 08) -* [94f4de3] Krisztian Goedrei - Merge branch 'master' of github.com:bitrise-io/bitrise-cli (2015 Aug 08) -* [12be6ca] Viktor Benei - godep update (2015 Aug 08) -* [51389eb] Viktor Benei - brew_test mL removed but a new brew_publish one was added (2015 Aug 08) -* [748ed98] Viktor Benei - updated main / default step lib spec repo url (2015 Aug 08) -* [8f327d9] Krisztian Goedrei - stepman migration (2015 Aug 08) -* [a0f4c43] Viktor Benei - Merge pull request #108 from gkiki90/master (2015 Aug 06) -* [a5182d1] Krisztian Goedrei - reference cycle test config moved to _tests (2015 Aug 06) -* [605fc51] Viktor Benei - Merge pull request #107 from viktorbenei/master (2015 Aug 06) -* [455d9c9] Viktor Benei - Merge pull request #106 from gkiki90/run_tests (2015 Aug 06) -* [7b86e33] Viktor Benei - _tests for test bitrise.ymls (2015 Aug 06) -* [5844849] Krisztian Goedrei - reference cycle test (2015 Aug 06) -* [ec2e74b] Viktor Benei - Merge pull request #105 from viktorbenei/master (2015 Aug 06) -* [fd98b01] Viktor Benei - Godeps update - pathutil (2015 Aug 06) -* [5bb93ed] Viktor Benei - 'setup' is now called automatically if it was not called for the current version of bitrise when 'run' is called + code revisions (2015 Aug 06) -* [4226622] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 06) -* [a561b0d] Viktor Benei - store `bitrise setup` for the given bitrise version, so that it can be checked whether a setup was done for the current version (2015 Aug 06) -* [ef60060] Viktor Benei - Merge pull request #104 from gkiki90/build_failed_test (2015 Aug 06) -* [407df1e] Viktor Benei - Merge pull request #103 from viktorbenei/master (2015 Aug 06) -* [4de3fe1] Krisztian Goedrei - ci.sh fix (2015 Aug 06) -* [710f90f] Krisztian Goedrei - fixed ci.sh (2015 Aug 06) -* [884006c] Krisztian Goedrei - fixed build failed (2015 Aug 06) -* [5f9e786] Krisztian Goedrei - doRun fixes, doRun_test (2015 Aug 06) -* [4942a3a] Krisztian Goedrei - reorganized code (2015 Aug 06) -* [f4e1ed3] Viktor Benei - switching to the new StepLib, hosted on GitHub (2015 Aug 06) -* [a683856] Viktor Benei - Merge pull request #102 from gkiki90/master (2015 Aug 05) -* [3aee65e] Krisztian Goedrei - removed bitrise from gitignore (2015 Aug 05) -* [b33ff0c] Viktor Benei - Merge pull request #101 from viktorbenei/master (2015 Aug 05) -* [6bd0268] Viktor Benei - start of v0.9.8 (2015 Aug 05) -* [17e1c54] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 05) - - -## 0.9.7 (2015 Aug 05) - -### Release Notes - -* __IMPORTANT__ : The project was renamed from `bitrise-cli` to just `bitrise`, which means that from now on you have to call your commands with `bitrise [command]`, instead of the previous, longer `bitrise-cli [command]`. -* Improved step dependency management with `brew`. -* Log improvements. - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.7/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 0.9.6 -> 0.9.7 - -* [c1759d3] Viktor Benei - Merge pull request #100 from gkiki90/master (2015 Aug 05) -* [a35b632] Viktor Benei - slack step titles (2015 Aug 05) -* [23a5966] Krisztian Goedrei - tool dependecies (2015 Aug 05) -* [c77a3c8] Krisztian Goedrei - Merge branch 'master' of github.com:bitrise-io/bitrise-cli (2015 Aug 05) -* [0f6629f] Krisztian Goedrei - changelog (2015 Aug 05) -* [c484396] Viktor Benei - Merge pull request #99 from viktorbenei/master (2015 Aug 05) -* [4bfdfc8] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 05) -* [4b9d9ca] Viktor Benei - added another Slack msg to announce (2015 Aug 05) -* [4e47b96] Viktor Benei - Merge pull request #98 from gkiki90/master (2015 Aug 05) -* [6d0a9ef] Viktor Benei - Godep update (2015 Aug 05) -* [98a0a76] Krisztian Goedrei - flag fixes (2015 Aug 05) -* [abf0257] Krisztian Goedrei - init highligth (2015 Aug 05) -* [82daa8f] Viktor Benei - Merge pull request #97 from viktorbenei/master (2015 Aug 05) -* [88ab022] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise (2015 Aug 05) -* [a06ac0d] Viktor Benei - Merge pull request #96 from gkiki90/skipped_handling (2015 Aug 05) -* [a9de033] Viktor Benei - Merge pull request #95 from gkiki90/runtime (2015 Aug 05) -* [2df0d40] Viktor Benei - renames, from the old `bitrise-cli` to the new, official `bitrise` tool name (2015 Aug 05) -* [4b16a00] Krisztian Goedrei - run if (2015 Aug 05) -* [2d93845] Krisztian Goedrei - runtime, dependency fixes (2015 Aug 05) -* [c5cae54] Viktor Benei - Merge pull request #94 from gkiki90/master (2015 Aug 05) -* [bc46501] Krisztian Goedrei - CI flag fixes (2015 Aug 05) -* [1a9a1ed] Krisztian Goedrei - chek with brew if installed (2015 Aug 05) -* [0fc0102] Viktor Benei - Merge pull request #93 from viktorbenei/master (2015 Aug 04) -* [93eb603] Viktor Benei - start of v0.9.7 (2015 Aug 04) -* [a205dec] Viktor Benei - changeling template + changeling generator added to create-release workflow, similar to the one in stepman&envman (2015 Aug 04) - - -## 0.9.6 (2015 Aug 04) - -### Release Notes - -* __BREAKING__ : `.bitrise.secrets.yml` 's syntax changed, to match the environments syntax used everywhere else. This means that instead of directly specifying `is_expand` at the same level as the key and value you should now move this into an `opts:` section, just like in every other `envs` list in `bitrise.yml`. -* __NEW__ : dependency management built into `bitrise.yml` syntax. Right now only `brew` is supported, on OS X, but this will be expanded. -* if a step or a version can't be found in the local cache from a step library `bitrise` will now update the local cache before failing with "step not found" -* greatly improved logs, colored step sections and step run summaries. It starts to look decent and is much more helpful than the previous log outputs. -* updated `setup` - only the Xcode Command Line tools are required now, if no full Xcode found it'll print a warning message about it but you can still use `bitrise`. -* quite a lot of minor bug fixes - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.6/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 0.9.5 -> 0.9.6 - -* [1dd2a00] Viktor Benei - Merge pull request #92 from gkiki90/master (2015 Aug 04) -* [96cab0c] Krisztian Goedrei - PR fixes (2015 Aug 04) -* [58c4154] Viktor Benei - Merge pull request #91 from gkiki90/envman_run_fix (2015 Aug 04) -* [9964b04] Krisztian Goedrei - PR fix (2015 Aug 04) -* [dea2cdf] Krisztian Goedrei - PR fixes (2015 Aug 04) -* [6c1e275] Krisztian Goedrei - running step log fix (2015 Aug 04) -* [9acbefb] Krisztian Goedrei - log fixes (2015 Aug 04) -* [e136db0] Krisztian Goedrei - run results fix (2015 Aug 04) -* [26d0296] Krisztian Goedrei - envman run with exit code, log fixes in progress (2015 Aug 04) -* [d2f25ff] Viktor Benei - Merge pull request #90 from viktorbenei/master (2015 Aug 04) -* [4b37119] Viktor Benei - Godep update : goinp bool parse improvement (2015 Aug 04) -* [4806c04] Viktor Benei - Merge pull request #89 from viktorbenei/master (2015 Aug 04) -* [48ec5b2] Viktor Benei - at setup the "[OK]" strings are now highlighted with green color; Xcode CLT setup/check revisions: warning if only CLT is available but not a full Xcode, with highlight, and as version it prints the info text (2015 Aug 04) -* [f643144] Viktor Benei - dependencies: no full Xcode required, only Command line tools (2015 Aug 03) -* [4795201] Viktor Benei - setup: envman v0.9.2 and stepman v0.9.6 required (2015 Aug 03) -* [71ce059] Viktor Benei - init: generate new style .secrets (2015 Aug 03) -* [1b5112e] Viktor Benei - Merge pull request #88 from gkiki90/dependencies (2015 Aug 03) -* [3635cfe] Krisztian Goedrei - print header fix (2015 Aug 03) -* [16946a4] Krisztian Goedrei - godep update (2015 Aug 03) -* [95d7529] Krisztian Goedrei - refactor fixes (2015 Aug 03) -* [c60cd93] Krisztian Goedrei - godep update (2015 Aug 03) -* [28943df] Krisztian Goedrei - depman update (2015 Aug 03) -* [9fd991f] Krisztian Goedrei - log CI mode, printASCIIHeader moved to ci.go (2015 Jul 31) -* [3c118a8] Krisztian Goedrei - dependencies (2015 Jul 31) -* [5ad6512] Krisztian Goedrei - dependency handling (2015 Jul 31) -* [575be1e] Krisztian Goedrei - dependencies in progress (2015 Jul 31) -* [1e707d7] Krisztian Goedrei - fixed env merge, models_methods_tests (2015 Jul 31) -* [3662056] Krisztian Goedrei - test start (2015 Jul 30) -* [b08a92d] Krisztian Goedrei - godep update (2015 Jul 30) -* [3e86ee6] Krisztian Goedrei - fixed stepman update flag, typo fix (2015 Jul 30) -* [630cc2a] Krisztian Goedrei - refactor, code style (2015 Jul 30) -* [9af552b] Viktor Benei - Merge pull request #86 from gkiki90/new_envman_models (2015 Jul 29) -* [1272839] Krisztian Goedrei - test fix (2015 Jul 29) -* [1fe5d13] Krisztian Goedrei - PR fix (2015 Jul 29) -* [d442821] Krisztian Goedrei - godep update (2015 Jul 29) -* [91324a2] Krisztian Goedrei - godep (2015 Jul 29) -* [57c2476] Krisztian Goedrei - godep (2015 Jul 29) -* [0301e85] Krisztian Goedrei - use envman models (2015 Jul 29) -* [9d52cfc] Viktor Benei - Merge pull request #85 from viktorbenei/master (2015 Jul 28) -* [654aa47] Viktor Benei - start of v0.9.6 (2015 Jul 28) - - -## 0.9.5 (2015 Jul 28) - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.5/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 0.9.4 -> 0.9.5 - -* [017e896] Viktor Benei - Merge pull request #84 from viktorbenei/master (2015 Jul 28) -* [87dc5a9] Viktor Benei - require Stepman 0.9.5 (2015 Jul 28) -* [4a20cb1] Viktor Benei - Merge pull request #83 from viktorbenei/master (2015 Jul 28) -* [04910f2] Viktor Benei - Godeps update (2015 Jul 28) -* [1ae7e36] Viktor Benei - Merge pull request #82 from gkiki90/log_improvements (2015 Jul 28) -* [a2975fa] Krisztian Goedrei - test (2015 Jul 28) -* [63ca923] Krisztian Goedrei - Merge branch 'master' into log_improvements (2015 Jul 28) -* [eefa57e] Krisztian Goedrei - build failed fix (2015 Jul 28) -* [dabdb97] Viktor Benei - Merge pull request #81 from viktorbenei/master (2015 Jul 28) -* [4ffbfa6] Viktor Benei - BITRISE_BUILD_STATUS and STEPLIB_BUILD_STATUS printing in before-after test in bitrise.yml (2015 Jul 28) -* [c698340] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 28) -* [cc9030a] Viktor Benei - Merge pull request #80 from gkiki90/log_improvements (2015 Jul 28) -* [9ca35a0] Krisztian Goedrei - template_utils_test BuildRunResultsModel fix (2015 Jul 28) -* [3e7f3ae] Krisztian Goedrei - fixed build failed mode (2015 Jul 28) -* [c5c94eb] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 28) -* [3cc6fad] Viktor Benei - Merge pull request #79 from gkiki90/log_improvements (2015 Jul 28) -* [c8a6ed0] Viktor Benei - before-after ENV accessibility test (2015 Jul 28) -* [ec7c9d2] Viktor Benei - Merge pull request #78 from viktorbenei/master (2015 Jul 28) -* [0929268] Krisztian Goedrei - log fixes, env handling fixes (2015 Jul 28) -* [68ff822] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 28) -* [6fbce1b] Viktor Benei - Merge pull request #77 from gkiki90/master (2015 Jul 28) -* [595f6fd] Krisztian Goedrei - validate fix (2015 Jul 28) -* [061bd82] Krisztian Goedrei - log fix in progress (2015 Jul 28) -* [6e8ce72] Krisztian Goedrei - comments, env imports (2015 Jul 28) -* [2eef2e8] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 28) -* [d2e8ca2] Viktor Benei - before_run for install (2015 Jul 28) -* [5bd2547] Viktor Benei - init : doesn't print the content anymore (2015 Jul 28) -* [59e2961] Viktor Benei - using rsync instead of cp to copy local path:: step source - it can handle the case if you want to run it from the step's dir directly, for example while developing the step (2015 Jul 28) -* [179ed5f] Viktor Benei - Merge pull request #76 from gkiki90/after_before (2015 Jul 28) -* [811f7e2] Krisztian Goedrei - godep (2015 Jul 28) -* [b4e3c9d] Krisztian Goedrei - PR fix (2015 Jul 28) -* [5d5fdd1] Krisztian Goedrei - Merge branch 'master' into after_before (2015 Jul 28) -* [39870f5] Krisztian Goedrei - log fixes (2015 Jul 28) -* [1a34984] Krisztian Goedrei - validating bitrisedata, workflow logs, (2015 Jul 28) -* [1b8747d] Viktor Benei - Merge pull request #75 from gkiki90/log_fix (2015 Jul 28) -* [4961f6c] Krisztian Goedrei - test (2015 Jul 27) -* [47e65d6] Krisztian Goedrei - run in progress (2015 Jul 27) -* [94d9574] Krisztian Goedrei - colorstring package and usage (2015 Jul 27) -* [821a6e6] Krisztian Goedrei - Merge branch 'master' into log_fix (2015 Jul 27) -* [388b536] Krisztian Goedrei - color log in progress (2015 Jul 27) -* [f4482de] Viktor Benei - Merge pull request #74 from gkiki90/step_source (2015 Jul 27) -* [4b59182] Krisztian Goedrei - bitrise.yml fix (2015 Jul 27) -* [189453c] Krisztian Goedrei - help messages fix, code style (2015 Jul 27) -* [70c1719] Krisztian Goedrei - timestamp-gen workflow fixes, step source log (2015 Jul 27) -* [60c5475] Viktor Benei - Merge pull request #73 from viktorbenei/master (2015 Jul 25) -* [b394e22] Viktor Benei - just a bit of template expression doc note (2015 Jul 25) -* [0af570e] Viktor Benei - Merge pull request #72 from viktorbenei/master (2015 Jul 25) -* [e178a1d] Viktor Benei - test for "$.Prop" style referencing, and annotated template examples (2015 Jul 25) -* [e99c312] Viktor Benei - enveq function, for easier ENV testing (2015 Jul 25) -* [1b482b8] Viktor Benei - A TemplateDataModel is now available for step property expressions, for easier "IsCI" detection. You can also just write ".IsCI", instead of the longer "{{.IsCI}}" the "CI=true" env is set at the start to force every tool to work in CI mode (even if the CI mode was just a command line param) (2015 Jul 25) -* [6dfd682] Viktor Benei - Merge pull request #71 from viktorbenei/run_if_and_templates (2015 Jul 24) -* [d232bad] Viktor Benei - first version of Run-If template handling & a couple of revisions (2015 Jul 24) -* [8ca8b9f] Viktor Benei - MergeStepWith #fix (2015 Jul 24) -* [026a752] Viktor Benei - Examples & tutorials section (2015 Jul 24) -* [d81a27b] Viktor Benei - Merge pull request #70 from viktorbenei/master (2015 Jul 24) -* [0a5a60b] Viktor Benei - the failing steps examples are also moved into examples/tutorials (2015 Jul 24) -* [e3bd023] Viktor Benei - a couple of experimentals (2015 Jul 24) -* [5cfd72d] Viktor Benei - examples/tutorials (2015 Jul 24) -* [afaf8e4] Viktor Benei - _examples folder to include a couple of example bitrise cli configs and workflows (2015 Jul 24) -* [c3d9605] Viktor Benei - Merge pull request #69 from viktorbenei/master (2015 Jul 24) -* [ccf3c89] Viktor Benei - Install instructions now points to /releases (2015 Jul 24) -* [052fb87] Viktor Benei - start of v0.9.5 (2015 Jul 24) - - -## 0.9.4 (2015 Jul 24) - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.4/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 0.9.3 -> 0.9.4 - -* [017b840] Viktor Benei - Merge pull request #68 from viktorbenei/master (2015 Jul 24) -* [5f5be0f] Viktor Benei - Godeps update (2015 Jul 24) -* [8db50a3] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 24) -* [bcb3ec3] Viktor Benei - Stepman update - related: IsNotImportant is now IsSkippable (2015 Jul 24) -* [80a1348] Viktor Benei - Merge pull request #67 from viktorbenei/master (2015 Jul 24) -* [1268b06] Viktor Benei - Godep-update workflow (2015 Jul 24) -* [6e1e1bf] Viktor Benei - ssh style remote git step in test workflows (2015 Jul 24) -* [0238347] Viktor Benei - fix: in case of direct git uri which contains @ as part of the url it should still work correctly (ex: if git url is: git@github.com:bitrise-io/steps-timestamp.git); no path to absolute-path conversion should happen in CreateStepIDDataFromString; unit tests for the new "path::" and "git::" style step IDs (2015 Jul 24) -* [760c42d] Viktor Benei - StepIDData : now that it supports local and direct-git-url options the previous ID was renamed to IDorURI and some documentation is provided for relevant places (2015 Jul 24) -* [3fc9807] Viktor Benei - in case the step source is defined as a direct git uri a version (branch or tag) is also required (2015 Jul 24) -* [ce65526] Viktor Benei - a bit more, and more thorough test workflows (2015 Jul 24) -* [ed71074] Viktor Benei - Merge pull request #66 from gkiki90/master (2015 Jul 24) -* [7e8c95f] Krisztian Goedrei - git src (2015 Jul 24) -* [ee8ce3a] Krisztian Goedrei - err check fixes (2015 Jul 24) -* [23a365a] Krisztian Goedrei - move local step to .bitrise work dir (2015 Jul 24) -* [4517333] Krisztian Goedrei - Merge branch 'master' of github.com:bitrise-io/bitrise-cli (2015 Jul 24) -* [c20bc6a] Krisztian Goedrei - PR fixes (2015 Jul 24) -* [0dd36ff] Krisztian Goedrei - support for ~/your/path (2015 Jul 24) -* [973b4fc] Krisztian Goedrei - local steps (2015 Jul 24) -* [b60f4a3] Krisztian Goedrei - local path in models and model_methods (2015 Jul 24) -* [f620295] Viktor Benei - Merge pull request #65 from viktorbenei/master (2015 Jul 24) -* [b5cc18b] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 24) -* [ac1564b] Viktor Benei - minor run command log formatting (2015 Jul 23) -* [da057fc] Viktor Benei - start of 0.9.4 (2015 Jul 23) -* [b700273] Viktor Benei - install - 0.9.3 (2015 Jul 23) - - -## 0.9.3 (2015 Jul 23) - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.3/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 0.9.2 -> 0.9.3 - -* [469750c] Viktor Benei - Merge pull request #64 from viktorbenei/master (2015 Jul 23) -* [8628a5a] Viktor Benei - requires stepman 0.9.3 (2015 Jul 23) -* [121afa1] Viktor Benei - Godeps update (2015 Jul 23) -* [ee301cd] Viktor Benei - Merge pull request #62 from viktorbenei/setup_improvements (2015 Jul 23) -* [269aec9] Viktor Benei - temp switch back to previous stepman min ver (2015 Jul 23) -* [1874e0a] Viktor Benei - Xcode CLT version check and better Brew warn (2015 Jul 23) -* [61d42cc] Viktor Benei - Merge branch 'master' into setup_improvements (2015 Jul 23) -* [41bdc3a] Viktor Benei - Setup can now update the required Bitrise Tools if an older version found (2015 Jul 23) -* [9fb88c3] Viktor Benei - Merge pull request #63 from gkiki90/build_status_env (2015 Jul 23) -* [bb503ca] Krisztian Goedrei - set build status env fix (2015 Jul 23) -* [db8e469] Krisztian Goedrei - build failed envs (2015 Jul 23) -* [b7b34c0] Viktor Benei - Godeps update: stepman (2015 Jul 23) -* [7c7878c] Viktor Benei - Merge pull request #61 from gkiki90/build_time (2015 Jul 23) -* [6b7de38] Krisztian Goedrei - total count fix (2015 Jul 23) -* [341b560] Krisztian Goedrei - log success count (2015 Jul 23) -* [13f4df0] Krisztian Goedrei - log fixes (2015 Jul 23) -* [8901ab9] Krisztian Goedrei - skipped steps (2015 Jul 23) -* [6066019] Krisztian Goedrei - log fixes (2015 Jul 23) -* [59b895b] Krisztian Goedrei - typo (2015 Jul 23) -* [075835b] Krisztian Goedrei - summary log (2015 Jul 23) -* [a0f3e5d] Krisztian Goedrei - build finish fixes (2015 Jul 23) -* [35d2390] Krisztian Goedrei - code style (2015 Jul 23) -* [afe217f] Krisztian Goedrei - failed step fix (2015 Jul 23) -* [6fcf9e4] Krisztian Goedrei - revision (2015 Jul 23) -* [b18d334] Krisztian Goedrei - revision (2015 Jul 23) -* [a241271] Krisztian Goedrei - fixed merge (2015 Jul 23) -* [ea56eef] Krisztian Goedrei - merge fix (2015 Jul 23) -* [f6136c5] Krisztian Goedrei - Merge branch 'master' into build_time (2015 Jul 23) -* [5787288] Krisztian Goedrei - register build status methods (2015 Jul 23) -* [b9d861a] Viktor Benei - Merge pull request #60 from viktorbenei/master (2015 Jul 22) -* [da46aba] Viktor Benei - start of v0.9.3 (2015 Jul 22) -* [0650b85] Viktor Benei - install - v0.9.2 (2015 Jul 22) - - -## 0.9.2 (2015 Jul 22) - -### Install or upgrade - -To install this version, run the following commands (in a bash shell): - -``` -curl -fL https://github.com/bitrise-io/bitrise/releases/download/0.9.2/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -``` - -Then: - -``` -chmod +x /usr/local/bin/bitrise -``` - -That's all, you're ready to go! - -Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run -is installed and available, but if you forget to do this it'll be performed the first -time you call bitrise run. - -### Release Commits - 0.9.1 -> 0.9.2 - -* [45e51d5] Viktor Benei - Merge pull request #59 from viktorbenei/master (2015 Jul 22) -* [7300a42] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 22) -* [8c75a3b] Viktor Benei - Goddess update (2015 Jul 22) -* [8f0da08] Viktor Benei - doSetup: install stepman v0.9.2 (2015 Jul 22) -* [a060d39] Viktor Benei - Merge pull request #58 from viktorbenei/master (2015 Jul 22) -* [86790aa] Viktor Benei - Merge pull request #57 from bazscsa/master (2015 Jul 22) -* [aa3c01d] Viktor Benei - ci: now does a build & calls setup on it (2015 Jul 22) -* [b641a4f] Viktor Benei - fixed possible infinite recursion in Setup (2015 Jul 22) -* [a1f6f45] Viktor Benei - create-release : now sends a Slack msg as well (2015 Jul 22) -* [2c31fc3] Viktor Benei - envman call fix: do adds with --append (2015 Jul 22) -* [f1d612f] Tamás Bazsonyi - Setup (2015 Jul 22) -* [4829d33] Viktor Benei - Merge pull request #56 from gkiki90/isNotImportant_handling (2015 Jul 22) -* [34a9326] Krisztian Goedrei - refactor, typo (2015 Jul 22) -* [6d66525] Krisztian Goedrei - build time in progress (2015 Jul 22) -* [15f0d63] Tamás Bazsonyi - setup description revision (2015 Jul 22) -* [f0ce128] Krisztian Goedrei - isNotImportent handling, buildFailedMode fixes (2015 Jul 22) -* [84a9d8d] Viktor Benei - Merge pull request #55 from viktorbenei/master (2015 Jul 22) -* [5b11e83] Viktor Benei - "environments" is now simply "envs" (2015 Jul 22) -* [26a3eae] Viktor Benei - doSetup : don't ask for permission to install required dependencies (envman & stepman) (2015 Jul 22) -* [0a9955d] Viktor Benei - Install command syntax change, for clarity (2015 Jul 22) -* [1b59895] Viktor Benei - Merge branch 'master' of https://github.com/bitrise-io/bitrise-cli (2015 Jul 22) -* [ba9a6e3] Viktor Benei - start of v0.9.2 - bitrise.yml now contains a 'create-release' workflow (2015 Jul 22) -* [b4cf5e8] Viktor Benei - just a minor format change for setup (2015 Jul 22) -* [3ae6a0e] Viktor Benei - Install and setup instructions (2015 Jul 22) -* [e38911e] Viktor Benei - setup: now can install stepman as well as envman (2015 Jul 22) -* [40cfd3a] Viktor Benei - better 'init' command : it now adds a default step lib source & a simple 'script' step with hello (2015 Jul 22) - - ------------------ - -Updated: 2017 Aug 07 \ No newline at end of file diff --git a/vendor/github.com/bitrise-io/bitrise/Dockerfile b/vendor/github.com/bitrise-io/bitrise/Dockerfile deleted file mode 100644 index 35993eec..00000000 --- a/vendor/github.com/bitrise-io/bitrise/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -FROM golang:1.7 - -ENV PROJ_NAME bitrise - -RUN apt-get update - -RUN DEBIAN_FRONTEND=noninteractive apt-get -y install curl git mercurial rsync ruby sudo - -# From the official Golang Dockerfile -# https://github.com/docker-library/golang/blob/master/1.4/Dockerfile -RUN mkdir -p /go/src /go/bin && chmod -R 777 /go -ENV GOPATH /go -ENV PATH /go/bin:$PATH - -# Install required (testing) tools -# Install dependencies -RUN go get -u github.com/tools/godep -# Check for unhandled errors -RUN go get -u github.com/kisielk/errcheck -# Go lint -RUN go get -u github.com/golang/lint/golint - -RUN mkdir -p /go/src/github.com/bitrise-io/$PROJ_NAME -COPY . /go/src/github.com/bitrise-io/$PROJ_NAME - -WORKDIR /go/src/github.com/bitrise-io/$PROJ_NAME -# install -RUN go install - -# setup (downloads envman & stepman) -RUN bitrise setup -RUN $HOME/.bitrise/tools/stepman setup -c https://github.com/bitrise-io/bitrise-steplib.git - -CMD bitrise version diff --git a/vendor/github.com/bitrise-io/bitrise/Godeps/Godeps.json b/vendor/github.com/bitrise-io/bitrise/Godeps/Godeps.json deleted file mode 100644 index 81e4dcbd..00000000 --- a/vendor/github.com/bitrise-io/bitrise/Godeps/Godeps.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "ImportPath": "github.com/bitrise-io/bitrise", - "GoVersion": "go1.8", - "GodepVersion": "v79", - "Packages": [ - "./..." - ], - "Deps": [ - { - "ImportPath": "github.com/Sirupsen/logrus", - "Comment": "1.0.2-34-g181d419", - "Rev": "181d419aa9e2223811b824e8f0b4af96f9ba9302" - }, - { - "ImportPath": "github.com/bitrise-io/envman/models", - "Comment": "1.1.6", - "Rev": "e914cc7038450a801f3c5713af755f66bf09ca37" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/colorstring", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/command", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/command/git", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/errorutil", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/fileutil", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/log", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/parseutil", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/pathutil", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/pointers", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/progress", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/retry", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/stringutil", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/go-utils/versions", - "Rev": "6eb582e08179a025ed2deb86ea6dffbd13ba1ff6" - }, - { - "ImportPath": "github.com/bitrise-io/goinp/goinp", - "Rev": "a602d207503f909271ecd91a17abd9ac919e273c" - }, - { - "ImportPath": "github.com/bitrise-io/stepman/models", - "Comment": "0.9.33", - "Rev": "ecc703a00038ed94fb22ae86b4daa0d4b8ba49d7" - }, - { - "ImportPath": "github.com/bitrise-tools/gows/gows", - "Rev": "e4fb47946c852c6f46d28b27a4d0d1a02f5eb0ac" - }, - { - "ImportPath": "github.com/davecgh/go-spew/spew", - "Comment": "v1.1.0-6-gadab964", - "Rev": "adab96458c51a58dc1783b3335dcce5461522e75" - }, - { - "ImportPath": "github.com/hashicorp/go-version", - "Rev": "03c5bf6be031b6dd45afec16b1cf94fc8938bc77" - }, - { - "ImportPath": "github.com/pmezard/go-difflib/difflib", - "Comment": "v1.0.0", - "Rev": "792786c7400a136282c1664665ae0a8db921c6c2" - }, - { - "ImportPath": "github.com/ryanuber/go-glob", - "Comment": "v0.1-4-g256dc44", - "Rev": "256dc444b735e061061cf46c809487313d5b0065" - }, - { - "ImportPath": "github.com/stretchr/testify/assert", - "Comment": "v1.1.4-70-g05e8a0e", - "Rev": "05e8a0eda380579888eb53c394909df027f06991" - }, - { - "ImportPath": "github.com/stretchr/testify/require", - "Comment": "v1.1.4-70-g05e8a0e", - "Rev": "05e8a0eda380579888eb53c394909df027f06991" - }, - { - "ImportPath": "github.com/urfave/cli", - "Comment": "v1.19.1-67-gb99aa81", - "Rev": "b99aa811b4c1dd84cc6bccb8499c82c72098085a" - }, - { - "ImportPath": "golang.org/x/crypto/ssh/terminal", - "Rev": "42ff06aea7c329876e5a0fe94acc96902accf0ad" - }, - { - "ImportPath": "golang.org/x/sys/unix", - "Rev": "d8f5ea21b9295e315e612b4bcf4bedea93454d4d" - }, - { - "ImportPath": "golang.org/x/sys/windows", - "Rev": "d8f5ea21b9295e315e612b4bcf4bedea93454d4d" - }, - { - "ImportPath": "gopkg.in/yaml.v2", - "Rev": "25c4ec802a7d637f88d584ab26798e94ad14c13b" - } - ] -} diff --git a/vendor/github.com/bitrise-io/bitrise/Godeps/Readme b/vendor/github.com/bitrise-io/bitrise/Godeps/Readme deleted file mode 100644 index 4cdaa53d..00000000 --- a/vendor/github.com/bitrise-io/bitrise/Godeps/Readme +++ /dev/null @@ -1,5 +0,0 @@ -This directory tree is generated automatically by godep. - -Please do not edit. - -See https://github.com/tools/godep for more information. diff --git a/vendor/github.com/bitrise-io/bitrise/README.md b/vendor/github.com/bitrise-io/bitrise/README.md deleted file mode 100644 index 1d64eecf..00000000 --- a/vendor/github.com/bitrise-io/bitrise/README.md +++ /dev/null @@ -1,120 +0,0 @@ -# Bitrise (offline) CLI - -_Discussion forum: [https://discuss.bitrise.io/](https://discuss.bitrise.io/)_ - -Run your Bitrise automations with this CLI tool on any Mac or Linux machine, and use the same configuration on -[bitrise.io](https://www.bitrise.io) (automation service, with a mobile app focus). - -*Part of the Bitrise Continuous Integration, Delivery and Automations Stack, -with [stepman](https://github.com/bitrise-io/stepman) and [envman](https://github.com/bitrise-io/envman).* - -For a nice & quick intro you should check: [https://www.bitrise.io/cli](https://www.bitrise.io/cli) - - -## Install and Setup - -The installation is quick and easy, check the latest release for instructions at: [https://github.com/bitrise-io/bitrise/releases](https://github.com/bitrise-io/bitrise/releases) - -Installing with Homebrew: - -`brew update && brew install bitrise` - -Optionally, you can call `bitrise setup` to verify that everything what's required for `bitrise` to run -is installed and available, but if you forget to do this it'll be performed the first -time you call `bitrise run`. - -## Tutorials and Examples - -You can find examples in the [_examples](https://github.com/bitrise-io/bitrise/tree/master/_examples) folder. - -If you're getting started you should start with [_examples/tutorials](https://github.com/bitrise-io/bitrise/tree/master/_examples/tutorials), -this should guide you through the basics, while you'll already use `bitrise` (requires installed `bitrise`). - -You can find a complete iOS sample project at: https://github.com/bitrise-io/sample-apps-ios-with-bitrise-yml - - -## Tooling support & JSON output format - -`bitrise` CLI commands support a `--format=[format]` parameter. -This is intended mainly for tooling support, by adding `--format=json` you'll -get a JSON formatted output on Standard Output. - -**This is still work-in-progress, we're working on providing -the `--format` param to every command except `run`**. - -Every error, warning etc. message will go to StdErr; and on the StdOut -you should only get the valid JSON output. - -An example calling the `version` command: - -`$ bitrise version --format=json` - -Will print `{"version":"1.2.4"}` to the Standard Output (StdOut). - - -## Share your Step - -You can use your own Step as you can see in the `_examples`, even if it's -not yet committed into a repository, or from a repository directly. - -If you would like to share your awesome Step with others -you can do so by calling `stepman share` and then following the -guide it prints. - -## Documentation - -We added some documents to make it a bit easier to get started with Bitrise CLI. The documentation includes a quick and a little longer guides for CLI, a [React Native](http://facebook.github.io/react-native/) project workflow guide and an overview of the Step share process. You can find them in the [_docs](/_docs/) folder. - -## Development - -### Guidelines - -* __Easy to use__: the UX for the end-user, always keep it in mind, make it a pleasant experience to work with this tool (and all of the Bitrise tools)! -* __Code should be kept simple__: easy to understand, easy to collaborate/contribute (as much as possible of course). -* __Compatibility__: never do an incompatible change, unless you can't avoid it. Release new features as additional options, to not to break existing configurations. -* __Stability__: related to compatibility, but in general stability is really important, especially so in a CI/automation environment, where you expect fully reproducible outcomes. -* __Flexibility__: should also be kept in mind, but only if it does not affect the previous points. - -### Updating dependencies - -To do a full dependency update use [bitrise-tools/gows](https://github.com/bitrise-tools/gows), -for a clean workspace: - -``` -gows clear && gows bitrise run godeps-update -``` - -to test that all dependency is included: - -``` -gows clear && gows go test ./... && gows go install && gows bitrise run test -``` - -and/or with `docker-compose`: - -``` -docker-compose build && docker-compose run --rm app go test ./... -``` - -### Release a new version - -1. Update go dependencies (`bitrise run godeps-update`) -1. PR & merge these changes to the `master` branch -1. Release a new versions of bitrise-tools (stepman, envman) if there are changes, you can find the dependent tools in `./bitrise/setup.go -> minEnvmanVersion, minStepmanVersion` -1. Release a new versions of default plugins if there are changes, you can find the default plugins in `./bitrise/setup.go -> PluginDependencyMap` -1. Bump bitrise-tools and default plugins versions in `./bitrise/setup.go` -1. PR & merge these changes to the `master` branch -1. Bump `RELEASE_VERSION` in bitrise.yml -1. Update the version test at: `./_tests/integration/version_test.go` -1. Commit (do not push) these changes to the `master` branch -1. Run `bitrise-run create-release` -1. Fill the current version's `Release Notes` section in `CHANGELOG.md` -1. Update version integration test in `./_tests/integration/version_test.go` -1. Push the changes to the master -1. Open the project's bitrise app on bitrise.io, find the triggered `create-release` workflow run's build -1. Download and test the generated bitrise binaries (`version --full` and plugins) -1. Create the new version's release on [github](https://github.com/bitrise-io/bitrise/releases/new): - - Fill Tag and Version inputs - - Copy paste the Changelog's `Release Notes` and `Install or upgrade` sections to the release description on github - - Attach the generated (on bitrise.io) linux and darwin binaries to the release - - Push the `Publish release` button on github diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/README.md b/vendor/github.com/bitrise-io/bitrise/_docs/README.md deleted file mode 100644 index 949d6eec..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_docs/README.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Bitrise Command Line Interface ---- - -# Welcome to the Bitrise (offline) CLI Documentation section - -## Documentation overview - -* We recommend starting with our [How to Guide](cli-how-to-guide.md), we know it's a bit long but it's worth the time. -* If you are in a hurry and would just like to get a quick overview check out the [Introduction](cli-introduction.md) that was created from the longer version. -* We also put together a special little documentation for those [React Native](http://facebook.github.io/react-native/) fans to make it a bit more easier to get the Workflow for your project up and running on your own machine. You can check it out [here](cli-react-native.md). -* And of course we added a [Step Share Guide](cli-share-guide.md) so you can share your first Steps with us and the whole world! We are looking forward to your awesome StepLib Pull Requests! - * Before you would share your Step make sure you read through the [Step Development Guideline](step-development-guideline.md)! - -Happy Building! diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/bitrise-yml-format-spec.md b/vendor/github.com/bitrise-io/bitrise/_docs/bitrise-yml-format-spec.md deleted file mode 100644 index 31cf0c40..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_docs/bitrise-yml-format-spec.md +++ /dev/null @@ -1,152 +0,0 @@ -# bitrise.yml format specification / reference - -## Minimal bitrise.yml - -The bare minimum `bitrise.yml` is: - -``` -format_version: 2 -``` - -Minimum `bitrise.yml` for a single (no-op) workflow: - -``` -format_version: 2 -workflows: - test: -``` - -## Top level bitrise.yml properties - -- `format_version` : this property declares the minimum Bitrise CLI format version. - You can get your Bitrise CLI's supported highest format version with: `bitrise version --full`. - If you set the `format_version` to `2` that means that Bitrise CLI versions which - don't support the format version `2` or higher won't be able to run the configuration. - This is important if you use features which are not available in older Bitrise CLI versions. -- `default_step_lib_source` : specifies the source to use when no other source is defined for a step. -- `project_type` : defines your source project's type. -- `title`, `summary` and `description` : metadata, for comments, tools and GUI. - _Note: these meta properties can be used for permanent comments. Standard YML comments - are not preserved when the YML is normalized, converted to JSON or otherwise - generated or transformed. These meta properties are._ -- `app` : global, "app" specific configurations. -- `trigger_map` : Trigger Map definitions. -- `workflows` : workflow definitions. - -## App properties - -- `envs` : configuration global environment variables list -- `title`, `summary` and `description` : metadata, for comments, tools and GUI. - _Note: these meta properties can be used for permanent comments. Standard YML comments - are not preserved when the YML is normalized, converted to JSON or otherwise - generated or transformed. These meta properties are._ - -## Trigger Map - -Trigger Map is a list of Trigger Map Items. The elements of the list are processed ordered. If one item matches to the current git event, the item defined workflow will be run. - -## Trigger Map Item - -Trigger Map Item defines what kind of git event should trigger which workflow. - -The Trigger Map Item layout: - -``` -git_event_property: pattern -workflow: workflow_id -``` - -Available trigger events ( with properties ): - -- Code Push (`push_branch`) -- Pull Request (`pull_request_source_branch`, `pull_request_target_branch`) -- Creating Tag (`tag`) - -## Workflow properties - -- `title`, `summary` and `description` : metadata, for comments, tools and GUI. - _Note: these meta properties can be used for permanent comments. Standard YML comments - are not preserved when the YML is normalized, converted to JSON or otherwise - generated or transformed. These meta properties are._ -- `before_run` : list of workflows to execute before this workflow -- `after_run` : list of workflows to execute after this workflow -- `envs` : workflow defined environment variables list -- `steps` : workflow defined step list - -## Step properties - -- `title`, `summary` and `description` : metadata, for comments, tools and GUI. - _Note: these meta properties can be used for permanent comments. Standard YML comments - are not preserved when the YML is normalized, converted to JSON or otherwise - generated or transformed. These meta properties are._ -- `website` : official website of the step / service. -- `source_code_url` : url where the step's source code can be viewed. -- `support_url` : url to the step's support / issue tracker. -- `published_at` : _auto-generated at share_ - step version's StepLib publish date -- `source` : _auto-generated at share_ git clone information. -- `asset_urls` : _auto-generated at share_ step assets (StepLib specific), like icon image. -- `host_os_tags` : supported operating systems. _Currently unused, reserved for future use._ -- `project_type_tags` : project type tags if the step is project type specific. - Example: `ios` or `android`. Completely optional, and only used for search - and filtering in step lists. -- `type_tags` : generic type tags related to the step. - Example: `utility`, `test` or `notification`. - Similar to `project_type_tags`, this property is completely optional, and only used for search - and filtering in step lists. -- `dependencies` : __DEPRECATED__ step dependency declarations. -- `deps` : the new, recommended step dependency declarations property. -- `toolkit` : step toolkit declaration, if the step is meant to utilize - a Bitrise CLI provided toolkit (e.g. `Go`). If not defined the `Bash` - toolkit is used by default. -- `is_requires_admin_user` : indication whether the step (might) - require administrator rights for proper execution. - _Currently unused, reserved for future use or will be deprecated (undecided)._ -- `is_always_run` : if `true` the step will be executed even if a previous step failed during the build. - Default is `false`. -- `is_skippable` : if `true`, even if the step fails that won't mark the build as failed, - the error will be ignored. Default is `false`. -- `run_if` : a template based expression to declare when the step should run. - If the expression evaluates to `true` the step will run, otherwise it will not. - The default is a constant `true`. - - `run_if: false` disables the step, and the step will always be skipped. - - `run_if: .IsCI` will only run the step if the CLI runs in `CI` mode. - - `run_if: '{{enveq "TEST_KEY" "test value"}}'` will skip the step unless - the `TEST_KEY` environment variable is defined, and its value is `test value`. -- `inputs` : inputs (Environments) of the step. Syntax described in the **Environment properties** section. -- `outputs` : outputs (Environments) of the step. Syntax described in the **Environment properties** section. - -## Environment properties - -Environment items (including App Env Vars, Workflow env vars, step inputs, step outputs, ...) -consist of two main parts, a **KEY: value** and an **opts** part. - -When specifying an env item the **KEY: value** part is **required**, **opts** is *optional*. - -An example environment item, specifying a key-value and some opts: - -``` -- MY_KEY_FOR_THE_ENV: my value for the env - opts: - title: An example env var item - is_dont_change_value: false - category: example -``` - -Or in case when you only want to specify the key and the value: - -``` -- MY_KEY_FOR_THE_ENV: my value for the env -``` - -- `title`, `summary` and `description` : metadata, for comments, tools and GUI. - _Note: these meta properties can be used for permanent comments. Standard YML comments - are not preserved when the YML is normalized, converted to JSON or otherwise - generated or transformed. These meta properties are._ -- `is_expand` : if `true` the shell environment variables, in the Environment value, are expanded/resolved. -- `skip_if_empty` : if `true` and if the Environment's value is empty, these Environment will not be used. -- `category` : used to categorise the Environment variable. -- `value_options` : list of the available values. -- `is_required` : used when the Environment is used as a Step input Environment. If `true` the step requires to define not empty value for this Environment. -- `is_dont_change_value` : means, that this value should not be changed / should be hidden on UIs. Mainly used for debug inputs and for "connection" inputs (set to outputs of other steps, to connect this step with another one). -- `is_template` : if `true` the Environment's value will be evaulated as a go template and the evaulated value will be used. - diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/cli-how-to-guide.md b/vendor/github.com/bitrise-io/bitrise/_docs/cli-how-to-guide.md deleted file mode 100644 index 3c575efc..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_docs/cli-how-to-guide.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -title: Bitrise Command Line Interface How to Guide ---- - -# Installing Bitrise Command Line Interface - -Yes!!! Our Command Line Interface is now available via [Homebrew](https://github.com/Homebrew/homebrew/tree/master/share/doc/homebrew#readme)! First call `brew update` and when it's done you simply have to call the `brew install bitrise` command in your terminal. And BOOM! you can start using it right away! - -If you choose to go the old fashioned way just check the [releases site](https://github.com/bitrise-io/bitrise/releases) for instructions. - -## Setting up Bitrise - -When you are done with the installation you have to run the `bitrise setup` command in the terminal and you are ready to use bitrise-cli! This command checks and installs every needed dependency to run your awesome Workflows. - -## Your first project - -Now everything is ready for you to take it for a spin! You can see the available commands if you simply type `bitrise help` in the terminal. - -Let's start with `bitrise init` - -The command first prints our awesome looking logo - don't feel ashamed to stop and stare for a few seconds, we also stared in awe for quite some time when we first saw it - and now let's get down to business! As first step enter the project title, next is the primary development branch. - -![Success](images/success.gif "Success") - -Great success! Now all that's left is give it a test run! The previous command created a bitrise.yml file in the current directory with a simple workflow. Just type `bitrise run primary` and watch CLI do the magic. -The file contains a simple workflow that only has the Step called [script](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/script) from our [StepLib](https://github.com/bitrise-io/steps-script) (this Step can be used to run a simple script). In this case it writes `Welcome to Bitrise!` to the terminal. - -## Creating your own Workflow - -Feel free to check the example Workflows that we created to show you a small proportion of the endless possibilities in Bitrise CLI. You can view them in the [Bitrise Repository](https://github.com/bitrise-io/bitrise). - -Let's try and create one of the tutorial Workflows together, shall we? - -We'll start off with the `bitrise.yml` that the init command created. Open it and take a look. - -## The bitrise.yml - -As you can see `bitrise.yml` contains a header that consists of two information, the `format version` that indicates the version and the `default_step_lib_source` that shows which Step Library is used. The default is [https://github.com/bitrise-io/bitrise-steplib](https://github.com/bitrise-io/bitrise-steplib) feel free to look around and experiment with the Steps. - -There are two main parts of the `bitrise.yml` the `app` that is filled from the information that you provided during the `init` command and the `workflows`. - -You guessed it right, the `workflows` contains your Workflows for the given Application. Currently there's one Workflow with the `primary` id. You can use this or you can create another using the ancient art of copy-paste, but don't forget to rename the pasted one to something else eg.: `myflippinawesomewf`. - -As you can see there are plenty of extra fields like `summary`, `title`, `before_run` etc. For now we don't need these, feel free to delete them or just leave them empty. Your `myflippinawesomewf` should look something like this: - - myflippinawesomewf: - steps: - - script: - title: Hello Bitrise! - inputs: - - content: |- - #!/bin/bash - echo "Welcome to Bitrise!" - -You can try running it again with `bitrise run myflippinawesomewf`. Let's carry on and add the [Timestamp Step](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/timestamp/0.9.0) that is used in the `steps-and-workflows` tutorial. -Let's stick to a minimalist approach when adding the [Timestamp Step](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/timestamp/0.9.0) and only add `timestamp` as a new step in your Workflow. This will tell bitrise to search the default StepLib for a Step called `timestamp`, download the bash script of the Step and run it. You should always pay attention to the indentation! It is crucial to keep your .yml well formatted to get the correct functionality. -Now the Workflow should look like this: - - myflippinawesomewf: - steps: - - script: - title: Hello Bitrise! - inputs: - - content: |- - #!/bin/bash - echo "Welcome to Bitrise!" - - timestamp: - -Great job! Now let's see what happens when we run the workflow! We can see the `Generate time` step title and that it finished with Success. But what time is it? How could we see that? Let's do something about this and take a look at the Timestamp Step's yml. - -> When you are adding a new Step to your Workflow always check the .yml first to see the inputs that it requires and the output it generates. - -If you take a look at the yml you can see that it creates two outputs with the given timestamp: - - outputs: - - UNIX_TIMESTAMP: - opts: - title: unix style - - ISO_DATETIME: - opts: - title: iso 8601 (RFC3339Nano) - -The outputs section tells us that we can access the generated timestamp using the ISO_DATETIME or the UNIX_TIMESTAMP environment variables. So let's give it a try and print these values simply by adding another script and echo-ing both of the variables. The Workflow should look similar to this: - - myflippinawesomewf: - steps: - - script: - title: Hello Bitrise! - inputs: - - content: |- - #!/bin/bash - echo "Welcome to Bitrise!" - - timestamp: - - script: - title: "print time" - inputs: - - content: | - #/bin/bash - set -e - echo "ISO_DATETIME: ${ISO_DATETIME}" - echo "UNIX_TIMESTAMP: ${UNIX_TIMESTAMP}" - -Now even the geeky ones will know the time thanks to your UNIX_TIMESTAMP output! Great job! Your first Workflow is done! - -But before you close this tab let's put some twist in it, shall we? - -As the saying goes: "It's good to know the time, but flooding a conversation with a teammate on [Slack](https://slack.com/) with the current time is the best!" - -So our next step will be adding a [slack](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/slack/2.1.0) Step to notify the chosen teammate about the current time! **Keeping your private informations private is very important for us so we'll use the `.bitrise.secrets.yml` file to store the informations needed to send a message via [Slack](https://slack.com/). Make sure to include the `.bitrise.secrets.yml` file in your `.gitignore` file!** Let's open the file (or create it if there's no `.bitrise.secrets.yml`). The following inputs are needed for running the [slack](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/slack/2.1.0) Step: - -- webhook_url: -- channel: -- from_username: -- from_username_on_error: - -Now that you added the required inputs add the slack Step to the Workflow. When you're done the Workflow should look something like this: - - myflippinawesomewf: - steps: - - script: - title: Hello Bitrise! - inputs: - - content: |- - #!/bin/bash - echo "Welcome to Bitrise!" - - timestamp: - - script: - title: "print time" - inputs: - - content: | - #/bin/bash - set -e - echo "ISO_DATETIME: ${ISO_DATETIME}" - echo "UNIX_TIMESTAMP: ${UNIX_TIMESTAMP}" - - slack: - inputs: - - message: "ISO_DATETIME: ${ISO_DATETIME}" - - message_on_error: "Whoooooops, There's no time!!!" - -You can notice that the [slack](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/slack/2.1.0) Step contains two message texts. One to send when everything went well and we can send the time and another to send when there was a problem getting the time. -Well that's it! Run the workflow and wait for that awesome Slack message! Happy building! diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/cli-introduction.md b/vendor/github.com/bitrise-io/bitrise/_docs/cli-introduction.md deleted file mode 100644 index 2e884401..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_docs/cli-introduction.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Bitrise Command Line Interface introduction ---- - -# Installing Bitrise Command Line Interface - -For a more detailed overview see the [CLI how to guide](cli-how-to-guide.md) -Let's cut to the chase! Our Command Line Interface is now available via [Homebrew](https://github.com/Homebrew/homebrew/tree/master/share/doc/homebrew#readme) so first call that good old `brew update` just to be sure and when it's done simply call the `brew install bitrise` command in your terminal. And BOOM! you can start using it right away! - -If you choose to go the old fashioned way just check the [releases site](https://github.com/bitrise-io/bitrise/releases) for instructions. - -## Setting up Bitrise - -The installation is done so let's run the `bitrise setup` command to finish up and install the missing dependencies! - -## Create your first project - -Let's run the `bitrise init` command in the terminal. Your first Workflow is ready to be run! - -Make sure you are in the current project's directory and run the `bitrise run` command. It will show you the workflows listed in the bitrise.yml. Now you simply have to choose one (after the init there's only one called `primary`) from the list and call `bitrise run ` and watch CLI execute your Workflow Step-by-Step! You can add Steps to the Workflow from our [StepLib](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps) or even from your own StepLib fork if you have one. - -Happy Building! diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/cli-react-native.md b/vendor/github.com/bitrise-io/bitrise/_docs/cli-react-native.md deleted file mode 100644 index 4f341da5..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_docs/cli-react-native.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Running React Native projects with Bitrise CommandLine Interface ---- - -# Running React Native projects with Bitrise CommandLine Interface - -Check out our [sample workflow](../_examples/tutorials/react-native/bitrise.yml) that uses React Native. Some of the used variables were added to the `.bitrise.secrets.yml` before starting to run the workflow add the variables specific to your application. The list of variables: - -- REPO_URL: -- webhook_url: -- channel: -- from_username: -- message: -- from_username_on_error: -- message_on_error: - -Now that you configured your .yml let's see what is in the bitrise.yml file. - -We presume you are familiar with the bitrise.yml structure from the introduction so jump to the `run-react-native` workflow. - -There are four steps: -- The first script is a simple git clone or pull if the source code has already been downloaded. There's no need to make your funky music a bit laggy or just simply take the precious time from seeing a successful build log with deleting and cloning it again. -- Next we have another script that configures React Native. You can also check our [guide on our DevCenter](http://devcenter.bitrise.io/tutorials/building-react-native-projects-on-bitrise.html), this script was created from that guide. After this step you can run every Xcode related step. -- Now we have the project and installed React Native, all we have to do is run the [new-xcode-test](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/new-xcode-test/0.9.1). -- Well to be honest that's all. The final step is a Slack message. It sends a message to a given channel. The message depends on the success of the build. - -So as a quick overview let's go through it again. We have a clone, a React Native setup, an Xcode Test and a notification step. And we're done, it's that simple! Feel free to try it with your own project and if you get stuck contact us! diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/cli-share-guide.md b/vendor/github.com/bitrise-io/bitrise/_docs/cli-share-guide.md deleted file mode 100644 index 4270ca5a..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_docs/cli-share-guide.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Bitrise CommandLine Interface Sharing ---- - -# Share your step with Bitrise CommandLine Interface - -Now that you got CLI up and running and already created a step of your own there's not much left just publishing this awesome step and to let the world see how great job you've done! - -[Stepman](https://github.com/bitrise-io/stepman) can be used to share your steps. To be honest it's as simple as it gets. First you can check the help by simply running the `stepman share` command in the terminal. You'll see a colorful text and a step-by-step guide on how to share your Step. - -## The process - -Just a few words about sharing: -- Fork the StepLib repo you'd like to publish in -- Call `stepman share start` the param is your fork's Git URL -- Add your Step with `stepman share create`. Don't forget the Step version tag, Step Git URL and Step ID params! -- Call `stepman share finish` to upload the new Step yml to your StepLib fork -- Create a Pull Request and enjoy hundreds of thankful emails from developers around the world for making their life easier! diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/images/success.gif b/vendor/github.com/bitrise-io/bitrise/_docs/images/success.gif deleted file mode 100644 index a5862366d692640dbc57c7d286fcf1750349ceec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 960828 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%0DGM6-yPCzgt65L;F$ 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 z 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 yazry2Tc1a6#&XU}^c>jUrnh_i2nJGMTS>K9V$ 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∓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Ⓢ&^9RcM9B1L6Fj zAb?dd-ujesfYjUmiL&DpB@LEEWW1bfdFWyj&q#ya{mHLdAZm9FKJANl%OOPe#b3v6 zneKS&uBQ#($whjwB)Q;gLRcqjU*D{C@61VW#*`WuS2u^~Gaq8bf;E`=FjUbM+`kB& z(xv?(ZTONHZMil`x8w7_`a&UdwE=80>ExyBpAva^gjQ%--~DP~-^tBqZH;Oq!3XOQ zd9;rK?Anb74)Xi$_K8#rQz{cf0A7J(TS>>P+x#Ff$8Qt0|L&e66T z0EcQfC{kCdsJ+QDWQJ5eRcRf^6B2RdovFscu> z&z#U%u8PfOWmIyXZ=UZo6Gd4h>Q$6tsN-G0I!Z0HU$U=eGFyxo9c56Nn{xw38MhIv z@8W1=Y)5uO)ZE34JhY4`|J037$Pn_T@>omq=331yge^r!f%r$rAW&a?cXA0`<7`D= zsIa|glGqJcmK0oQCRxOkGB7&c%0=X=#2BE|v(3@w&evALt7i$HDFaZ<<~PI{0pz30 z#3sfP%L=d^d^Dx7wBs}{;zC4UQhEZw7>^HJgmp#&ZkrX}<(t*N`rAlTKa?CQdfuEg zjsss~2>o1!7g(^WT}1}hFuKF|$ohmO;Y`0xJozH<4e`;IsX_4WBakuk6Gt&v=G@m) z11G2|{9d!k{KWKD-+#1Oh%aJ4qo@i#x5@_B#@iWoPf)9l*YflmP8}9@FKRNv zI8RvKeQF`&VI7&sA)kx0JOt9D79%X@S?r8%jnl@VS-OwFDeX+T zMOLt&CAul6MXa9OP)h@bT{qLpC%sPTbFN=~yEr^)Zkjc2kTu{S9xC=9LF_+Ks(QrhW`ydocjpJa?4K3Vf6ep> zDuzBa)~jQkZ+Yw9vgA!1wuf#E$SezvXds|VH-=+r^ypi6&DSG_x==$ST;(=Y(X=rF z4R(v-JUhqj-Mxv#r@Xt)_A_`XtT33{Q8$p;`%2 z>q`P1CIzx=f$(@T6yD`TKF2>DxCZ|P&Tt^lIPU}$xu@ec0f8wQUS@r&mVKVUIXxP! zI~x%7f(&&`CM{su6P4AQnfbbYwK;aq$=Jny%i)>8@ ze`6al9D|=GUwDigKgKHOX}4jKRsU)8iCq;`Mc0!Rw=fiTb0A97qA`i|<91M#B{K%f z90{W(#rZA1`*Q%ll&tD=@@M~xUV-h%(c$+ov`MtfcJwv#e+U{I@L=A~6N73WlOoFPO zVN_<~^+=v%oE=I;Mz}jhr(2dw^gmFBG-_i(c|*ZjJw)tD}iR&B7*xq3^kq!>s2i>KuNVK3%=qtNw8tiz+QmLRJy;_W0X@;N~desh0!%ha_< zH6u|9mvB=!-pc8bJ0MPaq)?xKxBtsTIBoWX)FQZ?Iq(vDQflyQ$Y8YF6DL4@VrTt5 zM)g3g|Bsa6U&3o;w;QvZuJ;z!o?lR(;E@b(KEABch96N6E3Z@N#qnbkMFZE}9lcrY z#1H3$m@A5z2u^9W-tb^R?rCfPh|}vEx8A;orGktbD$F?=^Oh!{V#aU#L-Sh9ARTWT z~ca_Vm|Q`ZSp5fC?N%h;fGNry z*=VM)0WyU4;+|X`1wQ1`D0W^hwjX@Bm~ZqEh(+>=qsV$CtE4o5wP|)|_x>xEgv z$Bo%C=TrR1#XCF+2}*iNO7#hk{a4*{K=efXYnJ{Un!B93E?_6I!BqzX$L|L}R>Ak~ zW=X2fvQax_8n}OY_><5{1al)d1~9AxiT2Nh$@Y37Hhss1JX~}e@EhU0bpC}jc~61z z-T}FGb?m4q8hy~lS3)Lr?xC-D5>pgoun`40-A-*NO!i!z-P)`;%^2(L6s&z}hH;hSG9hln+nF(df4BeXTKN9=*yoGLRYp5_2FPM@V$P z#t43T|0j(a_sQxV?2q941v98s{e_);;vc6KkY2)HkN1?3cxiV686J68iG^*^DQ^P4 zFZY!_eaq>--z|{m3!32BoW%EhTC9g)9}#3=P#>3DbM9}XgOopq{N3AMtOBNDWA3;F z_6Yna_IG-EH>@H#_Pf~62IhA&{!d?Xb4O7BIEm}7b`0P1n*0&Jx+b`{g}$7xVoK6n(TMi5>=x_=PCIo#c#Rx^Qi+1zqe{t{?pmwmOF$L%Wv4^Ul&?Wu74RC z?|zD3@l3nnKtemP#FXL?{bH2G;aAO zM{!ZxPESPSNgpO0NiC~P_gG~h;swLB7Wvc6s*nK3UJw+;f2~uB6O3WzI*BA{6vL48 zVnjghG6YF~(-?6m1rScDKxNru$5Gp@PMfi^Z@L#mtZHZ6onAzTGk3H)bqYANdum97 z4G|<`zcsZd0*2AunK2QqcD)hnOWmdGExAL{|AD5I?xxa4>ntv5`tJno4&QsBc(auF zsBEI}Jqzjk8XhKL@zm(l)g_Ku$$6vg)b&WFl>9-&%CW$FX42-EFXi)=U9peOf&P?N zpFOJQfC5u@UI%^qosA(2q*7BIsnba0Uv07R9&u6n{slf5`XdWB_~Yc@W%kEK66hT5 zy(p&6_q+F_IO>=hUb378rSuPT9DN}P-V4bPn`Vf-en#Ahqm1uhus|fx8_b~S>7{sh z=-KDSAQI1lt}Os*Wtw2L2E3gtO}MddFAYP2SEXx=z%?`FDnVchxvf68Y{jt=FohD8 zes#{}hIBQ>0Vysn2SYOgRp(~0^dMjMm(E1fXdi%NS^F_`vW;PG2E|aKLRJZHI9Q!G zRhg73$lL~@FK0ep%%GCrJiOyi}M`%EJ6BuhD*Y@tt0wmnqD zBHtp>O3GRw9|2C2SuV1#8D@NX7}#zQ?c7qrRZKgy=&R`5gD!pgIq61G@t4N+Url!2 z8z!tCuoHWmYc{X+q!-OQ%NEXtV$W3Qlkg_Oo!<9F?4hIN@k#8>^jlSY^-!5r(avpCL z@KSbCHJ{KMQ(b+!Jg_B>+5<6u!%A^xHOrJ*c_kiofk~tk*2t7F8;7Ri_BJ)*di+QY zfK*`s<5NATkJ>o@mfi^X_<)XUf&ds>w0IC(N&gsOAe^8`QP~%)P%C4?G(3{VRm_{N z>+>ntX_ul7+Z{(6<^p=eXVtL7tGL!4DA5@524~ufe-MLim8X3ysv72R`)z0ZD zY{A;&uLEA)Ae^e@4|*307Oi(^_4K??`W*FKE~q&3j!c`1JdKdj1@F6`iw}p-`zaMq z;<>{+#yNjiOyBDrjMj+~u1^dX6Y zfM9-BzXO$ztSCUHkelpGv5Be0i}clNqvto(T(FvKFrOr?j@_ID8GinX9iStN4$bE= znkJr89^JAj>6(5nB|tTGLSERUEoM=d0x- z_Rmd@g#&BlR(I?2=RXGOvW(B5@8+6Wxug{qx0ci}5A0@0)sj*~7I8A|WlRrD_7hc& zaB|(nw*vl=;y+ovlpp^2C@8@#;bc=3cNH}=9!y0VK7TU;m&cBHOYRO+EdF=@Xm|aY zb-L<>)<@zNgN{7qOq-g(3H8$(byRD3Nf6JB8tjwptXdC>T*3PhTkPU2?7RZ#f(w;Y z+EokCk!D`1_uBkow|L*PI_WJ9v_2?xsRunl?Z5BKJu<$^HRc>=$#2w{0@tMoLZZXs z{RLlC8aSaW0umA^_J#3+wx)Qpc!qG^|axZKvB4#k0_e6zqwPZLp;W5EJH|yW%ppx+ILmb!OmZ4`vtT$6w1^V zvZ^%T3llex!(-kC4KC9Zsn$wfA=$;uO#U-_iOlve=?(ezT)_WM_!vm!ZlmDR~|G?VDt75GhG<2JZa*^$Z5A=?HxvMeXRPilq$nn~i6>el?b55?@0XgawdcMx9Id5d9 zruV6A={~Q?h$W}tmxLP3LwbMLW(K@Dsbuo9$4j+N)JoVX11)oeMy~l+KWArIt=q6! z0RrfPc=Si;tgfNJID5|Fn48aFAm2+5`GM4G_Vcr^9p-1@@uR0%x~pqkezg%5MwDNJ zYKXHdUvKmWSHph!UJWi{pUAvQ!#=Q%H~jjhw#>%)A!dVvJ3GH0^ybK9XvxUl%~#@3 z{VsR;1@kxM?Lk}~W75Ji)_M|z0uq7B47@{vm>ulEidcX`NuMjiBW9*i%rhy7X%ZsQ zR4=66NbxU7;?P3woE}LsO@~{C&!%Kd%w!HOssoW4Q4e#BESsVVX*_0EEfZp7X`?wO zWMkEo`gWuNW72A1rv6mo59Zep=Lg-hgjt3?5u$O;o45uwY44Fow)h=}$sNn>ZdOLr zUs{yxBzLB5bpi{c=bV&U;(Lxf&OgAu9~)^ew$o{KGPKPz^_OmYFR?Bqwr;F`45D*A ze~X*9ayoUmVrRoYh9-YxQIp1QcAh6Ql>L)o8jvk71tWX z+^EUvrlB`vrsK`2FEnwpv}T&>Wg{&rW*e{9TMB3Ir)HLVj~S_tY>nv0y&>sYv&SbsqZHxb=w@FLo>WfJ} zKcMS9d|l{4S?WO#0GeWabY%i`hgQaBd6FgH6&bslEeUBcAAk8EKkO$*R=p|HS0m|zk$*f zs2f)`tkUFQkt@vx+e4Fb?#3}HS{r@Lh2ELyAWP4i6ZGn|TNqkKEi>lgI6?p12MU^{ zGvpBAx8;ubn{;Y^3lQ3~)P(+A2nHbEjPJPt=hAnv2-`lu7D`yU)T$*06>Zc-0ns2*b(&6mBbU_>TZ~o=drTtdQ2A@UFbXn z^j4c!y-c1MHShkT?(gX6){x})!~9}2QRF`L%#HVd{og$ZWU>@Y#=HiI#qq{1INms? zmQxV^ZA|!W41D=B!TWG6@dI%7{U|bn@G8nXMZ>Q~A3kg3w_>LUOhcq;z~Q?Vx4$mm z9`lxdN)ti*p)Ultc?w69#O(^`8B(1F#IxgfWB^j<_wl^+QyM`jUQ5!_ z05aI|*UEUpDza17mMyVC3O)`wRC}n`ap zL}t$P9h;oa(gv!Td1TQCHedUX(x#m!;}9RYS4GP04Uo1P!g~Kb zY9v1S)9}RoKl=inMwSk{%mcSSM0)^tPN9kCq3vhkM(6sh)~*AGzB~Ntbi8uo#-AL| z;9F<3dw#ZMJ&QV1v|EtK^k0WU=FZl|D)fGTpU+fm{^St;r*-no2*a!4E$Y-pohe1&P4L8Fqp_^pX*zV4oD3yD1?p7Kj0ayuV4`8X|LHWxTfb_?uq=w@&q$9ohFCX|7729&wL0a7a9vvq+qW9`N@iUDvJR)y;VzV)jNGo$0i>$+Gx*`U|J!^WzcruzcPy;Pnykad(sy z^79QIv9KQDZU)M8l)u%W0qEp7a-pCeda}1V0rq+;9>V3yy>Fx)<}(uB7_H&kJSn zjAIPqU`E|TuHdBJq3fH22l`c@rp8FQhlwWrL%>a;BbBmCb^e6B${q#SxxZ>Wnpm(A z7BAyH{@Rovw~}eRD?Sm|z;niM1@|8bFkhH5*tJ%6x3n}lOY*mD)|87)v-^NA`B}Ws zv2m;&G8iDCf2OV9y8RLJN$;QbP-3s+B$SUE7Q4gZK-1zp3BAPKs+UPWwH;B2Mc0qz zP3x;S&shLUcNeQY;9rB? z4Z6D#@G4w5U9nS9Jx7iiH&>15{l(j(Q=;rMlZ0Qq`C?QU6G(Z4T`;Dvv;8yWcxI73f}~sQgnn<;%gexC*=aB~fwD@K&u+-8z)yZ# z0;{&NB=4?W1bh9Rf&Ze8%GzX$a1S(nL;WQ$o2%$Pix?kvy;ZU4mWInSJ-i=j2B-g@H4YSH-(gYTN5Gn}05@ zZHbgoYIRlq)h6An-TU_EQkUrI@oyz0ic@_5FY&>Xvd6tlyBZSjQ@fUp` zTE#2^bzyyPhGej!c{1rs^nji~iTrO7XjNHoI_A$d!>^5GpXNQ1GFNFS`!ox)J~m=w zEUfhy<|1t_124p#YtQ(pQEPWcE2xsp2Igb(3UT5Ivj?rgb0Iy089r-245F@M@q4b@lFz-k4XjIc8^6;~pQJAD{*;F_a#O zfL}cf5xoC;hV%1GsluK1e`SIk0{zQvo1^J69EJUVeo9SeLj8VSStc3(`p&y!pqr#Y zmY2%VV-im`NwGM9bQ5EU1Jh5#L3EmE6sRJB0n=^e4Nm^{*x5WsH8kOqiEfm$WVTPZ zDH2)n{TaVh<|RBhT`NlOp=Ge%>} zyy9H@V+{_K4o76CO8B{+T9J@45?$!xFaLF*_@51lERGHN0;0lfiR69#p7k-234C)# z+m!VF^h*Ys<0Q`qo$u;k#M_L))fjpohzmYSsyaateJb)Bx6YDvE6TRByq7~Mg}rY} z_Y3v5YXe$z-)3nH>AVN=LHqZ$CO#Ov7ezU}Io}W#%zk}2mdp6())zA3&-aD$@#B9; z0v%uzSEbD|=iE97Rt&`svuQT<3?U4gS_P06T9tyj_U6$R0xHXXAXGpZW*l~Dn%i7_ z00p#`4AOTkCm%;eO-$Xb8F8(U6I?BkQfix)4^`ta{iBtFw#U@S7RN%NWB@C~60`WzP%wa4VYduavRba%h- z+ehfWj=BC_J0kH*rcO{ghvK>LtDk(ouTOu_Nr)ZRba{Q{zstIBK2PxmU%jmDtd^w< ze>KR-flYb%kMw*O9hG?6?R13E{#_$TE?zR(VEhC!BH@5`Qrlq{7@|3)c zcQQ(TscaA8Wwky~9b$%jTn-kyLT<7e@`fhemn9aqkIXnnoIShRo?F-vS)~->V)uGN z2ho4AN@X1|a`vB&UKpVPKy*2W-@sd_p`qYhibr$cVB=(9fJV1QmC$6J(u}m3<_hXH zXGRx$;9%C}#@LtdZp^VgJsGc+ZLjmx68{eQ8UI&NSs}q-VdHt!;RSRMMwPxLvbm zeGn)^NTN!|dJT(|7tYqzhslQLvN`RzKWKg(CO?4PiukkefUWqwP(Y3dSMQ-&+lYgT zfxSQ5e2lJa6eU$IG~H~Nn=_OM6i%dBiZmpUoPawZBQ%laF^vn3KhZVhOmAJQ(Lejs zUr;$H%+9Q+Bv4vPMAO1j!?TqrLfwGI1R}_UGvn*ij`9n+g`dju_`*r)`s=?=%ve^< zNxe|h1wX}Kq082O@@7U$re=L*;9XzU{)WYjHSg#)k z#t3Y+Dq`WdPZ9S=16Kf9nXo_*PszVpX1)pC+EVdsNn(t9y4Y*1$u6C4MV6K>wfKTw z{koE0xm8WjP$0PxY+V0WqNQRTu?=zaNKUPYH5D14M@vd`K^0P%{T#~bck{egpG zw-PDwwwhMtI6?aCpWC{eYa~Q6ZQZG`B=DniS=~~ zuJgu)vB|oF#U+b7Be%^(n!!hWyy*) zS?315sT7KFT~$*bqE6jC^;#7aRVP7yYJqXT&CS1I|B?-wFjp=2Aueck*R?on^~^+F zm`>Cl0*&6_nh1ApdmkoRflD`RslSXytcljKYtY~yS5J=?h%U*$ZM1ioO+4l;S9p&V zGCYxG3F{cbMRtAJ7l=?t2v}k`8Ty6$^U*)L|38=+>usWw;QQ9&4S0v8>7*a+m-Nn= z?+zLM;u|M6^a!s%&GN;63^R{2>H`BU;|LQMTbHF_M#~CdCrA=e(M$mm_757c=pLX+}r?{rCO}wnT#sqR*F-xBotNGHPrKQ4o)3RC| zkraX76P`bKs>M#92UV%V!7)7&A_+2kj%hK$L3`#OYP`bF_@`wArvZ_sex7EgQGiuq z_{+ke>?ntX1P3~x{)$oU6^PR^k8+N?^ObX(_h`)u@ql6w6>yKJJu6dCHxq!Y{_PcD z*p4Vx#bOUUFtK%%1!~C4O+hIv4XX= zf80T+>|@J+e+%CvgE9U8f^f!)Sus8(UtG>^H*d~hsRd?Vv_af2T!@S&tEu9fzt75P=K{Odup z(Qd$hM>9G9RQ`{f17*RzEpLT?Vwx*j%T|)$lBAF2d`#2ngy1`t=^?U?oyt+2#{SQ` z?Wg!sMAXrzsFCceHh1IO?eRoF50mAsLed)U~RbCT-a^OdW=NsT^99amchZSW5VWOyJF;H!ip$> z*B;0#oU!^#-n2V7-jxMPWq{rv1706loO(w&RJ;Tu)ocHtn5Sg=@j>2LK4;&avD6!5 z?hZq~hDcs6zpbhvr5nSPO#dE5ALoo(NTLdoM}7!|q65qMXuu6&u+$%hjiR+p(^h9s zh7B4I&skAFB-1v&G+ZOd>SNugWo(>5VCdjMmZ1Rj;S-F$W0Qi0!vBGI zF-!q<(t%cn&V|iU)Um7+yj;l99QLv*?;!eys0}!du8wTf)_y)(&Va$&AO4#vG>{%o zh#hB7GA8qWiO0iH&m=LfQrT|f@q5j2*o7Fb()j1r(wnUQgU>o+xyGBg6s*fsCpO5I zk_10V$rluyq)7i3+_lEu9+v11leD!Ry5ykIm`-h)l~Tu`IQ&xv-Y_i=x`|~` zmtZ|Wv(_F@T`e1Ld~c(FVq43=^DnFD=98ep&rf@PfvF5~0+J53@Sr5Z>0PrM!5yq( zwSLUZF=Mr_8iC(&<|dDwZ$AcbKSpEYi4U^gw?RJ!UAEW%Va6Jz_9I4iFKb;8y3QI1 zu;CkeRw_-3mwonKy-U2yW&*tf%$C*K%Ykxp}fh6K|4?fbgi1O~L z=T(ls7-qPloFzC#iCl%9HvA8B1DAlsZD^qpJj-)HgCDCw)92Y4SZ~PTbbyKc_fwkL zCWUl_sHG|VumJ0+Yw9#ET5VPn1KRcS!d$MH%nH?(LlR@ZX{`-OLKKwGf=0O`Ps^io`;TyCEf~ zYUZx0U{+J>feK4Z3Q`n8mGgFaqM8EOT8f$;Tkk znQcS(Lj}uiwsS=`XC%bCUIG;rtY`9Hcc0)IKb`uo3MR$HCM6K~T|skvd>l@F9oZ3J z8ZU9#4cEzVa5BGbu%6%lKr($Y=yWwa$L884G%HSG?Qk%-?aPhqLD4|8KX$GYoSh4?>dtJ6N8RviCk$xBq3DfT}hLtRp;s7mFLNk zRnP9j7EP}9KIUBfC&1p_6< zO#?ng8=`f=XDHfH$uZgN**M0%sLhYHQgI;6DQ?%nobB$bW}WI>=bYH0=qC)`_2P0;Id5sMgn++92ilRVOOurLuv1;$ zwdj-7`!h(vA)~9GP)t0h`Zq**>92F3-=0_HuPHvgw&(c1L!qbiQ{=9DN4UZ(Tx#ux zP*df7Tc)_zwOfDuQRnmhJOkA4bN3Wdbd~$mKTW)x_3rpIamC$AS3J@>5#jsvI9%THiuLBV zk!vKtdtiEz78}g-GyUpX;3NAp73}H~+`%OCk5=4{VX!m2Ce+`}@(+bvw@>XiXd}#R zS3cPL*?e8j*VV7J8pE^SPzd^OYQouv=BGe}d0Q!K!1I}0NFc0?P71$9qHUox%Hh{M zt#7^LQks2s;B0qT9;qvp@O`Wv*m zpty96)Bp6-F(kZ)>iOy6oO_w_u#E=aqS$lUfQqCGQUU}ZZ-IGM~sdw z>dfKsc4}|twQfNo#vvm`NWT>y2RINGJkWRj(V6yhs|W!A?A9PcF!V=?v~?gB6i7FE z+9YWnA(5dLn*7Pe$hXX3LMHK&T^T-189)c$2)~IFdT*YUwN2;3Habl^sk#&v;rxuT6l+{FN~qf#Q6(#CUJ2#LV}2~L z?7w|(j}jhvN1v9`><9APFv_~{Q_EdyHT;2h(w?pkoUV6l2+{2Gsh+ZGT#LQG9BU^+ z`vrQ{95Ua`@!u3s zrx3lK$$nq39dxt0(NKr$^*7MUb+%qw-zzi8bW7!S&_6t|$8?HZizdigS7SkvKu*it z(j-qqQc~_&SF&SzXZ1g>VH-OpQ&t7gQ1v*!F&hwgQ+`NQJD=%ASi%aUBCiZA$-LWM zcDiJgAnZOk$9y9G7{{-;d>^o={Oet@2N1+#%F8o2^7oC<2oU~weU+pQD9BPr`a#Z% zCKUnKMni?*kE*9{Cb`*a>Lq8A6JOu%&n2JJ9y`AJ=6RIUyrXXSX6$p8ju%34&59p9 z4`SDQS}keDZZ?f`ThHcqO{`9UDi^-My5@bxxPnDAq7vBJb$pg%rYdgMZDk z*p{qUfqm*|u~&nhHe;MIZ{pl;K?WY5{i4;b3-8vQ!))yc-VEx#V)gGN`OK^p4Bd^` zLEXZFG?@LAT9=b01_DN53{uqO&x(-zBV)%@BSD2El_zKtEf?$qF@B)8&PB0WZ&GeZ zRd3zPmGOg_j@AeYYjtoQtXEN=XL{%|diciNmZ@yUa_^69;&7UNFB-xQgxvY>aV^$5 z{3a#~-)s1`XY=>K&nNY|+{c20Ya1mi5e?PH`gRZ??on4MkA9x4!>d=I*(YuC zY7skxJPWUvZ9^%%f^P~gF8O|i z&wJ%EPo4kMMxmH+>sbi$Ui-Yn$vOQam%G6u8q313MWm-l7fzUh5$=Rl&VcvKQHt(b zxw-h&MOO}17_(R^Rc~)%Gv)No#2`)dc-eXral35L!7}+Xt~f7QEY@CzCy>VN2jiBd z751Q{kvJ&7^o|7w_g6a$!M`|~880R}gEv;*-_%-ajyQi;z5nywn9y=d-czh)Nl79D zUl*sHk_EKgU52!(NYDXf5J~Zrk!EJi2WE68$C7hwrn{TXw`s41GOVZfDZK!8{tc!_ zk>+eGUBLJ9=NC)5J%?DV92`%}^iNa)EBoi@%ccjH>K7^pWoZ`*1UG3g-zV3%u4+{B z5(m&}`Vex;1EpRE=A@4=P$0+eTZhYQ#E3RL`o)#Pz5hnJnZ(;-Cs+fx&mr20w;$st z=*z&br+*}!xz8=0-hfmq)>JK;Ax~HU_^LY-Dq{O@ z=$Ri}e{u`6=FB!ykSNjVY{sW;-mT0dCM|2qTHy<~r?pR0^fszp%SXC|IU}gIyXEw>5gRdebB#JR8Tu9YULXDw? zZ7gM`T0yN6wKk!eXDvo#EgoraNrpCB3$P7!lOm~7C@-@qD)zxZ&gV-+-nFoBSD*}J zPv&)-r{>PCUZwIB1si4=rNw}AcmW_~A_X;CR+%{e7Nw$J=iKds6c)l7E{B(JG_A%M z!k9IHV&!812jLq+{5|o^8sOe1qHfc=X|Q13jf{XBE+rEuKJ2MI_*d0U#JkjBttfRe zx4l$eS)|qVS9cK6PXt@uP}6U9&pA8>=-7Xo;m}`6<$TJJ5xYER>38TRI7|-!up)_g zPDW~%cqbTayumGweZabSkb==879q}A@Y4c_HTEjp2T}x-LrBv3{?*cH3q};VYZaJYbb$e%jq4u_% zZvJ7LekKbq{u*2{oEjMrYrWE=hfG1%)<9NLcwZuU40EETFJVV*K|gYf7e_Kr&9GG* zTQ1a<%kMQ{F;k&hFVHxUizyyW2X0gu$ zhR?26vN;$rL0qlXvaHWQP9HhJ6-M>)lm}BAGTG`A zq}TgqtoyAnA@jLTx5WM4G&AGZf#beY(Xto+d=q6(aCJ{qIQ}U-v5CFg@&$?x|6403 znIQ4-w_oW%5e(EEUCHg@Okp+WuENe#8^6F1)nOwV?0!g!lpRfQnPFNB3krI3m;7Ag zE6+B{G*NdW$IR(V@|D?EOWoo1>}&ZhBA|Nf|5JGV`Q|m@Lu}rLP*u)=V_JZ^A4@@< zxdy#ZhUK}tz?o+$YZKBInv>0_^E@yMpDBno%L_KwR-(}Fq{V6;tkBeo=lZ!6VNO!A zTxxs6rod>O<>|d_**bQ~VITeudAxF=(hJQdSL;UDO4A}O3APxM=_#`TEG1&`-gGi+ zLdi2otGOIRb1F|mz^m5Y^K))NhVNGifh$CKh`L$G*kU^`Kn#)GeOdS#NVB-(vjgU- z;6vOGOW&8$opyoOcei)<8K3fG2aq#zH&jaXdGd;*El9Ig>SsupxZzXEpVxMc$&X1s ziK>4iiNW##L4t4~6p>$kP&#dDq(82YvmQL|gb_s=yjgB64zVAfyHu&w;I|Of6Z!8@ zunPEZ*UYRLEEZRpDwtJIw$M+}u4jfMHLI(8 zt_CZ^XMW$(cT}TRa=(wH%8w!0A1CAZ^pO_wd&W?g1o|RJqTp{c`KYaehuGBSC z^$0`x6oj-JW02BLVH@+KvS`;``tNr|BB(x@*Fxu3(M_!kb{p%zGOlJU_V{F=GwT?h zct`zxHH+ZRU=WRthvjt#(9YKO0~XBDI1BYK8~Z5UJd4%u7IGPQ_N)7^_{=NeLH5c2 zX@ym-JAyBhPq8$f@#PayJ^>Gw1%UE?Z)(51z#r~q)Dj#$^-Im8u+#sBNQm~c+;Is| zXmtxBoD_3Tz)N8q1StoSNM0`fu}@uv(nv;G9;i7C#DDY$0I9HS2K1dY zyZHn=S)DBp%W7-QYAp|ILSM-|xH8Yrp7=#f97zc1L6TZ@fs|3>MUyUqM=yc2AE_$hLHZ{W?Ks3vblad z0EEV~#`^Oa;DP&ZK}o5s$45Wl->`_R&RdCb{P{6lg@=4@r06mbViS!((qK+B*oo$w zI0|YT3cALP>8~U%9}!nmP($sU&u9oHEUWY~YXOpNe8~3MkZm6mp>zWKn&SnS2|xbI zBcpdYsath}NA$4WXVS&^d40aAb0$+<{kTJJgNXCsQpH9u9l#j}1~cQC&8iN%!Vrx+F2HwYz1A$15aaw)5j!qf)mG5?bjE4yyQeDQYl|4CjUZ3 z=Q`CE{6yx~)mA8XlWgUcc@jU-9M|>lZH3)yK>}NdVD9pUov@t!6_9AJZwk3EZC;ym`-N3mkY{;0ImsFcl!k?0ABnb%l@eHQ}zzqq*o6$gCx>6Y(LAVynIpI|c$wKyUXdt-?3xviuy)xGkZ(5;h zlW{qlqJ%)77(DshkGdn&-J&Fu%!V?BVv%P+6a8g1_A0gXcpTxU8gL|RejBKdhS}=f z+ern78iF;kkZ=x`@H-7H*n2`Ytj$bdcRBPwG&^rVVot35uii!ht%&TmiGKsxjyX5P z&0TJ#?X##HN>1%vot}!+{~BrO){EP4)@1@p1Wql@%T+M373zGpAl8pl9NkwkP1FHG z03$qE`!)Lw_9U6JAb*KpQLLCkgkNe{j4Y>4O2`U}_BD5vXi-Z-n(?Y-RGe^gN`P@2 z_$|@OaEt)|Vdcv#0s}xx5gA^zdMVN7uhtu)L=EK~fs3$RzV0UUz2lla_Nc~g%>z(` z-LSU(upX1WWff$9bZ`C}tb$lS@Ht>BIai>wv8&ef-&aXqLQmyq-rLw)=^UCE9!c8o z3$hzcg?)K{UYI>_Y5nZd97SZ~{K~0M%nw1QaK4mDU6&2U!wVBpLN1ki4Hn~9ZWwnJ zSZY1wSVw+O9 zjrq1?xrhk>M*)L%JQs1A<8*Nj9MuXen2yWl5qVrKaj>g$EZ-yIw(qm{_4G?E|%6!}SXC>F|J-YHU>6G5+hR3ZJL7IA<V+ypa)6O+XZ&wC5A}5=yGevc`Q-MM!ni-_6v8EB#_4R1*oi(JmKVB%9NAW!EE z4d?g@h^n#NRGpL-8>}e%oGbRGV^B;F(y|N_@v?f3&V&G%UkdP@YXi^(f5%d=ZkZQQ*QQ- zf|-)O@aEMIkwV*ZX#V+Zjrrind3_{10t|8`0@u6WxcWoE1lHJLiF4eFfgQ zhXFi=`(wzgoC*T^G-QZns9e;E2o={TnrsY*Jd)49LP&R=w46$KdFREg)B&(MK$gM6 z^LEm2Ch%?yqIDjz!2}_(HMPi%f(?a+T-fm{VJd;NROxL~m@jxQko6}Wk?MiE<;8pbXoH#rOLC`l7 zV7mU7M7ooERPVHT0w;@$ko&?aj1FHqWG(y%CB>Al%#7B(9i82I?dFlcTnUl6$)R5k z(M4h+TY(jw7bSifjw3lF&@9yu|D`(aGqII{n=+aoT%VkWb71_vv|tlxnWso5T?Dhv zr}Z`(aGZ4N)Hj~=g?`wbqWswEV_9bFvIuDD`T*+QI?tIl{#Vr=dvMvPj}HTiJ%1G^ z>DyE)DBXV-^Kc>PBDWG-+{M0RRBdA>pvl(Y0i8b-m4k;{KZ%X-oPJ6M{=@^%KH=M@ z#Iu#uF*UN89rh~AB7<|;-bL9DyHx)kh9;(6e)`QsxD~u#eU2yxBh%P*@L(_J=Uu+T zSD!rBy8Zk{OShpBnC(Y z@CB@tS@D~zF}v?fYCySpZQ^K1!FV5SxUJ<0PMZj6DIxiyP;UC2~&cvVs^8C6V zScvE#{fBkvw~)g>biVr_kFPLTDHGhk(j-EOY{56g)Bjw$WKNiK;N$pl=bFNL=R|8s z)B5Y#lWG0BFC#i`8)Vnr)(TddZB_UXRa3eNY5*y9e<{&=B6u+xSV|XEMzRVxgV~F> zp2j{Vz23}T_o{b~tCi;1Q9o$9B~(!1%>d0vF z&Us+a9lc8*x9anb-#V7*MQrzyYKzpXEj<<4h7~h%3o17f<}gH{==pJin4xTXk+OGR zAbgpIc<=2!o`4zr=F!;XPq;leD%04aEE~N(HltLOx^|)Gme`lYkOPb!{4wlR8;={O zeKDt~bFTR$NrW{++`?e}`N`4D)8F`+cCv89tH!fo03*bg=<{6VzISxA!(S0DT(@+1 zp)IJ|qTeU$c<#~q;HWPJnk0-0+bGljz0(A^9QNN6n}E+uAjb!z{y0CniqPt#%bzNP zo~GYi_Wk(!8BU<^$zR_|OVq1;`+qlow%f>xL|GpAgQ{tJ+wxjeZ-zX$&}#_5F4sxJ zA+F8g*%122TIGVAnSi-W8S_8RIAoFf3GduHGH002Lz)}G_mjZjOj-!Yf zIS*4Ty(+2_ISsJwsVWd8fQ>!oDk()UxbBcexnoh|nNaXV5R9Eh;9*lPb#7-d@Zq+w zb`fQ^NuHY_9Hi2(IvZZGyVHBRM<5w-T#B@X*p&N8zyA1xv%QRrzXU;$0H6?~)hO zFZra`mUg=N&9zILKMlIMl(Vl)T0TKYqa=g^X*rjQetq0b%Hw{_7#xIX&%~J`ucy** z_ms9)%#ga$m|?izIcIY%=Rb@Tf$NiV77vQ*Fkzha=W$@si-D^t`SA%<9xhzl_Js7E z6lUa^p*W2xrAKjh?9>#H*#GBxhYDc1Ms7$hYUCDIm1vAva+YgHhoq6h;SdMMktmxp z_Lu!u^kyCZY*c!r zL8BL&s{U&%e)yz8>2$!l|Ff3^H)ia-o~BK5tX!b|9Bl0@Q)gs48?h8*Pc{rR$wxh9 zS<+f?yh8`61@=uqmO}F~bpj&OkF}A6;iB`u?{lCsHtKqzOrmy({x2RYAgng?T`Lp6 ziX`cX1Aqe78G!gY5^qmiLkqLz%%|REZ=x*$|;}Q{EY_ot2^&<-^dnr70PBzUqq(;m~DMsm1an z%M^bel~9mSD>1RJ7nfm%#R*kN*E|ZVxAUD!T-(!u$eUv`96~|4#|Bu|vtD!HXncwY zPc?;2eU^~in6mMGOV8x*IyCQ&jf&QT-WiKr*Bo|RCuxs7pL08@)T#5R7^LD$U$j|0D09sr4nW{CX|swYT0E`qx;*bkb;VyE(WQNrl%|44QcFsZ>ugOKZ^c01d-bs z=D2I*&T~(rgx%g!)3VsF$1_Y-|Wsy`g7lwHe%FKBI_+*@a@^&B(1XH zr_of(?RoV?%{~3Q=6Y3lnkT*y>u{y#6H)wi*Am7^56?<&5t zt~2@!&Hn6ehg*L+-}3@w03gBVUHd#@wpqa_pwE-aqQI4@tK=NJu-mMKz8frE6F33h zThXj+U)PJ*>PUj-IggE`O&{g;0#Bvo=0bADO5ZNXTB^@KS?QN8`w)})OPL|^yFO2N zH{+vr-O0-BAGJQ!RyhbyjuVXf2+u_!>U08uVaI&s0Pa9j(RfBGKS5GN=sliQCcG%Q zok0;l2laX>ksh@8fGbFTn7uzo<98k0IQXic;1m8*q1EYR`zqS3Y<0)o%XKMO(VRsi zd$+O`n5E<`Muv1}C4cHss^eO|$ObY|*mf2BQ)CuZlc(Wm`8BcylxVfnbv z`M)?QkGkynp0*?mv%BQtSVr(4)EeP#!0|Z&fT^JEU*XHzMZF(mTEezu)ZS8uZy!~C z)C_Dk`R8CIx}HdnyMY)yf2C?^{~KrhtP2<(w@nswtGoH*_x-=!Z=dkZ(1jGE?|q@E z8!N>*kpg9f{p`zyxZ|-1m9-90Px)H2JfB&wt&im@+;2X;tY2UdyXy4khD*qmB4l$m zg0nyTQ>ZDV6%WHSk-6BNaO%6=EXqCp4LIYN{P&)p&oyzgo*pA--pSZZj8$U;sq_B# z)tMrnfM*pxxI#7#VC!!uAug+I75!M0vk=G2-bTE30@+OtrOnDBLa|zg1(YjTC~x-7 zyBf-btoeirtw-RV~5CINCWvcSLXdoNRdH-vsqYGu^|=sDW!> z=(J$+1;%n$9BfPs=J)`BbiM>pb8b-GqVP8OpT5S3#e~`!Qn~(S6VVc+Qwf_@`-JSN z?hix7?*K#0dlje5{%95++J`*5fJ|Mx*~N8Re&mimhPV0BU%sk!^Od*t*;)t5mUJ&J zmmX8aX+EIY8u^olv)(CTM_rA!IXe%2UY=E0jfALBQ*6YcvcT+>Cx!rHbgyAMi>;o% zEf#Y76_AzCXabP#$W+XNeq|rqu&t(6WA(lLG9UTJ?9ODT z&-n#Cz$8F_EH+b&X0QMtxmP^w>=8rL0Jh-tZuxsJ477X|JKy@H_n?OG8|i1+YcJ?Y zJkk_^dl5y{^DQQu*_q-}36i=AgA7#50u~YFD)?RKfk%WYGPM88)g<25qU&>+yfp3o zTtS_$_cs9GXkym$-$wh(oXIwT5Op;H`2sOCm!oxhP@@`WOdYax;6D+G)`PmE15HgN z&G&%i$F5pbUSc2TfwjPla2Yu`jis)4jo===DdT z5=R9uf^E@I7rkRpM;J)t=&2&g^2sLnN0mq)koN%(Z!#tK0Z*Q+re)&Uryi)WNi>k9n#!Jn%W$uoao1u0cmHCs*7MO zpim`)XSD_kwVJzPtxWb`8%d`UWX^MU24b|9*lYWjWxBxW|JA3rnyH-A(0Ub3wylFa z+Dy~;B4X%$7CCHnUWv+owAS~AB0y$HXTe`#_chF0G#6`8Myxv2E-pIQs8vwCT<|(u zatI;o9RS57K#3BlQ2F8y@{q7yZAEJo?8HRXN^}H}X|DWm#fc~ z(qV~~&i2Y6tBGIWXeu31mRF)VZtOjNCjuV2q0gNmJ*1(0A8k-l2k7TzsVim?_JfbX zCJfGse)XeFJvM-Eq_rJT;jW!Y)iUQ8GG-#p{ww@xRITo_{Ezd94oB8_8RnEsj3kp^R2VoTRP(Z&`SVHka!IEkeftQ zK<&vxLQw-c6ATWd%!)Vg2g5eFgCmh#$CXxBfN}X_os^JJ)6rz!`9~hOK5{730y9+D z-527>qlvFPYQ&7z_;DBinu~uXRCZY5@l~Nhkz4k#o*Ve|92`R80GrXIM65wis#vwk zS+C(up7GItNbxH7xPH_p=76PSc)?*`UGo=RRld!$W69DLV6F*^d^1QML#ckay(mHy zK0Ra9+nWlYEhKxDm$9JN^^gGl zd+o1kd^EH&q@SmEPFR?AW>YDqG=NZO;Vmqr7Dkz>FH}-y=hi$ zzr=m|jK#U7FB9f%U(p@QEU#0WJ32>}^H_kBN&H^m^>+7-#*)o|`q&Qjx4agyuk!oL zbjwUjVWyQRQl->2nm{aAEd@kiP(m4_3epr98m!%Hc}<@o$YNCsQsmbmyFoN&{jFth*(kg-k8RX%tG^ zcb;G6IBz5VqE$%5b zzMzUD;>h2_BtsNY13=DBbIAdw2YQh-)jbR0k8%y_;VVKle)Sxh!qfqwGoo#kY?xD} zqg>Zd=HI1A;00a4Q+ehX7Dlz~0bU29^t&t{bwIi#@W- zz#NFnSPwp17T}r#RM)xu8A0)sb*K+2`@Z^~LH7JGz zU{Btjcu9<&dPhsb5ugqU#BYy@Y|d18@#{D-myn3MHw)%}LMtF(4k7g<+XHAPb?%CG zdL)4Ch*X_BI8$9*k{3f0J*_MLqC#@ujig2?x1q38Fvcy=RqAd|JxM_q1x|lK!CKq9 z=m9Qd5dU%NQmr;g?agGU45xZI=v|#0dtwp+N!m%^+{GtGeMoxG zfH)LyGW5XiQvoe&0c&>$Lhi^{oG>?000nif|I)Jy!%8|)^@l<=b0=gFgB*d+B{1$% z$^iGB`Sa4+y0w!fqS41s|pfhSmv04m#A86@C1AQ zbL-W*QT({XMT@l5yN#;}5Ysz)gPV|o(0PGR4z$)9)3FNET!Ty!pNr2RhfoUZibi1u zw^sAPTe>x_o44;Ign0HUt}}>i?CjL7sijAxCXKzACZ^6X;RwCVNZb1v!9|%Z8&{x3 z&@Gbcy_PlSOBzq0ZZuB8Q0JSgr;5frjjFT!`iQKL0ZTFfwPhl|^+;?`UCYg-fWyMUf!F#oE~G4k2JDd?pXAvpoW2^?#6k(5*}@$N5y9g`#6&W-5Hafq_ujD8~a zuGLw`f-j&gAG@8NnUqmOoo)s811&#dRy-z1QmsIeIz&UAH#Ig9qe7N&%9WHSr+mrn z2m@-MDUvkm#R<|>^?zU_IJHko2AvezLK4POX}e@tp#vjAU!Fe!x1H?{ZMdDDdm1MYnP9%&gL^ z@s}n2)K9!trZOMQR(bWDE^6M9emRyJYq_!f{hqUYTD-W-MV_UAoDo2Atz166`r3BY?BI_< zwn^>j+OZ`d_+x_l~APw{1q}r?gwIp#;0pRvEZJU`^*26CUKYB zO*-O%4cob`vCxzqOLdSjZABt97k>~Wt;;LvlE zxfG%RjDoRd9Thb(KY&HNSxBgz$?qX)eF~;AXZj>l7rra$Kdrv^if!p*=QKm#qQ1}> z@QQ&wwQIf}k<_GsL?HDjh7&|%K#Fhstx1D;`^z$s1WQCWx3(C?Cn@+kbBZO(zL+=Pp_ma#7DLA4iUAm=9HSg(ic|?7rxq#j z#%uBJ06>jeuAi9|@rdWz(cT?oHn|O!n{PmLjM&viML}>zog$Ksqv;6K%}T z+%;-Pw6&}cEI6ai?$m74I#P19*(9K+7lPVb`l4wwIGH{j>Hll8wZ9VvwvX3+`sLK^ z3%xwNDovyn4@LS3!$su}zr9P$YYi70CYfvZ_w zu|1EKSh{y@`;z9%>^~d9vm(8|LuY~GYassbJIQ_gb?XVHq;|fa00W}vv2<>*C_|VW zo|GfTNQ;{RJ(w_;7IqEM-iO6@&GGEcb%kU|h4=69_UUcT0XgRyVi{13p$M2SOQBE; z;OJqdHpZ31x6^zpOvNYKREQO0MIEoPD%GVbWyFM=uU*utjT!3IGfyAsbP-ZV^}?>H zqpSxQi&t22Zm!u@ZL|7U+4#SB3Fa9}ABx!))r$bD8-1LNqd#>m(r>7bC%_dqsIHt- zfJ3g%G+CoG=(1=LfKsxlwtYmSa~0=0+>0L0qEx%5^_e7Pb9f z0iGhluHEV+%W#qF`@?(3jH`}!N>5}(j=de6iGsTCr^k-@T&2g5UC+yrU-$+0#|N;0 z@(BxxQDB<2VU0Z0L$q6gATi60(b1i8pGE!oU^#b2uf6*_dPA{%Hu;;;O(@j-363dY zmtBAzG-COQ$sC*phLs+vZtufH&fqff+W(v{XNOND1Z0;|zND#`exF{*V_*}F3m6*1 zfkhmr&3LulRccFnYD#Tt!;aK;lEZvrod;6ZAERZja#B%z;7he$#4U8zS zgk1|gJ*!DdXpJj1nMi;Vjqcd>y)X)tcv5OO?s~2V`d$DA`V0^H^+;<*DSzqsAWn?z z%CLozxAM;zSNILPDluUt+U=;|biE)_@C<9xI!r`Wr+=9>YJJLdNOivO1~9hgF3{t6 z5?C%p4!u*E?u!&!$t`R3m_CZ`iz_@>^*QPhm?V=YKdFdxm=+>vR{6_4yE}w6!1GOEZ$+916F#lPN6ir>a@o+F=^3wd?9qUkMu`J-_2A1F5FE zu{DwxJ^EmExC|zp?bywIwqHIY-$aGwB+)bQ!T5W=g+yvm&MBL@^42>}^9W}FF5ONR zcT@7BnvrCJmB7Yuhe|yT0k^8Gruy60mJ7@7ul5o`DA(lHgqXYeBaIzvnqj;-=?5{) z@4XVaha#*hr=rpg`khN}w${r`_b);5d`Gx9&oga$z)yeN&#kb@e8Cxd76Gf zg~NZ8BH&n)P`}pp62l}VJJ1FQx!Ndm%)9Yk(zYYu$ZwZfP~1Wb?BDg;TQ%IC>LTVl zoCCM_BPAY7?EMraCLx8&OZYL%=Qz|N#PsJSz*CnL*YX+K%1Xpek-T2()vM<1ek1TH zDgj z1sQk9soT2NE)*A1-PIc*){RdOmqBcXtdWzb;D0Hdd)FSBg5BZJ$^Q4o=CDO_X^w#uM zi{Edb(I4ga0E}y-J!g)edz&=Ius=bbw7W(-{f3W4cI`dmL)Z;&Z|VE@Q#Kp9d}Q=v zngpPU`Qh}M>9Zazyo_OKUrrBLaGullngDJuKHA3_5|`LYfdU0R=XcSR&Zj5>w8Fdk zJQ&Fb+=C7xTg3#B!jC*(;S>>WNl?SEFe1L7E{+eQ-o2PHxn->8`9<8fi#&bTO}TYf zluCG0n#`PFzqwN*FmX*ne!cvBLrsIdm*r!DsdI`IeYSu3d@O;P4zuH zB&}={0YeJ8;iG^2&EXlZdiVPS&yCNbVBwWY7hcD*mGD3yyj)3Q^bBLq8w$qf_MC(- z{1Uf0uGvpqMwr*5+E%2EJ3gk1|KBn67O5yZkS-!Mx2MS2Tu^aO4n4Us*+E~`?0LU& zg-=>+*}uE8HP3jJOX}x}?OlOk$_>f$w(2UDiUO>+2ZJ34%}TTb{+kN!+iML<;h3e; zC^daH8@-}Xm_Kkvbx`}d^q`a>!nsblo`aE~OPNEjeA$Vwzlb4Sb6cdAN$lUB{u)yn z`-mc^ZgcQO%(nc8q(^4@p^-l|3IO*wDG$bhk3*ddRC5)l1H4^Mq&<_hnhD%vvIpB1 z2Xfv2gRpG=c8@Z_c}9<(FRnXAqqTVck3f8EPv(WiHR?6j9aleT^TtKOyjm1x_%zg~ ziUAZ|XePQ`{Pf27iJNNc^M;ERyWF3ULE`yw-qir$QF8z&f@d$I{{$R&Lv;6>Y`mGo z!SLSGsIwa#kqYCS3=5mjXO@1t{i&k^F5*<4eznHPc%-!K3)!kAVo26C>!NJ@>Jc_) zcU*&2EAA_8^T|`53K^Iylc$QbYvm9Bxu~GlaCJ^jJ~{p5P<2(NA4+0c8eb_|lV}M( zFWKg)!`as2DZvg2j8gJ2EGFWcjfAXGz6L$6$2rh>>IXdELApec{Cp;>pkh}}xpQtq z>R5?mzALx%=&O=}T|Rf1Wy6JlZ_j0AFG_MnHGe>|bh&4WrPZ?i`=={jFjM_KF9trB zrtLyjuT{Jl$nf`Ju{onPvb}^Ky_0?mo5%~!iluH z-NoI`P)te4Y$*>0K)RvP3 zBuNBsv-#3i1as>S=={w`A~^|1g!kEL0``ujmzI3HY?IM3(6Zm7g^!41g{&&AlqjV_spWP^TRMip|W5dg?G8!yQF{qKZ3U!f8L#fh%tv9Xipt+>6mj5?mJN z>7AC()R+;Gc;M~L_sTq3g2n5yhM__a%AV2%sXKy#l0z?fk4dRHxkD1rkp&YcTPVZU z{6&C|^ec-OsZve+BWV<-DU*puF3VCEhBBB87ZvDhYvb|iZEo&@8U!6#SiMFO-_dO0 zShl=Csg6m7CieqtldFIF!+v_ax&Kxd1E|wmk>7Y6@`@#UIz=azOd9LmvCbe?sn(cn7Ked7{ z`I|XcQWrsJ>15Im9stdJ%uv1@*`qzfQ;C&DZc95#c>sw1$fk7$XBSc7K>?_OG8^d!VzT{$xTnGD8LgwqozV^PAZ`4=9Fgzu*5P}zv zZU_bv0O8NCtB)nCtMQ2AFNpXL*BX#++ySG8k(O+0W5npM5YTTmso!S&#XTv;^wAs4 z@Rr0LH+gWbZ`R6}kgh)mJuBY@Qu}B&462R2aYA}(B<6U&T?5Hry@1pzB$f0>pCyvl9Xp=lg=d@0S4DFLy<3rNH%hmqMwI08_C=Zv0W zP;8uYwE_y>!#wxI9C}U-2C6SBl*`I|@h*M(DK4f&Y}!(}VFqaP*y1aX zzG96b$T5kFbBtW8s^BFPVzr6f(dVjF(?XhUX`s*eLc+zP_jRfQHLXn`ivkxekRMuQ zC9#zHS<6c@DD!>C+fP;O-Tz!t2M1G$$yb5Bh?QQGHD-Fe;%q38qf>Ovdq6}C{~7YE zmHM*Dya!shboM~L zTMg)LWj~rL?MGR+3)FmU=`GOMq+1XlB zE9>NpOu`hy)sg?6-T-i77X5^qSxQTd?wYr=9bwwK8)ZZm>b;tkXuk0{E;Cu1`;{aB z=_TxHAx>bYfeB3_maGw@Bl@pS;Z%?Lus!qpq4 zf~Epukwzh97^*}eY3e+$3zPGc{_y%5-aArRV2LyYsT4nw?R!Z8EW-8ql501axRH;r zTWZ_Q!$_i&%3nQJ1NY2e#aSH|YxV}p*hu-_4Q6gu(fs3Ee(j&>YWM!7)`gDnf0fgl zoeXAJ-Npmp;xC%`is+7z?e!m>|iGU&m#byJUBhNj(}Q zzWr<~cA*gyDVz68blqqnxwo_!kZh>Fe{9;-9D#R z(*4`+*)?-F5iqJmJAt8`-J`+-sqj+B+04_=QztTXZd&FpkxY(CK$gu4mrg^hI$yP2 z6I$!1T82$lQO3WTx&_)&FQFlWc6_Ck{?J9pHyy9A#JDSvwD;3*3Eg}<;}n=E!6-lT ze+iXWkIv<2iZoPvjR8-n2_eU8<~%|UHY3~*bAJdaxh66yOV(?*GHDj^k8{x+; zolsYD;APQ=h0DUl6~#_6sH)!!d}{(SgJ zvPr7fHRlws5U5OUQPc42HhO)Z>B}Q0`&t18#a3X=a7jZ>qLfEsuK%7P&2~n)mBNm{ z{YQ`fsN_ieD6$;rjB7!xv78|IZa4Ma=iDB2!~XXd2+Zb#PrF)gf7wCR=2u^8PYykA zbN_H9n=f3ybl`)508SlS0^ zarWDG-wjse_q3;C-K8V(hZ8?T0M1Uqh0ULM9lu z?uI=FHXOQMS;;z|i!s#>50SQ?cf&S_Va*1}ai6AcT`#50oe;sv%8{|zKvmZU`6vM` z;bY8n>P4M{`fd#qLZTS9K*haNOS_>~i&!ijM3vKxe zd;$m*S|=@0Y3cDwf=ofFFBYiZQ=ZKvs&GeMOwtexyYy41{D0cFBNYQXw#UqmolQ_6L? zDXj;h;pjfBsdGQOK}o&2Y2=vhZhfG&xadbi4OW*%Z$n99*L$;Munj!@5uGA305CU!Qy87Q*T7kwYOTlPag#SUh&knOa!^oArk+s<`7 z|E@UX{62DW$oM`QBM>;G5Sj|GFd}74T$sSouCgSX5jhv4ywtDmHzFx#+FnL1`W(X{ zh6T3+b$^!H(uNfeUTfGgDma8Gs6%)_r+aZS`SgAq5FKi?%Qo_roogfO12`{Y13ba_lvSr# ztNuTkkj^BEfY{^lMe^qo_wOFsEQ{(4koz1JYzhw~B}NAHWclQq%w>$n-A$44wpr#Q z7t#qZy2;*c9zan|h9L!*(l2Rr0BvQw+VgrU3)P{B@#?C;*)o#F%!imbq(2#7AfSZ# z$*?ZNjA*RdV8Fx988E8Z36#fluVDcPqEGNU6w4WP^V+;FOR9xZMI}2}8z;;kzisqK z0NOc>QI6;a>Ycv{1~#QN54+M1SI&*yHeIhUTkP?cZ-xHzXCR{fES|gm3~ai}gP;1K zx!Zx)_aOP;Q+Uww-Ypj=sQn84tI2*->}dJPwr4#Veh#KMf^`)PJYThZq^G51|$~>S^MufosWoolcpO;zpZ)aOGF$C zroS_BWupC|83Q8Z5&&W;AA|9k?l~Z>Z|nDm??p(2%lzY>SDR9V=5J>u?^pXid;pYL zTvu$XZuO|}J}a!WDL~FyCk?Nx<>&g)l&79K0eoM`-BlyAOTkX4p>H@>v7<#Jrqdzr zEFt1X2q>n20swZ1aQj7lKV#jmDYs^79r*VAS1A|Z5u_A10@~l68jbCX1wx)(Q~ht| zU6=5u@v6^fKZa?nwMz(sgfc!GQ&BUo6bkOWemhWlMHYygP+;ZQ>+Sk{sd|?)P zCrDYQ&1T)_)gR-)A3aiW;MI~(pHRAr0k|_BO#Q;SoLy1i2~s&YKs;Jt)Z~XON-Ltr zwu1L2))bnyrb_v@zJsP3BabDpWP{R$7CHL2>H>?M(6{g&wK~3SXI^gnn@xZ7$=bE@ zyUO*`$G{ddl!MnzK+=WhQ$vhj?s{@Y2OEV0y3ca&?A!=rAasNkPqC}Ql!rOJ_qwDk z_oRK-7hkGeN+1R*>r6_q8m}RRst1XEN*Oc%_Nw_ze7eCu$O=iI#{oXkS8G4&gVyI` zUq|&CKS+#%iajzD&%#jPSj--LDWR| z2bMQq`Js2u2ut+nL*^DNg-_jj96G8QmtUiDR~zv;?#%$Vh`)4wee=moIrza6{k+xG zELzYis-i^|T=@a! zQFc&$|4M|I5{{20@WuNKmvpn}W?JYA(*~4ZpRyuW;oO=ZvApLR>6@AsgqrmSM*ZwHMWpjzbR|qxQgO&Cd!FQjZ-u|2Pd2{|6@2?s&`|rSY(Y4%kBmJ8I%G99A zy$Ut3X3~Wp=;ghyHl^U!A<+W&{dLO5d4q=RZzzSCJvi{RUj8?5iiZdl_Q@g2PQoE_ zAN$52o*qOc<^{F&8~JQJ3|}lp(8KPKk*~c_SS*><%9g1l>h3dr|Mw;jV$a5 zWWu)CieaD9oM`**atD6_fZ#=d*! zyXNg5WZ7H{Q=u6z$GV8s1lgY+1jKhZzHVq<7XrlACUAwqUr8AW=^}hIMIr+rF^rek zb#uK^_@g@bBuxUPB_Xa%sY+Lm{THZWD8f^%J0U60xgR%Hx#`_p^i=b%S6?0|DL-8O zY{0nu(2R%Ic}xyc_jEh2KRSok_XF?D+RyqK4CL`Al}(fy%=E9-r}tep`SjP}nW5rR zYP_;^Vo!3R@-}_MXivJy^kwn0T=2V;KnC{enDd}-ka%j*;4k5o5QgHoA1KLB>;az{ z%iIPQW;V|1$!D!FxlV#)xH!IN_*!u08UWqEXF>E=?)FII=MWR?2ziF$p+#UPTk-GUkz*Ngf&!JmUK72m-_UyUy_A8!+PvPTuc^#KS1-eO z1F+iBU9B;&A-s+FH>hA1igVU-|KHC}5v-8sfn}g80mxRo_+B1X>FEKJZ&k$EH}C3U z@-@$OMo~`Ada!j^`IEOB>RY0LOJmjqc>^f&nvvSGb0dl{OD>SogCJ*b^GzjpTN;>9 z1qefdVM~f>)lKQSJR{roHT#ER)SeV&y&BosC12_oh)8d=-CtyaecRZJ7 zB}A=q=?VdtzJGo@>FWLIs$=v2@J-C`e}dI&6=5zE$e2h5XLCfL7b>I1)d=fv)vN1F zvAwJ8a$kAD;~;ShApK|HfZkx($;5$UI^eAVT6)yAD$lC{*^(Wu$lyj|9N_?!Oy*gdA%A)Zd*YZ0zsBc$e#$2mA9E(X?iks--oDZC{P1lfyR!>$cb>QS9NI;M z{RAk|!AR)WO!DR||BJlu!bN|gAiDumuBdOU+U*nmO0;fD+`r(?eoTBSif#YFRovw$w*j5zQ3A@bo?9%JbptW-f&?-}!BD(AEB@5$87m`=5ix znR_nu0IMVq)wF~3)-sx(9jf@0;dV&iBn)W~kRgR;Ikc=wIB4QobKRh|e?C-~2cS@5 z_m)8gSS7I*)OWsQao}UumtH?dwm-AK+xBDQkM53e)t?xN!?+0lYf?n|bAkGb&_4hY z&)>JF##f4gA@{^ssH+AACE63z18Q~88mrBJFuLNR;f3%>_QQ!6zy-qf@Eq-2fZgy* zEq<~8!A&iG$4{?W_y(PXRus>m*nPwONWLC8di~V*7<=(-{GbT_Nma~p4u$@0I+R#Q)5L0Xoj>=B9F>|8mp%g=Nq zGVXUm9-7BtJc3JiGkdq`5uL5Wk@g##+M15!&EPOyBjCV(@kb!@PM+R=5e&uyW+t}vdCcb4DhOXJaeD{o> zwdRCKXPc_>k71vpB=?7cB=>l2*b^don1+Fh&=&?~V8?~W27fZn(x$ik0a7>pzKg$(p{H<)UBKfz<5r z6V`aYn2YSN4n+&aS!fwn>EkI+U7_NSgI7@V?I65F>kh~@wSNUKBgljZqNsV^e*R~3GKig^_HVxMlOrXBh4PGY4m zSTZ{(%8-O<-9km#dBV?yh)efOay){r)kw)RFy2_x zI40=wI%v0Cru%c#tnfoAXmI@)v;l3MPqV4Av&JhDx`vL?zK3OEJ&yz`&Rq}_=)|fr zyqa?#{`a44$F6wr^_LHy=gN#XytE>A`!PRLAn&GvZ%w>9f6|CyFET4W;58r9b(gh! z`J17v_0^O8p36q2IjzoSr#}3iW*F<7`txYn>(QH}pOx3|lwQ9Rqp10)taPDmUEbL4 z%qzB0Jn?KxcgGiB()<`_bHpN=m?4F?(lKqBpWS@V7M17@Rn)J_wbmO0!wEX%1Oy?t57g2@vnQ`L<(Ym-<@~UVct}`;m)R1cz(b9A^(miam4qe;VI> zehRtoO1@M;gm=07DqAU1-awlaS@Cn5@7|D+Llkst#B81{hXN%C^~qg(+M2t!iSib0 z%byK&-p%V)KO7Y4>0$`jCRw-Q{$^9yU5Y_mrTNrM6%=bCi#ELTA}rB&Vy~SUjUA>k zZn{Ij|H?2)UGP_HNS)nd`soq^(EA34if1@f#N6;~KXE){HF4wPoV@iZ2agV}pXFeX zy=#aj#3dE(gR~3VlkuI3(AGR$NOAtIVqc+o%iG|?YNbiA*?{edQaC78HN(S164}#W zK(zF1-T0Mbo!sD5#Lfwr8$(96wZ)Y{jhm)#29B2QV7SU&cLY35j{C1v+pD81@cFG# zd$)H1iT@>=(>_n@p-H5v;o{x{hEZ`kK9*9=vRwl&h;@SRA(7?KUOk`@c51pXWZXCIfFxmDj^A%8)Fz_I5mSHoSwYmp5hOWf@cMENX8C< zFa>HKXnt=;CZyJtY`JJs@WDhN*(339vy>Y0`uFSRH-b<5)V-hhc5}ya%o53lvzN}} zLGm0X6f@;A$`_-p97+E3RtV86=Cv`<&c!F*pPA(qS|a0EPX~6Pqxv4}ImoV~Bp(Wg z{4*am4WV>(|Mj9I^+&$j>**5vAUfv%Rxal~gbb(pI1mD;IuD1G`oz}&Fj9d<@~y=} zYhTX=UCk;#AUr;n*+&?uI*75t=Cp}b5-?E`Y{@ER2P2KviEXybq@XSfu3Fw-NHiX! z4m)V6QxJ$J#SBZbZgX+E-TZ7vk-_}N;+P~3@H4e;9$o@J@LKB-!1X-=zy!X|-zqkC zcZ?bov{Bc0c?UhVJLYlh@zK)5T@P-3_KBBELACb6VC5dkatj!kvbRKUzz3mZpNgJ` z!JEMB`c?)ZXO-inTVK-Y89bK`tAw4S;M3$qL-7h~0U@mwk@iDxe0Lp!q(GwwrNstL zjDEQ%O9Vms_Tx{>b|V$8Yn^WG=@HztPMnk5FPmUM7qf;~Q=`FTnASJFq5GuSL4^RG zvspc znd6qr6O?|dOtncGTiS6V8)+8OmmrhI0O!CbQZF;?wf^pTR_j&FSTRuTTAZ|f>D?!J zV6v)uNNd06*7yO-pNkH_@u|jZtFLVgt{F^r01wIf#_A=z_l|lU`xab(>)7GwR~k}> z<3B>r92p?KiTCZtE;(YX|8pL1*Q>)>rO&!9%_$4u>lL@JWWdOw!WuuX;T+zp2zvGsFOvrTmFUSd)ds*4 zojaoe` zkyFM)ZytU}=t;RBJtrF*ZZmtA3(B4lftJewTkuws!883%c}0T;1Z&A+txTG(Gho>; zcn@Cm)n$^GZc@!{WX0OKj7KpKoUOh0?Av(kPQC>?v*F#bXL*eJn=7xSCQ=W%0g|1xJFj`F*PRIS&f;7u;ifjzIKNGR1L-za+G!E z&db+=McrIbEeB*_1w-c?j_AoT6_g%yB|ac_h$yk{zw(8`R(el>uy&1 z|He@lCwA#3Zt_z9*C1uSYt%z>_KnOxbJyM1HkV8e#XBFySs@gfU-1q%^a2nK5<&*+ zyg#RA+kFEd_knJ8{}>%)F#86*D~Olz@2%3c4;@J~m%?WomQ+n5c<3nFExQ#~+WbL9 zdY>9p-;F@Jq$6Qt{!e12>xG#0tfLZ6yk-9&} zplB9S*f3*DNV6FS`p`}tMI7)DW{AmJLsKqVU!|!8zg2mm>iT=w=m#1$bW|=TEyOB_ zK}ln>k`lAVn>>uOe)$-CeW_Ntcc{qJ{D_|^{Ti3ij;Y%(EmFny zDH_6Z7-@^H&N4B_?1jgQm-R|tI-TZ%Z}8*?qZypp9ZnVfA2q3VS*EV+{9lMHo}kz4 zF54^x48)=3ikv85g`Y=t^f=)6LC$luNNvN@16LunYp3j@z32g&DzREf?+NNNS9Dq+jm0gF+mC0uc;RMmOqx!;Ce94U`}h-$6tzNqM4s#eR8 z(UE}Az{|>VgAmW3=Kvds%{9bjx`~(x)KKU)G8^-J0tLbG6`6b^RU0J$T%|``#=|{y zLM@}vU1cISe@fMhlkCt#I6y~X>8P?c$$4Jz-xc&*p~oRrEO(ome};|82V;`rP2Y>a z2~?o|?5^R<@dGLL;lDitR6{d~sgIRUmIawt4yF#eWGd%7tkb0T)j1SONIU0)R`Afh zLU7Lq?G>>1q7wStEL5FgsbVOTGyyN2g@r_s$&6x!cBgKjEQkkAfrl^D==IDp<_;x( z>Ie@{DEU`R2j%PjP3Am#3bYY%?tfz*z2mg5=d50&Uod^769-#NkE|4AJX}6dOU=2+_S0==(yDIF7H>b768# z;=wD7f*w{n#Qn2LEZrG&X`P?d;jTr&{7L|pF9I^SsyVyzbxhRr>o0m-%||wR`22=n z1QZnHV+8R9?JP~XS44JKyxhm?fSaig{fRpuzorjf7F>+gl;XJMzS-H6$6B?|?Tn4M zMTdvdcKULq!ox*+GRGRlP*G0yLLi&2U%woFodR&2C>F)*-QYkV?eyUwK;WLFKkZKU zMd5EhFqVxV68U=4l0Xj*7H0f?y(N#@&WQPf|C{qs?a!BWd&#?0@UJ0ay!c=RJi^u7tP zh-U{YLfKpiV>`T-Lq71-&Yk06B}70IkCte=v$OU9SP|%UiYkwSUx+&45pU`WfboR< zlW|p9-)%yPrdm&fEWbgd=pI@kWa*FO7~{CY8ya37+kmr>jRv?Nr@G@RF!;K+Y@4LMAK4Z0=03QDSfB%^FsWBcj= zLK5x$5ot96(Kn@^-sV8-mzfHwH$pba3#?HqSN4p6d6vyQQI zO;&^w6>}hJ5^3#j?}xw|oSlF1J){N{FY3fy047fvG|7N2+f-#usVN9-yzjY>RS8HH5fhzidNyTKPlyr(pO95jc8wZ+j)Mv$Fc`lxNbYrtnbempkz*>X!!n&DSfm zf^Z;3x}U`}MM*cmvpnq8ba>n>(3cJk<4M1`3vTo)YFdKr<-up^(x_1#E#+e6qL%+T z*Vx@DIlN7WwCsg=@E{bg){z(cc5iuq<2%g{I4%C9TT|pxap;W>BrP*Nu%cDvpjb=K z<@diL=8a)iVW{G@=6@BS77my(QC2ry*W{t}$WqJYgw_R|`0#{rXmLFy9hED+v!D5y zz_c#hiQ11R4v35gSEAf5OC=Tnzpa6X8OnanhoY^F8GBXy=9Fhzfjg*1z5(xfIqmFg z8fqgZEll0j9;ys%OjO!a5}2#RfIgx0(wV&!QeJ72dv?DhRET(br_s{yqWL%XR^@Aj zG1snd<<%71Y0+;-Pe6}=*!x!EEl-<;T)i&yv07Hzq3YN5vR~I9m^KY;o!oQ-2XSFC z+q3W%j$hoGxjI)Yjt9573v=d4@5~u4YLb@=b%@r-R{f8CtG!!#zP8jI3RQ-s@*tCn zuncbaz<W^m%GLOiU5D}j|a_yQCzXo zS;enzVn%Bjul1oT?T&92q0Sue+^3?OigyK+#AOZra5&SJCwDqL9D4nHcTkCKW5hA| z(8GcQ*ubfwQ{77=5h{{*>)v#4*6XMa4BvOhzCQu`yH@5f3yE4oWvXWa9=n=9$M5Yzq|d=*yW{G+XMY8*(=LgPE@VPyGqFgKs*O=hC>xcO z=&W59ls>wP`@w)7E=e+J`JP;IZ%OZCQVr*^&p%0hxqj)jBJzFV;AVZPg{St@N%H;u z&M#)Y@);!BccsW2rCtwz)mkOTXT~?zz*Ow$b8s}(?VNt3(dP^PC%?5nPzyMF)0oDN z9{h7Wjw$l(GqTnZxlxrhTVX_84uKg%ta&Bf^Gfg0-n4Bx;UpgGmDTWW?7{>cd;RU} zYDvjfZ8fzq8~x}!aX|2mB9C~bYk$=+1-B%Vf{;2fn&m9>Fpg~DGlYp9%BhkOtDX%lzcE@mWt-zP@Sjg|RZECXjanFaFYefX&s*CS zrC-dtluCf#zdlvF%zKWU$^ZBwn|{u{QpGG+`CvelhRo<*TBS+6 z=)S|%6vsmcZlPQ#!0mg{2mK!w_=s3eB4;upd=g67o?EPlFhO;J%^0dasvxM)l8<>1NT|@R3XxiD-uieKX9dmW101T46Aqtt!rOE+^3KGz@&xc zJq~BRT(fq&m``4gzCUgMU?s~V;Ozb*SKGF62s~gB8oW3>t?cHqxKDBI(6=2hV&c+Ntuys!!-6nY7 zP4z45OZ6R+ZSB4GJ%pXxxYP1tW^4JB9X7;`bxYFQtaPGulvq_aLj34~JB7G0F~ZJHhVs4KdUUYjddSi0H|IDZ4yWg4KR2olrr z>Hrvk-)5&91m)}xNY(cA546=)4oM|5Bwa(E)-#_{FyuoGPaB-B>*(1{X&k!ZQN}Q0 zbd&*i-=A4!ECRhT<_2%KGNRQ7wr2+2}rGw-P`@Gys7tL zxyiqQ>vUc9qlFR{gIQ8K-VXy4wzML z*{3&$c(1nM0pfqoBLS4c|6}Mr{Gt210BuRC5M)oFz?yRh2RYv3N5h4;r*-0uzTKf6<^ZxXq(0Dg% z=|R+~WC3|poixOX)GCvtJEb6Bn`_%PgrQJ5WbWSHp5-XRiKq5+Yib6KDM-huadJ1* zfwt?bry`9Nn@UR?>WyWSI_#Q@BEe+tMD$uNZXRTw{je$38`y2{0uUr`wwcL28vC}A z#!Y3hIL1}q21!oIJr}y=u|lfNlZc6`RCSG2`y#w%`*-~HY^1l*>%_#r8=65n?#NsI z!g=Eh38J5*D}ah0ea8|QKj6%1Gxvvt%Y3?7x4D~cXLSR+J14ogVuEUK5jdx7a?bY& z@RpkJd!K;>v8c(Vk-Iadz=Q3x1Rpl#&FrIRn*Ry3JOj(>CIU*mPEW6R+t!N6>|$dPi{@%+0zm^U&f+jaU)t2GOg{2>}A^1-zd#iKFe1pOLxbE zYs-%loj1KSu}TeXX%__T8LES6b=5_s@fRAgsm$gs zWD2mL0Am3Ze6pP}RH`&qpOW9lf7V*A#F6Dj-eqB(b z2Qtvvef-}b!6^XvO9~3r*Lo+VD^Nc;c1XhnaOcmjKz*R%)D1ywYI>5j;hET}1l3t4 z_$k~diAYJZ?^F-WsS{aJwFAa)V()kogHHJH0ow!~_^!juiEm1ci=woK77an_SlFxX z+G?E6fb*`KRAKeJ=0%TsI6v-nxVn$_MKLfqywOm8HRhJ~AVjRT^!7XD6?V$(fUU-K zzvXPpUA0a_lvz*#xup`QiLk`Gs8Jn?)N#Xhii$TmW!A7KaYn9|s6e!_sNQZp`6EsC zQi*$m$1VOy7qDYU~)jNpwgjEYd65t@j=$ z!-usd8k|n}@9DiI1qk1s%9Pi&fB1H5%=5KTN>m2ebSTo8bH9@3(#}mWt}?0(zyco3sS^jD4 z7;`hUZoytQ9D-a8z0;+wQRCY(!EtfKyktHHBvAlHsR4&U>Q@?`oB*M;Im%>1Tke|8 zPUC$f*ClF>@^qN%c1lUrWe%Emg$%sM&IIUKiDJ}7G1Tqo%;L8w`&SJ?*jJd^%23=n zH4uC&OD)+u!w-3(_*vzqaL;OzjvrBZ6km&KrXe52q?V31g~koK-1;G|WIY+#sNJJi z3z2C_73z2qR{5Jds&*k~niY*m1JI=W8g2gi2Z98GlEKO>I!&k#9Zu$rbkRo#M1^zaK;)4Q9u*HxX6-zsk zDDDrtJB~>>4a(C|rEpsgrn~Fv2$N^Mfftxo@I!Le6Ie6!Vmw4DW?BAzz@Sq0jI<=J za?MDNQ!#ODn}Kvho}A}9QBmnvM>-ZnwLpUR@+=Cb`4W1l>X?n){?C6JPQ8z|QlH3} zYjsFfIy)qV^nL@2A_#0o6b`vB>5jCyJx3=V@@wB!$Q1O)x}o7dIz#HRDtV8TFWa20 zWpCXCSYySum!Ip#w6GwFl5o&2HjwSt&{&6LrnQQLz@U~gssacjCQ5fpS~SpicDjMMaBnJ(chz}m)dN=+KS6QCVCto1ix!C+oFuNJRD@(QA@S2UsRD-F z#gftFOevvhgsC3&KDR6NipZF7P)adXOuGdn@kPo4QhUJ9PS21qu|eK_gvn`(T8@;^ zt%Wl`CJ76y_&W5VT+5^q&WvWpB~*t?lX_)c##g&*BY%D#CDfElp~VN-R$rEvB5#2gP2^ zgKFH$ZaE2$Ohlvrw{NH7C_+7kinE5TPrTLMq$U?m<)6-NZQM?p0CKj0(k9gm#_h_g z5$P{1p8W`YR){bq6RQM+%KwC>do6MmK2LuV+qI}e^ogqgnV6lE%;*o1l^dbjY=Jp6 zGXv1vPGc<|QgREi+@ZA0j~2px>bL6u02&u&u~Vk4aRf`9!O31h?_bAIR{EJ%nS6Kky!6zdL8q8 zdR9>0_+uU4Y2?i<-GgAB){9xF^;(XtB(YdPIEnNX2XP8YKzu|5AM2s2=!A|_`zZFV z1gN6RgFqK-8sza&XLD*iBmO1G-MD1<;wiXcZk8xI)xQ6il*Qy(z! zaB32wGZEPi?V7jj(NWak@9H9}ZB~_cl_~1}sU7SssHTeQOIfwea@G+zCx45Q(ySih z#H`ZDJt^h5nde9@2qmob2!Am#jSM#oZ4Xhq<0%FI?8*)GrJveO^lTYQqRQQNH``2Y z^7KLKwsTLBHCE?gh_ENzsKl(|hs=KQ>Vc+9GH2bsr*Nkjnk)5?1fcB8z_+5jG(jUO zEqQ4X9ZcrsqtHxPXVa1aEL4R^k?3`1o0DZ=?IYZi#oS;igRKp7D z^QW6GRNcj<9T^4}jPSKhN~+tsHe#@1cJ%^()1O)AH0I4%u@<-#(`iU0_M-%dvN8Ns ztcC-|o_GrH+naf6i#yEZ4m|(msC`QLn02h{{QOkmAYeiolNd&V1}J8P__!k_Ew`nRW@WSoy?j{?sK4kN6C&B3=YSf{n>OW>O>FV`>dNv%l(XNr(KDd zYUXHp!HbSUT9gI9Ukl$qxKzUOD@C2DBFhx0Z-zb&j4rMWmYIWa3)oT<1v}|TFngCv z;?V^bbT~6Px~^Q?wQap|jzz2LZUFM8w2C31L|4$%KLHoGor9oN+q&qbYY?qpgnWTQ zn4?%h-80M}N6J%Ls5yA&VoJx`5U4k?NBft}+M$ip$i#a= zBP<-7;ClBP0OC1+DhS1H=&AKHQn_-1@j=h6=9Mx(p@in9az`f@(+!~ZIcUVIh^*4M zzS!Ez0)*yIh|dGri9O5`KJ+s5=c0=%rFPNkbaj`-nW_2u;8J(l795DYQJi<=nUdrk zWg=o$!{R2Ik3b}1g~BLvC9xi1ENCQYRa@aE%CyiFz)lt{3tbYhD^}BsD>IjDbXgZA zUI4dYsShfvh3_fZx28X>e*HL_iwvg@E}i_Zy7}SUJZu7`jsm-3b%?QW?GupHbJGcj38QK;K4yFgdv#1a^yy548~+ zjsmlHxZUiVi&GSel}$E?Vzv(8L}rcA4fPzCPp6D!AM*WCzQZT^&=<(L3AD~kP_s5> zbS*PJ&e}TCEdOp4_|cfT$IZ%He)CBkrf(1T`I;@Bc&b*$=9K;{U^_a8QZ35!E?-x9 z$~NWW*`!aHx+}7%oTo!lg>Lw&{gw8)7rhv0>JxNrt~dqMG&^{2z~ga218#Xyt3^9XF$j0-mt+I&WF=&|RX>9LD!T>r?*%Us$H}+O*J~6TCG*DL5gxduPz=KCE|P~P z07%{h{KlP$Y^)#bxNiBXUY)8H=XJ)Db<{RDhtOHBk zY;HFtx_-wvybP;wo3g+w7NmPTT?oAX&~wyO(T7z{og))036nqQ=)Yyn_626Yo!W~# zex!r6Ql#^_679MmJ*WgvxRA(1>N8pc5s=)82zo@ksT9iwT~5r}nl63rTWy`$gnr2i zksElddryrBYO)?p%Yfg0=Q~23>voli`Gf51qmSyC8vG z5AP9k+Tld1o$@{jw>7s={dhp2ti^f#-ECSHR6-SM4uESKXHFyV zpON|=_WNdsb_0HNKb?2jK^-G~-M6T2;F1PMT_R~YxKaB2RPj3F9scd9l<#8HtEq-u zyC4ez`K86-#A)8c9PEY%*6HlVCWbed3ec;`sn;g8WHHhuL-_$E`~U>!4I?uxhZ8P2 z@&IkR5nqL*c0Rg<5)k!uU9aP<-ncZtpXX-oE;99jZfZPq&Iyulw_z zN46|IP9|dpAk`lJqj8Y@@w2+N&=X)1uG328z9)-Wo}Zm;N~xl~d;MXvZGe5P_#C`Tv%AS~aHtskH>yiQ7e-0BCu>}WQ|uD|)GqxoeBlgXZZU;@xa zgWP#+wa=@JKEIeN#DTGM9A!tm;t9w-3Jl0Si7VwMI-aUFo(;V$6AaTY0CC`w3`e5+ zn=hk=w4-fL-p)wEhon>pt~Zx_zaE=3pyeZ7`?PdEFf3B~+C!!J)`a96>YI&E9~4)m zns24atN$>DQb8;#F1{2EmpM-E?(98QsDa=zg8!Hjb(i9W%*5eAKh`| zK-HD!_)q%5`Nt#YCMNWh#JNTY8oQ%V@8Y3*$I@wH*r)#7ax&7P1BE|DMQCrhGmV-| zu!2l08eQOpNf|+Mb)j7VE-a_V zsL8fiLHW-G3dl*nk%v}A9 z7O#)?_$=inrGH-^y?rV{KPDspq=qxq)JhDCc^K5_{w!uf_fl8%28)(%AJ|)LU1E<< z@xJG=J*%}(ZL~AC5#-GrLZCDPhKtR_D`x?B1A8hi%=uK4zWwFLSuR(Wt5oQcqYl2j z>KPgmf2!qspPHgSw2o5wZ}|F;;dDHRlZ2)XhdC{)fTey(DE|vbGVxsYII9G(TZ*JV zOkEO7MMew_C|`yj7$3=u`bn22v9DH^cyhgYRT-e#?K1B9e7>+3EeBVna>!9BG`2om zzp#qtdpvdqtR#`DLiBuHC>?O6;>5>|$hn)J+d=ySn55!r3WTJukeOJ}8e} zIRUWj`0x>U?&#_?r0q@o=*ymW@%4k047Act;EGD+rIB+tA4kJhlMvsBDT)7m`%X!C zgk1X+$QF_!+4uC|l2tw5i%PWb3&p zu0#3=6hFyT!dVi5^P^9m3C3b}O2fY*Qr1{1Wu2~Z&fb%MhO4liXsgrc<2|&w7Oo7KyG6zE`39 zkjG~M+ioPoPeP>L%^n@=YGg*FHF}Dz4K9eO6R!&^Tc=dKS3?yiW7-a$I^7RzdyqU4 z{8%)(MU_woI3VErWMDeES}Qz+n>JD)n3JsO_c7S# z4gW0`kzX~6J0#dhfa7tpK-;ww@>*9c7vIf2tdyy=rFF9eV9RyH)48^e^_-Bcfq|#o zUqJrToRy5q!Y1+<=$28gcbhlLEh$8$aV*`#EJ+GY!ivj-#v21(=?r7#EUjJs|6|3=U;|@*L5r z=-38439Vt|+X2{hBfq4(}Rg)70m#NVyg8@z%Mowf-)GQXZs7~Y9 z!P+TK&AbxyF|IqoE#-A}#k6i8!AAySsPm*b zg0KO}2%E4~ey}OPhU}ka#mwidP$huklgM-~$rWr3fkPFGW>^k+5ZM%uY*+ScF9YFd zXKVs+pvtf0k7!d`_pyEkyL+#nZJnS|d7CFM=Rw?^lavn@0Sor}9& zZ&E&~Hcfvuld&+#i)E>o=Il)**h%wxO0Ss*5}!O>2kX;w24L_Gy=YfOa6OCspHB11|Q(hOn%p~Px$sWk-7 z_;`Z6Shav@bm)x;2i0}H`WSA}wZ-3SB?Ow6ChJ?IIaXHEO>~YZ>8@&A)d<-l0@f%> z#D#Wpm!2_IGo96jTT@m~%5WJ_<8Rx#-}q%7HMpo4oiJ%`( z9PC=rxlkgNhM8iJs(KFc3MoICjO5X~Jy!IuUeq&3j@AQq;yHFolbPu;p|b+9Ek#9xELaQiNjIEe2^P^2&j|S4f*h8zVeiPyIOokh8GTA zI#Tv9dutCWZx+dB5cVUcA^2MuX!%t@J-cX~MYed8;T*dC6aSp$hHR`T8+nuewBpdG zb9;^|^U+^jWc88Z&AXhWeS1@I0>$Sy7^^K1^c<|4ljOqwEI>|c5u26^RG48$JNWor zV{9hCQ`-xtQ!d4LmuLEY33%)W&NA2r!Q#FoUFJ++4-e{R0)9Yj;w>;|l+JQFZuPBw z-%GN024ER0ee3pX=(i)?#@#Wsnh!>q@GD0a^R7ORx=zzl?H&HU`LHQq=9>GOjcj@K zd0wI1b-PH9-SoBU7y^*_%EH|*y&lz;Nw7#_%R@YJ`R2-_0AMr;wyXG)z2HQP)Iqukey z7e16+tG$-fCmy>mE%0mnVfioUzZ>>jM$$`<^5kYL#x(QqO58$XXJ9zp;b|Ir|V6{%Vc~ z!4g&&Tr?!Uk?j*80*c5>`I-X-dfZm5DX#L&sNNj293sI1VoV;b8imY{OW9WU9s{Z~ z1%T>Tm2X?coPUj8eu627A4xB{u(AJ_h^!HFs4lk;OS)fj9L@I?W!+IT0iB$8@L^$B zgDpkS;vz!DX#f7T1qPO1=w6tumxsuQR^JaR-UYuoo40uXWF)6}0;3r3oE~`THy3;9 zFwt#L*(IU)*jMeHP?_IVGLqH$&9~#m*`rkX@f2#%NcItK`~&*z^GhWeW#j(ABY{El zo-I88Et>{98tH`@NmRoEWndD_vt<({>AR9G^QPLWL|IBsMen&oT9Q4a)SfaZS6eDa z=$|H-n7WvlU^k3PDx)SK@Y9*Nr>j-1iB)RL*5sNwI}7Uqq6y&*Tf=Mn0q!%0Qg3Rw zp>n2fPJ;pj;+v==2d79o;crXVIBvV+C$XKmcUB*KUz#?wgo76lNu(n890K#>yTB2fm-WL<0J5qT>qVwt7EZEBwk=jmldws5J{( zwZb%es~lTg{^P^`er6RCC-0Gla@Na!Gl$_WBV}XQ_2DhmnR}6bKYmYdkHaSylf2{S zVawGu)>7fujzvQ^w_}UdL;l}s*0ijVos`K1*^SCeOGZy}jM2`sdN%g_l$gxY5<;m$ z5dio!$Eu9z{KbcXmxe|Rs_UA*$&?kaU@QM+QJ(W!z0Lm&LE7<`rT8)r^3Y(bl_H$AJWD~%(iiy z>T*z)?n>*vZxEdsS6v+Ek|2Rl$aPB)e#w)0UFdOP!Z(X*l~z(e-Z~=76_hG^Xl)iy zUqot{jEr;ABLQkfbNW@sFDQQ~suaj5%g5uv8;lW7QZIU66c!D9F+B&-Oi)i`;}Rs^ zQD!6aut9cHHHs5oY9_`?ZE)=8C@60m$-aK6lUbGQSxRm&vM|ELmzt6=c&CASW+~gd zi6#8not+PYHU)0_y?Vj9Rfh_Ws$G8sY!D9XHLBGU zH-;7fH%F+e=oB+El6~uBiPVQoY=2!70PKM{EMW;o=j6;5g_aUe4gDtUulnm_47S#I;Wv;BrA+LJ@%D;i~m-yE>hkIHp4ESAp zt`$0er1gN<<%9DiMjG>wuHT;Y!D17hnr#NgSBLrBi7iGUra&1E&9GH1xI-avLaDE^ z@__7o&}6FDuN72!q~m`(cA_TYJjc3J&G#7~&po4(*YAP~sI^NAw3;)F@|id1&gc={ zrP!({f@WQBRE?@!t2579_d;A)08%fOF{128gpQ>7Oj^DLCEIizP2Y?F6_=7Ef-J>q z)|Tik{)@d>&3WHsvu*o)DvV#A!h-pwKMn|W_72rFIV10?v~QCA!2Szr$V{N2B5qc7 zpGwy%eyQ*z^2;pl3$CR7F&&LSo^*f?TS)A$tR$7qi4D+g3+p@dm6ql3wlNYHSDVLi zAADbttflUbOZMxwd?O4g96p(Wlz~A?{V%qtssl57FW;Xe5AG?NieMA><@&TDF$V_{ z_z1A^y3`+AV-rFmS26vSDQ3O}V(@Ef{t&{3Tb(+P{bn8n8j+?vc z=4KLpXQ`=kk4$Utjewzed(I{o8QCuxX8ls6Y^@G^C1C&M=)BH0_dc6ur;k-TIWJ_X z{JI_XUub>j8MlQut9T{+q8|4t?f08l2Bg2_?FEGuT^r+#1trOnhDWl0%f3?E2igW? z{S42SR3@c|ty^0{GaV3m9y{0;i5!@PhGqhfW&#w}FZMI@J;n5n58K(@kM{A#bBPHI zFJnT+T`pHh2@$NNI-niafBat1iXV8@OSY`zs2fa_bsp6g#TEp;F^N^jK8E04b5v2k zU)HI*gBpwbXCfrz=15@Su}L#|KOFoD<_?xDULCao<#|BPM$jhext$u@DDk&1O>Zmr z5BlKHvi*O&d4jy>KkS02Y8sDRVd}ATI9XsPx#HeHRZ_9BV^<`6-EpP+9e)EV_80AP z`+OeEOrj# zGd`<}ug*EnW1Ivt3B&|tV#2ZU_-aY2P{xKJ%B@5;Cg?R7oG6ER^lKq59fyAobhm-x zvYHuSAxK+6v<~hlhdALGpXQH*fXkg5+ODa;~7=7YIW9X6F@lw=c;Q7~1|fA~?n3!5ORU z_c`wbGi6xPB+gOHm}7kZly@eqx=F+DxK)};Na-d}dkK+mI5bP8+1t35FJH=ad(BFW zx>Vy}Z&Q>pz(Eo-5EWjkuW6J#GUXdoVG3^5oIAlAwu*O1;;dsO|An}o@_mO7Lekil znjYL!jARGzwTa(W?TJ->ZrXMMsq&n~o01_&Y*}(L^X;w*8ethW;t-|E_lv<$3P8rA zPiV(C1Rl&C8UZgz_)>?85atED4A?&qaHW$}Bou}jt-#+h`az!hX;j4gmXcLgy>)AKWv7S?Wr^Fpa9JdaCyNY1b$}xVm0ESp*??G;>|CzT|G7t0U@E9zk2%&Bp+X4J{f(RW2+Y| zeDld4V(DM|oczbz|7HJP80`EB%t$Hm@%Q*>54~`Uw!C3ZGl5T-l0$0d#*@}P7vXhq zBN>yxY?2f=Y*c5z?>Iv{(Y7JHNl;^(w2z%hw5Qnrk|em=@}qdkiMPOh~vU5*2Zh}7?F+2f$GBVxAK zuNVeavg0G9l31&~0zxKt^tvMVKD;0x#631>`$2PD5ST~GULmnefpYh8t157y1ildS z*;0;cLD?_*^OwAuTlewZH@?1G`gBWWF>ExCJCE-SJuy~Y9X9vL8L8YCrEvDk?XIt_ zaDy+R&-F^91ExzAF7^S|3)LvoUbhm&q)p+WkD{us{=>L;yqoqrH|ED3(*YRD{;6NF zNZ5II|K6*J>yLkb3RkhWOi0A-T;Lkyx#U?ldH8%Ei`ab3B80G^vA?dhTobZRh!y!>e;x zi9nKQE0+M?r4j89TV?{PDe;L(AkNstA#;9eE?c&@0+ZB(eHqEfdtu z3@8h%D}j|NNRmtvvvj3KtMCsVe<%^K4+)kc+UBIzmXKH<1S=LBD(>1 zYv$sKN%ERqM8(=X5GP2yXuCnFZ0LuWsk^fxz@v>W>aKCnWlgg7# zDg5jx!5YtxzK-i}wfRGYHjk3~$JM#KPkb)czn){*;bwJ;jQkA~9l!7USS=9pbu5>= z_~^o6=Q@Ar*fb9+lJPH7C#>F#Pe{Y^z7uL%@H=aB&$AS!9$y{xfQg2zAv01>b)77x%$3s2>K&GPjK2yagr*6ytAb$zZRnk5Pff9~_9K>G zN$7>=Yi|mg{c;TPY6^TnqGVTlg4YDCwLF!DtXcF ziBh@~kX~Uidzp{kU00G9TWJVaMnr;(y%s;PdOj}_`~Gr2j(T<^-m}{Ob0zKj<7ww} zzmgxomy>UB7cWoCWyn^L_T3~5DI5JsM39(ZDzHEj%nd?Ov%Yb8pfY?hFI@tPdL?r$ zR^W2dcB!@nl>ycic1qO3d(h2x$Sxp|W}G$|>r`FIRv%>KzA{~Hm!dsY7qo;*F=QAf z84-;|OI1y+b?s}LqSmDq?5MC22&{-=BNt>O;M9_r!q`t$w6w4s=Z_LP!fE>>PkofC zggy{74Bx;}yBEL@`@pxjZ8N<~`kV!a2?|ZbOQk&pUSBG^#x0svnBDMm0FG`yfHwl2 zk_3TbX)Y#}ycPV&fO&ya7IDVV!Y$)3aatumqs&}3T_%*8mlOPdvrfHi$a>VQr5rkd z6*%6qJKx~^1B>97jJ(O^!RVJzAxGq`E=f43jt1qWAVBC^8@bD?4xr_5h}wuK;Wa2 z(XoN^nvdTkoqViD-!pv9%avl-HU?~tvAcO?dJ2iNr&@*Tv3p!&%l=t6IQE9I=6+Mm z8QW6WJWuWCRlwS6j^CoI?@M2U{TYlu)KZ;p<2C5j%?aAM1vC%`yYPUK?rZ^0z79T! z;X_BN;+`rzYR4BdRyOiSmCorF36NIr9o6iVVj{EHk0L`OI9|jS+z>Yv(d!-nJ#BQ8S{I|?>gr~?Gaqjm`5zV3_+Th`ifk%9dZ;kizhrDmLCUmNaC+6 zpcVKmupFvM92hK{fVoNOIki;>#s6&5GzN+V%k@||x5q5{?Mk}3&!DZ!wU1~1v%qnt z%PJ-GwItuVO6#Z|K{dTeQgJi&>7?2sbNhY0RA1LWAr=l+q<_O&m{$&uv*m5W8gu#= zZpI#B%Z$X;pB=WR3x6FPL2ccXw%_GcG2qhk=MlaL+ARzIP1E+ij#Rx4&UlOOON~x=e&zJn-{<}kKZ)05n`w^VJb~GKT|cM? z4@|^6@YPq6-zZy8BVT@Yd%llQmkQg!*1{@ zvq1${rSR6@Hd<+fB^Q$MtYMQ^n5q{WoM-5Z6$R%#5;&n_B_Mp}U`XTvTv#!PX~wcmu{ZzOc(A=Q ziQNU>^cQAi8&ozM*cz9hhHlIctN5`Rb<-aS)}bPMjr6&8-YEWpdJAND1B{mG_5q*q z%c7c}KVt^vhvH8FYCzZUi3Hz#E``|waA zlP98zhcd}Kciyu$;cA2N#5O>Tf0q;6E27FW#M(}&N0vNQ0nH?Es58{;<&Qt3^2JBg ze+@fl9oL%}F9@|a0&0xZtEN|dcA&rtBK74|Ggmhe`Z*Ys$V@;ECiMB>$t?dEM4#Ec z#AUz0{mJe$)ucE-g|y9}JZNyN%;Tm{n0T9%SZ#cI2A8r@KyTt#2mt&dx!g+fq zK%)`+;1M}YWm_uydtX-0i-uJnbk2+VL!X_Y2)0gdL}!QqipE!OIZW>V<@X%kncbwn z;f*=rG~O#hr9htIA;-lxas=R<8m6>H+lPYwcV=@xd;QM~;ZG`1Bvx%O!%eYKdy6eT z@vhpDI@AcerHaL<44pe*GZ5beep@5Pc>DNKvZsuZas#IcL*APCLr~_VV9>pnqbl)A z8eSWe>nuMUx4-I%8BH5>*jJG5=gb|+FVOF`83JZ;Ic6P(J&|TT5oQ9|Wl1Iz(a`wn zdzLwN8RJt4Nrdqp4;7e$xXgX#9q63RD;5=rGT+kWWDNO(DxdAh$bI%BC9bFW@bet? z(*dG@^FiwP$Zh$%mGXO$@)%j1&YAv6(30{pcg!+(PF~a`HJA87nQVQjBCY;EqYJyM zOF1tth<{W)c4K8)c@btHJ*K^$rl9PoMus8Cp&%bS>a7~Ax-%it351PPfFwx)B#Sp1 zO!&izVpOSwgT)XUYM-}-J#A01fTB|?7Sp{LQUp-Bw^!uVT_r?Z6&C=>?0}ms|^`4VP^I==5Un={vAnEsxJ3e)99AKqdJDn8HU3Q z6?44%XHf~!>-dGMKEqqg=qeMn|^l7XNNzR?}?BWVMCQ)8m&(T z@qBjI;xk*TRqG%3pdT~Ew5T5heC8|;>5nz+C* z-Ch<#vQU!%{>Lw0L_P$IO@3n8YW`vHLm7g2=(!_v<0(>=(_F%tgd)v6Ca>FTkZmnF z`#F!g4GK{hjCL?U7ut#c0PPrNie8rQ1O*0-d`*mmxsU8VWhx+7MSO5Q#W`k`0+^P7 z`7sj+KaIB`AcbWjRa-6l)J)3ECp1K8k6w6QENPD+AaCvc>=459Ge7d?C1kQz)cu(A zA{$XOd;GXhU5Y(gun-VOn*mLOphP}J@1Kfn#-2?o5HQ+kNVzFb!>#E(aaQCH0}fqg zw;%o^ZlO_Ui5v$aJ#J+MwZDsI-&qy9Om%Kn?_J-&Z?&g#3|xv~+RY#Men=kni2}!a z@_3)G%KbNnnH}c^Z~-0+#!WMM;wiwRW(Lpk9W=x4#@tWu3wJgC?qBoo6>!t!PmwJ< zT!Tp07wKrcSY)Sa3cOI4ei^CZes%yfPXR>(Xi1WGLJty>3+=duQ-?FLpIc@#5DA0N zyB{6|mC??41%+KnmVD5gXt#=fOa?YfIxanYG9nY=N3d>@o1bf!ds->KdsY#WWW&f; zj4@(^I=kHb6)Ch)QYduc?*Z+(k?Y=Bw_YQ(C?f&?8Yno-g}nJvh647Zu&qmZC=BBZ z$Bb`DdKzp*|Ck?<@-~D`=2Grn>Qex_sq!BLWjnDune;Em{fycEiE>I%&Xk1gC|VW@ ztM!HfE0?YAopkHA-XKj{865m~6yfWdV;&o^W(8Kda{uy&!(~q50{eJX!K+)3LKd9k z{0sdJ#Uhe2Z?Jr@IZb~~cDNN?TZq<-$DiabEKV{!4&n;)Ut&l7!6^uz5R_$iSLuu6 zJhN{b*iADplMGI&4$6`1`IE?a+AGP&zssOi4Y&fyT733CdgqE|qM23NilUW?64${+^pLyKfiKBO%1TBuZrdXcwkyL}5#zka@@? zkrS0_EX7_e|KVM;`tiI(^tF|XD}8+K-7laY*)y0;pKO{AVw;Y!(+FqIp$KKTarQI! zy!pJW%PZ<1D3Cc83jSN==-%Z*L~l-)X$zyKE?c+0QLujRjQfc76(@yf&be*dU*nN1 zJ}eNXyjLuvs(p9TDXb*hl?-A;m>p?q-AcW6@?)b`ys+D?m)$HL2eW1^A~>$MSvbz_ z$IReLMCxrNo@5QBxEuxCB{Rn7z3Txwjp?K^CO2bc7mGcgCbu8GXWCM_>GPVzRf#ICkn|L z@Xp%l*3s@1P1JV^AGk%;F2Bf5e>q7J$P*ddsWkfLlkpq$TQs-3=I`T2>WvTWKrY?N zn#U$|lE^sG{n1yCBi@Lnh8HVPs)F{?>&6nE zRh%fDC*fGv5&!m$chhaqIi7iIVen=l7v_~e{iXjpM5k*tQU887r~4eeHnILJrlS}B zZ6E;VZ#MSHO53t*5Df|d@{Y*J)=64cCd;7e6>Xu5apkQ5w_x zsq%E*L1B|-{M)w;myB6Fn`*^Bq6SagFmR7&pIRC`jkAMXz~*0s4exgIWWVmBvUiUq7K)SWA~a!GK;t%fb~?`w+7SRga<80hQ7x+ni2IRd6}5DoNoN zgw}&Etn)foxcbrf>6 zeRh*qR@D;E+_5xC36(k?Uy2la-u*z)DaBFAQVn^Z_hh}$U8f&^UMan;x4&;$j)`(k z*1$rGTflwvB#yqs9~kTTULN*j4jCsyDzR)>SkxfpBJ(?&INn7N$65tuvoj=Hj`e?= zlfg+Qf)_TeOEvkXeA=B^BsO)P^jkdjGpk% zJvppe$cM;@EimFN8HjvoY3S`cVawQuzzK%`1Pd&ew+iSI^ALm$)^JI%AEdRP&v&wn z^05GKr`GYxiK3>t!E}*~d*zIpS|F(bV3PYA z!j!%s;(TNB#NFlizo(Oz{T>AjxdsYTYsDdKLDHGpOEC8oO;(#3o8~d-IV8}o$KVd^ zDPgB+NC8fYM$MAsjR>iFzzGg1mk%BfTmIDourE*Vd2$4k`p(4mx*{=gY#l#|QlAUW z#eFg)hwf0pW^(^ibQf+-eh(YKSBwn?jArEM?ncBhx@2_2Xi#DxsiGSjDd6ZvxMZbgr#*dfxe>m53J?A?2{kbqx&bEi~JftMYVKGs8QeWq3B-oBSK6y`=@g?${Y;}n!|x-l(#T%WMeim@&CKsb(+Y3 zyYJt6u`lgp9jO0^X@l?>DrDY2cy{pXXn?i&HC}3^0HC{1WGb2)t;N$mj1=_oU+qu& zJ(r|gK8}{TRXF7}DpU&()X(@cpvwzVW>K>c)CUhT<>ZjO(cb#)=*_(l`y`E*Hp1SutGQ)PSyLUygCa55fNzzNWF1) z7&)?87#;tU^$HuV*5O5)+mlsR8GppgHhL1}1}=+k=0k1+g${%mU;fkPCvgXt7zY2576=z~k1+-` zaOmH9!`|lpV$esD&@C?7>4#p7;Z%yFdIjjZCpK(UhPl)0-Yo`n z|DZ57V$pm$2V_POvA70*%WCDtfE$-(EstTv`O(o`p={FmpXov&zV-6A+pO#cwwxIw zN2diZ(;z3qbekG41OjWq!*_XezZ>WKG?%k9{(ivdxIE*yJa!oeH;;47Zv|O@%T!`KK99<4T1rG0V*e5c?)H7vD?8{NN9z>#t{8h38 zUSE$$a`fr1)Cp%{w4kVl;O#(}`)iYwh@ljp&dQyq<8o+EsDP^{@E*X_^fQI^Mf%qN zo23GOTa$rfn=I%yeX-lvkwO+xR>>n0qy+~@iFo65WS4CAlZTolX01w2OqHl~FS6s9 zAF@nr?AbPZCFAUbCX$B%*yoLWkdF!)YSWBS<^lugQC&_69IuZN|7$K*m7hwMlJbQU zp%Hq~b(x9n-|5l`SJBSJlKtwgdfZZo|G3Au-n(B6(WHM$*L(8Rl|$u*rr9p~)0LI2 zp!4;{vgzd7tI>%VA^uo2t;|62cNf{ewo{MR8=pnOvL7Fm9LyMevn=URozqNcYKv@H z1gWeb$A!+w7Q;x?O6h(K1Lz&I*K-O>ZhbN!7nsBnA*X)3sQF;QU2vud7ZpZtKV&yGVrmHjcU^;wvt`~SQI8#b^F0BS`bk`uM=Q6 z_0JIbf3DDp7XuF!vjfXQmh2pDkw!T+0~e_v-v4xp&PRj0rnxyIym=)!ZwaPxnyuEy z@=nFa7B05R9XN_>l*p3K7YJ9L{WR?Ss>^goPN8fZp~uw;|Ne!}uyw_$V~*A)##2G>-mOiZnm)TorS`u8?vcZcrY8W0cBIU@eY zmDwZ=;1p3q5Jow4b3CVgrH06m3m?BMh(dV4(om5M(}SsKQ3j8SACQ@VoxejTrAJ=g zPI6{DPDPviTrv;jB5Ft<6XT#gZ2fEBs2En`QS-~G>Cf}_3lTu6BmYw}bU{79+#%xU zV#FfFg7G={lF>~&zPnW&IY=XL1Qdj9Uh*+C9-L8Qdq%N>;6Qb(OqIK+wXjmP5}h4( zRWup~By(Fq1%<)lw}e>tqHW{#lya0%n3&!uk0>hK0i(9J$qFR0{4y@6Gf}&!oRH@Z zMloHMNM?8pvoTowf&3X-J0ZR$;;r`; zfd7SG0ca2_=6Q-RfSoOW;tIVKf!LI5@mJ8oBH9A58(=pU@O#I3AorS{pY#PQ+K2tJ zdkyt;5EM&}_g8?4fNp%*qt^?^T|HIO)3XZRM~0rmX_FP+(J~Ot}fJx zlJJX;o){ylDlVUCZJEq?W5;?%jD;SbAjAxgBr=)~<1LKY=nPo|t!O>>V|iKVGpUG= z{d(;y7hY_lLUB}J=3GddI53x(g#qZ@%l7KZztd%8R8nPRo6A6cLsQAhHJYE3JWR7w zVC-7%0$k!c4HMvORe-fC1TRbHI-;h0vPcVMgCc|jLZ?F=m;W(NzJNZXpoS( zX>Ybr9@cQiz0k_rLb^=i@xE3t8_c}FaLo5tJdG1TthFnk3Aj>8^4ZY^1Z<|I?^~n+ z)7wP@Qhquoi@BtF5;oa#bI&7sMO~rb_zBMV6b+XvKcV8rbYN;0Y|rw4kZ1`ntnRjO zJ%_5kx1I3ZLn^CV){dp+ko&r*$^;dt1b{1T3JT+)iFH&#JT2!MRS}~mdCV$(e8Cb4 zpqu1~>@9P+XCZ0YR!i8PHLf@-ZS$H@7J_ zcXU`s2AB6rxzacGUWG_163<-}AR~l!dMx2Pi!*CwHhp!>b0<(vt?vGNmV*CIcyqV- z6P!@PUHsqlB4G7It3nb*4DGU3T@b5~she)SRygZ$nMg(dFEcVF`L3H*1?%tfF&Yqr zT|^)BaM(mdK1a!%)tx>Jx0!os-xZ^UffABJLQ2t~b22QGAq`FAi@d0hpL{fmM>rgb zig+f_<$>}yX_pJdSyC8RH5#c$~(JNH4p}R8-Hcmwp!o6;MIw|8KHtakenVy{JmAtwrEdRR|v1N0E#HG zE2CS6oLf3b(%EUwe)dv@& z!99~oteLo5pXx5%#Zg?M4wd?ZKr`_y2(1V;TnbXWEFF0=NVKsOaEoV2HTZO>NpI0+ zOAmF0Z6zA&X^NCb& zb5B}^;1H6cy%jFoeXDZXD{ROsY;e$bvo*8HOMf5N8a ziu_E3EB!Z7eMD#~tZ=f+g3~mveF#hwuh3R?VZ2;GvNAEAEFhKMvH#r$zF4f87;Dr1 zSkrV`e%}-5PX&tpiNC4tCQeWO;fP&Zj#A1I=4u5|TZI#xp%*C~nmji-*hTM2(HOWu zbv9vihq0dfU>`E0)e&@V1xoJ`f^uEvw4tC5}Q(vJ4NaY0vK)p zKT*BY9(a3>arqb+XaDiGZs_CT@>!8UZ``n8FyjX}riIpAncH-z=)8ifWteHd67nMK zp{VbeFk{QS+=}O|Rq(ALFz@9&&s&xY#xCXR7u zeJki00?sq{Xq|(pR16%Ar~Wv{4r&WgaZm>wbg#9aZh9cZi&dVPM)uf4@-mn)T3_{< zR;Lvv-!{V-#Bgb{UeO)!unt07(OLlzZ#P)?h>T7WiQceIK{U|16&UUUJtjcAOkW4| zz+-se*Mg>``x%~T8a_8{PE@AesSbT)-4&UHM9k5!9t~5Hz1be4EhQB}M#jlu^QnRY z>_gS$j|6~H9L`_f<_6O+{RMZCpzn0>xiOTo3!a zF%!+KljCFFDPecvC!Dd0puuS+Rcqm+v&lQoua15Qi#9#%>;<6|K-c;ovEZI7^U}9% z!uc1T31k7U5oq9tFy6nb7RfBvcc+oAw0N%sLEXl2wZ`JO&WUHNwr8UDXC6^R7^kF| z`aCO^nc;wHy*)7yNuY^+4&qUSA?9d*+A782r!Xamjia|9**T&Sdvd#Hq8YXri@m)| z->2S;v$>=A^ZJ<41%c@X7yp&qlwE+sbPn5{`pFFD2~PlmD^?Um;BhImY)V6 zMniD8WpK4)pPRexO6VbXLzs*$S_VmkA_)&(&oM6HK<7pbPa&6{pPYKM<7+W50%j+~9&< zB8xR4QL@ouH{0RMb&9)hI}hLT(m*m5K<%bYBfWu>2js}*&WJDb zzCRFlyC(cD_e&{1KG38(6PB`xkW!%kbX#UdxT>Xsdm0-)My?vJxZgzK%}dyPQMQ@j zi`rjwc$u|ic9s8>6%~My*3WNE53AVH-*%YUY8O)cp4sQJ(f8l6b8_d`q=KS7iOEk> z-a-;lgX+K_g}0n+ZyqCx*Hzy{xV^GC6($T@aXvE;YG;lsEonDhy@rQg3a~tYPM!Yg zJ%v3SKZ6AXF_<{}&u&olX}$N$QbmFXN8n(UTNyp_vJ~&3FrelOViWcizs|+r)J65} zzW_-0TZKk=YUeQAdEZzQjkQx0?KzUT_b0lDMO8U`m|@`5X2fy|dGmcM@H*lEb@d(S z&0&)6J4z=w+g)+ymmO^C+HX%LUhW0g!uMSr+D^|Zg*Fj;`7lN*toqpGNm*(zl4t$Y z@jtrM1?rdA0Qnag;UBE!p8w%p)a6GSR|h>^%2m8J+CqG+S2KC$41rwpF}MzA*-mCw zEGQ1Jf0qtZS`jE-afw^;zxs_5!|1L6tR_IwO2xX{LXj5A)3Pccljz_{8_}$XnpvVf zRAGAqU1{RmP6kLC9-79m2mSEpnw5d=?2&=WjGb`;nmKm-Ly+(Lh9G>|M+A$P*9Aj} zo;pk*XrT6p$CdHPrK635S_2@EffGwXJ@2K)h81@$ilizVC2li`dH#;9p`H9KL~>78 z=JjN)+IOsRFWqU7S9m2@_lf=54X+=iPV5`|WjzgJKCE+U!Z#$3)0=l7GH?a&9*NuRw-F1|H>v9FQ&pg>!*uk{Wi}hT zYI7U=hQU&Y? zi2ao|gDI^Qt->#F$9li6m!`R>h4%M_okEAF*#-70fiC>zu7HnoRWvY#(if@`D~FDz zqySw$9KH3b6JC5#IwIrqS$zjuU+g6LaQfHv_uLwL%XR-fx&H3=N%&lYFa_B{?MF;! znbfMjY1jP7^@c0$>(doNZ2BO*aKsFj-t87c*%c*BFpA0SuyvTl#8s?#HfsW!EGM?A zi;B+)!;cP)7PZOo@gYBswh?W|(d?JK7Wxz+B_+Z1^>*==^xr3+<7v_824ZOEJXAEW zY)-#O-!E*mPVLf5EiQ=ynF1dXL0~b3jCfO^)*ZS!N#=TzVWa;=v-Kv0wszNa?n!?* zAU%`UQK&L0H7EQ@(%kmV%E1Mt22%(~6_$2h)o55b-q?i|M3Ld7FI?kl6^?<1McAVYGn8iP23L&M1x@l7q1EhvfxJ*jZXrxt)~U){*> zc&2r8s?CpN{{I+iL}jukP=A;KSjv2vV*w;F=^4iT_kdOXLTEWQIZ4+XvvWWCeX?`jx0t$1eY`(&gHGtv`! zt=f705$#3-{%XcXl&<{|5=CgR$@=;B_LOv*Hr)X!>!N*Qp)eHBGKP|m%o;5Og%85H zt0E*P#T7=h=_E0g3keNn{eaIXse*PoDhA$%W`0~deS?EmAFH=$DCo6Nl`fV`UorTt z`+K?j_blmvSC@?$M3)>l`*IJ=yIF2L8u`)Iv#6~f`z#G?LT9@Qb;!2R_>% zF-FN)ZEaS9-+nttNINQj{Z^HQE?S98cipW1ah}J$7rEco4<>=)iRMUP4I^Q$&0#~D zW3^lW(f;OM!acS6_gBtk+C(;J{CX!c=4#g4)mNF`NN(#Jp0 zYN|#H%M2piur6m|TX3X`Hg1V9TjF)|%cxmJn*{Ob)J6E}E~^4;gv}y$^wC``ySA>L zd7TFPWSp*JhJR6ov9^(&G~&6Qsf6hUQcS{1NY_Z z*l~i5cIukjVRC_uq9N*9qnUhEt0uB!i}o6UPf2N~a4jG)qIfMW(Cbsm%2zMl=P#BY zE9_RR@6D=?8DGxH43!gL9?^Y!|o)rG?%>qJ8y6%srUQ!aW%62N%pWY~Xt;`&^SjgbE4P)$8tLI+N} za(jEYR=+Vdq<8^rOH6#c5n*~&*t+h0GE&rJ*H2lxB6IyL?}7HNO?hgTcStprX+1MM zXQ&D*Cyw-Vouwz{V7@r|j4#HHa(Wo}sEs;zyA2Cv(G8XcnSwDFo-EPrbGz|Eo{3ho z|ItZy>3dkl(p*#d{~`%39N7< zUuu?OGR>-T6G(9UVSx)3ts5yh~RO;yKt__t4|vRRP4Ok>8kgzqo5!FNl|DE z$-i^E^r=W5Pg`CY@U;M?azQAK>H@cHR9mp z@s$42x{-yE2FSDdD|v`3qSDJP*$ZLrra5-*0`1=a>j%R|<~M?9y6griwgjHVsEy6P1L$zV_F z64#vP#dojw6$`3{1O`zy%aDdLA^>f z`bNH+b6Qt$*QO1!dYd5C<`$05L$r0;z7>!6)&{ivbG@z~f?9|r%z%pg;CpjoSF6z> zcznBmz5AVE-qr60V4jPUW&zU%Us(?j+m!ZWVfU~ErC|_DP6WOhoOc@6^LyA`iBR*^h!7(k>EI;xmkpVls3pY7z8vsb- zluj3=r#z$2dDf9e>fKu)PFFIwJ<02S@wR*8-xNvEw+W{IL_ksnkHwJ<2YQ^%>`4L4 z7w;^QaHg*KIis(ChSWRcV0Z4j865nP-LmlHeHVb_fp4IbGCvQT?-w*_`86>-q%ez3IA zz1fcE^jkN?X%9pZ`?Sg$PB^9hJT`FOV~l$gV+#AA!a&s-J!{^OY3dI}vC4-1AbI zx8Fvhl9~HuVAg*u@Ju|(qltTbkrGYhh7}ZrDrARrC58~7oDxD>@ZxL`#HJuY=OR{3 zOll}IoNlQqz8*)UpsTQfJj&~IwA5DGa-L)%?q}$tq|H8llQacTMgNNycSq0=s;yG8 zs7C80u}YYo+&I?oV6TzBphAidMlv2pa5QN6jI(tRKAqL@E*4m{5}%R5mVuCx1VFz+ApkpKe-DnZ?%; zum+q&3n+63M2EKK2QH1o{rGp-nrR=mplBn$rOREcn9N7M_!p=U0OUQ$(pXr?j+AHOY98E z2~hz<9hJ8MnDzZmmWfhtST~4G%w_>h@52}xE1+1_{|BED`2C@O{N=^*kV!4MP-*KeJ3%c4PDh^Vu?k;qq85p+sRD9kyMJ9tJYDEP}hgd^eY~D~x-#Qk zCfTIBv~Q%r0YJ%dg!z)GVRw3Tb*>u`!T^b%A#}D;nhc~$zmq9S3NLmy^Y@NqLIlfx z9h2%G0IzYW9FHsCaRLa`lT^RdtRGbyPf@p}T{N0KOGbFVx)Ne)@FRJOxYSwx}PJ%7g}=C4%OlQgiT- zKiI~zBKJtq6TbjozEuYELR`sC2zjgi~yLRu7}@|kotVpnWyp3Zr`p; zvMjdoSW0{1phq(pQSYo%XFgf)gNVO5N1L1L=16O}(B>Pw5|j)H>TZ*EqcZw3%;K48 zNEFllZpj^`17e1K*2rE8<`~)Ww{kCOAzWi+tY?K-ZHmADZvSK60C8 zTu^%a(OByr(R$lN_D?22vR>y2So7@#OZl4s3r~|o0V>g$NR5uDi;=Pk3s(xk6XgO> z@kCS4`kCILnch~LSIQDj!7Kw0vRH-M6qUHEov`1vo-~pYYhP4^)@K=(N~l;3b!gJ% zXwz}VfV5YOa(}DI_dcn2q`QGEl9bA_c6*?=*246K?%8-M{g6%0*B-h*#eDk;%-b`1 zkfrKMOQM&4_vQpXIIZZ|Zafn)xk_a3vWNGTw%-s5o$Ca00Zpw}O|gp%U&+P<2uqEa z!*7RA`+2|G+Ur=APg^yMPhYv4=6OS&%KpHtYd{ei8D=pfP7sxA5)Rkqz#knkv+Eth zk&ODMKzM(x0LC#TlW*R9Y~vz}>b418Y=(H8E8gZUOsHw%AX!|YX4>9aEL}r={=Lk& zj;fqoe9R!6OQU@u($;rOcgm6?Gm(|y1q_p*$T4S!Iw}~^sL2ipN_AbH0@gZ54~031 zO@C1-K;cnLLoQukYi#0{{sSQ=OUT<+WEGDJQavf4*#1ZI<$p-*jzuN!xOs(;T>=Zo zNegk?B>t`xenfcx3VVFE{+LEm*G$^tTATB+#)e5^030Yu6~TLomlrz#( z7WiswC7T4i>mhR&g^!gP(9~i+8s7I|&}5O+}F6sc`clJ{OS zUSN8$41#ShNFGSeYIl3kJFjSt=#jr7*<)7#YerS9%NI?ius&U@$inEehR zgLgMgHkL1PIpBC%7exlO*@EbI0)Snv6!wEnd0gOGbdV!%6Q6*Bh(<(Z^24LL=U=(0K)gl8HqM`B9c{B34O)esP457QnvxEiC48t_wIc0sQ-~FrL|1^ zv9S5+ji;MD*Fp~VPsD3&Refuv7@`n~SHRG&6}YeG(pDS859pIl#b>|Sg})3h73Aui zl`03A<*@qu4+iZYG2M)P6kci-L4>$}c45kWcfDhSv#PdIJPU+FKKb@iX<4s~tB;cD6OUWA*#z5Q2}tmMeR08u+>(kA zd?k(p_+RN0?eix$$(KYpJur_AJDSX}c*AblLuPQjR7t|6>?o#KNayJh(u^Lg)|;j< zuBzp?$>X%|^^&TJ^Y?$r`ss@?F^tPWWH95<2M_QLxYhEF_qXp9lRVj}s_?F?TFkjc{ekAdV)+^b5KV~XFfZkD#>y9GB7nBLA1 zS$wXaS3V<5Jc9`v!r{q(p+;9(!&*~PZAr?WgQdPegAnk4k3$0iuXC3qY|3YqmJSX! z;DHLo{{w+lo$ah^B5BKuY45Ekt*6rN(7Al1&yN@)Z-T&OECz}&$5&UYw6W>9EG>rd z`$ESyR(`(E9>C{F$^ej0dJva9-_3jx)oWyAJ>|u_;r8#iR4@qD+Rb7&Pf-0dlfme; zYjDMw^iv^GE9RHC`Q0^;_?n3!AgBqymFTufIjoTR%-msi_UnG!g7}5%FEW%W`DgUc z1YkBscS#`xp(ql%Pgde03OJHNLZ$m(KIYYfB5gC@)_>f3%F8v>NbTF%scE^ozQcS` zp8w5TvAfSx9!qv!rV_8qO2QipBP_}*WIeUb9ei>Nic0oF%edC{kx2)4Kc7APeHxr? zPYVM{!CcYx+n~XC2uuT0H8{XRD^-TPWWE<^9xx&0Y!wL=eHrxZw?#Bg;>XgEd~Ce+ zgo+6YjmMztK9Q(ItI2mr=wB3{E4y-=j#Vw^(|m;<{17h{Gzfr>*NVMOn3l5laEuN% z$O}q9Az6Nl^i0}uym1SNKJPA)aeBn;>#iAY+rq1W2Y&x}@vit+=m)?&k>Oe1HldVe z^fLw&ubbEG6Cl|1xFqjncd~F9`>ok2+%9x1D{Kf5NZzJNvN@AXk}cX?1%ha(7F|)D z8AT z>%pD}ciQ})@CV#JI|&{1KcfoXOH{u(sBKKBH4}!5Fq^jFWHYplyU_$kjmy{sJ8!xbTc`;~cn&arPT`TW z8tfb^v;1#H{n0&x1*1n6I?GE7Il@-onT?p3>*KG&tsIHU>}rkxdM5KFG(Drx{-!$b zf<(N^JBsnPh7iVvm}(orT9u+YXk3{tcA*6?&j2-Ic4dY%zT+lL`6*U8V$4XE9Nb5V zTUaN#d3poHyFb49%JXcY*F9Uz#4HVtJN<0-e+;YG3O6hGm%2PNDZWx*V0P5~tUM!y1h zMwiTcJb;3FQjHs9Z(}k1^0;;4qeRf2acaJ>FBfY(#2V=$)!^{MDn;>F!(qkgmd=&O zGAnMPW-e0^NqoqgM10wTVG7TO7yFG(-{vpt8}g^BU!z$~jifrOEu8=N3-A*zKMo#5 zay1Wcn9x4RIGtHf`Fi@V`})ieo&Hx3y7PVSRlKAA z?W;Q;Sbd>P(qWyj=!<;1sL|@U*n?}>9t`hUha7uOgSgo#UVce9hdHY?@Ja!ZX8I_v zsdY}u!P{&ZVfRd#x{ZiSo?h(?R}J;)N2Gk7+50V|zd9To5;kR~+00DtZ5ZV$8U(0E zZ86x;e2c2mnw9I{p&#`8XmfL5q@MT9L*^1sprWG}osTHjYJi;D^EKXo2LXg zb|M=lQ7kTBupxrE2(&i8`1bS({r4}RRWg9SE2)nbL!^}{fYF2U6yg=lc^FM`>Lr40 zTqZ%haxox?Sgp>vlZ^fu8Jboemi52$hiC@iq)dqqbD9rCl;glDzgmTwmD4UFiNfhS zV+W`rRAQY-&E2R4IVPP;?dUHY{&u&P)wvvjDW|J=HptQTbVNGTX#k4Frx`~~UI!Z( z3UuZH9ec5$8>hSqM?wvK8d3m*93FhmxPC$l37CRgR0%9D~&&NqD{L~kOE=5qB|;| zh=$>_N!6^zklzdAbeChsZByiH1L8&BQeNNvc8fM>?h?4c<#E_2B*bs0+P^>H2pz`* z1gV2`kDoozQK(EBjTtcjm(Lr{r&sZ(k1{_NTwrspNXZ~Vp#OWOVP!F@Jcf0<12!4? z+1(kI0Uiu&!6gk>Z?fpryR^vtvQhW5tav2b08b6VF+Smh;^jia3`5LJ={-OSd%CuebAg)-#Jsh zCv)4{%N^`7wQl{p+W$w9iP2~7C+mY_fx5b!SKn2?j%&Y^yScs*>Q@FEQA<3SdVQEw zTYS510?vJ+pY((<-!o?s@;=yQ1A5AhcLdu@D(pN_!&@U_^f<Dbr|%aGivW8~bXpDTK-*6mU%5s)gd9f@H_0Xx#r=sF`xWnojx1r zYb%+q4GtTzR|m%#cDZb%y6lPCrXEEAK>OFzrHIcKZQDn4$!gIR0=3U78vl?#oT009 z8E&|5uRL4D56PE2Gu+eRBlqQkVVF%ef42M|1M~T*?%I)Go7)%K9=a)i>q=F4$vy*u}-fg0jL`ojphw%V|KUGENp9B$<{2vD$6zZKQ zA8fIM>l=3YIzijLUYEYRF9Wp_>xDIh7j z4oLr{ahIX@cuf3L{X$;Iemo_K*U#z4^y9RKi0 zmPy^%2Q;=U4+?XiH{`xx9uZ>zcL%ta1|y4F=_f|?UvUiwqQ*V`<;sp^Q3HoQ$B|V; zj5FD%xlV{;t675qWEdML#{^8aole1I|0e2=1R%y$;3xkc>H;PdBJA^Dut0n$1{k84 z#66>`74BrS8IXp;k0V}e5`=p1A#>;nkC7$wA1 ztG)yMvQWagfCz%9xXQfj(?7>tu8Q8QB0AtVCp{d=H>$1ps)>pP1IwQ8;9`t$!$kSK z0kI8Ts1HUTMW=3UAmD=$xugn7TsIV>eGOgpXkNnoGS5`feBxt*1Rnfh%c;_#PE%ohA z0xs3p^F$lMu`p&-0GUC<`zRIext;?A%K%%%cC-VwY@Oo;x35icdoY-lf* zMU&&e}+!YHL#foY>3YcI- z$1(1pSyPiST4;uPJ^ImEKj;kDa!v|asuAC96;daytzU#Pcs` zFcdHC<2r?4LH%?JAiJeXJwd$-C;`1J;{cU;z;@SBpkN^MC01cJH6(t?=&PywnXb|( zhE-Fz!64W7HlMHYsA3w_b)3i+6VEn0DE1dnWFKFY2lUSaYW}aEPeiXdvmD;-Z0ei1 zQ;--`HV^7TyF=JRX7NaxT;B6ZokVX@$nGIhk{Y*OC|7Ov=a0;ujNU?qtjql-eFo?T=QR{v1o0rioWb>ORl#kn z%rK~MvY(HDcB0aC<`}rw?riqvYZ3+zD12SfVr2-1ejSPP#EfP;weg0Xa+GH@5kpqBk^!KHlTc`}!Muexb>+2|f*M)KI$ z-3`;%=g%XTY&M##jAs4zbn8sRc<{{ny%t&7n5^9OIuSRZ0eR%z2lKVk&9F5wwdK{9 z0cIX1C#qFm{K7Lmcb9uxgwP|9##4ChGbG|7_VOY<#|9*NwbN6dNuPF(1<&l4Jx#GP zCo&@XYlFPq-hY$Z(B-39nvETzGbTZTh*|FWOyB(!bPhbs4_`~o*OXYnWH3IaA1S)B z9AWfSFYjwvhCUK+=mdjh4B1@S=={s+A`tP27_5j$#+?xdUxI0n9ANfmesfTM)Oo@N zFwx_vRu~bi><3WS&*jkkM9{u*E8x%Jz?aa4@yfzFJAU><>H3ar$sEXn0TVd~M0^hTwDt=oIvv?c>VdQl8Mr4* z4cFwWVW0VhGv`240zW+b*lf`Zo}!&H_LyK^|CtzbpRSN<9E%k&54T2{ZRtbRW58gML=@jB zJH$H}V_Lo~UqM$KTB0k`4Di{=a5?ivu_+t|7{uVXoFTPr1(;avGOL)!28>Lf28>o4 z$Mou4dMJY@GE_^X%H?Ox7?v!)Fl9nL>K94-5Q5;+7D_{}$%!T>u?=2mdP4^3WT+fCinVMu4=TIrhSqzA68QpmA#4)!iVG^TKZWWe~JwOFkB`!NU8UH&GyGc zi{M4+US}xcYKuU6x9{!o7DX86!}l$RB4Clu3=Ds>0|1EnYH@rbV_X_RO6RQo8;0(Y zZ*DB_+7!8$x}E@8nvaoAm`bjrWs99h)PW-vrA9HuneOO>=(}7ymiG z{y8%2m$UxY6m=v-A1hoPGFZ9C?8~MUMMzKXi`-2ydt>b>)xwn;7fkIfcyRlh%qji>e@@VABBNSg;&A!Kn6gp9#!Xt47f~VdNRyP?kMjQ_f(N8;M}-w>0#L=dlItk; z#0Q79a~V?6Jm7)QGT|~u8e#oGoXL7~xgK(Bged%9jMKqs2GLuCzP{Xf=Q~hfgsjOy z7eF!k+H4zD;9Nx;DJuM+(+BVYW!J!NW_{(i!{!?(0w3!Ol2o3&|0Q98PdJn##)?nY zzHfxTu$BV6?u#Bu^fXv@#;|@IK04B`e>qlnqP4JTuh2vLrBe2L*VJ7i@2^NX?dLYq zeNq9MdnCllD|nNN$e>JDwelHP{}Lg?=BNDETOgljYF+#nNf1ry7%V%+CJRaF{B5is zQ+OOF^ekQ)s~tOw3DETY5EsD#jNtecsR6nQhrC#oh@0#A_A}JK%+*ZgHrq&S*EXsk z=bz*+-CQ6n1~mQKgNYffwV}wbcVKb_-z<8H zNiDcgGgF$Btg~C0{VSm+H%+K0j=y)Xp7yGW^V5*uYk4~rFBn%zY~~C+(!Z8XfUq+g zH*5w3&l#%YW;l{=a{3}>Yi76LF9@L3@Q*<_-&8F|Phc3^xyCH|=y``{`HfkJo7PrO zF<5N`Mzr7SYpa;)KfZ{&jz7EsV1yl{79%DTH*R()`N?4EPwF{XznXnR$v6|OOGout z^ZA-3I>o8)G#=$^mX|hwqGyfL*k83O0A~v8{rs%DP-P4f(#v8CWcfIe&1aWvtBWzP z$PvCH>$DB{G*iNP=%!!_XqcymjUU zWDqIDYeFX$*I%d>yim_p8JUJC{t>^n~^JR_Qd=U?e`>4z@`TgdnZZZotBI-zLnmIuoV~o z%V_{lq!@E%14)q|Qy+%!5uLzMd|b~C*GUFL|9e-YdQH+=rfXsE5;EoLeSDO6xFvUf z$|LsWR1aH&;FSLP=uF{d(&kO$mx&0(oM6)bZb}rVQeW&d!qd)48fP+DH3Lxi^v8i; zy#qv&R+&7_r*|#)=l0p{dlC5XZb@0sDfcCEvG3Il_C@`(e+WP z&AV#jhTehNYKgvV%|mDG<=a}6Qg98QJL`c0?cK(p)@t;hFA_CbQj=}<{CFUUzy3C$ zsaeHp3+PW*FD2VC_q&!Dy6e>>YjL^K{scQZUsio^MCNt-?UdT2G6C-54_ zc(vEfdT(Yf&Z@Asw_lnoR+zo9tK}@-(DI{7G&v3S(O0TH6wJE1y2V5#d<_tHjovP- zDNks#e&(cj2V|eq%v;-GUWnZS@e7fgTkH63Zdt%qDcKfk;11Jl`q@F#*X?K8Ut}!G zxNM8NN11`#QuWMRC@KE050q+pZI?+ig9z2m>jS+@N>d7E=E~Fe*1xybV0-?^SDTx0 zi#kbu3WOKjuxYOmi=47k-&k$E4CVeN8g(sY$3}sXIbfmnKyRPSo*+#Q%|ybO8(I*N z6)TUTWi>yl)RDT##ApBGV5C-pfWr=XK1x(U@csXz=)41={{J|B%emul&bl+t-kiOP zJL7EG*_|1ZD1?+d^XxM->#U-Xl_=HOv$8`fn>NzWSKZI=^VjE(&%dwt=ks|!pRdQ0 z=lE>jr`o(5EGN#0_#Z5H!mYlTY42(=bGcmcU%ZaJBAag}2qXFW#<0FBq zS<*GHmq*Ely$Hy= zTbIG=NWp^|`eoX;OfQ^iHLt)jw3%NS0Zvo%NoxZ{LV^DN3)ASBPA8_tH?r(iv?rb|yA6ol%QF;IpbdOZ7W*5~ zK;Ruadj!~~`L9rnC%{t_bcrfU;DE#}G#+#B5t6CdgzvldijB+wMA;*}Hup_ppUpPY z+8TrfO;|g;MgZ@D8HPOW&~$}@9Bu>vqQf)XA~-5leRMBLIUgP-)y-2C49_vZ5KoE& zOLdE!LTNy8XGf}*b}`E>CxVCbHso$YfSHn$yfr5)3q@9nj*Mwz_ZcSSoTmbiCXLH? zof(8dT&H?&zQOc~`TnuqRQh$#J@txf|EMsFcY)%|r)o*J z+22AI0ZA!!EnLqmyp>-8K$n>qe~*QpsxP`Sqd$w!19JiZNHq5;4(^6HxPry08?C|) zg1bI1g`~;3;>lKrK$D5&TVz-Y9V(qHluUhXCMfZ2ZThbFfx8HlOfrh}R`Mq&IE;k< z5{b(G%(p4n#|Gs-wFAlb6W0 zVw2qBz5j6bR=hgtoE9&?OPkH+xgYkZP}@a zbp$8|i#3AHxSa8~&fi8V5RnAT<4CoaDmVO_C4>rXwCdz4Nd3OWxuQs5v!! zlElaEkCtFyhP+H}x4i%?Fs@pi!EwbsnSS$WdMsZP+kda+R8NgD>+Ub?H{6nWZ?y^u zq3T765JVo6=Xkx0^`G)S9BrcH>U&k^(_punuD0buAK0;ZTR0X9|9+c41(l|T%UeHr zbH4VX%*zcWp1Fn?tp?Inm(UrH(ub4F?7Da!XNO78JL(blHZ_zJ%GtxU9OM)%HUu{o zPfnJ7#3d`8m!7A*9B|Q%y<3>aayMIumRjt0_`M;iAi;=#y7Fx0LFyrd&lT5qX*&9Fn}qaNUvWRIy>>CHFN>z2#o^>h|Q0 z!;c;f!~KT(uF+%7nD16jY^v4)<@bFp{!FVqwf%Dy2V_T(^oFJzBhXVHSmr1m#;Jei zekE1~mzF>0Z_NKw{Yi8%5u_wkQf*PU!#a+KLf6DgEf3h5`e2*!f;I#{0w^h`ze(0G zB1{0roLmxA5`wK`e-9<<+uT=*2r37ge*QSv!qDec4ft~=fKmM>0dJZu&X4v5smWu| z7bJE4{;nv$llcT2$ditz1evFXMP3cv5c;Vxmv3veS<&i$Ef784HCkC_S63aRT|ePl|1-QlclB<^s1Cv z{U>`XYJJO@1m6{RP6Ba1%@|`mcq`Z}kXG$#*PoM8J)P3G6Cim9@36akhBY(yoMsN= zq=`_91oZK}VP*TV#*Y7Pi@wU8Q!fX&(ezIq#07o`Mt9JVGmOV|Rp8N%Pi#&TV&cmR zGh85-0QPJ|%UTM4;Ve;Fzv>6{)vap|*zW(ATd)8|l}fX;kWcwBo>}BDAKMNyY(ay{ zx|_)+XgW#>FgmXKef1(m`&DqJOfZ8ha%ML}v`rqb*l9KKeq7#mVergof0f`@nUz7+ zrY1z65-=feva?3$cnpK3uhXlA9uqc(*X(cg{gH}HmWXtg^LIi^%Kb#3&W1mZ>d*R_ zCVg(;zu+yDR@?W^kYnSKvo}G@#Ul9J?CD}~>YomJ({LPl9kgXSg- zoz=kOt84+R&p{ge0|TtYpnTd#g_~C5a(*jPdE#W#63uMmT#DX}NYKr`6^2UeOLdFk z9HT_6Xi|<*maSk_js!~aoQfZdHbD<2BlxsUS*;Et76NG@T_1PYH)V1t?2K?TY}v?C zx=>#TZ?)*T@$WIx?W?wDe>#94%O8fl40hc*y!9E>z8Bg)B}Yf3Mg(`&t=o!{oEY{z zVGpzdRewGMBE&gWBG~Pw7#Kh`9koT>O{-Pv&@5qen6}73(rdaO{8?4fLk;Z?>Y0GnJ{=yOyy7V=Ibob1ac<$V2<3v!ASC;^h?;LQ?!3$B1c83`c? zV?p)yoD!%%^j5cM*cI^KmEbGc%t_)ozx^KLL+&0zmf_O##M6pGIHP!ySg7{79Q zC!NXW(vSc^FI+jTd`Dqq3*nKI_>LPBGwItuBFI|3fEsRHv|8|y8A&l0dipXEQU?@A zmZdeH6C7Mh`spkQLU;JGXxkVGgkcOL1a(sI5v*AMfK%SY;c>qM#?w zkYMv?c6BeY`mjwOMxK}_Noi@Qrzd*=2R?VT%xjs$Y6*vGF1RH^zb_U`*KZXa#@QME)aJ%WlFD28-f%eD%_ z@lIPk&hd%_wRhZFu41zd8^fBkWgrQR$p+3~GBJ>L@uf*W^-8kNe1DTpHyr}KuZ?f4iQpjZI1o8nTH+T5An zr?MB1g?eveLp6Z0W8eT_A%8XJg;!+r+*+O!G-pzK?;?kmpJp(d_H?DG`|}59E3<7Z zK8?jPDD~TwHBFk|LM|OaK=Bqrq5v0HriDe|x2kpc#p8^~6Utl5njqpM2C#-1?u+yLb88OM2_XvX2hOT7ZMJ%@k#&SUJ?xS5IKu5De#?cb* z)J){2t`({f9Muw3{E(A)P{67=E+phzE=WUunM{Y;5WS37QL&8;!TZlz7tm59h=)0A zmj*H8;O)3-h2ut2L;+EL|2*xpv{G=&R7A>d3HA|Ad_LvpFimTy-{M75vdAjXP41_w z^-Y)iXGV>c7G(a9nr!J~X<-5dIjtZKzPOKC8YGkd=)33kA4(PSyiCwY7a^s?wKucV z(hWlEEzZK@q_w*&cUK#t6A#)J&aeiD0vH#8Q8vp)jjcNa<)(a&TYSE@_-wm@&=jF^SrR$9tm)>;@?syqef*3rSZQ6LUSNdS>NCe; z9MT9au<}wDho0gm_NMx`yCxbC-WH$@cyuW%7th})beE>STZz&)Pfn^=3Z1cRFzQHp zE%mE&{sL{;2NTZm!1#=th04o2$}Q_~XJ6r5J=KCTHhjH|!d2P$B*%Cjn|e(8g~jqd zcT!KBE6B03Y)>|pxhyReZ0GN1H~UJ8jLH>VxFS`eV`Ia5ox!#6{lnbstw-Hg8_fc2 zZM=M{W8?N~xXUHb6qgA)*Uv(6K_cXf8D69&d+Q^55=i@R6LDfR1 z4kTxr?zd5!bw*{&KmQ3@jELuHZtJN{^Q|~v&-Gw4{hWlp?N$8`@ktB8Nps)qLgM98 zRURa>aPK{s`}hE>z){>9_shR$k@Y8gwmBI%RUb%`hnP&o7ymNs)*iaanjn1wD(5XJ zH#eDa1d+*j(KIJ9Qz0(vFD>SGvBq%~)3H({BNdK7*)y-vrq`5fPH&%_aFS5Yyn2BL z_R&)(0dn$2Fpr1@xxxVcsaar-->z(uRAqX(?qTShe5h$w@sj20T#A+x3{l0~2v1fJ z99NUMZ2K?ZHrZY2S#>Y_iO^ZW)>$p|ENL1H0*D_$STF!4KnZYi9Rg#302mX%{Cocn zg`@?xMvSVFR&P^$pqj^pa`q;JnIIMw?!_raYvND!<@Tb}4~tsy)X} z)y6@?<+>NURxHCeh7-lE25lZpQ>JP$gi=8Mz8BFKbPcX~koQqRlaYaV9R}R%@KFM0&W98JGQsC_yS7ny&KSb$Dc3=}UE|-2JmR4ji zMU57!`M*E{nZ3P7I=&#!VqPt}yRl2KXQTYUrp3X7OeVhO z*>Bm7dEOQH5ub1>3krC0#}QMZyY;3=cEmQeE06X73fyN3>8S zuzq9hkQB!yI8+=D>d~<|BY&M`t&te%!E29De#V@^$ zcQ8u>v|9UQujR1GBi!EgL1A#C)(~g#2OQ;jrF%gM@Nx!Gnqqtr;=-38#XVfoT(8;3 zd48H!0Ozm#aYloK0;e}^&dywt zxJYco7hRS`-r<_joV^3uii(Qw8M0{g2vjhASTyJA}xK zkfE&)p!4(v3`Q&4G*EimH$X6~)YVGpw5xK-c_AhVvh-GBs|BCLt%3wl13M+V75YkN zvNPBZH#U>+M=`{5$6 zru3Ox^?xRZg$bNTBxnh2rg;LppIYu1O(bTYk5-th*^Q9rHs2YO|K+|yP&pgLO;j|S zo4U-YynkRcBR#G$)o^@*q`O2n1{`BUev$61@iiCPek{9*YTRn`NOoH6EGp2_Sssjlci*cV@)&T z3nY`mU9*Wdb+){7DWaHnT7$R({q;nt4Iu9q_D4K>vlLah02tQY3z zIoAp%d91xI2{6lr)i(NDS`x+GiiHhjrR6W5xAq9=?#B>sWw?7;eJtaCeykx)QJmSv z-37}Wu~+X*O&Z1EQkR+W1X7!?ICN`7UKoq^{^)oC$^Tfs(izKN znq+cuxWKpzuA@Y{6@kKAeyo?W(|G}t%=SB(0S22yuP% zQFw%ipbXEGCqvmx-$$QjZg$CNvwh7U0#`RlxyZEdw_6ry*uw37$14ip1y8Qo9Xf=u zG<+WlleEe?bi9hvXmE9rI(myxJ=g96U=O(ORl1ez)^qCvpPPf2xVh*1S~-%$*3H(E za(ilq*dSXfl&6(0@Df*SrJq zOssG;T_Hg(!oQ0BgZx@M)RTNwV14_K@MWvtOSR*0DYNk3CWRiKQqAY^97m%gf;3G^ z&=j5{U3XA8n0rq!O8ik6eUej{QS#1e%i;|1jj3Nru~MC}DWCQS(c4c74&j53FJE>V z-aD)>5vw!I5* zT9h((SK|@?k-~CwIfW}h@Q_3*iM~gvQdn>{eq8u5U!yI~RQjasI;l(fJx6{XVoV%oUMxhS zmkzW(wT|p8P2S#VO;g!ruzRcg77WGho#;=j+-8G zG{V=CIrq7GygER37xSU>#ttirFYHdyj$XXARdQ!FSlCmt6eL}Cr2!?b8|<>nEmrY;Z*^rFqVC7|a1lJ5{u8vj)X~npDPkRVzZYS;=<4NBQtQ<=O6j-b#n*&5{5Wvs2rYtHGOu7Ph)`j z`mFvVdD_7hjp}pnrb86SY&B5PY8g@mf1=`=d~>^&H=ja3N{KKn`ZqngshjV^YvaqT z-u&1YhiO5^wjj#^fs=Hi^=O5k71+VCXp#b1(FEQC{6&x!f0#-#f>kI^9 zNeCeZ0F7ha?}ma~fmd2V;GQ_G2p~#LFo&X-h;$dMCV=`_*^;24zA`ea(bY~A&@^J6 zBwoR-l_{lueb}2yETHgPW)LqUr_$3my{Cvs+BW5F^7YJ8szRjUA`>K$+Ri;XY{;bF zXqXeJ8$9>*7mDk$h<4<;PPNP}ZgIOF(I+XcNkdT%@UypOG>crxa?%9(he|Oq0@2Qb z))8V)v6Qe5)O;&O_$D{66r#18cx8jKAd%Bqi*nBQx4(l*908J9W0km76CwjZ1oU=I z_$KirLltY#0NK@L{ie(MjvklZ3Up)u6k9>~R-iZ8OuY`s9|1jzC_6z5ej5c`h=5AA zg2reZE6BXgR;U41IhYPS*w)(|5FC%zwqN7*A@N_sgS{EogrRbYNtmY$o5=u*!nF6Y znX@{J)5qv^&H8kOHAhhXiQjliF3v5nCSQrj{nj11&J00{014A=JVxRoMnIk~PAJc4 zt3Smz?rZY?I3M~mxfrf}`pyBLt-tssGyH`PL(kgt0?<=IC?Q9RFpnj6N?bayF@|%G z9@L2E3zo5H1M?$ggnsZpi#Bog(|GHOovpjFIgq5dmQ?>Rpt@f78zkJelSi15+(nN= zN0hx=D}Nu#w+C3QRfqTzqADCSLtJ=% zJ>vV5o><+l;Nma#8kpCi^E6M>fEav zp&{^Cmh)H^AsnEN94|;mWa3z2TA}aD5l>p#9reo*_Hfi5I3HQxtjDT_WED!Sw~*lG z2oY}LhF+1n0}G~KBk}W7@BAp{51qb`@eRC$ zcVXT625VyJE)CFq@%0b@F79GBlWJ&HjD)wLG7s+1Wq7`o-L96gwkR~h^pU(NE{eWN z=Im#Khl+;Ojhnl9Ja+)YGD7GM8-;3YkVgKyAdP5>R&eXRRI}p87wKVt6w$y zCz5l?bF|vu1<%zxbJ4~3(af|CXrJ}`CzH(Sn~ISh^$$@?`lTT8QkC}Zbd6l&8GzMl z?n(sW9hS=~^ufESxPhQmiLSS;6hknF%9Tg&H-tJ zMoyt~tHA+9cJkF;ff&!&08pPi(nCr+^rKkU48JCl74sjrqW|gjz=98f>DmwPDplWQ z?m13>QX>rnzjNovX3FqtmPjYgAZNr`#~(u$YbTazwg<)sisL}|(^MPpM+09OF@Qk9uG%_*p{z96zg9FD< zL&%ADTsMmlM%%@9=w!slh(9Jo7ZE`$`ZEerGRu;w1_R5w;U+O>)`!pRMRmVXVCw|r zEuFA+Nu2lP*{vRfXjmYaTz+9{@CctmMS^zzG`G_@h?5-p<-nlU2M*)qf}YLCbU9Gbewd*-yzJZNBSEmcdfLWL7Rbm9IW6_RISMD!35`rkSvJwjwjf33Kp)Q0gTJg?#B@!Pt=c?ZR>E;_uR0BK4r1&aM4;Mt?Z z?(0|AXS)(2D|uQ=VmpnqwfkBeSo}O5rAm9%KhEM_p5T?|<=3?150(kXWWSg~z}m#M zO$B4Jpe^}LqsxLXd;LGn2?)1JP;S;Nj&|n=l`duKQp>m>(jhrhTo&cPd>Y3HX>ha^ zPw8i5;%SGJGp8Q^jHPJ?}>FSQ)oot>qt=%BLtS%OMfd>8>1UCmpLK%WR;8p_DvjyJ z`gI*bm8kTD$%}HxrDF6aJ|`*pD&u^i<%wv;Cu$cRkg1`HYKn4d6{9+?UM@^>gCx;= zrf+tLPb>0#MyYzE35TenngTxA$}HLXOuB-%__(dN!d+@H#~eOsKBghl?|@s8qs`i@ z$DAR_7rZ+8yq{I4l~CeZB0V$l=y8@}$=G6h~!QHC&P#h5_sOh>DEEtRhHoV*>+J$3FSa)#XHRU0}I}g`E2!{B|r~ zxJ){|iTicFtZXwXeso3iq0_{e0B60u!CKW6R8XuFx!lgikxR{C25Wp@J6BO7LWEoU zxnzFze%~R*V*n7pMB+XF$+g-;$yH$f=%$~?>kp?SeV+&tI)JB0atlbf-1*rBl<24M zD(NR^UXx5rsOXm~(c#Ss)t;?#c0HVe2%|``xz{d{08|{9HaI(I zJn# zbV^1)8^30|l_g5L^dCZ#P@9Dm)I^Gg{rpCnfR7{Dcw3=?ZoAsX0TMpSxYQK!;jq6h}%sxsEi)-DlGo1YjV8y+oN1>ZW;LC_Z(cruZqaCJm z@6j(Wv*^!@_c;oc-u5wc#*4UVpI39hD!U?7vF;n#{}SKcK6~!@t^c0?SbzRaOt*Sx>7V)Y0e#h}<?8Cs zC*$AtwOsatJ@#*6pl6@#Fe-!pbv^sNScq7$E^a)t@(0o@&zN{Nz~yEpph+R5Lq$ew zjg9_1to4~frCp5ID`evvCn@mMGhm~w;xv*7(WY#^Fdi~zP%PB|&%=dc%?QNN_!d$6! z1?o4bSwffQR+x-Z&8@le3XAM~GO)R0e>htuYN`lJIb~JzloA}gCnorg-clB-TNO1^ z@^W8OJf*0b!{c&Dn{8?G;P_MszMWKk^n@iWZ zjF`uYwB;RO(Idt;2^(C^Xz^0idm-k&jwBlkv9=dsIcRT^a8Hu zy!S!pm}{>~(832BR3*_(#6uh-GKQXPBy8@i)mN4;Os#`%BhGU~Ill6_oGe7&2wv$f z_hz^02gmKVkao&uNuaVa}HzRI|3kRFd-g8JLx-`TWzYrxCzR(JgD3Q#+6Fa1M z4g(+-9_0uJ9iA>ZmUK}5@FJmHT6y8=ga_DR;VNv8Yc2Yd-fQ+DW6WSEaiPiqOc#iGwe;;&zhDnYQaU>T!W$1v_t9B*D*@hOAe+vQ}S_@fKM5^ z)A)fEI#6=3K^N0A{RWmi=HHEN$Cg*kAOgXfFgS(kZ zU#x9LhiAuI>p5_KHm-V5* z;i)wMpb1j5M)!wYPLm^3H*6X`>hC#J-HFWM;W+R3L)V>hN1Ug1(J1Js+ke04aHv3)ODyjF3R4l1B}sK`rv%gR(Cym@3e5LCoLfSa13>lVxn6IuF|U z?+1{09BC(?CuE?O?-pA9ccy-0KZk602)J^U2!-ZTINZn_{q&2cuN=9gj9L>Sg+?zX z98UWLyabh%^F9rpbpX);)i7JHMmH@aETJtk%cQaqah2yH@5{0WOQLsk-5$Nu52AB; z<_bmjrweVii%+(>cEBR4d?76oAM#7O@69&vWt%@QIF+uxJ~gU~Jd1YH{V2SRX9v&v zag<3&#f;agTy-ywFO3rIHP&oce>j*9-j`0j;g8d7a5>jxvxw3yR!zeN*mHDz$QUYp zuo#T@*9N_R_Dp~wydR7m>`dXbYxj0|F&N~y94>tKWJh039DJU+2+;Z0R?_1ZgJiCz zVa8ie>|ry0e<22Js$CXQ>I(=8{vxB@Mgb}@*m>kem+(m$CP4Mu16yz8!7e*g%}m#R zz&G12|2#-SWHsHlFSu1#A;lxgxgj)|C|Q#O3J|FEc$&fTQ5GR01-X*I9zvK~WCypN zX?mdM##dxgEj^c?cm4Y-l-Fxi*qWW!+SsLpmkx6Yn;XbbHxt>Hz7&v<+u{{nQ`z!0 z=gV#8K3mm$DV`Fl{E{J3Z!e6Y#xrlr^rMVwZY8lG{3$vXY@_5uknE3#LjNddBt;3y zCxcWV@YlDMBg3D@!`>13Z7?V`8Z3dv$%NBfp_ScDOs@bnzPV*f5N6eMreHhAiu7gd z+By{Zl9bMoO-EhZTgDu;rI~jZbfE_zhT;cZS2T%dpN>Y|VahbR8xLL@%^1D=kK4&1 z-%4Y4NFrMHarwYf_6^kn4!hk)I@+>zg1$;r<9h^o(rKA`hI&$l$H7;CMAzU>&(whG`O+~n$fgs_f(AZykbE_PzY63SzD^>0}ev%2> z(!YaDdXVNk=&#iHNO)JgHCH%W0_>3lHgwPVstMaKv~sf)>Zd`+hF4%8di&J-Uw?fO zNz_)}C24s3OKHu@XjWg>{C=?mjAelVn0){=b2X($$+l``)$}|i05-(0RK* z$9r=CjddeS;PGq?qgDJ?IRl*G@(9Vvb+O;aBQ?CUrTDThKVpR%T%TS9rUkoT#C~wG zp%}?jx#gN{r{HLLE8VIrDkvi7voUN4_6XOX0jKjljjIWza~y6I*_Ks~d8(#|z@=A{ z@72;aOCPj6DOxzvzoeE|IGQJ4nsf%BXBL|bw}a4s(r@$>rz2e#idxjj=5TDi&$DVRSjS?fk_Tkpz*KT-^o(|TP z%3+w~8+aZ75d2ye6|*r z+|%V`RobP_;5lW%?0kYg!}id#jQ3ohw1Yve3tsg>yagC1ltxO98^|bae9G!ra0=0$ z#(=3d01*~xUAU~YTGM)pdZ$HNi(a961El7dV*5K?2Ln-c5R{=k(~uL=22kusfO@yW zGg4hzrAbB+WZ7VW5 zLYfYiD76G+N9v4N7}T??SRLIB)fFL8hPQ$b7ypaEz|06vY+4XE(}0`E4rtpsS3jYiz`C%>0m!qhP?h&qQ#RDd3e)pD{> zDyA>Mm`+)z081P*S-oHie|O`}KN(%fqdF=Tot#ZgTI98b)8X!^E_(v&ZmD__R0;@jUJm`wgsFDbS#@)@G*!?6T#3 zf{3&xzk(=l3h~JP9`yc{VqaQ|o9mSA*DQjedgWk~wVK(-)|7Yz^FLgm@vh9 z${^x8#sKp-{aPQd>pIxbA_+XjSaU{a;!DSNVRIjnO6WJ!fDci%dOAwaDZ34YtKy=W z)YS%1&Z5&-#9W&FSeh;wie?+WHxLMNG4MKbs)!~~Hr~9OY_$luM9++>d1YdghM-Zn zsgNfV08bGhhL-fN2=Hnd03T(Ax&fl<#+zg3)q2KFVpC-CGOB=y6Y6X)bwIZXYN?kY z?u&$Oc6^+yY|r9CM@&!{Mm{#vyl;CPf%qM(v%rL$SG&;p#masF1%A=lW6fIZNJv0~ zYFnKXtf(3vAs_#wjSa#->z-8pn3HvvI93X(oq6c<2{d+45Hr}$JYfP_?ItU4Ii8fr z5U3*-xK^!Eb5Zh4y6!z~dk6^21=>}`_SRV!+(W3H*3HQ?DxYTAPZD8Bv#hsjA=JhU z?2w--5|JQdt=_aYeHLM=%9LqZYpKmAaf}8?B#WBoRw0Dr)FG)oj^_weS4xm{`N?bN zrI&1U3i{}GdVqbFDws#^e)&BDj4V_*n+=iw4HOUr_>Zz;-I6%~Ly0E^zWZQZ^bd23=&#C907BS*{^J1ZJ zK}0CyX7y7k`1~~+7Vz7IL2dYu3nG1M;p?UNov4vq+ zTa_PfJmch?AE}?B1<$c0@#%uUY2oVp$Od)_TBV>bJ$1ef?meM3HXaYnL|_KwnbhO- zKhn9a`e0K%aYEUB>QA}C zGnNNPH-%k>FT*Q*m9irM+;(jDmdNVplzO;O^HKU{N01%fP)kqOovyCs3wm7d3tuEB z8l+_u^7tTEk_X6VnkZ8174G+LzrL^B8-l2)t)JwcrnDNp;yWwRHt~u@|I5dkiYaT4 zBiql7dOCIY@nvrX8sEOL9Ue5*Vs0(sD|bb0aOj`;H^bpKkN!)Mk2Y7jQfOswm3vW8 z9+&2}>VIx=hOi6PEKE8<+*%>E(P4sDdp8nxll+r$heO&u_uLo2T#NSD(@!6V`q!r` zEw3=(Z%xt0W#mycU_!I3#g|l!=jpV=O}%mHfO4w!f81?;8U6Y}Xa0J~`C94+yegMh z%Ep7mrztF@V7oo2&%^E3GTnPfuZwBmi`zl-rbaEFSh(?KJKdM~UN-x-cVb8Vr}-!d4a?dK|Afp$;$Zo%v^DrPb* zTG!Us^F;DsfmH;Y7gC!s5JQEa<>mvBy6|{x@uU97TnO z_FDl?Oli}QRG&;L;uJaGUQ(PuHDZK*+m{ZCr%M4{I?~D>mh8R8ftC*pId(z!Oy3lz zsXw}|ZsjF=@n5R{YU)}cPlV{_X17d38VK+La4%LpItb(r*a>*SYW=2Sw98n8ktGn9 zE!&QmU2t`;3}nR=g;M23X?}^}muE^QstjK9DCdxg)bP^Tlw0?S^`wmMj+9ZpVb~AJ=dI5ZuNku+qgL|wjouml%&|bi4-WQKW$h$soRLr2*sdwsAZs%4VV>AoJa6GNu zY%yNx@59YMxdxc&c6~EMPr8NLJk`^a9zN=mP8kJOF4BH>(K2zTNqX6@e;Y6Ef(#fI zxX;=aL$`m=*H^42gQtR7+93oCZ=(FC&fx)H;)Tu|cfMQ(v;GEyj=-uoa6LW^!9eLb zWE~}__u3hrASNS^$VE`0oU)UgaW#`~d%jc~&veSrw zXOH*NUXkH{tp8mQ7DcDNxCnpJG(iF2mG!-^T<3V?fJkX_O40pNUfYp71F)EPIJ^*w z%Ahf~c9-D!$>2Q!X;`)?#V#dQb;)9c&*G=vnKVP)7q)0}w#bXz;w#uaHu=R=<+rIf ze=hE{K;Se`On`X+E_~zN(d=fL-fqfL*sYzOfHmbm>zhxWI=?-rf z3nZ|VQgF+!(G5+HR}3bEk8{zLd=8>)z7F?wN_G{&mre&5-^nk1`Y>FktjvU?E5m2x zkuT?b3C#D~f9dux7F9O@6!tk07KT}7G0dorn%Qom@1JcR3?qTG#xnUvep-;#`=S%J zz&mjOyzMrEeHvVGy}e5xhRowd3fo$O3P&j`B>(= zJeak$gs1#m6mi>dF{NWalh)(J-+dQ+L%@6NqJ}r8 zehnuImWMPfWa*CNJk1mec8l0Hk|f?k|9*sfSl~{qWIAo*OIb9~0YyC<97~yDW3C7f z@u5-(CYdHZ(dC!=NDTaEblJ?v^I9L0LYhyi(?rBYhy%9$U!?Mlna(t%m@0Pw8LIe!#*;0`AzkcjZ~24C<;27CW6Oc1%00-K zs70d*>GV3=i~3S8CyQLbg9h1FqzWgEmZpkxlv4g|y;ciRbD$}OM;E{F%F+HfZ-l|0 z6zYbK&G5S~=|)mTB=$?D#I$T_4ut$KtxLW#y>3XDrj310#*4$6Y+L4(m0+lx6aXAj zH{In)Xt6!STo@3&FfD5Qd0)Yz#PWVV3y<@KW-}g`-bw^ZZxyEmbqZtxRK>#DHC~`moPSC!qiM5n$45br_F5n z=Sp8OH47`@-d<65m;0NGrWBNz$x}Vkn6*tY+;(SyDUmS^0Y~4B#91 zeJKnUSmQ(OPqTTk+$Q=@N3=?IEA0u<_-ECipeC zls5oTvx+j`*RBQ*#6inJ?#qHOCt))-4BngY`k^z>X2>vc}uI2;wSR!qW$9eWdI!8d)#fYLeSkQU=Z7OF;94G(ryD*MJ$3d~gF= zTfA47bdNIbP}45+0A3&FBk{)UMu8w?ZvCRKeLs4blx`P>XM=4Ft9^8m!~abc7-0;l zp?6A5^%Nqmix+tYtN zY(FX0DEak$73&DGy@1*)Bm6UgX$F@N@noSq8+DB$#YsJ>yUknh=0cNCB`x;zw2xB* zfQ`(u&^xC2Q6VJOb&To10NjK4`d3VEb$hPgWF!~-(3f=ROM9Z-3c{Kiv#FC9P-7ff zV^K772rvatC2q3?- zUsVzEky;dx`#jC~ZLfGkdv=u5rM?=z=F*||-l0pLrdg}UGDMr6Aty;dA19lW9@-YK zHpHE_{a7lyN+y7d_+(NWj=z5;VoKxghU7;Qjcf#M$F|qzv?1dxUiao|vFv{|t^8qu zN`kmPW6zLcM%JzF2gUL*-CX<~H8d|NSL@w59{J9jWR)W)4%cx@WjE^vB4U$)fpR8N zXtg#(Gc&g__v<#yHRgWLHuw8o z2+cM3=31m|Gc$50QL4Elq+F7(N@Xs&q(YQRat~2RMah1Cf5Le@&iS18d7szo`MjJZ zYE&#>vRYJYNC-aq6DEj^vvjT)!#tI@{}5ua{+@-awm{LQK?Y!P9!} zNP@EM_sLu-J1TkbYW3(z1D&S)be^)+-e|nN%9GVwE2`jeT(LfYW}Xi?Ra~K%cBFd! zp;%ktzPAHV6>PDMVG2npV?yPt8L}sdl_2s446^cK@*Ww;m}5j6mBEYSZ9|mj6ARS= z&PB8)RaVi2xsSC77xGG{15vlu!Bb~ym3Bkbkulz&D9Fo`B+}$am>i1~)HwE%{+NY2 z|L(6|M^>x>-#HJ0;8q~N?p$o@P^R_b3a3eW(n$JvS^YT{I4DH0H@;Z-cJRo5&s7Sf zoQryGt-T)D^Tc|IMhUrH@`EFgn{r~S{8sjC&#P`s=FCcgYOHm2^P7mJfk3CXZQ&qq zDK@ACG2EeW;Y-L&XtXEc2IFqgo5?_N;>5D*JM&*@g8pJ)zKAh0|0|;TZ(D$A-unqRIEJ zm}%;~5gTRs>*{Zn$n$?w&sRv^bD~AJ%Z@aEIca(Fo!vVm_Ic$AWTQn^aZj*%H}!9) z=%YERTZnYJK9)r1CWdR5S=bRYUb1W6Gx1DEM(Cw#vlBXs9Zt4-7;M&9 zfHfM**N~$NPFS9Kh0n=uK$PopkLl7AoC2akuND;dH@cp5Ip-%tW0@!S|S6s9>Q=c@A(>iG)4d9fw$DrZV3=R|E?qYKkuVK;VL=YhQzY-V0NI%yIimG1ivJNR zFGK6(BF#_8J5y#`Mf-N4=-!s6cKvf1Uk1pu$DBj~)NsiC%(Y_p z;9aN06BaGWQ%Il#pOsg)!3kF zdO8w`c@P}x)w2KVN@#4PV-D|CzfCfBtNfiW4dB z2K2Q|@fudezc=^kwS?0Ii^L3@hAR~6qnhW;TZ!*+;YP0{c_;jgA5j#295B?L$5Kz`Nj{9x&$h_1~arlUcTnZ z`NcDTI->hkyU)}ktqCjdCG)&qa{v#o;q830OXv-XB!*U)v$T1Oj;rbmWsZCw&xPc< zKdhQa+U+lKvfSae=-<53J=9~f+y}_(w=L?$&`-IqoGiV12#gP$we%t1*_B!~1BjK)=3M@Gvz{N;@X^vx>9NFZYlR*q>4~_CAbcJbty}VK zk5TBUv{^}x+nBcUDHuzdv)^}?@KcZr`Wa_;9_J+081ZP46P9)HT^f!hsr zoXe-6;wt>{BvxaWLVq%qc8?_t9|VuSl&E-*8RDWDul7t2O2g^T4RLf*L_u!YgFmy+ z0<0OM!&Z#Z+WpEK+wU!SuM9E4WgEEF14mGbodwb#@7Pp04)F6aY_ z-Ipq5iV%$|HN8~kCZV(ra3J`LL6uvM(;<#56t+ilnx&NEdK}v$Jw}~c$K6=E!du5h z9v#d?zv?0Nq0Zr>QwN8}A}x_8EFwlt_lL6mh6vJrj?V0S|3?jVjjOnH-gue!OS~eZ zfodx<@4p;?!0ykTb_q~D+IxOW&WAvK{h6Q<%O+TJ4`C(b?Wd4e??QX$?#PhKyc;s^R!M^8^;oqd-IKW>fMDRFVMb>p@Y@w#nsVM zx9Vh4T=T_o`O4Cl{b@ooJ>V)zcfSNY2KOKWK<2okn+hyj>rt{@MTv*SuPT8KK=4uE zbDy4T=+O%R=xAGgnnEASkrLliP73;_MzFJ=WJBz5JTIyU_=-~hZ~hiBUkAGt;{)#f zHx`Eni%3Vd6|N{lhAD{(T-;Aa0@3t@WBJ=EK;nDHsAd|jno?!^I#@$^i@#;7lPP=W zx(hO<^yvxE5)0VHD)S>N>bA%@Qe=`?*(Ds2$KGHV!B<4VqGqzRVinPGD?(@==BRQS z(~D8aMC1Wn*Kgd8>i1buezYKKEB3+Tm0D>kd z8ME_8uu+Obnxa7VKl7Ic!T5U@w@k71Uss+ z5>Vt*@bzCjcqH922;8WjeJ!z$rq6ff`J8Rx9z9?}#l{7dlESjvZLlcXMD*oYO(2j%`7moLqr3zYM@L_W}E@B!awzac1W*u=b(xQ9YYV3@c zZ)iJs%gXQ=@Gs-r$+(DRDl7M~og^fejk7-7grAZ2wkNll@WH2v=avU}49i%o`LK`L4 z!cLEw%~Kpd8)_8~bX_#Hvr<|7WHCt^^+Kx7p8HOVNDw|kkT#Vic-Z&Ek^|AxixH*) z^yY)-mMw|-Rz760u93QSZ#{oNt`hkS}{w7So+=1!6R%1BZIng}x zcd?Vx`7-Pu=1bzuytonuEs6VOR#>cSP=%$ZO#nBO@9t=j{p(-{?$WdOUu^c?oZ~uu zLe5}`cs?fO`f%aCWoh&aa>ePM3(YpsFC>^gw=!C94%kw-``zLPMCrdeYg3as?oM@L zSn*uK1*B2V$Wqd+ORoThIb-lMr__1{rRj~(8xw9ZDM_5WIQ|~6WWQs%coI+XCLaLv zAIC;RX_{l7u3WU`?59uZ;|5Y2NiL=Etm-*+faP7>y0KxJd-2$lFu>rTIUb4r<2#eguf@b?td74j(p!0 zaEJXj{|SS;`+UBOYD)0B&4wOj1g<^1qV%ufCBU@jjRi#Rp_Q(FsT`nB19!f{D%#xn zPW_;9U3Nc+ao|ZfUl9ZGB$#%r-^9idkRWrDh^Xf&1K6Hxu*_(R7J;foIJH|Xpi_FC zDlYxs_CpUm4SF_DEy3%4>@E8ACWIb;qu@fpnT=?IG)`b|isu+>5?6@@^xq6*Lk~}S zk*0vUoNN?v4<2lpBPip1v|*UjZws8#j^J>Hr@w?A2g z3`87b2+%d zV3>B=0#aqTjsF^2Roostz;wDC+Prrq-L*6HdH#0-=EdK88ABy^i-HNhExRMoBPeIg zoX}q1;rMO#;pZNPXq>Qc4=Lwkn(u-f$H~ky++0GT5y(bYwFvHiU^Uz9ocdNG_4Q$6 zZZ%f;4;SCth>v6WfqA$POUJ{t?_}MMFL zna_|^3jLynEw~+C4MCKv(kF5nn1}O<%4*!`CB=O+e4;kBuxi($V`v5A`Ng$ zBTkH8*q~hMhmo&x;zB(?ZknZv58N~V92G6xwM)2zeE27=!Bxy$CF1^YKkVzxA!u+O z1f_c^rf^vGFBi>4hv)14H#n2a$e^p^b0|LEqAfZ3ReWvk`$MBSBsIZ=IH-XfUU{?# z6LJ>a34PBmu~{;|a#L<$+lj8NFA-X_*s)m99gs5o{Bc!*+8MBw`l^-qAH($5PY(Q~ z7@yukCF`$_q4V>Zp5WDL7+hW#PT-JleoYCAlecZ+RaD((ql{#Q=u?UH6&*~KjjaVa z{U9f*K-PjT@)b4Cqix`$WWz1Aq*L7LfP~bDvOIsn!K#2my>cTDGWtevc6ne^7D(}| zNeHKJLd}HI5t1~Ad5ZC8`1UFE5gRgOXz_WD(RxJ>bqw1P%H{olipUQfn`>n{vi|VA z?uHKV+{6C}ju!6Zc%F7Wc;x1jGf96s?#^$J%8^&oD>1XO(6sKJiDM7iI>=Yf% ztI7R8Gz_tdjHtd{X=O3*KRZ&^Pyc)fQP$06H$ zULprnv5lW*6_*v&=*`9dnRf#NP4T6kZJcmhUaHyK5Dhl70@)h1jz= ztxl}VprO4*DL!J;eVk8(VaFndd6?&zx1RxWkr(kVZtnD7F;OJCfj8dF!*!baE65NA zD>$@$NTrRu?2%mxjgs9p1c@^xrjR^$Ai)5RYKC_^aQrCRWu&+<#N&SCC3_myytS&~ zlW7PsxJaVg+x8gz2NM0S{pa;h@h=SDv3-G{S2z$)tBv^cuUVk!DGiR)lJnouMcsxv zgQEX#!@NjN(;t4cK($(q&z&FgD#PpZZ8-$6UBxYp>nX1t@g58<|9x1E(ce!!P=~P@ z3z8k&|DQ{)k_B!UjF8X6g5`~j0k++9SsDkng1Jl}FBHcz^(VrKyx6l<{SQ2+Y z%$rvrdDO=dj*dnsgdruabYdZ zq##@WiOzEB=(1ex^fjV(X64J#uPC4XV7zgbRiB`xHWMb_z_cmSmhAD`cU$x+C%p&_ zzNZg0!#$7l0v=b!V={a)*_*$_)k6*2eTIY1ilCFRK&pOoh=q9`AbGL4w}JcL}}F zsv>31a-P)cYWCh|KEM8!z*QG53P|IAA>To=-vUqs_B242B4*vweVd5x2 z^+1wAajq-~6OkE*1A_{R^Q&Xm?7{oQ@=o0{<_22m0Bd#~n#CN-)w_f1bJCgW%Tqki7a|*t2htf@jaBhB4 z%|9;~lhhj090se$Zc;fhzY4#4w^TgHVNjU+<`JTGqF~?=^wu&wvL;2I*Y~c}At0ci z<{=o5#JxI7R?X-mGZ|zWVvgIT{sJj-H~qA0ocrBtRjpZi0HBmQezb`>&y=@UCaOFL zVN`KyGTfzvwreaOT>kHvs{+)vRD5EGP*;b2>6qSTa%zwm@0>SYW5sx1IaZ{o_XK{b zyGcuw{jVEhRnD^`eP&>;E`{V*{Upp-adih1pluthHuqI0s*QW|MP zoer-Q92UcD+y)Q-Ir3g9+gjPbfREQWq9x-6BSOG_bY52lP6{O^^5kigQ>~)sQZi-4 zd-9b9Imb?}bHfs~%Me13B9%<(M-e|L*^e4r9`VE6a}sm(?nfQ>d=%Jut=!sfw+8;N zjn4+jq34P(qZ!Z}76x%IlEMNK}nlG|C<`B$E72rZP^zaL{` zPFNp-k_@wRE*H)wtz!%eJ07rZsdl))Nuy!A zg9A15HTx?&zox!sccD;YbzF?yMYBhuwlSrdLq@cCenu4obuhb3ht`&jL4G4xUigjQ0@+`cJ4EqFylPy#s9+f~7ilZqoDOpAUh3>x#>uK~osIKRap)$e zUZ!#+QmaS~+onmTMq1f?=O|3Tzd>-@yZW4}5OGg3YJrnnU>XXpVh-=suFGKqa*+tB2p^319!Zo{Jn&%7@iR(yGPSSA&q=S^fl< zN?wITeva?Cu{M6a9Qt#OF-k_f-%Z?Yc5Gwdy`d(ZA!2WVAJJ!;3znO)3Z(@~<`W^Q zGca?@;TZ_{(*hqF!jSE+Zy~-ug#_PI=>4T0{F9gb8Abq!)4f!e!|MHA`%#VF1;Ct@DL-18u!yltL(>5O=s_9nv_=DPXAUyc z!(&aBO8>f?wvaig*;PRn`^By7IE4A|JtBo4#LxP&rMWu6brs4J3|)}cn3HqGKQ4;X zG>wYg>DNT7)*v~s72hT&zfQJV#~Z_+R>hMvtZosTNW6<1Y-U@vPc5gBg0AC0G%50@ zcra^>`28S`;}rlZ{XK;tZ;BiiyRq4ux7gSkCKDPY(GG0xQfwk;m%RNpK%@LmYRpqVQUun#oO1>F+`7=OGNQZqfV6^wUJ6MUKtr?^ z>s~L^eebB@Mt_KbDTD}J*A$SPTbi)5>lo&n#=)>X_HVz-*(!ID7cjY(#tQI}6?0<0 z8V}d!n}o35VEC(|U+MEEwdnxN0K9}!}cuqD8Lge-=ffwnpAzzX~CDf$+%&aP`g zFAbO+9^wDyc5FdNV98yGd+MBh>Qe6PLep_lqfSH*k3J6~bor+O?bOr3>cCKxFhqZ% z^|yPNMM^UdjVN$5*7-+U=yDa5MJi-#c=I+R)0jLiB`AdgB%o*1mdE<)k5GCXOt=~` zq++zWA(U!uXB>=pLIhQWDHEVs;os~x)~>r3kF_p|Preq3ChsA8=N0Za)or0(QOxvV zoFB5+C0o~D^}1|Pb9OD&FGaX0w<4{ha%DN6_>iA?XVp#bSV6v#uprN7TMPHCRPo;E z)t&rr{Cr2Iia2CQ98okp0t5-#>c}c)e~3SsK97AIrG-*~UN|i$qbby%rCVTQ^Wv8O zy*oB`ub&gX|CC+0qTb{5Cq!h4SqTem!Sr$jPN+_BqW?JYo!pU2!f_c|nrEKbtG7{1 zP|kL(bDB79wWP!C%rd;geT^4teDgl4ITGDei0p0VIGj`MC;Ww>kH>8( z6CJl6%=kq;raYpy#*xcW>$-!%`rsrYJuJ{Xo<2_5rpsTC%CM+t^%P!Jxn9oYyEtcs z{Ug(pAC>Z|80bEEHcx&BS-Qn03j5amY>e2|DpZt!%s1OEe$+r5e+vp&l~)^e(pemt zNfXeU9Yof(!~6>E9+Cp0)X|1_T4g!(eQH&8=LBfg1n*^O4(GqPF|cC~zu}_l(5{Nk zB$D2Q9GeVMyMjBd zd?%&P#9gYjx}~SY?u-?{E)AsYp3^-Fbk*o&HRD!lE6aO!`xjJkOLDzh$5q94E55Au za!B$lWb7zPe{|+|?6jQexk7&xZ7OonR7hZIfG-b6Fs=_dUUqb!bB*l^FL>rQH-^fN z2g^`L7r?el8>qOiC|8Z;lz!+Tj_U?(h2v#v%aT$n*ulH)MxMJ{Y`N51GE(_6ANsdk zFBzHpO&`MviI|hdxV_P^zrfQgyG}*ItvJRZQ4UjT+ca?t7+#4(@~|5Ibe_YLiFeIM z>Hii*Vd~Kf(#EwH&~08Z_Mo^;pUd1^??_N2Tj5D7l zLxfQVr}!Q{T5In(hr8GHd#28kps?c&p8ia~j+o$IVbN2&V& z#AGN}Y_>@iA5HO0MW(AE6DCoP7wyXW(49O|$MKxb0tTz=r0yP{%e>m?dk?uZ#1=N- z|L1}xt(nF6Sp4^!$U|r`ea9%R&^NS9{pi@#M^M)t$kE9Rrd6M^=?`VIoM>;5vI}X4 ztV>S}uf~BND90^e`LD?a^P)eD|T48zUXN}rKHB=~aSh@j4_ z9aBRns_|s|0P9(;tWwL#m&}hohF62sO94A(q3XNe{@fMP*8P~ta|fT#R-fbRo;&_C z?1g(N&c`AU+UxNw5A`-RfrmrC+nNsWc3`t^oLpCUzW)$=P2WE=>93x%Xz;!`mC2?& zH2(@u6w8w*2)^qmfN1eQM`BnTq1K^Xs-c{(bd;TJY1VExnrK~xJ8q}na;JQQKO*zh z*<8Br4~L8?5A8kYNC*g zWCA5EnSAhF3iop?N#y3~V-zKvxmz%KF`5fYi+3J)7HglLX79KRtt5qs>KF5YOQqI- znUHGy^kU*1M$fKx_BZfJj5dALcKH#-{r;4pPNNasD6!>Ztq%J?8_xHkb`rGG7@rS3 z{n6gB9%@XktA(=kd{k3ehHyQj@ojP$lNa`@?2|Fl*JrBfW zYqUj^)N$&_`T_^(Y3SHe{zF!sD$JuF{yw`$wfezPHHf^lKP$fMV zyRkvX&#IeRq(0nM`$OLw3*|bf{N~f=yr`hI+OUsAchvenI4$)c^d!2%W50R9R=hX= zw!aLVnmf>X(MsP^vf|K273`g|tbN$W-#{YH`YxJJvk4{7=-4%Nt&{P2!O z*)LUgwb(wvYm);a8OG37jeQjDW|@oB*g`4)lS{ZLmHP^s;5^mM^zA9Ofqgi2m(yIj&3yB7hy2F|Gd05k zUCx6a{T?rel3szi7n1n>*%hvE8r(W%n$3^UO`J0q4^$qdXi1D+3S>)Tr}!dfuq;WO zg3dVp@z8`TLECHzu03o?C-K`SHIWFJkX$jo(4wk`hFn?M!o{@T9d!RyikYY&r=` z`z;_axkDZ?|IkQ=!PZhuHrdLPy<)Rxr&G_%TwwG16g|?G*?ukfrV9H~S8mCMPw$;; zwNr@kL1K0L8N=z@H`0zTPnv_st)viX{WmB&XP9!H;1hEFyPSxF@d2n>zin}}k$(SJ z0rY3Qjhy%}G(>wQ571lqrOIN-mLD1EUOK|ub#c&aoP6V`$G17*gnyhg?fy!%qyLIw zj>?L8aJz)J7`F)t*I+p8HsIxdVtLp6l;9r$DPtxlw&$f&HS}blEp(Ni^ZJJu&)AOj z6b9$5{W1~ZL!HQw%Bb?`&)PvLxEf9sH`Vzo`NmNp5k=>4{J|fA!g4(iv6aOxWN(ltprVC3%Y?u&Vz2PaqEhLT3nB{N%$57UeCn8u92~g6 zhF3c<|5|zC#sg*B*Pt|Y=9f79*2rIc_utqbE*YEtwJ?z}+>h7l4(!Eds%W>C)i*Ai zPEsG|zB0m=l+1YhEJ9`)RoNB9eftmB<-;*wGd%g6an#oh>_MFDx? zquiPXh!CB8I|Lh}{+mk^%k;jM`I2DzyJ@-#B6e^1a`VL?;}1(w22X&mY-ULmK{G`z zgRX#Y<@ftf8*v%;hRFW7n$2-u!Nuf@kidgF9nUM6A z9L!ZCJ&PdVN2Q3IpTit!+W1~H2u0^k1dOOcnHUmEMi&&z#esZ3hD7KxS$u)hfFn~6 zF3->sHB2tP)TxpQBw~fyhREvt?NpH%KqacV5Gn=0N9x<%E?;suN!9i`Z(98M7>46Jr! znTR{Pt>T$eQyv*1?+`{+aSB*biC6-JsAs+z@uU+B8fMHB<{&1c@rk!p7*P>F1>VmQ zLXgup2cbeY zUHzg>H-Ld@jNA4p^P%SZ+;ZK!KMJFhTrd+Ph|Balo{pbuS2%+*N-P_^RD$E@qMBYSTD zdF)YX8CQ?J0Mm^ZniOmfFYNRi`l5eYGH{NnfA!;5%eiD@jBtoSCyUJWT8lGc+X~QV zo3)(YerxymvgxE`;6tX~lQl1!lS@V19kYMl-u55#Uwc@fy6gdex}FXHa7RqNar}g` z`C@v!5s$X~SBK%_$^`!vMDW;m7x;#nuHk`4;W^8t5>UrPq?jUq

zUs{j|5Pi@#zZmV zM>4D5Ke#7@QR6fd3H)=`Qh$TY7yd8?zdOl^NTLuy2f0}OFn`?$w_-Em7usu2-MKrx^;g6+By^KezI-CCbYS z0>6V4n{INf6Uxn)MxB*h=v5R<{Cz8ssp^_ba9qq;RXw)7<#E!Ra;b)g zR^GFavK+wWp4GC7fC4IK4$@1xS9JL;rZt zi{p!6D&_wI+&YzY^NN%_Eu9@_d|czix#_fut^V7li$Z}M9~Mpb0EGcGWtJ{9{(vq9 zPH(CRD`VrcqJ;q*Y}-)$!MkZL3Xy<8*3*KYTo|I0Rh}wTIF?8H@~PYsbKt(D`-w== zxi>hmyi7@-+!T=^~ zPGX#X_#Pjf*DShSEdC z%88<8)zP`0?x&JGUc4n9%Zka4KS#fBO-}@*{uZz&0k)JlNjPKe@mLRrWV@SWJ21mi$rj)g@yqVC(N1?!u2_|1>#%G@P?4cz55#>ATvuyT z7ZP-r70U)lbuo>ILuzo};;!K0Ckebq$kUrdpa+~wyBJoCMwl#xOarr57424lG6{yq zH21xqWFr9YG)8qo$1`O&wPF1Rj5n?vbjDE=m+$2XxjDq~*El{;9N!p{&jqjV+j&9! zUF4@h?4p=sEfl*;LrISDz%z5r+Sm}@Ilc>XJZHi)wA|rOk%{av9C#b_ya>30b5_+M z4`Mmw>c|hRB6z2T2q6e`kbi!Vuv>_*_L3rp*U@LVzq5t@3zXAsYUGW1J;Qja!#3Bf zBJcEX;VymPORck13DN)b0~0}#u%80gw*`jW;f>_0Y+LJN%cp2c*Hn^e+4p0zvc=s6 zoq9<{&dk`Z1e-=wmEUSC+?V&+R;)vonv)2idu9p2gF3Y8aSk#8uSep(RFz)OEGpEd&wgi6f09z-I;KE^SI6r5# z{S;3Br1eEdEkNLNtZOP;oWycO-~uHlE=md^zSn^~TtRAB!KXb5f=L+(0rQXVnH?7RivWztm7j+|DzhMQ;Jh5Qq!1;>G+mf zG3^e_=Ptcqd4t7dOP5^4P+8?mxE|KaJng)`GN$b46@JNL9%HyHqa$kG*l4BXp`QINGVrA-1}e==m#I2GdKdh4?J(Ro>rl7S)Ve#7WmGCa~xEy z?Iei>hRTq&E7s;`TW7CLdR}Zo?pk4KdyM{LOPkau<@DUi`K{Fp5uH1qjvp z_+!`zE=iQzngd^7{ARMKAV>?Icp-pPJyQhl`x73SQ~rLenv2zJ_Kr(T3y*`}H5qe0 z->86XQoy~rqOa--DY`fMQ&?@8|=r>$6CQIya0_Yo0H>%&JvB_-aOo2Uet>Y+-m+>s&6S@GwjQI(F)(B5uST^psmI z&N%3-Wl+_i+CWcGJFiCRS?k8K zdj|t$)(g^L3Ny6#g&lu2zPK%?L}naFH2tXzo%{#s zV6IEu5>G&iXNf-hqou)tmpXEljU?)~^O&8G=P%|9SH7F!pkS7n`{Qppw0MBPtvbG+l+t;mq*S1*swlgp65#iC^w@hpV-LIM0>s3*=@hPe%ZyT-9oAvecu4y0xSn^nzP< z1O^|%WuMz5+S__RfIJuwW5ds+FdtozIN!!~R+t)M;vjSUlJ~|*xWJZh)vol4ky(cL z@K!NEy|qJ|75cF@y(Nj``E5DHR=$ro&1~TD$vKX9u27K^wI|Q^%y4z~9bJD08d0{) ze9>?Ik_n1|fM>^?zJ#(!m`h@gEKnCK?rmc67$@%ce;A6cmT}knTyX#*vsEw?hD?6Ao^xiTopEwN$jkMCsK3UdqD0I6KLHs;G4zmV0Mji?N;HlX z-GsqKJU7rDw;G06XUcno{28E$d32?U{}w6S9E3>CUHh1lrG8$rfufc&lbXnz79V3Q zcR>>2>$QJGJ^UdM)ML9}>qDvtEc1lVsNCqxwtI~(oevTyF)po$I<~rPM-|uP<+|zd z@x<27@JFQPi92$)Bz-h+e4hZojR{^!lU&cW2fyWC@PJU{1*F&x+uT_?qbE(iX9nId zRc%~C(-_Eg5CSNpBK!Gdt@GkEj2^Ej?Ea%lC8zMhH^505Jo)oWh2i1)-k{^sx zriL&HV)YU+XIM}4(C$3IQ|Ego)) zVxl8BQ2c-N%`kw_G9)*w^77Zr;_3nV_(0>`#}D>z6)X;9{2;d1f}RXZwJsDj6+iDr zO^8<(OT3hlcplWV!$X<_@6T~slesIHhM~dt8;b5{s-G`LWq;-_jZ#FUcHJnqH;vCC z*-E9){5vspB7O0bS@nk#EoC>`6cC5;eDARQ9`*JC`@%l|(zY(dOaB%IB5(*2L~X9~ zaVu{WH$G+!Eja?$<-lXx%Mn`SbSZ^2387>2>li%yohrpbv&@2X;Nfd%%cb|a+Uobn zv5_n4!ptCWu=a*aHSdq*WNcMrzo~-UuDl3 z*kVnx1fPS=VhoOYL>E-DYzzZHmd(Ef+TZYGv_>J2;}3d`s^qJX&%vKW+g+`sZd@6^ zZL#%4DDG*zR~(inXMyvKyzJ9A6QB1dNVgmIL}gP9H5Ms|VLsW_$8WBDuveUGG*{8w zstmRHC-6JEjKJ-X59E7QG!rqN<8$mYIEslH!#d+%{75Q5w7%Hy;mah$ztGkj6y@dR zPa~s(x&z?i^L*>xqRMS#JBxSfG-2c7ci<^@SI@n!`%hn5q}5V{Dj|T%cRnZ|n_sgt z`7=`@8FNUoP++Oq`?*_z`7aUZ9Y~YZfD4K@5N>n#?|bcSv4$M=t>Ye$*fERMR-Cv8 zc6mU-3hkE&;38_B8u!&y=By&wl%MwOqDXxTH?~_|mg7^OtG}HF{uB5OTrZYnQY0O5 zIPt7+<^HK5K{=ApDbw%3j>TTP@^sl9AZb%j_NMAH#b1gRHGai2hwDprI8Eh!Ot}Xj@PYD$)AsTvajM1$1D{dwB zWxzy6w2;}eLsZ0c>%nQ(1lWOncu?C23TFjM`ZymU5pL77IoZ(rNk3K!%;OGa46*Gj zb2WL+gFCGV3H!);I47iCI+ zUK%#{Pf67>QvWJ`?^IWX?(vk06SE;1a*2t(fZ|q}z{B?{(;ITGcd>^oI92lJvq=K6 zmrUvHi22awa&v`39kkWx+t{4VU-;aR(aYYHjqC0&UVc+Avu}{UEVxFqj{A%INBa@^ z(fZLI*Tqo7SOMwX2^dFx4!ambH~?vppK$-=Jhst_oxjPC*vE!CV6Fv(;^{b!v)f?` zp@+U(Hv?cJ^~`{xN~~0<`cgPpGby6}N?@7WiX7a+(^@=C8Y)kDLe@@}DgKe#m)Pj_+OzFHF8CxkgW#^U2kBOFmjsaI<* z86XLLO1EP^Q0!jQ_7~}1t-));O^5p&^?tDRCiT{`evdmH*62IT=EKGsCRUeFi~Mi4 zop|rjMtd}NxrtzhS*UHwoGgA-2Mw~)GU^NAgGL9pc9#Wg5zulDY{(JgbKEws`NR;H+r%RE{ksE^MJ@WlM7vZ^-aYYc3;$Fw|C1vzRy+xhdw9TOUqmlKS; z%iH#}g1o53W?>EMczA3rlHaxjhQ;c=gGpL-`n%kp(LGuChA)eYw)j)dGw)c0&--(> z#OVy7bE3wlZ(V--KC=1JH(GK1`+FZEJ@=W&sD|x-5_7jAYXO1+`P?%ZBLT7jNsCgA zPpNMQ8ZV_UybPaB;|d*PU3mQYtXKvRGW|I;Wj;KUn-)24kpQz7T<9bc3Ymo|?d$~# zd7~>tu2i>jAZ`gwjj5C0|B=Jc?k!UE#E1J8_t}~*R^I$qi;*uDathojl!@r!+fcLR zP{MLf6s@3oN%pUcCh9VnTXF`(Vb69zX|p3ICV3t4uJ>1=^wlt>z>u7&nqaW}?gFN+ ztq;7xMrJ<2+KZl_=M4JRZlG$drRhkdhFo;ZD@Lw}m9Spi1}}o0i<+pSBwt%=ZKLv5 zE$_I{+IZ}ju8DlG;Q;=X`9_fR8xbX+zCZpP%w&@=yDxG-iNhN;9z~m`ixPBcI};x? zHCnh1&fcT@Ogy*&#JZ@N-|vCUS+%Be58-AlmZ`E!Ot6S05uptym$cD;KClgXi7nddJM9{svLNX@QDV`$M^fU#5~{7yE^KBJ zi$6z_6)3DzN%dL57PDTM%lZ{5!3wl4vL=J1LkwBcecTnpHeyQ)?FNp(gyyK)CD_8% zkPB7?587vJJ>kalLvG2RJegeooABl@GS*HP2go z)-cB6U*uBArcF7D0!a(=W-V_mWTUKZ9)@yDP9~Ahb&_;bRtaJ_(tX=qc9y14vS-jQ z(MqL5&Nv2l?X6rbac2sw_IfWGQ8h<1N| z(DtdWQ1DclkMWc1KF7yQ*2|d4NmT~w_WaQ2#+nk&aWMGM4WO!UIkS3yM1PYh`^yK= zWFE2(@phd`4SEphBXjDvPYx2CDsYFhorD)>LS=SCL)Z;;?c<@ZgpZ*pa3iR_a=Tz@ zEH6rdWgF-#+Ftx~=pyFY&0RI;ZG%YltcOm~U&#=Sw_{|KVfX%07ofi*?^*h^AF<&o^o@w@nAL@r1VUhB$& zLnsH|%xCLx&FqW8pQZ1sxZ+2&^Lx>@x5?I>S3RL0tQzlluTL1goz#DLH9zfVg{rF( z*)a7WJDHdJ`YL<@`r9`7V*KsL8N5H`-c6sm8-2`9EXhIyz4z*W0DM4$zeozef(y8~ z8VJr$vPX)OsE*`k6X*g1xWNq&W0-sP0vLq;k)Q{4TdVteC4d;vg%S@$ug8X;mwh=A zAP-cSge5XYQsyHaT?`}jepy6j%n`iu??%$xpUh5T1Z)zd3K2{oXmD1Lh5vy`I1%kp z4AnHvHe<_N`*In^1fKu+_gS&g#&*&02==|-U~OF3b*Ofb0<~+ z#lYN+C5@^K+$KY}=QbvbehU>yGrCA~f_#_)q#2EW*n_Ka4X0ze$^o@cvyI>|4G7E) z81R){xt!8rotFB#=wP+pA%xu7iCnX_{L%^+pn`FDJ7-9MZfJwI6EGO_iRGJs3D}Zz z*pd*z!7X`%M6rNqt184>1`89PuG%WdbC8jk5Ld#NWC*u^FqkVzD;U#?ALFYP**tmj z2rQHc7-1%o(JR!;7?>ED(d!5!7>JcYh#X0Ylu#@=IJhI*3C98@D5ESUnG&Y)Ak2b3 zpLnqjG642Np>pydry&!a7?Y8x0POp?L2)hBibT|^ld%8j6O}WP7t)AA^a92SiX=LN zWN0Eo1B^q1qWycQ%8(4cXpH633taGvz<8oS8w|m~A}2V6P8cVQyN$^)jaC_nS#g|~ ziYZ*lsp232vG^lonJ?)`yXzo?XZ#0KP=$XefuVW^JSd23XoqKL2jHosx$A~*`3G$n zFqTk(VXzpNd5d!ZiYW*Qc>D*&`G>-*stXwq#Pha)SceIDJbVEk@41i*kp(DBhGYN& z^+A|+?P8H+%y8j-8ovyqihvIt@}Ux zc?&jp3H^|XDwDV|!7R7X0AbJxQwbqL{D(2|5Ci`h6EtCpf`Fkl8Hpqqi7@z+8~TbI zvc!Jc6C6^KjSwh#`WdKFoC(-~MtLY+Pz;Eg6zI~6$v}*|STq0>jK2{KzlbiwxkU_E zg9_qB?((k5v8iBm9NUN~Il2`+@)a)7%i_?wWr3vU&@Yxq1hvbx-T{woD5`6~gGwp@ zQz!^iz^NI8fCAuw3&6T(IyPZgoW-F5PZWxD0f94+wg5rA0*Mf0SO;WShHU7U*zBq% zR1kq7m;eb7fyo9S(8XrJ7&>H$WAnlmnHcoI$k5}fj{rj*OT7YOAdi8FW-^J|`>`a^ zh#TvX8c_+4KnZ6X4HIA-3y?D5gEAekgdP9rAfq9gJHxUB${&b1l#OtTF}VpeaUrWA zv+VmJ@oO#CLOE}#&z`)lE*KZ3*^jy41}B=7yZH;r=!?k+3`0ANp|g~Ratyn9#ld(C zzQ_zOp#e3*G_HZV$1z6lvK-`qfnpi8%VC{jK>-w4HJ;jr*CD%Us0VGl25R_#6J&{N zn7ea?i5Y|m7BC)|kP8|}6cbP*8Zd#}qK__+0&Y0Sc_GLP$(t-?kOw)X4SPHXu@K!f z$ZTK$2`I6I$+08L5-o&?EKxUGA_}{LvFF4qcH&7VZH9Wm}Dx5 zF1ap5fwB!U&-FpR7n?X28Yc8W69WH54>V8$80yKPY!dyM32vIG$ zt0@$>016W@flkUT3(x@&U;swJ8;N=%yeJI2C=9afxh7gP|GOwmVarf~l)$(}Zdij5 z{VrZ)6=75z%!w7@kQH4a#ta;ZJ(9ZXkj6zaB$ZbE$bzWS zal<8!2|bEnh>9^9y-J{mG)^76GJ3NgXEMF*WJtYQ2sNzE>O2TF95R-0AbLQ_-TO`i z3kji+i*B-@Zkh?G`2(IzC!7Cp8sh{#&2kEkTMMJeIGEsrrSua#;U}qt%CAAKo#+S; zaRcaUk5+xs0Z<$rSO5lCl)6!<#o*8h?NGd+KQG!M!zqTdEQU+@6u12%4?P4Lcmp_M z*40>*Y^@bs`Lx@JDP!r(g5Ux!cmvcyFJkYyxSd+WQaTf zao7-0YkvRs7uwAxW7hVw?1pQgg3Ya52yljf$YFDpLF0LVdF6r5 z1YIp_k8cqQ^^h>Ds<47ANC#P^2cZyy#kR)_E8C>vg7k)bNFrgVLIF}Nv2fFds7O71 zrsjo6c1yj}lhcL}xH!HaHe?xkFj?+(S#07$(rT=jbgY9CtzCRnr?`pwWS?Hx2%hB# zFKd9)DjNd*2h;y`12kBf11K9Mkl(LC3xDF|)T$Fb(F!zBC-Cf&tTj-f$PYV^0#5ka zw_yoWgwRgWRld+Dp#v3HtUpJ?qJ|2b>Bu4r{fC&~X3( z>Yc_VJC^tW7chl(o#BFj08;>u6YK_OIMU56h;GQ?g2X?($)3C^5DD=r@o7SS;jnz+X8}ou6)*!Y7%k1apOh4fA=60oWj7d$8IV1) z>*WZ+GKe-rlD)d0(Hm6ObJ^Xa7#C@VE$IUARhMq6GN$3lp*a+ta7bZ51FBt9Ir)dv z`iHV{0~r4jWlDYumUv_(A=OWY+C1?|=yN{<{R%dSw&^lpu!Wnvu%ZVgTM2D6OsPM; za4un$VCl+?3=WAAy&BDFz^7Z*6Mc=nEym-hI+)nj-ob-e_U8^E652spMqWk zgmwTos6x@(POTt{kC8~(BOoHn$mDG8>!q=f-H|fvx7m~09%l>2B4>?wG$5bWJ&yrjX)l!+6gK- z0}THlBg@dgUbR2N2sGlNe zi%;?DLtxTiPHUKeI^*cuUs+MTM1^Dw9kIYKmZ(whXom}*#%>tL&3!v@1dnde1{XM2 zqEdwm*8~&3^*XYLIHqdhuzt?svC5YVA=nx3gfR#O6%g&Z zqTZEd&Ux!XFx;zyU|u#3tUV2&(tg8ylNpb>(|NPrm(gvJDY%b7PJ-Bh-!6y^0^gt6 zfeCO55IRJd?pdQLq9P)u?z59Q$pcJ?b29Jge-NlufT2IR#HNh1t06=d+9XBL+IpV= z9k7FZ@TggI6v6;5S+Jrodhh%TH2;f5Tl6`u#v(@>BkJk|&a(8#5eufeQ+9tbWb<&qyz=U7!A=XE7{VwNjxz zHG+2Q+qM6i8nM``tWt{_EOwb41ze;-vQ>x+54vT+;UDnWx?7t;> zEmpDEz#;;(Gcj5G-1iUETBcvaPTEVUu*w!q{Oj@$w;jZ+h3Wq|gMrgAC&!o^JGPV8 zzhAPL{6o&`nDbx9sXtfF1Xp$Lyng-q4t~5vCQP1#XX3<3J{mM)!;cLEDhi7*>O&oc zp9dG%A1wa&0UrOjfQKmnPJ>51A3S5xGbc=Q4S}Y(K+QM}{?ko2ra05hObNK~PXW$o z^3MYmRfJI-Jj8K^cdl0T6Y>CRTfl=fnbM4idy7_B~U?@6lVWR^_3JFQc2~8pfhpsS|0y= zYSE@UxKY^wqQ#f$60~VhnHOEyfm>pXTI7ZW=nQwj%UIt(BM1~{gK z(n&GxGE+)4`6A>lE+GT*O(-RK6EZ-B>`e`0+z^H+Xb6+ll}?3eXlGKDr6s0vVrqcT zXJKXLRa{2ZR+%%YwUiN2W4ZHKm_kiu)N{qS7oq<~SJRAEjmh{A2xYTj+dsn0rdMtM z{AQ)0n=aL7{CBgTY{UO;=H-kB`xR6PekpYEz#v3j**qlUm@-Zy2l8XO`H;RNI$=YQ7m)v}IptbZ^kJe=d)==z8YCbUi#&Vo)c zwsW4Hv~E03n8O(y_YZEx=3A017gTy@5#R}LDfn3m57cE3vq;5x7MVu8{&78lePCe! z*v1J;U>1Ibk6-w@hJ-8vh?!8~AJCA-KMDb{g(YAnJb;Q|@HYWPFkoYw>)8KJvJ;D> z1Y`_^CR9`wB$uVk7fX`a4UD3M8#w=fP;ZKjmZoGhs{G}gOZbbNprR)d(y&ZbnaiL2 zg;lhw4MLIbo(Epqt>&fo({3)tptGEx-dka#pT4)HQG86ycRRX0xkqf=J= z2N=zAgT8dMTOA$aKMbWdz}TS+ilya0mbOH!(G|zdIbBtXKf^&G}W9aaRN9)Xy zI?ah4v{d&x)fHnWuLC3?(^ESqtl=MN^qpxi2_GS#0ITyk-d4v26;#-R3x;~jTzn?4 z>{;Y`)S$*TwlPXqYK0I`d4?++wAQ6iK?E}q0sQ7ykb(?TD^oZ}<>s(4jscJ-4s>9V zWa8MJq-;k#2^j->v<_q-BN_idK*t}HP(gzBq8d+ojVL=f6RctFGn*Nw3B|IOxlm1q zbo$DaN*GELN^LDtnOaxW^3Qgb#R31&06qU9+SgdX4j}M?VH9&xpJ_k`gCU$SjG(Ed zNGUX0;lSZ8sujQ0ilC_iKzCiUl(LisiCXD`VvhI+H(kzNl5?J#?&J=4XwK%W z13EnN;=i4v4j^$c)S_}nkgt1AbduUE>afQIQ?;rWFF7u(hHH4HsSgSWzy)%kNlGak zF1-4cAX8G$ttOV&QsnAA7tf>w&%h8h{AE|Yp6`6!;N?Fcpbc%Xz(o{`LmZ~Cg9)r* z4&3kt0zbBkXfhU>D|`R)lLxq?9RbC$LP^<>G+T#meijYkyiy9=bXud?(1RYFN?8ac z!>f@AOIKMFROH~>q*aA2w|#SGrkFx5Eg>v!$+M`~mXyDQ>4zmXu2v8;1?5@rC!3lYqf^CeQHKHvILE6U;(gHvl42irWilPrK-hOm>=yo@%Q>ebQMkJ~O#R!ZwpK{)1LUWa6UN_75G@ z#u7V-k!BK?B|!nvyrbTzSvr-6P;K!w0DuYyI8cF^TsKC_U|bgzp&J?~ils^MY#6?- zgfZmfjlnwPKQi!xKA>(X$q_bxi4D}IHk+xdgLbnL*&Opso4pghHsvo2E>uYJT$#)^ z3gUnQdkljdS6>1eHiZ^LPou3TUJAZP8RMmVK&~)8fx8`a?+1ym8?^^J_8m2X7KLiT^8}vQ-UV6 z&V;F*Us`QRITMwE!nTGwRAE>0Bxfx7oGt_H^IQ0dH8SDM(V&-l6`gHl%Sl;8a{a@j z>_AH)(PTiyZ;TNdTo>u#U)_jWY*f+RfXY9F!P#w8X^dTWWfafNM2rZ-6zGm3n1F7` z3gLLxOyt20jKK&v0XN*2b8L*wL>j*f0E%f&GynHJ*dH&aS`# zjhw?w?7_8t+frm6CGid>QPO;9*zpLD3UI-dT*{?Ti7*gg>dk~(O+yK^-aimRER{eT z#ufiu;oBX`+cT(wgm{QE3?Bq|$TvU?3)n{-?7%skf%2KeOEA;_>_kjd-}Fr!HLc7} zpp3=!i1uZl$92=GfS;8(g;}W6D1uO&ty4Q)VA70JX`Kn4$rJzi+!VzLp2&#|MU5<$ zlY0FH0YZgnWs60egQG1}*(`?C3BzAlno|76qo83@BuP*Q zoCuniER8F=kesa$>B$hE%@BGyUDG|(pfp2e%!dUC#!@7g7qEd}QpO+rg+vL%1T0Py zJ<)a5Wrf8C-jLvHSften!(yzU3CPL>RLa+t;5pvF0?ffWo(FnBLOf)E4muF9Xh*J@ z&Up0Et`vhH{ljafP9eD_vbj!lC`Ue6z*JQs7A~ake4DljCxw}XGQ`P5UCRGwERl5m zgV?CvKh#6L$;(I{K^(&29L|J`on#4cC%*BEN@585?9%)E15GXkAr_npZh#{4q)MpF zJgUSq$%Ibq!X(awETD|_>1Ru{gj04?-%zD^VI-=ViJU;s^FZTREyWJeMQ?e}z9m|C}J zW7la#jmAf%2!`8T<{DiQr_{nXt-<9)i*(4s55$8kzyT22(N5hLbl{`0$pS7gmUEcL z;t`cjs7^pG6+Y-@wg3+%2|+^sW|=O<7xvB<_Q%mkiIiC6pm?ZZ_KW|Dt=m$BBwUfw z5lllI%HCV~i$?a#N-hN$cnAbQz(GL8H#`Cic)=Rjh)(KcPcGs#O;a?<#Pm^FQ9fc~ zVVq8^15L;RH~42Lcw&I!lRx=MmIMtc&RI!G#emWU{jK6!#)+A{6QB{D&(T9&44Op< z*Ov5*W+YKva%eC7#V@|W(lL%5uz@950Oe4f8$ey7Jjy@VM%%cbcv-~R8BR8OD|{Tz zV=hG%NQrta2Dfy9wQW~MaX_mq^ zPU)^78+7>Kvw#OaFu)X8fDq6CaYDtm&4(5aXSoay44{JX=!O4QyhUKh)xj5SC&=+ZTa123^aA}kC|5+aKzLOyWA3h<}@#Dq%p zNKepbN<^Pd_=3nV(3MG3ein(aj6fIMfZrUz4NPUn!o@vp&~RaeS5n1l8I3GvNtJlT znsCKCxoy#44VA=&30;a?&?*7GDp$_srL9^?y#c}fB6AgyU#{rdbP?g4U}R9>X#a3M21tMmENQScDH1{lPr#9N*iq{E z;2$Lwv{9*f>_mDvUUAGp7fhi=L~L(9lDC+tX?UT1Sc(6XNJX29<#3Jc1jIzHzPp%BdaHyF^*-?t6#mEQG1P!bdFJfTc{4fLDn(HzF98`h?AcMgwhv?wZ zA#uka$^m#;n@HCv;o9SK^Nr194ulYHxnew#4@l9WtqhR zt&B@Z@{=vY!})JgXhrEkFlo&YtHR07`4c|9Et)_h37Mb?LL))_1sbqZ{!Ilhc8$|P z&s?J2bIrsr4i*u4L83g`Vt4^dsc?*R(NYM;EOVNh{)4I2f(=V#1O~ zMK~;C^zBR>PT6th7;tlPB!{mgg9MZTW%ZFj4&Lh&(ytvDcqG`C-Y$3CSF}76>;Tm- zzyVafLGDOwd_ZJoe@jHJvZ%~Z+Ui9xTLiw%Aw=JCyg>yabdSq6NE=o(ORf@2ey_lU zL(M{iUUBp{@Ia#~S;IBc=Cq?vL>d1|1Ifrd?NWHmrt&XLdyG>m#mH%wDeDQTZuJ@ev7h~+*-T8G+Xz=VErEI=ZeKK`0aU{+3jx8MgYM9Z z9asY6RE9DdrWa)OVvve3EN&T&Ychu#684 z$P_nN2Xeqc286&5aDsF|$FaDxvM}~giAQs+&QM|LuFzD!;?WR_PGw7h1-!u&jH#Fg zPgMmc7Gmr_d=hMkktzUX(^4QpJETmu2Sj&s#&0$E1hpx}ER;~`L1V6-nJ1qUo4H)$nqZuz8Jgg# zP>&ONPYGFgMwsBqcGpSM9obUA$g1{J5-EkIltnXS3od)X6!wPFF<=+LDqsG0Sy<#0 zZHnD2vuU(-TBEgPupRIyf;E%2gLg`h{=ovQfD>c`UmpV;j6evOZmwxZvS0^M?L@PE z2W%1*E?99P$tIN!Y;>5j6aT`r(ZD%`saC)M@OBHhgi9=Mkd!>saV>>yXeS)zsa6CG z_TZb#&IBH=;lMn@Z3_$-P{a80STD6f9Qbxg{EzeTWHnLhVhxZ*BzJN@lhQ^|$SlJ& zIi*ERr4gLU$njHbm`VRSo#M*TN&B(c6rm-I_FVg6ifW09XlDrxMbSE;L7=<_FYbVF z$%Po9VCJUR+O&K%oWm4sl^48$9S8=a7{&rNU=pp((q(R&x+M#nMro`ysX;h1lLn|I z#YsJmj36LKNxckmcpY$qaU{nta0488LI`949Pp8{5X-Nmjw~!*>%cg&%T6IFRlioT zf{9K$Gc0>`!H>@bB$4qJI%GoHxn}rH`LC=kqD*fhm2!^O!E%6T6autoPkKF<;1t#iC)1+B{!J*_bK10j(@IRm^X~)B6F8q; zwYqKE3A5YGo(Q`ZCm3Rs8a2+&uK$hxmo&EPq_UUvdR=r0 z?V<+IBo%HXcjw%sa|)Xv%*zy<4niT<@QMT1{(AF)oCyBmM*v{z{`@`ho2r2ZaJu22KP;Fcyzu_12`B7ws)d815a6J~ z3je7kj~o72gBvFX8t5NRDlrBKg#J;eqGMQOC?ktP`sJcxGNQ308B3x_mSS*X!pDdt z`Y}Z~Fz93g8fKbl59D$p3OQ6zd1IK_kP|Mwsj4xcnk)a=p{IK4nT8OYvckzKuD;yn zpECc;`sajc)I8J8f7E=2nt#F(0UEfRDFA|hzTv@(P8i558@>2yY_FmQ%PEc`2+(=3T1E9*u%YQx%C6QT-pl3HXdKtDh;zXscn(c&81Sc=Lu!B7DwoMK_<-Bmv z0-TQZO}?on9MxU^aH@qg1#_B$h6(;CmdOb(EGmzGEU5uRCOARJ32w?TCK)&cl7$z6 zSYk;Qi#P%aB%E*o43~^LDh4ElQvAnbk9{l|$X|#w@{Ko;^a&?zZ3-%8n~K^Arz!v4 z_3WSNdTItI4XTl9st>xl>Z*H;hVvh4w%KZ{GQYB_EIH*2>z`-B`A39*;F3lfZN6cl zuDc9P^dyLm5lFC;AYJUAM=cEu(@POchHa5Fl_i-KfapXA12OxoRN~|q)wCrv5YDMm zIa8Ii(Uy2kHsY+~jW#QLN{v_DdLs_P_#)RFr)In;N}PYH7!3`lI5(g(`KnEA0`G8g z@Sk&WVxx3`i<;n^=AQCwH{=4OkiY%#JO21l(VN1Kf4YF#3okcQB0~aknvmaiBb2vG zGeC60U?L?jh8q+ihA5+iPKuGEjW`O4qxnNB#-#U4_A#M^@^`|CGjy;r8npimCrjc< zPPAn+nF%EhTwn^$a+aK^l!i-P%8As*#H2g@qaJuDn$*amCbSqWCu&g>p7z8iu(3c; zT__Zy5VfeH0A_7DX-wM!lZ}7qP=}M+MIZVgH(6xEZpnDm9M0f|PsQgo*a^-*j*_#} z94=L`YE@2pLqXK|1}U=|POf@mr7UuWbGr&-PJ)Gu;~dTjiUqu20-WIPAG+IJdUT*K2JOHLf7k)ETm!q|)uMRD!wCQ?NgDfnE+?Is zf)|7_hMZV0L!{x}eKd3+OXR}|Ofc9nh-3^2{DWZ$VaWa-Vi6v>uOk0F@kmED5|NPk z2u41-(L+$?1S6f}4a~G1lYVl*o)BO=MbRTtf~S;H@oXw#vP#j=gp)d*upZm!V40qA z1QF6{P9uEAHmI?|e~6{7Z(G3{5~(vLVpEDqVNK#r!?V+XhI5;N+-!)0#mX_RT4Mwq<675~TXBwy zIMIPJ;_wd@^rQ;$L6$HaP^`^3ZWXV|O)uWyp4*k~d4t>?BXRNv4fqav?m1Rd1oygs z*ykVEyN~j4QapG~r2?cugakvO1X4DDd)f=Dh4@tzH+Vq`PT>E9L_h=wZy4hYoJfe; zB!Y{yrG#S_lTk`sG%=FUNMtusNJk#qBgp>ZVFxqP6jtWUmfeIW*%}JXZ04>|VX-MW z@!3?gM4D$jqbfzI2bsiam92yYoa&_0Sgy8~6T}k_*T4y%3h<8t+;a?F$Oktl3ebk2 zt!-&5s0|%+7{Vk}QFcSjNo3#&WTfpR6y4MgS5Siqa8ht+a6q-(l`2w|W^rVUT;_5z zyq;8USMha<7tsKm#8HQgGnKC#owL)NRDq7^*$j3Zpv27_#R#2QMHOhHFPK5k4!dJR zPoNqVVI=4~N255O<9cv~}iJjdcV!|3FC364ZT9i}XV6Gbg5sL=mguRyG zA+}sDOO7Gf7=h#>J=&Q4aN)7Tf}|rQ!AX;y%rTA-sS62O*~*x-K%c+>6%@n?AJDLh zYtFYP`r3ieV#Cb~n$Rj`x|&yV;=v2f8BQl;XHLKnPdvlLo`B-P6gmNfV<7i0ClM@T z+=l438OFMJlba1a@dJta#fY2QTMe$N8O#6^AB z%Q?;29eh^0)&BegUhqm@PbTC&WBmgW40;ewtSA4kc4hJ(rv&%ElyCz!Y~UDobuJuM zV2m=r!ABv>CH*EAWQPTuB^pTyjX+`}j&aPGOlWeCQp5xuD`EmuusD`Uv!t9X$!5|d z4X1lotmUA2OFtVoYp-@DrOBx%QgibON-z!9t~M;xuoF(QmJ_dOg9R-3MgijBg$;0n z=!ITx#-=-!!6?cvi1AC)>DJw|d7={V;O=BL)hG<)!wXDV;;?-hE9BrtIEkYbt)^ot zkG6(NwA!O@I>V~iM6PqO&Pjf0^} zz!iB*SOD6N%vX8{`48tP6%7!2B56vTpRxarijm012?2BlavA@pU9HDYjOC?-G3a0o zA!5-#oREwG`~V>hxg|w-!3&2wWPb~hNMbu8OpYo1;m0(|!_&r%m?RYyG{89R+$FPW z;>12TIKY@!S7xb9z)M&4*>t9=FL-ukgH^HGGu&(@Ga+qGDF48n zn5YUm!#)zBX8u7Dlqx3x2`8Yt~l3^os4 zKqCpj&D~C^c-Ac^@*v(U;S358#~dOVjKK^TW|$a)7aT(P9wIOjLa-!a$YkWnny({L z#3L3_%2Wgc2H;3gVG3vh`?_pYcmjgZfDgRy1~_rPrlT3w13n(-O2&^XHfS5Rf&Hf8 z{(1tO`@HE`6y~HTK*p4$u zZH#Dx?`8u+BInjhizzf5FhP%eV+Vx6Ac3kEmS{BcNUkhMCyK&(4ylnW z;Xrm^I(A_5rT_%2txG;5#5e=J+zpfX176(iKybo2cHpYQz+Et;l1}NA@*odF;SbIL z18__sbj%nw0S^8`p!^U=8paQU?Dig}B@!-vqzuX)jw1-`$%bSFVuA)J5wm*Y4Lokk zurEFi!28sY%+yF|^o9JCLMk+1I+i0Rd~0dcq#d?`8k}ZLo}q(Y%PI&V=1QPWo+ro-TLIJAA;1AG4t!A>_ zOsU=2ja}m9AL8&27GMQ%Odx&%5A`r5{t6_H!4HK`_#B2s3hq9G49PxX`4Z9Ll+r0V z#tBxy1dfCzW`Yt)Lz;vjO12{@Hqk&Tz+POgCpOyL2RuuuNs3H~G}dhr_qWl#i-8}@=RZRlYXFd3&2Qjn2t zY{)QVAQrA`GIsxnq6BIV{01r@WEHxS6P;xQQR`KzBR9@*zfN#A(oO|m)9_$r?`UPD zw(&IPV~kSGHsH&3l0%N5LKrUK32=f>Q!qWw<2Wm*C-@)-a^Nqy$@I3x+;XCkjwMgM zV?jWrv?2$+a$>ATuP1K63wDQ-vS&LjghI#@R4*h{dtyT@VF3h;ePy zLNU*#C!YTc8A(GVmO&&WYBDEEQDoo;I@2;IBN>ii1JTH+AzjDJSkM)>M3R8(oczYy&$|V|c=dHhO`Mf~p_?VK||vGZM@oAN7$ugDK?e zA9yV%wy!!AI9KV4CG%*$yC{;U(oYY zPgM#2z=aUv_LzVh^o>9j0wVknCx|j)_R|j|;$aYu5u=PL33TD?2C|%h0lMHEkc2@U z^dGRYL8jsjo^>-sMF|doaW$=H-9;*dq2z{UwaP?Bm*xY)qAl-4X-WX*V$lWW=^uKL z&-(xD8~#BX=9LAwzc-B&p!>j&4l&0ceTu)Tt z3PZX!C&EoVH{=iOU<7_)U|h)?B%lmZ!~i3vC<{>_?lUFm(@1mTvFeuj2-M$XK?acF zM*5ZpEPyJhQg9DQ2nv^Gp4BOm*FGBWAM^;4oZ{DvzvHUn@94>+K8XN!2V!|@ zR5*YPr$Y7GjRV}xfx!(XN3|z-nL{j~uG$us5Jq7#LL&acxo&B&T4aQYte0?MBsL}| zYD5v$_7?_W6Z)Ve2I3Gb04tFMNhpygwlYGc=?1)ST1lz#JaANmCTaOM*S7y;I&;Dm zQBHJoYb~aM8qBp@*Ah;;;ue347ya@d9$-&T_s_f_A65WgW2hmLZgv+?0s&)5aR@N{ zf{qyhpr+IUvjGEYzycyR#N42Vf7ozXaMd={9aDD+LQ^)>D* zJNfIpj1cuuFJk{74nX-4a^kES!2{IbA3*sFndBUpB-&ir0?@;efc6dAqh4mVK&)dk zgsqs7gA}#aJmZS3%@f96k4bnLJS|}85Tax_VFQez4?tujdQ7lvX@3r_DC5Usdez|$ zTO;I$BvyhWGN2SPKq3YrA3$J$)_I+A0^>%757g=lGn6#?7hOj5A+i7UANKh>md3Z# zq#jiCE7SrfKA;_N!nejX&g2OUpaB}dV7B8m5gy56t5rqhc%9 z*GnL_7a-&`-G$TsMMA(ZdtUNBwuh`Xd6WNO0?uFsoM0sM#|%Q?ARxwJBw`^3(VB&i z_zW(|A`8iwz((e$K>r~c$5|)MV1|`-CJ2aTu=*dkfD4ks6L0@O3COSlx-#sT!b5wf zOFonh#%}_Y;k2;&ThKDm`y;LzSlS&De%-*k*AW#21U!dJenK09$UI5xwQ$w(M-3U%Ls)_s;01feG;;UCgq*F|ZR zS-}HX;0BDUaRxPKF6_c4$V-wuCAa0NBz9_xA`J2X@L>#WWvo=m)9|~>t5WaspaMKi zpsw`Cl^|gNHXumU$CiQwuyKif9)`%Ete1MJ`SSl(7D^!=b^))r;SBQKUWCA2f}-_p zf&jQ+7+Qt1d!j2>!2_4VL(lA3@L8LBVull>Lv=tZsNvyNREcqd8m_^&*0N4M_ZgzN zCwglfwx%bB3j!YC|Aspfh;CCB5u`)rUyFkO$k6$x)0U`zYFPb?gT@USjtT!= z{AcH17du^m67)jw&kjpFyiO>1Zi-d{TnYa=_~%Be+gfl7BH)0)E#13y?-H!*)@@s1 z|Nf{YXki1VV@&=XgX2xY8N{+=F_HD_n5<*G{{3R|>ldzLzbND4+*xyHUY?oYqO6(o zKh8|x{3{bL8Q1b71nu~wXfNJ{3;vWS5OR>?(9NZ)mSvHZ$ z@{fXjG1%9G3hpAyF4^p2VTF+;Q^^ko{*%mt5*lbAHw<*4LqWZ)RFFmwQuNP32R(@5 zKp(~UqKZo})C)%^z4(wuIcl_Hju`(9`QtwZh=j&K21JLFLJA2JN*qZ2BZ>z~5%kkP z9sJV-K@oH*qZ(?6Lco&$plO{Kc2pGtP*nltg(d&&z{U=M2{S=6Tv3^pR&B|M*FSvy zgBJ&T9Xi*dhR)TOb8a}$mIHvXMbKAgtN_O^1UbRU2F3s}S%RbS0vc-sg@#LLr}1K{ ztN-xA1ZuJrQ!5k2+{OfAa3mwa54z&BLKnNy;F}P}_9nzXG@vqt11|BQ1SZS3whweL%T@M>_@K@sA51sOQ!b5cIbn3*S6) zL18OcmSAKH5_Fk@6Bcxgj0yiU9I=5CO33ga@%-RTHrarP48s(X*vV8YB1uq3|Hwfj zK?$`i(MKnnMB_<;J~U8D9mTj%MLOm z(6MDf5#SI36s|;2keENw(F#GfKpl#48j);)TUD{ih8Jx7Gp84V)!B?N)%{}@K~wx$ z&`K3lzyYEJ5xyv*gg1I9#9B2_&<(=WEexK4hVsuCIt%~_9Em;b#0UdK0tbS-9y1!J zxE7NuL8NuYDQLUOirO!-{$lH7mE8tMBofHr1VM0&K*R97A=`zqG!*oPR+wnT+yOTj zgk5-bc3bB^*@0j}xC8%yaMc?VjHR9`@2W!32j=79LLvk?Q_y|Sz;|zaK;>7^0|Z^b z!#DqY^G)CR;08Ai<{+RvOe+PHk;Fh$p?~axAPCApJfvW-56Qw8>!47`7|;nF>;Nbk ziHbr*_>qNRL?F^5lJt(oGa@l0NWIXJ(Ndz29jS?iIy;%q45Flrs0b)G`A69Zq7$un ztspbN!9R#l5Lfg?5LJ4_KcH0*4VX)B1pxt7wg?nUSYiT}SX*}P_MkCvMJ9r|RxKP= zki->kafTukq8LS&Hu_~PlgpD49MHV3*g*=M2+d@i00aqa0uGj$6=gWJnSa>IR-UPq z>rRCl*tw20#n}H0VnFi?DM%m#n_+?zHb5@q6>kc?G1k)nlcXCcfe+QX)^nQD3t{vk zJbK!J^{|IM3!LL07N|=1{$T=w*x{FbQP&6Tbq#8`;6C$n0X3R=f@gdHKKhD}{n&TE z2k;MSQ|JN%xUma^Stel)gV+KYBr%352tx&#P&P791{oX!K^|imGLj*(6TQd{P0Gl& zqOydNw9I5xJlTs1dJ>@#ltmSp5zMw#khaK-A|3&XMQzBCCAEZU1vyGc-O_=qDX}0g zc)>qjK!g{}K&Ahf0hfR>HiDE%1uwbTKbrBjI2Gh7a*7*R=%mZQTt^rd_)cGM8ejIJ znf=Hl9HEhVk#@2rIpv3L@c>c2=CzZ?)GaRsN-q9^gC>ZHAW$SoBM6`oj#v?xY_bX$ z3ljfEOQhuwbOVbnE0w1S{2{UhsT)uXXVt;+u%Cb7^Z0xokI(!4c)gxa&DR`k$1%DMeS(6Iy@;%D zXtu?lHnet|V+3_IaJ9ru=(buOiej}R3L4gTsx>iq|2ZCmn2nut{Gjuw zl9=!&9QVc!UZw;uM~!(yls}ILPIqXj)#<-kJ=LCx6XARCKk0Z@8f1we<#Q%q-afBFJZUn7hvogB@x}e$(#~53 zU^al*cwCr_s7^oLIeoOVQ~fqaf=Np3S~iVVl3>>2I@kiW$%vw0P*8%o!6s}dUGoeR zVPu8mS(J_kUW5QKa>=1kcdyWUSvY3oc=K5>Q}pGA*p3Gh?Le_Ljt=}K6wLA`Zpm?l z0O$NUAeK1sJqs`NGa7z3J&-5HUtAY{H_&)j=*eb!o*Qq(1P@F^7ZQSPG6J2)UQndU z6GBh)_}E?%N2yLChm=v06oy$B4btH*?QZRQQ}}qw1&dHck0k?HtYKj=zhT)$E*Szr z#noKMyn?!PSRO+Yi@CvIF2FWIfzRVESO{mu@J5~~&hjl16IcsQ-WFQ>9YLJ5|2-@I z__!)fg~c{v5URe>|B5VG(^PV1)epDTe=+=#XM z7D;+>K!y3K+?k68+9+!cYK_P_RJRz)R;1{kvW@Kw$aMk|zF4fJ7N?$3yz7npXNwFy z{1NEez_)}?09{Uts1!Pi1}+6#UCb(HUDoW3Al5?Yb6z>V1o^cPuU8?N6{oqZRAPvU zS05}k0d|FnDFgCc+0?kd6fJbtG`?G+fpaBEEwPE9aXUnNl8AW$Q$6wBxR$FSZ$6GAH9 z`V(Jw9Qpl^kS|kI29GvSI~mVL^40>=g0o=0qQFxwXRMJ|R7?F60TJl#_c$yV`o4az0nj##T%-JMxQM1-XPp_~L3yzJ;IKjo^5}CM_ z2Tzf0^On0(iNIbs><}nC{YmU`w4UvV-n%shpGh02NZ2%;!S_nInonOn#9iJ! zR-jDSTSGV=fZ2wkW!TrP7x_*u8@BVIw|UWj8d?<`k-lW`?~EJziD-|z!jcKdq+yuj zs_~T&o^qrL#K~AF=~mv&(nQ5n?&ING)NP^uF)>9W{#rh?_@c@>3;Y%cl3EmaY46iYRX!kZ~-^oG;+r z>FDn0fYF5N^?~#&iAT3~0-!qqPeuzz8Hnox?SkmLd?NCDUf`mL&~XWc7mlZu>P3~@ z@9PL#zl%JLQWE_DJ_vOcLS5qXd^O2c26i^Q|QD`pnh!Z=^o-4TeE6?{|J@z8kL#>h1mdlc=%`n&lPfFLmTEM=Ss6#c#lD9#L`?RS?mOJ;X`y|JHMjS$vZpt54o@21bKU|E@sr>;B}BzfO|p z36(dx8v@3nGGXZ5pF-Ckbi8AO>RXIY0C*RIn?AQ;?zV^){!V}Ji>Muq+pZ@)twa!6 z$FC5NJ`ugkyG_&}I)%Ask=`mmGf+IMVkr0f%O+`{4x0EDZB(;c_oM!LnX-(b@Wd@o z>zk;siEc9>n(MeJ#7VdjKm__?@BMNFMw-RF^g%|vh4@aiwmBgKCY0=Jc}lUiq=gXC})AgZx;ct6C`qQ$Ca$KkN8VJ-h|Y;9kv{w^Zgy3qza$LHQPI3ppzA4 zqWRh5gwtVBJ0-3qF*@5t9dCO57i^G$^Ey-?0F*td$p#;qFaN?-4BS++ObCu z0)=Qe-OC6D78r>qqCHPF-@*_lc*B1CzVWMpp~iVosQy@_-T=)sG*(rD)tf&#s*?H`^pV4~`7IXA}toRUM*! zt<0`%saMq2#VZY|T{|w`RCngn?K@#PjUJ}5_L`nVK;R<4n9*D4d-1%vst5jIlILD5b|5E4xfHQOc9b;rRY7DQ-_a(Wigauz z;tLA_7k+vj&1?Sl75|GE)j0WC>hu+o0z|Y%C7dDdejmTR49!z^>=EYj#&l8JQ$$0o zTsJPLC85HwhFm7k+j72O52W93AqzqDp~_rn`Fb1Q8;kz{U$m9e0js5M;GIAfat}D+ z?#rO-?d>W|D9ZSZr!l)iHC$UYae#hlYxBo>VCi<+7*i$)4@@x&yPdw?6=qsiB*9qm zc{gk-Eg+Tc{;mcn*_ke(cG9$CD{;jN@S&!6?D%_Ki;olLc|v-R3jJJOs#Xe3ofXm( zs8*WU>L9n-3cLgeEV1%iMIXBh>CR8UGB$;Tm>}^*XvdMS(1D{H&M_Okfr1{o!asFK z5)kj*cny1EM5gfVdqM~FkT6P~^=ZAQ3t!Kw{6f6|o-2Ne* zwvQcR03nmzC)1&2^aw6d=&NAf+v=6Jd2l6HNMlb(QyOvfy|kYXw6PHO?Z~{_`;5kS z;zgdwv&HSPQ};=4UP7F_hH=jB-fJQ`hi5BvH?9o2^scP9#Bue;N$BM$EEIP`qS9TR zi5j|c8(YnhyxoC4-B4bMOS}s*qi;!eGOiBmL&r8D4=(eM{ZA-W(>dTb+T@*h7$x%j z<}|Y8Y_C7^dk2|#yt0fJIn#Wi|L034Kb(7eU*vx;Ge~r9*Z$epcGvW?<-2134u@sQ zsJ%gqnf@+RwDIe7DK{{gTD^T$%k$x0*4GaY(?-LAYMwD%Tca9v*Y8eh8kHOYuI8R@ zPd}4I@4F=N_0=;nM+qt5yC2p4dG^+0gB^dp*CS7r!iO8bMJS&3Gl-eIx4+(aeBqw+ z0x|D2;Q70jc;tTD*0GnC?oJ3J()JI4B;DJ`(WqyPS&;@(#ULUXg&dskfoNjvCb2B7 zcyc0O@ZlIm5oK#j+l?BIn>SPT?F=4eUQG+qycnV&O{iKP*uNn8@1PJVc9 zV+xn~=jz7~`F_qc!?z(zA0DKw)?EAFRE+Br2SmaCwU?0D)F77>nU5LIR&@OS<78x~ zC8({51JXkJj)1;tY+l_vsC|ir{tR2L2m?Xta%H5s34jvmc;2u9*-PYRkdol;aQiL9 zHKN(i10V;Bgf+XH(c|;nTX;27wr<2U{;?M|_ljw%iGlphlB5SRVF>Z+i_y4@dWj7= zJi=*&2@?L(MCO;>T}$V685zbF$qS!yrTYr=DP4{9$X2a5KN=}>vXit{;c!sYS0B4( z5M7y8-tn_pUz5HyD3?|r#|!R!sP5s^JGFSjSwWt^j@2mtY`V+J_F2A?lai>$KNRy~rYB$4Fhu@>Vx4-ku?dNCEdY+di&xCIshguhYU64DCgO<2!LV%7!2FzPtK8DOYPCa<`yO)y02EAcvkJs6>&MdVrYqxzJ$B z&K&&|C`9^OPHVgJtJ|6`0(y0=cYVHQ%(NW5;nV2yZExGYw>`$!*<;l7O#9T$No%1{ z!Ja{^URS^%{S_`Jj-EE+pmfjs2=||g5mlZ=iln-oPPM%>K@iaY#P`<7G^BX_5aGfF z8uu(e+E=-h^q6=d>RV_3d#qCYVwXfLw~*1K4!<;O z8LTEN@o~SB;AU03C{OGpAjuWXVz6EaBxsJL>UBO>mR4(Xk?aS3wEnm!nkmV}oJ@|! zDu~R5T<&@^nfqetW?G(#!;C{Qqj6GDeZL?5ZX=U$8z+$Cwj$|QCxA&&MYZmK1YQd! z0aFqvYAKY{hKY`u1NZcMM+X!m0x~sIIRhrYY7xT0WcNq#616^v!~%wLNO0?GOmM?2 zOo@37$eTF%K|QXiJLhuc*IVr0ZD}!d;gSZ-t70uVUQsckEeBSjM=o)|$Vo5(yUcfD z3+j(@Ojl$r<^(Q%ms6pSh#^AhZ!IW76Z77=d2D-nG7$+g8Mmw>!`JXgdAG@OrOfF^ z?=iz?*p)%af&l&X8PMe|kE;(4LdG{1nCH4aF~!ka*GlgZD<5p+5zKLC5cbVQ4M|zDJRsyOcNHhART(VhrDA|Q?@|LY}v@;Lxb>N zo)-rpH>qRKqQ<+}CdvtdA*x{cw-uR1>Zc4ENCOhaU6Qs54&4c=G|36F9qxSyG77RK zr-@vKF0;8*?P?%AD69k>^HZ5X5m55JRY!WgWb5Ft$fLo?xc>RKP{p{Klm;FQC-xsvdoJ)*(A0AI|8$_69XzmBj8sck+$8gcd>cF7M1zaTPXOtVWxOFh z{rthi(9VLMyzX)XX>aWr%7-UtHs+|!_M*(=hkWvT+g9Q)RG!Bq_46()4yEfsaB;Hz zmitRW`{;1&KZq&`PHUnm(mKE$p4C{5U|obB9lD3J`Jc4=krMVx^z%M}=Ge@8rm6~N zTud^JzfABcps773m5Fs$_n7|Li}X~4fc%OxofMKhqF6H$*LuiE?iNIrwTL=#xXorb zm6Vi1novcX%v6-EiRfpJH7*;ih!if`pv4?L{EIs8e zqyMVxzL%1whR11}_ol5@>7tCbbUJJ~eatrWjA-)!L{@Q2NQ$v0hhhV5AjpXEwJ}%# z#!5SIiEsR%A8Dk@(ZUx=&bYx0P?+bjn3(xA7u?|8cburnbN+Pvoi3D`*7)m&+bP}r zLGzO<0*_Qi(4U9+>Av8<)tejdOmR};f7}24zWF?U8$`S&=w>3S$jm+Ql}m=7iXTLJ z9nHCDN`$G|wYkbe&GIOcFI-4d%D zLE@gZ7)7ky4zKokpamS?Xbsu&s$ zm)?lR9@z6E+#yoZK%vIP{+QiEf^#xiur8Vw;?IjRG0sqPiOdNor?Cc4%B}bXy zIL50}>oBpMpOx!>qxCRR!0i^(GgCsVmgmlA_* z9B0`&80MC>@*pxxWb%il@HRQ8t5$%%(FgOv{@}Im$nHd9ZN?+-liPUXI^mz)D0RYj z+7;sVkHT)1ZSPZ(7+;N#297*1P0Tq1^}6;{I?gB~tds7PctLk%fNS+8Ef2?6VD554 zCyNFrD@x&uRP<>xqcs4&zC9r}^d6Ywb_L*E|_HgzekH}~l%mzVi zruer|bm?@NMWwgMOp0*k17SfbKzxZzqOuxhOqC&Y@o(Am|CVJ2-9SH5(wm|SB^k;N zm%Dy(lppmY^DbiYP|UqWGoGy*P+dlmJ8uroa6dsirBfT^&I?vo8Pca}W2kOO$f#72 z=LlGdZD=VX##7PGjv+L#sM-t^P0vXB4>RJ-?6nyqTiC_c@qZQ)M@0s(}uvS4SO$V7BmG6EIKmJhFnyKk(eGkuq3TlNQ zO9r_R31}^2ZN7ru(g)-1?r$?*HD#`uRuz$8Y8xP<9^mETN4$Tvl_t8qH?QA+CiV$H zk#Mw-R(nkIZL8`L>To<0CXTVe`@+xM%oITa>Ez5qBa~b*Ebf_+eK=6sMs8Z9jCAf2+R1%MveFF0~L(YuAisqmX_IQd$GR;Y_GmLZ~61NpX-OPo* z1yoz)(IrY0q&1|?iv?wfIO1U?~@>|`O+@!m5(TvM4BWMnjMHFB!F_O3-eYA0CR4 z>_ypn=1qFmALI2MWWovIED^KHI+1C|MK`p;->Vrn1-46WT1r;mK7|rMK6o~%OAgKT z;w>AY;*!hVj^W6g7 z6dIjoKp-N2>}4E#?!fu?B2gH*_N*6xL*|4qivGxfOq%v53`$gCz)q`yd@X@Iru1h*v$FVR z*F=i}9hVM=PdM32-{+xI37a5fGo*@9hX~2;L=2XbC+m8S|97`4oX+z_iqCZGnt>xs zuM=hFJM*jJ`gR52=?vxNHS)_Oa5G!IgIua?sNsmykT$88%&i{!!>?A;=9LImNT4cO zDE`(*P(cW#j@u&<_V%~JAt!tms1aZoCa6linIi*=ZsbE-e z1GBP=$t~3~PRMy$ogS_amze-wPxYY(`$RV6)2s{W4%DevAii#&{Jwi>WEkJ7TkP~b zIe)b_3YjN^{#NULQowP31{pzgrjp&&LPdC=vD6>VBA&q{RH+UG*qt{l9cWaE#qex@)R&LNP)xJl= zP7*xVI-N!)R%C!C@Ngj66ShYjpG%)8eFL;h*KwORiQ^&^$u)C<#P|UqZ>gXK9cIth zOVFnie;Lj%3SJHo3{bx+u@d}&4!5*c{<@n5n`lbgBn=YYiXV9!=n0lj?K=AF#h*pm z+0Qd)`-I>W2JkSFcIB0~Cj)HqN)6GSlXxt?kCj^~ltKU_gTcmbAW}~gZ_WaGOT_^G zv8m^JI6qEARf{9>c>3q)iwwB!MsRz&+X*)Sl`)alJi$nZo5N>gF2hdC*nFsRO5Dh# zZ5Ge}viMMvdBN>|8e`xL+h+XNI~3@>gchF_uJp2Db~rsl4o|3|zwhNGP;+TOEc8pe z^-ninL8qnp=#G$`LCaA7t+HxM;Wj$9%=rzs+MG|cu!i5dE@zN)^F)Lab<>jGGgL7M zlxGN@z18_{g$V4io5a&$1;KL$_VCOagN8ST`EBm0RH7(_L8|()CWI+Z%lUiUIVKm)t?d(LvG+B4G+_+Ng6zK)>OU%_b@M$DkDJ2xO*=+)C@{1$A^SUe+y%Nvr7ar2u2tTiOwZ zV0E1_%h7qIeZ_2!<}krqOQ>Z9D(mtddFWc+AM@aF`m;4tw~5 z@L;jje~5q3CHE=*!T1BTf{0LKGcZgU5xVJ5UmTJkgB&D-fjx@HQ`XYp&*j;-hA^(< zb9t~va%3i4H#!e$^W3<-QyK3ciqA{J=BeV>(>6e+E4E z{8K^XH-RB>m%eJO8TFI=&EH#aQ@B8s+gFVR|OMFMxOw3vUvI# zH^2$}yTgM!z3(8@aoXyMzSe5@RKBG@>j?A!%rXHLqjS2y2)`Gq0?-l$$c2-v-qQ4)hcR&b?UJVF8SyOXcJE;z!ZZ)j1u}L(VbM0l#hg zyA-^(JN|)1cU{XyfPxA5wTC&N%4|O>78Dhq7mNgqy~rDkPM`QrekA#P5P6omJ2d>l z_1oOq%x#UmE8k2JxOHC3z?%ihPi7=u5Bdsg(OERz&3TyW&WG13O4!5c;Cm%)p;8*G z#Dq}Q`i{0IQ!?Xufy-x4^>G<5VIkhT(m$Xf-ADT|yr`F(+6#(1EP+L!f+q--xTB*T zQ({EF62cV{)NYn+=_j0RJ{R*kOdmeA&x1;tPytX4uoMH<#~Qn9ar@+$kBT`SCtxp& z-|WMpm;*_J@9h7ZC{c$P_6*n~w?0zq79XpXI%Hor4f<45Wwt@B+r4J;CPSl@R}ByQ z0;M34xG#f0WTK43!gSZ>_;=d)AG>k*89D7EtBT)Bx4|aer#>9}l=Jku#t6RCIfnM4 zxWG#3+h74)k)4hs*NoexYgXv#VHTAu*5*cIb;a{Xg3ivf(_uR)22qMv`Pb>q)?T;y z)121Vnlm=Oo}J&!x6#sSWI=uuNjSf1vOS*cj_;~OXDrEr9ftv@w_nJJMfaSGUt7E6 zG0m;`B=*Q%MZ){O6`g?nBFW6Hz5$=a6bGjxc3G(#>6ZBPW&!YlOVNLyyGsh1dFQUO z!ajiv;XyY*k_4T8V~RHM#|_#?`UDi^OSd5aA?q=`?=sH=J`3ytPHpa`?S5(qmyB7& z73|*o@N6@7I`d5Ml{^;Rs>derz0F17&5oAviz=m;uJVS%dpxV1D|}(rE!hqmyc&YQ zD>sUer@qiPP#=?5n^jXn=u~?Qb*07kD6TC#-(}~ti}#<>=SBP)H5w1`^j^KSJ~x!= z+}c4#%?nI0btD!@R@9vX>0$G|$lMpZJh}k5uF<_cjc69LEzOk=X1%R5o0lcbJyG;$ zJrdCz&0~CU2n8K@DkH}K`tR|naE>{Tt(t`A#FU*5S95glEGu1DX&@GL+9uEArj)VG zH?-kUzqE?e65-G4j(GJ+m;X#mU{S5FnvPxE?;Bi^D+vvLJSP9o`St9b(5LBTN^M_e z(w-JLuKp7G=6B2T1z!fjqvS57Ik*hO=I`#$v8Z++VS7)KdU+r^w`PW|1sJ3&rHql zoxWM^U2CMd?7Pvm%+!1aE0ocw*#Gygx3~Pth2FNki+<~~+_$g(yQ;IR7phCI&LnIOS-AXCb=*>dB_3<9c9$lE%VXY%5Igtx=CXOso%QJi@ zsLngfwW!DSyF$UZI`;{TJ2}h^EBPsLf}h8h?IsL+V8sByics|e+@0tZ%T#QoO(7_F zl4I$Ip;?nM>9vZ&*630ND+m8hc0Lbo85#+_IP*pTmKf}&B|VcgMY_n^Ii?zu6Fwb= z-(SjfbD}Skjt!87ZB4`Sa5-A!f)0QdM)RDvm#eLa&i7S0^t_>MMI4=!D=lLXH|kUROx=T*QP^s+Gs$;PWL!OUjx}LPI`zO^I92t* zm?e**;{}q%?fk=`h+qSh2%smfSPDAYoPX;rp8RoE`W$zuoeX`me+(?e8Fj~~@9RgA zthrlR4q3wbmlX2J#RanN;Zt-T*+W+)yudWEtls~eQde1pX?}XneE@I_>M( zaZTNyW%^5~;hm`dtCohb&3-=2tqv;QZh_6y;Us;Y4a`yMy#*EVU9^pmx@hpt672Q1 zh}ZKGig~x_SRexgt4#No^xymVH0}uY&gq#ox!DA}$Mrygn6J4MZo<%$gULH8XcrNo z=od+7Ix9^zPxQq_8)@Z)Oi}5ZVgI{}NHOZn+w}dajlH~dsq>?3c$bRQ=qAkg(UJh- z767hZ)juX!R46$^LY?2Vib9~7!^uY#N&m70>6u(Q)P@62L^@6*0JTb*8@4JlN3(sy z4UILIvZY!kE$c{my0mX{s6Scd>0ZVUMQi0jL@P+n#Jym}7HpCkKu$Q3Uum#C9$I9h ze$a6d>JvRj7;(N+C0Cnq zC#`x>j&CBv68f7KcWVv%w#SupJ25|fYK{pii09z#Y=}qc6tR5^W8|C|$UHdnz`X#y9-pdfCo6<15!+7X2qX#|TZ0!*l z^{9<#tKM^#@5CvktH(?txL!~3YhLwrqD$}iG#k^Eefg$(KPt~^ztkA|8J?II9^TK3 z*lYxMH9-axhkQpum&08E4}N!_xw2L5(=FRX*M4-&%$(qGur1|wO10$%S7XE|;inLu zPNlpc10Z~EtaQjNKwk(&*+~R9};$mj0lDNuSQ<>u!g>YqBWB@I=@Ic{qv$~YcAybXV>8%;2o_(Fz$ zR$Z}(=IuoUWO6reR<IgUV=ig9hlZ1q^EPt^RMB4A3r|K*gX`)_!T;U>GbD4{l%|vL-R}snQyjguVOqgsQ2bAjA zLRUB%TG5Fg{M^wp-1B8Qoax&;|J=?fHt(aJjTukqJmhHovkZNt!C9z z-EF?V%vlLpQhzr41g98zjf9k=!k5{fIU}p zEBk7Sg6i*r0jJFA6Fr1XnU^pTdUe6lT){8jbr-TyplFLN@MZ(KWpn|Ad{YurhTj2+ zWYbABhwNt2X?(u81xmAKaw3}+R3i}a++8$fk~Ek9&WrP^o5+Kv045}a7u-=;px_u2 zmkf|;exo(;9OwmnKTv~W0^chFD zi#i*UCe$jQj>BvEBWx(%g9RRVipYTExmQk!=Z2dXBxTga^(fHe4z{}I#x@x_$GdDl zWsm7bA8)FM@)tRMi6cW7*qRIMuQ7$YU9UR7vhOm5epC`8R6+k}Z!Nhg0p^q9QNa%M z1iAmVrsGt}Y2BHi3ee8JH7UlIFe3%gnltVKo+W_!yFOSpv!8s*J>y0qHOp~oZ!~4)1BrB@!!4Gsga<3=!8oskxJjx|NPR zpepS5YX!?7#umkbNw%RtF$6~}mV>Mi5T0WT7LynDtfjnN`9iG(3BvEB0oJlK7#k8O zL*to|v6q%xMcXX827*^evNTCIgX^(MvaJ_gO?rG}xHLe#nyu8|*FNJ==?dAz(ZMq( z#J6hFr4mWUvI;|JRZ!JV3EFVIijwWgb^|w>cxL_h`?Q* znE^jsel>gl8-+LgnMo?0?@|9lh?gX*DlHCzA<6+{Xq=^Ic64e+2fHVbt%)8G31r)i zJ|l%koC6h6gZR> zvPgMkYXnQdtdWR#q(Hz|dN;5Y4EhcO<*&`3?>E0H4{@FNWmO(Gu(02sw?{!ufM=lB zP@5dy!*ao*fJMr>`XWh)4{&I87Ywb};M$Ni22=zMD5>#^!e|O^j(B~GqmJPSCr6Q@ zfilvRu6D*`D*bRLP1Y;8c^zz8-NY9101 zUPvCbl~SHf8ilr>;PVc>zGf?qR}*hZl|4a!zl{i3>vjqn=W9#JXo*!2NiM!LRo;~<%Ek5zPLTv3IiP%i%Hx$~v5I07QIJKz-DM)g{7a`D`2(B`m>}_2 zli#g;&RqaWEco#Tr(5T|vJ0QeAE!H{P@eA|s;42V3B}hI6|3lC(W{(Al&wNINENb* zY4pz)R`IyamNXiSKj+jm;eTebHZStME=oLMa_si6qZa~8^mRFM|M3iV@*Q8q$&Z1g z0YG`*K9P#q;(KLbv37b+F@FIcg5IwN-Tr!?bnR|z3kO5vZuXzLWp}zcRI-`hdcR*k znj|?hr0?<}j@x{LF!wfH6aq>9^wa|Qzg1^rvAGjkfFJu@#tf)^4+qP*P_Es;DNpI0+C!T_+27(gpsD?Jy&2`r30XAli!egE; ze(Q63f=$_Km}T9oHlH_^gy;%$oY{fPZEX_Yfr^|oPzG(ozt9I9x0>^!sC~r>dJsFc zH3W&I&yC*^&GC?)+qQrGYPLubAjQ&!N`c@$b;W4Xz&p9r*Q8`0eHXwA;7)^#jw9f` zN=!(I^kD0i=Gg2dH<*>lVqAh`KfJ!V0aN%5Io&j{(a^K;eP*nAWvL)E1G^0Bi~yJq z*y?gs#r;z4Koh4ntq%~&zhoefMQsRf;Htk>1VTT>V!{$hBsBvdbIa+r?WFJ3R+2T-Znj*^Zj>=$H@)m>qrAd-Wa)0Gk7 zSoXwUiKkqXP<^X5Ww{{1OK#({8hJ?uDVs!-G;_tx{o%>LURj%%(J!KUXT-T4R(ybn z$&0R72r+*`xTS&UVU)qP;`5BW^*^TuPn!&lV;P(Dv0DwcNi8}juvA1iaVyon39;P& z`P%u>5dTN-1?Oa^D(Ic6ETvYVawm3B(!l+T&mh(POOg;Qv*o$i45aPZ!;Pykh-mU) zD4BG4_ZlA|10e$TiM!6msni5`mVO|Ts_V;^{-}^jjrJ$=Rsh>6(t=-D&fQ4F8!uO_ z)=B^u{hZQNj`) z02ELjte@o2Z6O;tuC=W+O~7p)xQVjs)JEGtP&+%8I|i~J62EXwzZ9ZL>YJS0M;?2+M<6)|@1XCV zy-tUz=IT`!0;C{j?w?Ndl|ZCzHbvo7W)b0P*{S%1T+gT$nC0v~P`A5hL z(c;>%H-}|92v*4gG$J39I!o@C5A3rnwbpE~re`*Lb8R`2d7LG6?PMnj%j@2^3c7eS z1zY{>R@IM+F7(~dnv!p~icroL=;=eUZp(h$O8=L9jK5pfnxbI<6hm{gX!#ye1=wLG z?zFpvbUSC_PZNl@6W>&L-sMHC_qJ%0qJjyRqnl8S_ z1ONKo(p<94-`TuHv%C5DJ2h+l9nix#n^V2ZmZfBky5Ac2&c6V;onqUO-1?1DHc8b# zt0Zj_i2yZ8nwj{(71z@3EB(+-qErf!e`gNBjRgt#DV)u1CqDBMdOA$1x4-0T*T`x= zf5zBa9|#KuAf+P`%fWc2H6k!w&W382wUO9&P9b#%>hn`PCis;>z7)Hjl=^&jk914j zjw~x|KY*26AS*z@5}F!lBInr2f5*OT9b1RHebAM876mb&C=$xw#d41R*gD*P9L_XE zpUTPFNsNP?%XInSXgTmG&6D7ni>?~O(Peb-{!VDZru7e{|hV0Pz?mepf zjJU5xf5R>Rudo1z~c5U;{Og!`bWystP&UNAg_Ls z%!%>h17?=Qq&fK`$AIve1igug#v2tb7GXlph)cJWQ%e7CR-49^JADZOsN}Jgb<*tu ztOHK=wS3cReiGK=GjhkF@=i%vL`9kZe!tR{{K-$ik~fg!TD9V%lVST*TwV!DjC1$A znJjk)@rXxCJ%4>b{NhljliEPPDM7rw z^v)-Dk%V_pM&zOT3?LOO^qZ^t?Ck5yW9KQ#uMf-Hpl%xMr0l@SCw~OIw#S6q8Ep32&G7 zwY*BS)!=!dNl0LNXwrr4=bi>7syuw7cD451p&S&NW3=_d@}b>?rrO?}9KdmIBr(K6 z*|%8H=$Z`~6ahoYa}Ca}nNt(j#%+QgP2_={XmFVv=?6-q$T0azPw}Pxi4h_g!P)2E zyEF9E(oWjtYu_fD&4o$BUjLmXKG-(D7*F3Ot9r^y%j{*s(}S&0hqT3id)y6f=ysvn?&9S;ttV4H6^bjTm>?y1|HI&)>=rm8k0>vG z{Lu`l4NcERePt0VWM~paHV>8cLIAdk_)jGkzcCI)lpyKo^fYB=TYa_z7MCy6%}^C^ z6r0jjp>MJ_B+LzxI2maJyu*(91{ara*l1%ms*Id2k#V4&ypKRxQ(?@fJIx>EWEOno?<-;aR}URm zlyvyaK{gXEmDBvS^F`z|!eG(>d_Hr^VLJBSjE_P;Qcp9ioIVq~n>CwOKJf}Juz;aH zqJaKHy_PxJ8HUxVJA2e*oq72-@N+yilA;?r5!UWXvMP13yfijpTP`2=`L7%t zug3j%Lz+MOW4*zhN857RJeL6JSA&)*JT{M5*69uh*=Jw$GFnAmJ{5Rbf@2nZ7ICxu z0>)cfx3qr7#N*k)H1t77pzzbbqsJps-^nTVVA zv+Bxy;~gQ#J=_2H{NW(5TOu$~N&I-;4WPmEpv^p^SEjo%bCiVLCNS_Iu?7b&#}j$j zpif%ojRoP>yqTI6%k};X>rCL~nrAoDKbt!MQ3-&xqrt%*J+u?6NFi`Fn+DR3JtE#G zv?6JYtn`Lk|Br%s=KH#2myls;Nw+szR?x2m(yh3Tr9*@PoZoJih*SlDuFRsI<@&Ng zL7VA#h7sfXewZEJV?Z_Kw~!>J-A<%}UuXu8%EH=U6)PmITN}wT0@*mYJKM+rl%J2U zR{*0N<%PEjx$_Y_Uq6xB5_8(9i^i5W?IhaO7gOL^^c<<7MV&@j4{%0fh4+D62=I{~8B z$c~y16$4%m5@o()(rKSvZ4ApDU*C}otAzK2!hPpJPzKwXFBo_6I`8W{ZX(a%fp-Gl z{k0c~=kOS;WXf{!hYR|a+vohK=Jdn6Q;N(?qlF^+SZvF~;@rlJJomq64`wZQRw;IK zKs4az(F<#8vZvXt(rN_jO9i@e%HvHpb}8T$Kz(PPBP&Au^5SL4O6l%mx>fHkM>*V1 zv`I1A>c5br^<=eH5VbFqraWUqQ;6 zw|X(9Qza7(O}y+OEq4`!_1grgL$2fm-0+yRQJcaPJoGZSH7WMQhfn%7-3sw_v78Xi z6t5f0#s?11xohFP5tOo%nDl})RiD7tdi!_`2pTRnU4DoA2!RfyKozkhK&V@sg_koN z2xnfsy?7$>zlio_i_^Z0$$Vg)pZN2~pLJw?*_OrqNTobn)LLr6zYS>3C)FibKHVx{ z=eXcga@L*79-7=|w=4`T7+md($&!TKfE*n^^4H`I6yori-z1f;9Yd{rb>jR;DFf^G_|NOPkzo0r-5(&$WF zlE1vQbX^8*(}5rPCySr1UXYe-6C2@7bGP0uY?M4FkEBx_#(alv>D9;k4t_QkCL7C| zhq?;fhJc4mo2*51j^#RkasRzCygy;+Mn1*l*4~omkC*P3THWL`9Sc#jtjEHtKNc8* zs?yPiv;_L#R~v^{xu&y~xTUa>GY3{}$DBlttDyStC?`Fh3|026H`KxnAQ49t(}nIX zTGz|TY3oks7y1oT3W-95=r^F*BGYz_G^UDu6gi#NP z(r6TqPEQQ-jYHop@p9X2x1J7TeAe_ zlpk5<^CIxV1KbM}V8b`ec9z|a-7D~?{8o4v;CtK|Y$&GB(Y#7?o(SXz(Z}nLyI<&i z+4sFf5GKqbSH7Is*1z%Mx_*8zSgQQd|5J4C|4ev)9N*pCXB+0eF*f&+J2Ce=BSNX>nxsOK zN^I`;=6*?&hxg;0*LgpmuXVg?QsCV*H_Gb5 z9Yn~=<#|TkeDo+YpKYbN%1s=pEqEs+wh9niUc)PXdAZ($T*Vq~X%KRllF`ZS0^b#q zI6h`U*n$v0Pq1JF7PEHuTN6`Yg)ZPjmuE6W3&=W=w9d*9tN~cRf(sX+`E;Me#@K4D z1$KdehhZQwm!#vEnm0vX6vi?WYMQ6cY91Px=L!57;|m8M|ImTUY#FUj0;^2g2kTIV zrWG>9LL`NW%@2gtSj6?1vo*;QkWg4K3J;J?>YZZJhD0sPMkMm@THdeTEXkxz3nu=@HAktV^uk(1^BiPL}nrb&n~Mm+~6k_qraU? z3IzXfAlA&Qas^gCWYI5wgS00B?_#0x0NIyThsHGIMu0LeYhoI|iJ7Rj`4WoymR=1| zvGR^zItgR2p7jiMf`QVyH250>%j4h1?w=oNjHX=sS)iFJJ`^dDWC=*43Er^98!G8n zkcAG^x|9-=+T37~9gileGhr9X>dSm*tNDvLfQi4fP)t(1H{sgFZSRJgSso!7= z;Q?@R{=wa#08Ec6I=FUPFmMXFV?h85tXzQN@S&&%tN z$xGVH7uL|N#>kCsLUZMMgXP)GFYM#u+`EI&c#DX=vrXZ8#rK9mlES4C#`Sls@|$i*y@Mg&I7g;*dFbej+o@<0^m6TsCA&otTm5(4%xWW>KL)$x~tm_(2rLvFUPi0}Hc9PHe#QP7%$QN6@0e|ium95pm^dJgoyrhRBV@YH1J?8S%1rsAL ze%KCdfGM`GUYf-p-JRzHgHi34z~)oQDAJTUC3JTA%Vq0d$joaBk~Y?l z)>0HXmVu#G=1rKa?{vF-{w$z5(qMLvz8@g{H>>GvYv8F22wNB4uMqs8qUZ{^OLGPF z?afKmh0?_VS#6==l$PBF<3Xe)-j~ZSOG~<)BEv_KH312kSXJs9RX!QWf`O!cIr>1# z;3!_?gVec4Y9Q4&r=J;^9P^FHO^Td!x~A)-28XWX=%rUHLelR>lK@CO71FgCEiDbE zQ6Z}|?B5XJ6qf}Hv{~C10t9I7X`I5VLW#F|rh6Riz(FNKwEwGbfbeLYy}3(a&HNC6 zvM5tv5@Q$%6|NUQFL+O2h{VsJer4L?72X>7B8mMM{5_Yl-YgYOpHhr_dyKCf)5Arc zrJBkGKrl15_bX7;8M~Qelv=gWpHsh9zE~Bw+Yg=zGO)XI-ZvCduNL@9EsM^ERSV&% z2$RNIfwgCl!{piLS8OA}F~-h(Xo|(^-XjvdL1*bLp>34Zul+egpnb zc3#R|5tr77Ni4yIxu5`%5{eJyz_of~ymyP7+7Ep6j<{)jKjPZ^h};?l89rVzB}$=PNUd$|?qR0T2mszOQLW7fALREL`Fvdd(Q+22{`B9S3DsO%QtZ#S6q~Q z$<#Vf9zyhZDx1&Hg9XOuB~jfqZWQ*5rdkIAjhd4JLLR1QL6Y~rSOxkK%K&gZ4Xt>= zxgBl0MGfWyHJAsWi9)cv1!2e`%uBjg2^oq}EFYxnTAYCI(#?ke0(*`M@01ACcH{`*QMWu43A--0WJCPccqnMQ zYB2Vuj2vY%LqX64^7;!!ZO{wH!PL>JAN0s*e2QM?B3`mZG&q_F&NZly_HvK#8feNE zDmKxwQT7hJyWK_`TgUiRL@(SrMvvR0$0;(SJ=z5Jel$k?Su-icJ(?EwKPkRVjyX10 z5;=@uko}JR_KNMjz`brI^Gf^Om=E-!iLNYF8iyj!Au9I~y0BZRW0)C~vp4eK25 z+2b7BtCrps=79kF63A+VK-sBL2Wj#sZcJOaa%FTbM)C6DaKyJ;BVqdbme5e3a1~&% zquMcVMsMf)-blGPt8t~(LAr9TZP-KED=GZm)KUTp8Sh4rJ}579aKg{mn3u0Qzgk@- zv$Ci2dX5UkQ(N>8dOTe_qn!?1#iFnif-I3QUg>6z{nye@x5ZdlMJ1CoHp{8Hm@Fzh zFTF#kI>%>jI;x_$=hw5hzB4y7kJQS&?&NkXy7@gmW*BkuSt+@-YoVw+85a?NJtC6#p>68W2S18kq-89Fr-?wXvG5!90VzzZ&mT4I@n$F>MYa1&pJsF zJYUT_muDC>w5eqb3vpX%5j+_>o9p}_22j#He0~LwD8v=fBl&8CUzXz<+4x4XOd}hI zCS$9NanV~#*^o0Z(94@-s22cKS1kspBW|pa|JQ4&6`FEcGNAPHw=81QeJJ&+o{M+J?eYT9!mLRe_ z@*wQ|%2?2#n$(wt;4lNf*^=mmG@!!`myA`tcesc_3q$D?ex;Q(CQO@GW0P%1Ld+(f ze;rPK7|dUh=se5xyFj~!0XlS|RTa}-HL2N+Va6XXemtIJM={CE3H@F$l91cNJ5r1< zE2DBkH_GpCw>lM7`V89M{&bV5928MsTW5dmKKcSCGJ5mxZ*3!&y?=i$tVyDl26<8Q z_ys;ol|ino#ekrx<(fw8$xpw|UD{sgE?YOrlQ)iCeZ~eKGjE)rxz56C5>>x+#Wfh` z*J^KxQmlbEI#gvk00ak@k=TG^LXi`M(_OI>BB;s>K&J_k4FgWdq=nIWNBuzn4%H30`&N+Ewj1c~Cb0@Zz4B_oZx+m5s0$vTehXOX>0DhRVk zpr{6m-D_`#U?oE(Ds>!mXQpk&v{YZDc$I4oD>}oZSmkK9wl!VetxElWbtnoq;H|v# zmH8S}r9%`lQ^%8??`q(nX;Nf3NM)1dVl^m(n36s^+&4@tcuFC+tDet)e8?7I#AE$( z54u$UsQh@YcNAMK9$x@I#UUO}4T=|wnF)Go1)I6|bn{5RMcXEQ?PN1%eEQy##rpDa z2z}EX3wcqDH0bl!h3Pz8N_(k%lDv5xwcG<7jC-zQcszRGYhaqtO3kOc3EbjOH!qF+ zJM`hwb|)Vxdgh;X>wJ z_QSlK=n3K#CVtaG!j~u{`q7`eV6m{furK`)FF>YiKXu?)N%P=$Y^Bu(9B^3#aR3nF zzMkRv8gkV-I1h3mxqytiQ8ZQv#;Ljjy`uZ_i~T~SCeRW>2uEF&hs&N$^bdWZF;7P! zgvywaloPYo&}}5e7AO#Q38SFg>g>?UZ?C)Ir7K?RqWX&n`!_l6trUx$a8x?J#!Vw# ztcJKX#@rC`iQvIeUz;B9y>@P@>TfD*dtNiAcJR@ret8ItyEh$Dq;uLvd_?-@>5#`- z)%4@i+`KdyJBGfUT*K>-JNF6oy_z~*(3+z{_sB_{hn1|Kf`px>>)#^Z{PJ+bue81O zk0q3sR>sDwJh=CC@$+@rJh?TKk<1hbet&;bHkoHK>EUx(BerU3{kvrU&?{|PBrG3C zgb0O?0k!}*+sfb&4b$P&Jxf|5dj1pmz_@;?asLnXoudO2P&*u^uj$F33$Mv6 zBm*2EV)l6%Hr0wwDb`0(_A)Rle8a&2CErJ$C0aCFP@m-)>@-_o0R14bY}iGglUc!y zNmmHws$8w*mpN0=P&J(rX)`?RMuJt{WM;a`W!vNN575n28cHb6c6n_qLqCptz6buF z!k!0TQkk1pEmHgz)lehp=Mp~eb`s&tVA*J6md|y|-tR~Hhdpa_<8p^bgJl<5Mr_iw zpOCE90__<()N@{MSCx&l@{^q7)upp}T~VKlp=_y48nuq+ zTE-)}7g-r2C66V0TW8DCVtvNKxX6 znYwq^WH!+0bD4LSr}t>MH*Y;u%52zd;ND%edQHEXrsJ(@m#^l3(fIE9B+&(VVQL*r z3`C5^^r=Wsb@eGzJ|>Wb{E-YxjkbUrVIu|=dx2Srvr8{`qi!AS zpLux+G=%hTQ8x!~0}Z#^$u~$ii~cf<&)Y)OaqfH-@`oMFmCZrKttTt&rYqXvdJ69} zNJ-X6IfZiCi_B_M3G_6$h!aB=_CnvlOM1eAW+M@rdlK);pTGD1L{b+S>!64ftQ6}{ zGy-QwO=lEKyS$e3QWY^>Pog`u85T0~bD{$?KTiL=wt`(yP`Z0a#xmj-wbcvT zkpcSVZfz3nNETX+m!H$G89o@XsRkP*t2%Bpz&zGx6(}K1$G>eVEM>gF%#z+dnY~|B zQB)Ol+1q!RI&hDjEfnnex%tSqCEc;!W7@3Ux2{e2VTfwF{wg;eUbt5!pgHRMC6o?wg7{pda z&MxZOTL@O_oym-50p=7<^D(rfN9?>bw zn*qz*M6;k8>+9Ot1sMej=3n_mSx53gIemrkuGRui2j0f_ftphPDoDK>Pd>N0UzB`G z;1B@Vo0l)lGof$fMkWtyLYy+Qhy{ny6V7{!~$tUj6 z(p%56z+3cGJ~KqjhT*k*A-Cp2na@slqjVc?yuVvgMPtV*9;mNZwce6)^77w$66%qOu&5yY$$6d4gV_WMvFN)(U6kI$j^9iIsU-?il`H`eb^2dOs7ti1vwiS%pM9071VWh2|3 zK6C?dosiZ-smi*LTX+qflhGpwLT}ZD6pv$C+e5l) z9VaFP^Xiai?PAKgbRG-9WC4OA`MK*7Jb;K#BP zBnT3K+t)T`+j}Z9DD5QH7Y?Vta^l}gr++Ya`rU#k~pa;3$N|CS&2yl#q zqdq`HqLe>@B67~s5y(ia(T%R0hlLNQy$N&5^T`@X2yALXZX6w{~UFk_g+PI#i)hl0zj)=Rll%9&cIe_j%cs`6C}F zVS7loj~i~}@i4JT#%_|u=ZmB-rLw8I0Ob0qkx9Y>@r(XGq(4Y+s|~@plc|2wSo{xB zyj~oS%BR(C5@3YKi$_q&m#Ji7#dU<3c*OG(m@!0n5&$M8Q?vJ?rPN(8#% zpJF(~;{ahl9Uxt?wZZS>7dh!?__l{Ld%xusw{v9tK6JVpgX|iGBd`U1xo&zy&u_$_{o2nBMd}^c>)AU)3RO0Bl@UQ>69a&mrnxA0cWrbqIN`sYHC9_4_&E?KNNt}<=ThO$8YCfZSf77 zyNF!CXU|3_aj4CH@YIveYQ6~%%h8|Q^5DuMOCSMOp3_$Opza5039yhRHTWM zgs_w(G<-d;CfO0};PVC^g4_;iFGseW%DZft*NN8Dy=A8VK=Nc%MK~>I3S0RN1NNYa zL`XpUhC`Pkp*}R1XBNeJ;)%ClI| zBQ}#55OE%JqItZi0Lw2xJ)xz@Uu%C^6=+Lr)nCpMUz(a_0H-`dvTUh~+R)Y<~ynU-3T|st1xRt{H z*uhS}1f?Zr%q|lz^*bpzmP7Q-DMx@yFYs@tA8f2Ofy@ERlPY50N!~AtRwmGjs*&*~ z*K56U<2TIwx6I5gNlJgvyZuemmIP~>t~=v zafwd7x2qI4sl8tIAF@+o@d5ziemm6S(;U)mjIVwSs~n|8*|u<9QMhDNRcseKhy~x> z3jeoLaDPj5@R9Vdkpt&k(FD!?m*3#x*{ zB!WqWP!-cnW(sL`gb<^MHLOO_dx%$(vfj6r$=h8V1pHr=Jbl_epo8cGR4oeUD`)Lr zi$%P%MWQ+ZRr>b{V$nl!$RLJQU5aV!m8Sn=Fc59_%!bq`Mxi!_}bV(0E#56 zm-EKj`Y0xi)`&83unJrx=*aD-K-u}~+vg+;P$}{+^km;m@*#S)fMEINBbHnrqfw3= z_z>x~0osKGw;0QBWe7PNg)MSqmt(>g?Jx4RqmvnyJ@2ps%v4Z)mr8K4PmM4R>Y3$p z_bm@0LejldcJdO$BjP#ij8wf|)kUQDLl3JuD*k@4n%I@@DZOtd$`Frx8j-+i0PxqJ znnGecasV7=oq3F9kk1kvys-D?(TN3{q5t&+7$W7I`k~{OiDrUN?-Ro_zKR zDvuSqx(?Qje6su=`EH%iU7J! zsbqR8JN-?C{#(p~LnGYfdM8@S{;z0+exj7&bDQ7whnqR4oe+<8?q43uS>p2f02t4r zI^RT@Bk-i@U{d`XK3#@TYly;c4_QqO|KDDWFB?q`VyQZvT6&p}3|9Rr;l# ztq3*G>dI0W9y>4|`pCFP?@p=yfjd+LfL-uhAS4BhCS&dcdZPvxqG* z4FsR$vM|1(JIG4K_L1Sna;Db}5^^1^LKjjoD0P09tv?qKa132OD>o~H*2dMt*zr)3EqlF3L$g+o z-4GL+m!kT8<-8J~$P`)gwa|Un<-_Nr-l~o@o*fAGSr_;h>F*pS`z+s|kM~3F&*sr{ zgBFiTPa%C_phGuhtVwq?kB#8`VT0b~Fe1#$|6sh(>=?N@O&bgz+nnu@h;dJ_lFMiD zDK03(<}Ldk=^{3N-6?_SwaC!3qrGKJl;7V2pl#XDPEIKBN zWgazqP)u^ECYmDiloFFWXBG{V56yd?Y*Bhl7$ihZYW}&R|AtyL1iAqjDvoqDZenS+ z3Z0!q0t5-b_mSVrg$^zmIw+S%;#px&kRiEQMZMbbF+#)$3(dP__qVfoRO5TB9D;|4 zc_}Ddt->SHZ@Uzfz(8K6swur?7gV<|6GPBtY87?SkSON|r<~(?ts#^xzDErm<^(?; zO{7ie-31CqB1bcU0Xp=)!DZ@&C*VeXiN(KaSFci?*O!c}RtO!g^1C%UuccgHo$2GN zl}k)J$JfD628922`u%Na>=R}CEj($E1=prs`cnQdsb~SI2#*5xdP~Q;f zw69u}ug8bH`lM?R%kQr}O=@o=*<8P=9}@2>LH7WVPlf&x;AAD;kIe}ncWnc?q#>l5*m(R}NyOuKB9^@W-0F#+ArZ_Z$M@m63&VQGib zBfu{L=fIvY{v!phNP&mOBTFApj(zWcMG~RzMrde$#Ir?_wn|@~ST~SMWSfvvhacBh zeO!81oeNKk>3(L~mfmN7@9HkxR~LGG%yx)YTHDxt@y8)EZ&vn+gjOujbBr(1(a;(C zHoaxIBO^=AE%I^-n#JXpIGycu1U1VI3Ppgl9&;La(fm`Sh9|+o_pT*gJ@dj&tNnXe z^hXDuYjpPcEZCriGw~wQx60~V)-{i8%@Y-zzeAzTKn|0&;gaSDxJ?cil>5{!x3DO; z<+1r$mGo)9ZGBM=P-7%DGfR8u@SAKX zzf>E}Sw-Ei^H?zITR}1D{j-R+p}VdUQ%8AePRL-aGg1AHMh9=}+vbEDl zkJ*^-9*mTdl{HQ#1SbodJk@Y+4NRWh3t0KR>bdV7t+D-42=aOuv&52uND9wdSO^}5 z33RmC6}6bkcIj-o_ac>?g|_~j?3S!Ze3FB;uLTNkEWhGC6bnU+KO=^R@?N9f9}!B{ zY3a{P)4SR;yXa1^DV-NbxK)ZAppT|V?wuGc+y3|+bNtWWrJEqJeR(M`F(8s11fO)b zfNeNG$MW+6=5@d!9%HS&f3^c?PJg(|oW8w7tlkrQK{d82!2va87~#HpO!A+FkV0Ur zEDeit0nlNBX>~Y<6y!tz2o$$%$AcWB6f_8*+7sg_9TpHf>2%Fz=<|&fJEt%wF^cYr z6xq%9c0<{bi6jV;nx*Pi5v<0&X@dHrnqPHRcLivUG8t)1FPfjLHiB;x@)F~)MbJj{ z!J;O?dG6fU9Wo1)i~3Ww-COtAKNa8HiQi3z_HJO!arYG2??s(mDGTHwUI%wdUSgQS0+6zMWc)jN{;cpT~k~~Z}^hf zpw~EZ4`t;jSr)UCE>NUW0Y{L03RK-5p31uIs*if~L!oNV(MI)a?*{K9g$=>f!)067OQqyq_Eb!^(aJzur=a907&^&%IC*Hv8%$ z-HTn7U$r&9REn|S#m6pv4aw={$QT(M9LvN(cY+-3ul*7*AU@dThuOXV9VSRu>c;87 za&s*99*U-wQJ7%q3^MM^G)Mtc4J?kZ5GoMUlKv?uB8~`yty*i0urh`4&f3GEk_1I1 zEJ~`;HsO+okbQrOki&i@*D6Tl@fg%rXi?$t-)d3?F2^VytQwY#EH3gz7Uc}91Yd@W zH1++yh=-r@5!M zjZ~}r%CN`w2x3XS0s}oIZ9^cbX7$4XPD>J<_#l*_dcddPR-vAh)kCoHu{HU5+*?&U z>-3#q-mAb;4bKY3f$QTrp?NxNF@Fvp#Pmf(Q!0sfN>KJ*zOWHFDl^f)#|-lRL&T%n zG&@LZb-%UnxyCgYd9#35AXbD>~zFi<0A-3o|E;(X)g#YIv z^*N*uchoz=LOaRhc3@YM(=5Z@c+JC7$P*zQHz&)Z*jaVpgp14;6a>icEd)4O=|-P% zW2Al*JJwb$cR@kts-so7XOenb=Gg*fCO&+VW`kcmcX{CuFnTlBXj4xu@BFM2oW}$u zld_^rASI(G=SQ9(?50eVr76!aDK6n8-OF~LmK+g62)&(N4R`%hcApDmUp!ti>kCu zw|qx9nA|ugmWyR8Bv@-N2n}9Yams-0aS)=jr>Re&gKMz4eZ|~FKL=WUzTC07e-4_$CVWLZmc!XV?dO=B(GSb#?L9 zr;lGz#Zr?BB6Bi(L^D;L9_2r{?zy92v!p0_;t!*waZw>wFsibmjgXuZrJOLbYMq5F zuIrlvrXLdUQJAOIP(wZj+irVp5*L9sic(jl)>h4Y-l9y(Wo9yLq+k?*dut(5_rR9| z3?DXkL>91aM97sU4_cA3yGKSw-sl*Oi`sYhV5g}s;+W#X(JFN%uNJ02i@&d7Xjb=G zJNClFxldpH38pYo2C%Xg7%ZLQ4}RSqDZHsY@$}^D@;4u&IZOUkw+8tJNXdzW^s(xxf@21T>19*_M}QS>?B?y6-RjD&Yc|-=Z^_FP3X`CbMewn5^sT2d@LNQm|R-0_vuhvWi!9=E3M%F3{euZvfZa zlmV6)62RsIVAH9=8O?Zbj#EW*2RYmQr{FnEws;2Q$79*dcD(YJzCHmB%0yWTQ(WWm zjncWgCkuG*B->!W`h<00m?L4&4-#ffIFnfw##T;~x+9E!Br@7!CU`A=L*$l2ksLLn zm{mF|aQB!l{7nXELV(~5ZeRszA@w-C*I2^8%)1@VCF`DIkDL*h?px4JgwOTIf=DUY zrSc)7wtR#ft>;=Vu}8tEXKqKdkEjh*m6HD{r5YoI_F%e=!c>VY;OPpD+2n>DKFEQL zHHNAI{-EIBj7XLunM)EmsyGF1GSJIF8qtl+9>irAn(WpjQh*8S0Hq`RaClY<5iNTZ zEe}WAbOQ#ZoY*)TSrx7FS;y%%y1}=vzJmrNf$gS?9$e0F?N%JyDJn1(f1%nB^lj|) zt$Rwj;iLLc?F#%kpu~Ts4wY42Hdp71mUmgbF`^4z)EW2$OtX!wig6+YYOuS1noO*g zoYR3@5YxZM!NV%a$;3Wh{BUU^IsI&YN6mHlB*&`sA>n_Vj;b#Yf3b6a1Jc~VAOe{Q zMOFwmVL%+l=DcWDlXl7?hR-n{qo?2hH}y|6LVh=0Xgb9=vP;ra;)_5hEYq}56>+3U ze7CGwat^J^!fC9Dp@^j1pGhgxKzqx~b8J>=v?>&yp28rhJHhku)dXSBa~K_x=XxnPCM=~CdFwYyN8_BK3nYHZTt3=&>z8`?Z-E!=u-Vn zjRoM`;}8|zvpyJg{dt?xtz_feQT*s?wZ!onVSp?q-Z5i_sy8uFfBfA*;^%>B_UJDX zJ1XMtvb25Gf}YV2Iyy4tf{^yjhxy_SJm=K$(Vy>o`ACH z$x^G#GbrGxuCyC}(hJwS+Z;1qW?!Gpe)6u?y_DwI7)PriDSiB#dBY`3MyZm3=(=*g zRA3rD(b&#Ycc~lqifFEw@yzFA-;O>H z3UKCic;!5$5mLywO=N6m|LT2rYJl?^Nigd&h>R)SzHYhZFwKJZz7K` zhviERIWV3cxX&18ik~W%+(~zszK?7}Jc$#-2pRC_ONpr7%d9aSEcE+W1OOIxCv^=0 zw{f>Evj~la*NKp1xcf!xOw?GX=pjI{QSTkq2j>pJFJ4jo6OSt2=cPN`4|_NH7PlUF z#J_x+MOfc|bdIId0E3s3!o76*rDS2==mv+b8Z-3~AblTf?p&?xebLtyE@-P*%yZYv zjtEnKG@b8jRENAOPHAbInh`20gmSXPwX;2jvYcaoF{;X?%D?cvi>u1{HQv2z`zfmG zCme7vPS`lR{wFJKKm0nA!h8Zb z?rnI=n^ch5@wG{CMaKWl1Ga}i1NVWIPOX(kEy}IPX7=dqb)6K`X12I44`Gxngs=@> zk?oFC|N6Y|(W5D4(_#mBK5(0EzAh`QIsSyz=B_L8yx1c+%DMD{#jNUoSynHC=OE|0 zoL82N$9fc!Z`WTBvRueY8-}D}5XY6iHk=K#JN_sI%5u@`oW^^L>YhDz{*TM2tP=Gy zQPAcMN!3oDUd-TSjVm?8hmV;8mznwfruLzXCqG~nsrN(`i^Yc#kZ9AJGxm(?$Ni6+ zg}5JqaA56|YK_tA96dU;9ng|*=MX~o+m^m}o4c-;aR4}s+Ltx1EENOZzqJ+^uWj== z)7y6c;Qf%=ryjMk3mHswks!vL18h!b>)gbTSnWJ$(5q};ZFL&1<&ALAYceel)+?=@ zo(){hc3niDrM20Eo~XJ9_aUWO9_f>+z2K_z&sDREJ#F zI|59%F+aq0(8!N*>GjP3{qlT-FjASVp8VP}U;0>n2W>`Eg)un9s4IEKV|+X@{c+EB z=_E=uSGFLOk|zDb`f0u@iS5O*EkrR$1_2Mc#OP-z!--woY4sq$uA2-M&gT~$&OvuSh>V6kgeb&W1`9I& zt}HTs%}BiSgUvuEr6vuZ5Efz9 z9}(*(ZXbS^()F@1{3+(;Dr%d=+Vc`~PB!o}Jh9Am7EUjWg9n&Dn7H!A>jkx|p^Y&8 z>R!Q@8efHaDjk>_TFdqBu`FsDQA<23;M6MMkOJh5%m4-9K-KjK_rA)JV}Z_(4nJQ; zyJZS~%t?=%hOpQ+Ewf{<9)9W=s@u78!;~t{nVvnI^L6LEh8rzKeMMs?cjf6%kGl2e z;L&O017(PxkCHuHz8YipKHqdXKuDS!%}%XX*L*zQ4a(pgH`?~QT7{7{?v`nt_jArS zWdQwX;6KMs*xekw-8sbaq+F}>C%CiYx{dCL1`^*}VwOtD!NOH(Blr1d^^-AQkn9SIkg6A76e z2&qGEq?u==_)gz<;LdsM2wrZOKSq6C)SDAs43r`&!lbBx7`voT)3@Tf0g;}s*S+~vycTap6P?*(+t#yiCH*hF$)J`%f zFT5ZHd5HDPfVw-(2n)nyftCt%x`X!hvZOR&a!>H>k2|qaB5G*?SIUJs2&o-DnH~ID z?nhiJ0MY2sP~Cm+lhvssnQmPuiAY%eSPEgEGkW@~)CyI4In8+Yh)8IrcRfr3;pGdfAQ&os92uNUu*k-UGRT&pzD;Om60`Wcz_T*OU&*a0;m#G6&Q3&a?Y?@-SnkmV&bzz23?^s=beXD%FV&) z67it&hd%4cpKqM~RTj6bYZt*vvkgl)%}$#gi`&z|{Mugm>lb7&m0$sY{d>4qp^*#%NT^gRh zkRz$3@@#L;Wb2DhZ-xX<{O>J~T8$i+VC6zxb`sP8Z&|hy7`XII3#?bcs*Oq-Fp~BQ zd+$qKct$Ye-PqpwKGV*`8}JGodVe&6!pZvP;3is1(oF%p+ z&}Vs!V|%48Q(AsirWf}Hjo;smsB*7;sq${q;?0m^e-mD*_im7kY4pHVA;YlaH8=^# z({b(%UnzO%KJobKmLM^dN=I(w#q;Mrj64xQe7zO>m0fdc5Voh#naGytclb_7Q#j2X zJSVh%&Xn}+qhZNw%!?y`oY#2$Tm^Ufs1K7OL%;Q(g}MJ+vSFp{&+Ici(#AEbZikFH z3*=r>^*Pi=5-#0EcEOQnHXJjQl7T#xg0NCe*mgcY$W~;a(b9V3R&r;j8CK?sw`Q%| zNFD*HGCK4e2dpoNzCU$?>2m7z!AW+yFJcOhf1LeUYWESpX`{$X`Qh#@ts2?G?;-3h z?kz1R{>kpWQR=dVazx6l!`_ZbC;63K?t6GSooGvT z^#7oee(gq)ufZDYS*zP=75BP^ z1CN{@4#wIl7u#4#pL)DHaP%y~wK31GbEfmT#noM`b@YUv!`Jg#v5PYHFu4U+3-fJ+ z1K>Z7;mx>@sSi%@XnS-J^)fr&@WYi~!-Nm<-#?$kCaU%Uvh=w{LAm-XxHx8l0L)*6 zLS(V%P{=a1Izo4SNcq`iYX`+GGb3S@kMo zMRrK`tZz;ZT8EyUqHkC3r1|u=yU&~}n)$nkiFUVKdf0eWw9IzO&Yf}@yO2}* zPBO5)@q!WLyd}l=Xhw1B>Iq5O)25)7cgd^hTSoysQc}Um`(?MZlA~cY+>(NttHfBl|eG}ympi&lhq9?ykZFEf)zvjmNxFjiT z@bSsf6MF3Jxf53ejxO-gvWtE{u|u3ABYP*V>bQR zfBYE0RR+j@A4|i7qzA0(YD7YaIJF*%eXPx$6F;kw7iqm#P7-1t%hqk>PY*%b#RP*K zn4*ii4&xe=)oDYZ(2?5+t(@Ot=^;uoo{W7sZY)(R&QjMc-0nbFo0y={nivo9{sfXy z(y>_Q!UOd3qp@8axkj~UR1w~dmR`;JtYFW{!f$U}>w5Ur#S93UtJ)Mbsc_7b_VBNl z=KU50Mcj?fdgnkizhL0hKgS%ce|QXwAtdo~kpqcz82z@zBjb+c@ZlZiOB| z*$Z&&4~4J5G|tQ2XyEl&ORM5zL>o-t zxV2hRt;3=V>;h(}QG1wD_6oY_$Pf5C6%scPGl(H}$6wyw&8Q-jixTk_ps329LTF(y-7gqS9yd3{Pk5*09{mC(WcgTUy_Im4!<2HJa zxhck@k^B~wuT1CkZeEfYf4cYeqy^vOoBF%w$$y9KO@Fn2jGflM>0Afs1jv5e`9^qo z>Gmt>pG6-Jq+dxz?E42Q;Xam>E=$3G4%S*~bC$+7S}V|>SOw@y>(H(2`X{{KnAyfY zfLd$Pwb3DaX`3*6gM$1o7Zxv@1@vIkeESeBG5flBMd1b(xGdZTtn=ivg3^ORmZO`} z9Gp)tUH}$GhPs}5=Wt`WbF#kTvs7GUQ~7*{_`wfnlq`4Hvgx3}be$~1X}LPT_nZ1{ zz;eF0aX~?w4N-BSOZn1tHaKp7#ZZ1lQ@nFg;aEsWca2-Ul0zEx{K9ZhY|5psIk~Tw zf0s`dHTVq1xvI|NQq6-)2wl^u_luOYmRuYR2fM7LI&AN}2o*||lWjOtp(Gt06kY{_D?cYthdP8>#u4iu_Syq^^U)ycoaHdJ<~+zx!nS60r=lDiTbx!&j26 z8J70`(^_d8O}FyXs40Y5OrHv#3My8K1H>8yI^QBE^SLu6Mcxk?KFycVuD6XCV+>f> zj2(i;FKx4j z*~iM_3k73VBdp|wW_(GQ4)&}UEyBC_7R~30NvME|!wpF1XyGxAo8I+}kgua_(=QjB zE>_IA!py`37x3VAY1N3aYGrhAC>hi`^dU5PrsjlOhEj?hPleD8%v_wX50vH%?CmO@ z+UnxNM7)ZjPvDpoo)Q8geU?4+=1HDJw+-oI}DhD(c~QBBs-={bbNAZB9;(zIkJ(P{a{sf9U8K4b99B>w^D+q+7{a&+O23{NG)Sx9@>u zn9+OwlvQjqr9aq$d3C)s8*4208XA=ItVlX?=Fs+Hxt=HOT~j_*rf2g%~C%_5RUWP4XGO%C}ZMkC;|!R(F@@+(BGU`LH-Z)QT}TNg68)s2(CjU zCmVZ4Wp74s;d^i{`*v;8DlYxOK6$Zra7lVn>>8`uGHz3vqh8h2Z4uW9on|E9AK{U* z^`^q%x;y1S@!%4C14Q=ekk|TJ`}m|^F@<%KqsR*@QP3-Sl;>}8ZEC`D@v~`IaX%+< zJ`}p*cYusAAE{VMj;#2=Q;={wV6v90F2gKncIG{f9^KW7HpkG)tbaM*roOMxkL zq$=@*_$8!2C}!XekI5YyV}0#`(?otP$2@R&iXV5`kCq>Yx$zO7(R zXWsP~WFORk&iyMH`3GPhfKRy@ZY>WTxN_?4 zBbB(^mwpqKG%5|9611xNjVi4)@ymBI->6jBJ$Qh9X)j2nvYAS{orsT1obfdArIE1I zZtkL3VyXI9D@l0}$u|*fa?YkPuWAXxsWhCUK_@(ON)&#TnU2(#7|;)w_F`c-8ognR zl^8fLp1{Dbj}A@np*hBpt~#@_;6_q?KV(0K>WP;FC0}irMOC{74QP&(S!?S7UEgvq z8Mqy9aEF)x6U$ZQH`TfTURwIV@PvX>uvbtbn>ERiOPB|n#P9yFEUjB1yEUHHJ_QQNKjVllv|eXcxbM;$5yKX^z({&VcyzYqJbn7>EZlPu!ixO|5A> zku(X{6~KX%J2;xBICMf8d~f*IDNBWg>g$?fC8$e=*4KOq+kvXXBK-aH>_)d-2#XCH z7%%e(@ntzxsgK)hgbEYv%JVi^sbn zh=lmH-mM5t5NxFA{OE{|8q;v*cG%*QVbX}*sh<59Ro^++7Gy!O$i)T6L;3Y3xKZC= zCgjBx+|g8X(p9C@tv~`8HlR0|2#9}h)zv>S*E`L8u3*H(<`C8JQkT-d=;5+heUF#w zahRxz{bXoOvYSma^p)xTI$S`&gZXKx)+eqdiK71O~|m_BA#U1(;NA*KJ4iFueN;_j@`qXW)fVmv%(_yw#e zO5FTUE%B2Z;=fEIKz+ugi8neeg2X-t%`+xoB9RjSv=PjQp&z_f9nRX!^}+l8$V?qR zAu^nRg=3n-bD6IQj^5vxQnHU%8X+QX@MX$QIF5}!n}a1G5qnCy>bO3K?gx_1_RP|| zuKs9OOYDMl!h&#W6&&xjJ~*^FShPJb6!iJ$(_Sk=L$UuSjrYY0-B|0~2HP7vrNUz@ z06psps&zDv?Y@k=BI`Eul3Kbe5G&xm$8V(psHvwSP8R>o57gl}UWpjG8&j_GfAzpX zKG(AXrS%EeQNWr1nRStMHfX?mw@=x=qA|T+{tVvT zZ2#@!cv%_MA8$SN4q)wx9}M^Eax)Ig4EYjoKCI0|nVV)Gbh94LC&c?(Vyj;xvmUC+#s+%@Y+l&pNGgC$E>8VstN0t(aL7)}EG zalljyOL$Tz@-vdnP`~1`PZwk<=L49TyBf^h+B}yXP_< zUpp@U3P^X(w>i(?awrBH`Y}1z*m2fu5W#rcxnrKTBVdu=5OcDj`mvr)kpFZVMtL;; zLP4;$F%c^53b&Lf*wrf1V{l`Fkffa}A>ow-FSs@F{Qyvq zUKu1hu#yyG&XN`&{A*l`j^#(NJQHA1Bn;fidO#E~(LDi~>Smdujc)nn##qhVw%l(g}=H)Y)Pp zgK;ks`X+>xZSO|Ap2K721;}nR7|n^R9GoPjItL1SjA@A9f*5gX4pUz4baXt9RIr0m z5-$G+#(;9y(8hOb^||Y)dJ915S=|dF&2hEE!JHRDMHFu?=>CO1i(a()BvIKXegHbg ziqsO7sC|Y)xVU>>8B;Lbh|HUb8C729=}orUH$))MGj|$N(|xD(j-ZKHA8}V`rbB8X zEX{YZ!|1rkb*R{6J;DuiqPXDGuWGQK?u8EW5@qr%q;5S`=cdQ2TK|x& zS#`L19Yy?6QjjJyb)7~?;`_kjVl=2+iFFVjIbBw}Hgi5Vjb|I`93(ht>tmUaanCKl zWJ@^A#Uihv;LHKl&!N%I@Qo@IrCVrnW;n#+*Z?0cfIRPsh?O zjp;F059!ASHxo6np@xb{?F?AG%A}Hr+mCf`JcxPHxn#>^R2$+$X5%c=6>$ZIjs^x^ zcj-B~ro}Q+$uy$(w-=%O>>|6hYDX#8FhKVaCuNwr%6NMMe4FyfPUKjZS)M3B*n1%1n17Ohk?t2q!I3K$mA-`n@>qd~i8ArRY{&+6Cyk>G?iG51iYpk|HL+U$ zwzHpw{{T1GI_fPc2G*ktvrii?qK8=xu3C=`PkhUgV|B0hbe{a1CN?q*cWC_g3H865{stejzRWw5Zcd-_{HY7vcM4=2 z_-7_&vYrpR0KZNXezk*4rlQbA&!qnuZfuT%erB*29UDF6QUaWC0+|Qsh?nUlbQ^|mZ zfoozimgYKnmu~;qc%7e9(v+D99UW_2=3j^2Fe}`nK}n8 zhf2ByEBj03tO~$5O2>~<`BbAnZ;Mvo`o3FA)$#)v8k^f#<(hqAeko5PHI76<%&>;H z?S=hp3Y}e{uOHv+uwj4FeJ%?7RF5J5VOFctzFBS1zOn>YGxxC(oYCb98}zAizPy8@ zOg;`ii@&4Sp`piRF+&rsuuxacQ<+esGZXKaHG8MnMK%%AA_@T8{9g_D4Fw)3a-8p` zawICs3ZP1EM8>pT~%Ib$~@Ou2s4=ORI0hFWdZfi)5mZIZMeOLR9-oI-yU- zv@CvXThtQNw9a2U5W8JdYjNASp~_IuKi3pnFT~JO`^V=|v*hvCl!zevm-{kA{VHdV znZ`%Ut*>li9<$N@7-GoG2pJ&#sk$A7;Wc+L4=1CrU4US-vXEIK4J+2$&SsqORjo@k zB6)Vf-rC)UNthBC(ySekCFg%0@7}cz;+!NIJeljf8uSL;)7{Oa;h6}Xw)l0C{O8A; zOKKaB7qkxv2b)w=@$EJ0;XY{bWg1F=0tL;bD_=?#kDL;h5$O5F55?fAWEI(S=nbOmecrOc_V%a!>`6lQlzgbTygr z3PIN@z{O&{PU3+{t*$aZl{1h0FjHK@gKr6r2)5yt`G*F3^s*avmm6gf^bxGJ2$zs3YB;ZS25AAzw zG6<({8~yO;9M@={egz6FY`Jb*F+`f>_FuJ{b;5v5eeGHXpO&{|1+?;0o( zFYZwJ zgTr?)y+sTJT16|Z?#^}^%UPa>NZ>UVKgyp@m>;qdnar0djg--pV@4B z+d$RQ5XC<<#(+qcfbo116~+RY`CP>>4NG_{&7?8qt`c4V7Ws}#VSa*7hR5ccaND>K zVBloA-*lM8uNiNPQvUpgA61tHXvxPd-1!ob1A&)kjm?VzDaI#6tkQ3TnV4;|bZZVd6NVKwKjp5f271Lts=8R>&+ zW~G-Gl#nMhrpvR1#r zA3q*62tt2uNg?g|^!0P;A@@XYGL|iuKtOe8Te_!JLYwupsLrwpolr;9r{hUa zT9!U|G$a)nex>kbw9SVP7z^8%GjerwB(P+LKxCjEusyv6oyB|X^r)Om9e3#qxN#st zz#a{MV!*F5lW;jjBmp5-Y$`4|FupW@v#f7pY&QKOSCBZk9E#DOf_T8f3!YWTn;K;} z80N-}$cxCQ8$zx;Oz|z3hVY*ZKfdD49w|7$)UwS4>#LzT)99#4YGXxw79>Z3mug?GV=9wIsN#u4o z!W7e%V$HUG)a1n{0k)g1f=N0f^oc zO|F{g8CT;-vEh9^kkpsV7e=s=q{h#?=tD3ky{fzSUrlQL26k5^|`kd#Nf(4TdwddOFzoZIGPb!Tk{UQY6u z3H)=C>2!rP6UBH!>mFN~bHyzxy92TAm5RnB;hX~^>VE8ce4m;4E6jIg6e~*CWY5c^ zlf|8R{AitlLjdWgh+7fvotxFT^xR zo}Wdzne=bfN`=8>RIvZ?G;P!rOAmUWX-{lX1mvH7qo>mABpQu6+0dQm$gmr{7D2o*NRdm`Ns0=h1(j)E4c-225uA6xE zy}sYn`}}l;Cx*&ZOXTYD^Tyug$|^|KHL=N_7-b= zEWVh0iGW^qm`+j#@padh`s<0P+>qV0=%<_0o_Fc-7u?L~0wI7tF80t?m?d|EQkHZ! z&xHh5imsqjilfup5F|yha96b6U1c`?%6ywbR#SLt=9RtrE86;bM{N#rrEx$=*N-JcpA1W{t}%XQ zl>BqHXn*h&x2k=lU%c2d?Rvhmt8QwuxwON?-xYx#sPmBbW>7x=k{GzqQiZd{Lr-;) z#C5Mk2K2i$Ir(W;dM|R40<9(9c9oT>gDgKB^0PIrwvK-`z4k8k3x7JZ`FM&6#vi?z zxNMmd5S-Js3gdbsDQ>ySmm0rEKXnf}O_Yt5)o65Qf!Hwu>NK;Zl;V6eVzLFlRKsek zLivy6)YigTbua9JK#D%{tXM&8(1@zoXjlBdk2?&uK`Md?WL)(r8mwxsI)S99{mGMa zNoc|M1E=s~zI!&YlQuAfI%rtn00bKL0Eo~)jx6UtQlgl*Gyo~!oEnol&%!+@L#D2Z zn7c43jqy_Zb!y5NYBT2_LrQqyo*x4)GH|w z{dzgOic_{%TeDY)j;y_8=i#@`kjktiT^9J>KgaALPKs1-+J-v*sbbw#eN&Pmka{HA zNYbRxyUJP)KW1vENcLrIJ^x?G!l_xY!gE6*=tjERbOSU#1hhYz3)7nVB_C>`7Z1 zWVs%EaHd!=e;Akr4=pV?yEPnpU#%wb2>;w`fFTckgvdgj`#c!mIji<{vWy?V=`J@D zD>t*>KGT(EUOZ7UlCNjD?QOVTvFkTats?Rweb}nwcd)p5KcJ8;`mr3KJcP-^Xi#oVM|cRtAYkyf=y=YW(~AA1tq-MEpz z&_n*CMP1|Sb1kT&LJJ>NPk`0BzVMrkGVc!DpZmz9cQQpG|iE=bjCdms}pbA&5{v?O6lf_g>mg?TmIAu@3nm-F3ylJVs&h}cDlM3Vx{iz4?jg4c_r*Dtm zk4pCC1K4~8=tIW1Vkl6-E0D2l-C58F@AykbbLdSmu*+)UID1ks-0r`JN8a z>*cq#=KNif=~Xhzg)D6(p&PzB^xC>VL!VyhDM(K__R4QV<*pa`^W5o=aH7fuawwXU z7!8BgEVus78fO8(f|EB#$2d<XrOr-~8gt0LC`cIQkq7Hx{CcNBj*7|Oc&1`&qp(Xwfx8|Xf zICN{wcTsT}D|C*=VqMd?)3?H1gfjrCv0{bzIRxJyUVR=hs;-Z~0XF(bx%EWWGy5Q0 znqYI+y4Ewr>jrkNN2}(JsxAdtfBjWYs|=kNZq5lrvZy$V=esC|WU$Vv*4Jc{?JMr~ zn~>LX1U7PhFS5yFqjUilIgV-vdY#M)$kqb0w>8@M)tWz4D81FG99W9l8<>rqU?4y?qFhaWc%B2I9mWK&)6EGKS{+*Ag^I zg`%r=yIG1Ay7p*^e1DK|UJGod?=!)4P924xF{xeM1AQ?$d){xVVOM!Oev6`e`8OZ4 z?MQwi`h%qRgYp$?8xD+U&xtz{K_YS@A88z7(N9c(AG& zWPY$=PR|s2q8t8Q^bY_|z_NPuLSre=!(ASbvCkKPkFFsy+>njUpEA{Z*aRHdN{iR> zMkdzwop&XgYbTaZ@n@2>{t|^M83Ju1sq=Q)O)jh>M%Zy8P}<_mgxC7Elli<_Qtf6{ zhxmJ2PSa(u>?s5MBJ@)v@?PPd%efhwCR+u#+4uaOE>QBz~L{4RBe3lbpK19j8Iy2K=62K4-Y>$LPTlt74n(kw4HBbz&l$II5lp}$orz4=lQB4VOYRBQ?9#e*xu&W z5W1Sdf-+(lYZ->u=09;DKL;@qs0)mX3*ciO=@F)wha9#&h5u%)F0E@Yc_5k5nj~?p z6b7HLNCFmEp%EIdN?0NdF+FUDdQ`QKDB*K~0*zYmJxZ@<6?}{aWvGRx-hxHi#s8Rk zRiCw7F5X3zjPq3y%L_eH&$7Z&Id*BEz6X9D>btk!2kgQ+Au8lr*5p$LJ9m7{Z4<{r3WvLc}I8Fb#2OZ1fc*(3y8( zT?s_KgqzUQcL&KejBGbDM~#zrKQ-sKMgB93wxyZgYdh;Hc-jeV#VDF=u_F`Q`YyBHsF}*22JL zZnkN+Kf~(D_xZ!W4dL0ISwg#F2MRL`HI`}p?$5Qf#CkKi1#-n+FLuZ{JTN_26?l14 z+{+ro-(~PsCSz2|)vBucbeOHNX}*cBq{MP_!OpFP@epJxC_6h_cWtO(V*ttwyCErE zx7`+P-~}(EhtVl|o?$$5kFRSS<6j&lfuf`6Oe>-phcGARa0lwre;`GaMlj2Dp}VAM z%0KYTNhy{NBkCq$-=xyuNTRUx2UQT+I^OI9`B#o4C)V0M38V`k-GWR=!2}nsQOK!l zCcko6>Dunqm)>z9J;K(0pmQsMZ9=+H&%&#l&bI zx48=YLP6TdzPN~m4tW!C;XMGA#DI5*=T9L5)yP#LU1zm46jCO1ah^#AE$YXWDqpE&ZEGO_s`X z_>4u^wrSY;7bh&WEOF@VUvD%(KsX0d>g2n5{2OD(c#xF#F=z6JhLOkNFGr*+2EhV^ z4|+5MLhG=E|N6n8;yqy--H%Bpxn^)bRhuwK^g)jDyS?CbBXBX0!9<0{$JX-vsH(7q z=GQ!S<-13%woTf<3lM&7QDU!I1&nPBi^&A6G8E5NL46?#aV{o9_xM8A`69#wC>F4Jc1II!NG^}q=JeJG7{q-*RW_0P|uQxB3 zp~pGur>*Z?Q4@?OXCYNt0J9psyd{{#7?w5w?^FtF*aoFhoJOn(FN34E4Ca>>(;46| z*%tc^q%XeJb?K}S{s-392sO#|a3F87__0dPEa_;4)~*Vi?av5gPbVfw$bY4a`z=|Q z%Ksr2NR|*J?aW-1tMj0c5j?n*OywLRlsU>s2+&8Ua?Morrws}Krx>C4yiCtRr?M1j z_4=;6n<=kQvW%#0A^b^RVO;TsD8;&*_Y)*@*k&ht!wJhA6P&C(LKPa8zV?|$OZ4Pc zmQ6hx{cLU5^!^7yjoNsx>Gom9wSiTRb`uNnYls(4UNBiG26%X?D<&A78aAR zr)1=*Op~Tm@uu1{V3l`}&qeKE78NC2^Jfj(8Qt9mSN$uI&0fljzAyil(M7!3>Kp^= zsXa6BCrjnlAXG~QVQkc-qOxs`^Wl}Q%quj6-?L{iRqj9)=0#>+91sK3ozm^|=JU&o z`%SLo55jU*vUH7sD(-)D#nI$U)M>I%F6N!Xj+4<-B}IU0pzB(vZAu)o>CM}R4K6#_ zS@skC%yjsy&eRJ0dlX0qk>A(bwwR_8|vaO*F9GqtJH`4;0E zWQ5>rnNk=IGt0iWU2&-!t0A@y5AU|?T1ga`;8-Oxs|R2GkP(Cv47DNw3KAKpC9I4t zWTGH}Y0J8hQExw?aEu`xLt?Rrmz2mITQw6f&bo6;S+KH}aO1r(1cnZ=#bn9y8Vle= zAwULW*OeQ=GYxC}OiqY$G~i|Mh8jpAG7&gMXp61!o_VL*tibF6s8tX)`siS)b~uT(^3PO?Q#JwDw^yS zD@niqGB7RiwrsLal>gdL`nDI>XT_(l}ZHpevvcEMM2J|77g9l8F0ronWe$HAv#PgVnVQ~uAf0Ee91vF#+hOrf4~d)hAVb*o%stF z^Xq#2S00hRDCMF^VoYp&-7*1U4`%44OXZ*W`^!S_nUr{y%Xl)mR=R~3JlfVE2%tW%1 zme+=>{PWK5iSG3j+}8rjj-mcmQsN&1m!IOZ+g0)Rwp0kgQh-KA)5rP4nO8r2@M*kR z*pnDfgUWv}FtP9Zc=c1M)M#*I4ErRv`@38I^_mE)LeR)bGlf&`o~}3n`;}co4;j|z z2RMFdP!V6*c-feQ-E*ktcU^%B@dIJ=7JnnlRz5U37F^%f+tbpr{8V$(HQ|Ry7OzoF zCR=W~SZ5~*YJBADGFEX^7V0QPr;8oFjAkABYklypcQ1lk`bui!FXBu^Ha2EldC$P< zRkQku63pG`oI0|r{88ZG3Sp#SbNT2 znsyhFVX#KeawT4|9(Q*dtLNSI7 z8oR{+`w~@RbczXzhtPu=i;!1N<^zMpZfDo1Cv$;IS^zX6hf*%DV=!X`k)xeYkkTu$ zyBXwz^hoQw6T;8$SU^{Is=Yb1fKPO1FUpn5>*w*=XT3+YHDy*e@eS_U6!g9#-|8vp zo)3g0!ND+fpEjY~j*ymkwstNxTRhWLwYJo+bl`@gE?UpokZYmMKjo?GJC6k9`lU6J zH*y;;PjUXW0_TushvCEN`{ocd=D`mJD**fBK_n~k>V$L_3mPOC6;Wp3{4T@{T!pqG zipZkHn8N)8tI+eUp^IRyS%e<2SnVauD(n-t+0&?7Aa|9gBiBCNi;cOW z&5Hrp5}~%+Q4Z5?XEh#6O+4_rP7QO^=DO$S2%8w zK}vX;yf&WK3r5NS1`Bd<5i@u5+u%lo5a%;F>jO6Oep;Ovti(yK*ZZzd8@%5W7{ln4 zvJr|{K?%CYsq|}8hT)n{juiU^jpXj{xY57ynM_KBPxCq%X%yU zv(FX%5)wY4>&q^d#V!V7&&M!>#pd0W_+Tg|Rkr0-OEZ`PZ-S+Gm~!-JyjpQ2zSBwM zyTyBWF)BY&S}b9%EkR>VeoPE1dT{G*F5-#@e2z~7@dGiED}zW=z4+GcUOmE`%xZ3kx&5&Gw*)7YW1}*zvLyS)8{>llr^BLZ6eBD@xt)}Ra%ac=( zpS|Ym@x?C(6mLsn;Hjd=z2V>*3|qUeh%{`C0w|BhRGO3_z^EMcQdta?`;c=7=RElI zl3cx$8Yt{b*R6LRfJ@chd{bo;WU-JIu&S{hw|kwWtrKtWtq2(?g2pI{q649{Tpe5* z7mxb;3jjoPqKbQkH9_@qD}260zdr;q9}UgKfV}ouZ|d`|`qR1`O-8JEL?P-*Fm1L1r zz3zcg7}wh(34|^KRgAVMyuWgekEqzb512x-?}>4|A(YUu5k^v&^=bA$x|oDQB=kGy zES7N|hiR1nF%ZPG9F^4LExY~1S_okj$w>UqYsWqhl~RBeM+LE-d`Ss`kw@yGd6c1G zM%gnAR7%Gy6q|h)ydqIBr}pkdFH29MTobCx`#GVKaM$x5REP@MLP9@FK_AVhcXn0F zyA-`v6XmK^nCWGd@W_!qR_Oc;*A9L1-WGr@EzT?{?hn_25nZMM5GXUd{cXjEG9F&2#nm4D`D2 z`9{~TtSWzuoQ{pdd>4e#7fV0(f_|++&q#vP`wXQ>j`0prn6$_n7{^Hns5=+)CxqRc zmHFup=!vKA+CG#!fHauV145C+;iVJqbfs1IQfDnT>1y?puCqKd8-w}HiQ%xpN?K$9YstCyJ3Kl)+`Ay34tW|cR5?l zi#Pfapfe2ZXD|J^rFx;?`g-|r{aGfV{TscRc0u}5zr5LRcBLwPzOS-j7#qxs+UFG8 zf7-H)JB!mXId*=T=M2-3l7HwiAIuo2aUpCqkX(_+>A>Ml13O{>u}Aer(p?u%Lh@Bj z;5^*!ENtb$j$LARxo!zLrge~OW_#|)L`~E>h|YdA9locJWUUd!V>n6~hgtR6c>`Mi*+t-khq_a9o^xk2Oo{l? z0G!sAHzTp?jUr+~mljEWaIPm*p7&ijwE_D(Mr6V&s(wEEB9A&?wdw&4=} zq}%$Mchv3yK;9H?_iL{$m3fT5P^NXO=Y|@tzE!W;QcnbOgo8NdiH&zwm~OYGO8%@w z$P0GbT<`wr*_`M3)0tyvzxPvlvhwc~%V;~+!bg2Y>I2>U2JW6ZxcZcL{omhLF-d_h zo#Spi5X>F8*H6~!iiC>cq1P~sqx$hLTc$?sOyzc7{_NOP??dPQ$X3^LrHz0Xu{xk$ z^NqLcW5(gAfAklFW)BA0*h9y|iO8N%dC+_)l1A$0jYWp`CUlssTQuo1wQt`lNi}G)#O&uqMcS@b4!@SaC+5ka=3TUf=h;xbu=D((& zOeneZ&C=NwtpQIS_@ZGPd}YkSGnh31`$>OE1_z|PM__aQQCi?sssxLf&>QPRYxv{FD-LaB$N0+?8mZ*B7&`D<69# z&xo>y2&TbJon9BZ=$n0?`-tIw60ELrCAIHYuk2@aH~YR=k~<3N!DbD)UiQ^Ps9|=v zUX`1>lLSP4W)3@My#=hio3Bxo(l?MYK349bCL#4B2&#h(__kpRN%0naWwT9mYN3rmARjTp`O)U{O3#9>PG%KPef`f>AZgK;#s$Nr}c~av~yK-JYGD zaetYAnzFxk>kdsP`O{^X-ft%GeePoisxv7lsX4-qhs(MP0qzyIN21^V7g9-rWC9pt zHvc14+U#G1oR8|$p4C6e4pU$FH=?QU^=8)f@bM0hAz5E#0|4QZ36Q9I#`IYGpiC5>x1+JI&ZWN!$*V zdGP+Q?~6j$$sr__%MjDgU{$pDhhUsFEZ3+0x6yUk z%B4wNf%B=s6!)=zK55R(i~DKpOouRiP7`sP!8qdqE?jck#VF5s$j&_SoA8dtNPt+Q zFU9-I^7j4Q9p#$(g4CVQn7vjt${ItNDna#CNgRro;PtBeYYt&p0r@=3XopqAJ#n#6x_a8^WK?Ioaw3-1xI1sXt>bwi z^s%)0-(H?^LUN-?a&T6aX}R=FvBi>%lA{xG5kqBvMMzUi_%muYKZMY)7!qDGo_)Ws z9?!U|07){@g%U$bEF{EL$R(@XnqvGc1t_@kZa7eDGzEphzP!VAWUY>5SvanRTdcj4~QRu~GS-PcRa{~FV4Z(=Z*)pRxfh)5dXkvKV*4lIp zUWWAK8^M?=tMBKSHsQ-(Eeo4b?Vp!qws2+!j5%~3;x5=^rCW`&UTGrnYghCp$BSv=ENf7H2|n1leAh3%Zcl4{?jrL`xa%Y})C?_7 z!e?mJ2X8Lk+klF7m0ifQu@>Q-NO>j5{p4eE**lzLN=z>R@~4%glN3foH4<2>ujK%{)v@1j_)`;u%lf-j=93%BI z>uKyj4n*zN}8ljEqMbsVZ=;!Pf;fqqvBQ@tUyY-+u)+wf%Ho^AqIlMa# zxyiB|wmP}JrUgfhjyxu6A-aYF5WL-vrQ);w**wjpE3{1QIx&$0(MXVqy>|0>iAMKE z;x!zR|MQ;M$hyNftzA^$jW(e@fe|s0-wezx$VVUCK0b9AAGNmC{ZO7bq)|^hzj-$m zjZH4ZU%;*ZqRXfizOCMn|IBpmfrrmsv8xYra!&9BM>8>wv}}Wx8&no35^i8q%0R}I zk`~3SLN2(M~>1C6TXS;yzqVwJostZg=#9yiizKZT|Mjqxz{ z3fJZEOV0_=_;BHs)R4-Wrlb#CIh+qAcUr=*{dx7VvXooB81q1g?oZ?^Gv^ET7`B^I zxTsIthC-QJm7DJ8QTVAu$z~KN?&-xl3g;HoUO6rZh_uIG8Od1aUIoG^A28rLxf3Vs zHS=1Uu4zSVb9)N@niG{`B;rYn;+HQL>}|fDD$jy$vQQ4%~yAEiNpc`%FvRP^x!kdtO$I20j#cS zg$+Vfu@6z0TFFzjWBul-$s?%xqYwJp8qo}p-pB(XQ!oFjY5shO{QXF=h?tQo-F-Zh zcbP2x6c2c0m&w?bC`aXPz$ogzDCJRBo5aW{q+``(IcX!+tus}Iaxbipp7S6@*sPOh zmfGeuF`n)sf+i&wj+j$g_}P0oUJB)8lA8RZ8#(-%`o~}NZz|WNjW=--=FifUgXub&D*Rnlai{D;FTL%-jD9UC zjag<*DGR3SnUMYBJF{niMwNe8TuTD`e!p%D%Z5;0<$hA|DuuMIaWy}FE1+ZKfTd#X zv2h3Mc*{T{QLA1|)j}18`yz0ytikg{v)eeU>P6NIYavI!jAuw8OBK|>{|uc4TN7Lu zhBr3GMsCE|$k8FqND;>9Zlq;&3y6H6C>uGN(JkF7rGPoQkrF{cDHX5)QNdsz=SQ4# zo#%a@`z8Z*BpLDsdA^rtmRHoaSXa!dOZ|R~6KyHn z5MV|yBYPMZ-AUKW9=KB%sY*fD>wCdNox9@az|l!YC-APY;GB3#wZPpJ{?93=yY;br z(L$lCpMG+U*AS_LxX+tJTH&h;(0;|!RJ>8@a{y7sHF*_0mzPS{&P^!z z4s5MA<)UjLA1LSWkj>4XYkHpFilnND=TP&OZ69p$qi4?L8IJ)4-nl(`^E!@hq}%F= z!v~2@JCgV37{7N9yXfO4&O_OYE#rrnE124g>WQYiiRTJhxR3Ud~?N513in;h?r!!Nr{mCao)ue~({e#c>pcv=zM<%(#(4m{+~B;9~dZQK)NkLF9s)eQ`L_=)|&wKj{a3MCn?Sr3}z!>}Zq zQ>i-%2bWei#~DDge)D8bG*nd`1VM=uT9@Gz^+o2@%LV|exkO|26j6=t(%n)YK!TGb z$GxQfq>@MFnfarV*lwqLUyo9tanA)b8RZ6^PwwUQ^xrGRrD*ShlwG-ICvK^`W=u3L6RW`~oQuo(p-u01T@`|CC%cXTi#9Dp>8%*6|S0N3iX0$~T;A#`Nn_)3o zHL)nrU7K*aQvV6f5IAQP^JSW2we4BAt>OrN>WKAehM24aIs{8jqp2eRlY=@(X6uO* zpE8R4Pg2%YqN@afYWsyIK2h)|ymb%9;<9oBVgT5u|0$puJ-=S{+vzKG;sIMHtL z+DhXF1tuh(k9;h95kyv4nXGutqzhdjuW*p8q5w^ z|K8AFQ(&lX0}eXX9wlTpgXAhlCXa&iM~$szU%#B}Ok+L)N;#3_ObRCn3FmBG?drL< z^}JW?9l+eWt=$#|Gi1;ny&AQy-(CUh0Ofi*h}A;Pi>{z$WC{2~Ka1$eGtQK6Zt-UZcYn3y;}IcoC)Me*sg<4a3g77R2EsNi=+bWLx3ACOb5?{(V4 z06V8CuGtK1tM9=0myV&Nn^69ZAuiMe!-6c<+tM)VL43Fzdc2ky$9@OiR0X7Kq5uS) z`mw+l8+7;F+MSKviONLqRVR`LNJtW`A>dW!bV72Z%A;|-r$q!KzmE82| zzew(JdauaTH87z1yD^d6yhR>Y-Rp-~kb8q^P77O<>8X0Ugtn(oC!xtEjjx4DQWXU^ zRg@?iGF<;CUrN@Ww#{tLz!Sc2@;M_l@ozKT#EVJ+OzL+Twmf_S<_b(FMAE9Yo4JfV zGhX4@4RaGl(eIQ=!Z=>*mVuN?`sS)jJzT~;VZZZFThUL5Om`?GL2_zLbCRhfz0(=c z6&+)sPr1fy7pELX$&_wL>9cvSVpif+1~omr;^fgOoRQpzR95R(eY zIBWO0Ul$mib4fAHuNiM`c8aDOJ56aE)jevT+K11p^QB~4y9FtEa}%D*S&)^pY&c*; zzYbYD27AdXx!(gs1)gq^jRDaGW7agyoN;Aa%T~pS$L3F^WiF^dF&9e)GR6&VtScQG z)k`CO7|$<(hsL{*x60uGla!h*r@e6z!P5pmX*T{jAj}i6-r6Uz3Ck+?C?`L!N=u5* z1)i@;V2JdC)E)ELf^llf7&XN;lQG!oiV+U`G3ag$iTXBIN3V3BnGF7S%cc28(0x`< zbYEhNH;z zokZthWuwnqp+tgVih9lctJ9};!l8kpmjttyUpliU@HIX)e<+$FxVgqZfF;YRLZK@i zyl21l$G2ifEroA}J_ zPrIM}?NAW~#4zj@eX9!>}{I8W&h`dR!%d%TW>zU84=Ovitl3s10opJ#j zwiQ?*KAbY2%Hhu8Fo%(XRrPf_1l@?8K6~dZpnFxvZcWzaOQ~+mz21Olf3SJl2Vk`E zYY~|614>3YMKUbhpwBV0B}yZwHCU=_SR&nTG?vF0XZ53f-`*LvdH}xQoYG&NRuTXU zBZCb9Zl12$;9%C}G_%RC$&-ExV2&-qZt_OsW7JYZ401YtDpz1?G!fIqOJB|46gm?1 z#YpKgQnL38O3i|{U_>@l%hM2yE$`sfKy4frg{<+rNXX?tC)hmy(N7=z z_rx8|#6JSOT_!K8VqPfolC^VzPT9%KD>}lpSX}52ho}rupwP1Vb?`Hg;YpTo3%bYo z_$d_tnd5bOl0en49%^CEp?h9-ZYNz z$bmnQ6S8d65!C*ffSDPgr(~+5=!Zs!_F0oJDJ7bb1#S_iZxPLd>KSq&vQ_{AParY% z4pYFZB$|Sl^hWE~fvRLjwMVwLh#1ORp%RBKh`|~quXSxsoVYMsl=keY^4i7v{W~F8 zR@r1HQA^d8fpNMrL%!JT_rB^gUWWMPMjZIV)$_zj{fB;W^vAWg^T$r_hJ2CJaz6j2Oie^( znaCX_bfmnS&}hV?n?pO8aRRg*(>SUf{qSAke6SGyJN4J}b)!i>^3}PcW7G-L%G$CV zSc=yE1Wz19)(u*+lP2mrG#l3_+KO>+a<{&2NkDU6gjf&;9f!9zNo}^MA5&}I;@oj| z`M=2TS@n={dj)|=-x7do2N(W%puzp>t1g>Qa$%8ph@o+mB1!(;!cB|I@{NYB*8R?& z8^@g0HA==zR4evcAlaSf12ZqW`=Zgb&}Ypg+PtVe^)bdU>EwVg;{Dbqbct-U{M?&C z|E;j-#gA>FPDV{6AQ^4li_qR)rP1kGZ((^GE1idD#b2mQ&%MTw38o%kY<BK+9`)cq67~Jy&oe}c*6GAHL$dSV#|Ku&I-$t| z-I53LzzndKr%p;`H7+6E{_Og-qTR#i5p&fx=wDXcn7Va=M7cOpWFTFDXvS^8K-At~ zS*CXX+`kC_XVUhc541eG<~?pZFzYQk(*t*@St)8RXIESqHvijwe%~p~Ek}d;F75;< z@rsqBgFq)bA==HP1V@Tm6CV3uNX%^ix~JqOjyv&6I3Md#E^YNAP6d6!_ZR8fu=jQV zQ*?5c%CcJ?p7HY1{0qjn>QEbV->3E{qlxiqYZ(e^coWCz?t?JkAQ`~b9@Fe&nVqaO zce0pu{$dP@coML=1(uP+ERcU8;Uip%GT|o7G<|RTbShsVXJ+IXIMZ%deQTU+ba)|E zW_4KK+Idx9iKs}imts;%HayLfVXh#M=tE)NOO4_0Uj}-Y{}i5%%F!q)c1!({hw`FT z5A1tVfd1U!1vb$Gens=Ya(sIuA+JFzY0uyZCYExMvYbvilLuTT@T7<9kjVfxT-Z7VmuOE!wXi5|34(a9hrxE>lfWI{`cVfT) zSXhlZy!-eo>6Fz81q?G-{>J)m>-`l1SRkNtgtiMvK)Y}uHxv12hK~q>>?J_<{Bf<_ zEQ`wpBrn_R_fCXu6kktxVO8GtQMvpTa+%4i)ls|pygVZ{skV}$dP=+rqCWYY4xAJ- zFd)Ej3`YaN4pwL6MB z@5yZJD3N4cKZ{`th(9wtL)Rkzhq}I{zX&~)BY>y4i4tmB?3SIT+18RHR^;TF4->z@ z(_{(Zk9*uoT=(6)>&g#?cUb?4Ti_JW3htlx;|yGQ1X+uuaROe4P3%8&Q%klM`sy_o zihLbc@YGd4ys^gb+kNJ1KjnSTct%jCcacR-C##@WXpb}6a&Lzx;*KS?@%!O_&%Njw zhf?VMiO{vg%;g$)fG(Xcbi>L1gOCwwh`}P(^WBbpi3Xbf>h*4jBr-RF9<|N<@4zsJ z*|#V}e^SiA%n)+E)h6ge)lFe9tM3x7Zza^57vM`@P7h}_0&6(gJ^2i0m+MuiDIAzNpp>n*-IRXNN6(y9Xq;d&C-D)K2n+-E^=Lm8DBx+BmP)7Q#sY68e&} zDwxdO85f^DjmlOSn#=J1jBlg+XQXwiZ0HxFWmTC`0Sfp`7vB`k~T(qcPa0R4?c;DeWEC&&Ep|NV%Xu^)})#);+7CDj8$g zop>^Ah~fE{)fE!d{*29}57u7ym}664@Z?7+5lr94jT9eLzb_Sg78(bv4@KMy3xjA0d*9OPeHQLBFzCaTd&L2Jcwv@9Z+?R>Lki zUr-r?v6$tqd}RA=ja-f-V=PPyHX#cPwFY0MslbVR&7C=B`&wr63-;T@sGee?FSqgl z0FHe$%sD;7V-cIF`Afg)+56A>{tAZYa!btW#^vKQ}%M`DaT;CJ}JXl`?9(wi3R1?y*u9(dMtWJ$r1LaS;M^9Gg&jPWbnq*)?hJN|e z0Ra2c!E`E)A)_Ng`QoNYf?xip_{nre^<;qwqzN&T46j{!E|{fz7k|g?=q z@;~^*O1us2Qh`*V4ZQ8fbolcUQf!Xj`sFIZWRCXrwK~32yARV|pJf^8bhXQ(UAltb z_bj>(bgsq)<9_^o5zYQPeVA;7Ua#1lh8$+10YHd4xj-Lk3V_uIxC@35!NZApe2fMq z7W2?yW|PZW;{XmQBDuh1o;l3{SDnb5z^G`kh%1{nPj{Z?xbc$8)$!`>{LDMq5sjMs z4V4kDmn5(;m4-H^0{$kdMS$81DU;0wGVN3zCn9%%sTD!s1JKw#iuJdY!=D3#u~N2_ zXN?e@^AnGaE3fCPcc!ta)89GRu!gU*YEo{Vk|2lEbfOztmyFGg zUDhbpGB@bD6tn0IvSE~oHHwR1+=l${I(F8LK!%z2C8_xgB|BkSCR~=*tY6K6B{uji zpA6Di248Ui1=xOO-BHK$qx7Jhn{4e=6qhOPx_45hfoK%zy>SibFX_Q`MsB4NuiRCe ze#RDNNS|-%;55U}z}jQ$)qP*c<18z`HCsn6=ofmn>_P_;C!j0JHuF*nw&SHvJ0xN^ zMQ+b*=qan)%a=OLrHmyWBrV-*S`4=^na8EGBYUZ%1B_nIwZBRs#SzX!b4I6#MQ<*M zXFbDhjL%v4vduaJFDGGl01%^tm?v{ABd?*3(C9+*6wXQS4zR%p4_?KiNHp<{1Kxbxou*+q z`7$6$jhz3sfhY)mZ3AA{0Mg6Rfz)v$=p?F62o*}Lw9#h_F*Qz7CnWIu_z&24V@IR( zQA5plBwfU~*;9Nn$0X9v({+d#zEZWg>!PDIHGjtG6$)p69xmAL8~ihOm5yXhB)oME znTE6j)HPV07Tn0q^w1Trthp?XdE*+&2c!3yAvJo;J_!pWi}7f04?0+c3d9w&Y2^xib=l{a71@78QSK6WZ#HLAs%Q2FGnaRM)b0kih55hsU~vzEV0M zizq6Q9|h8wS$;>*)9}>GK#dqp6mgLBn4jb;E&)Z`sdSdZdG_K<8sx-dT``oN)|1!G zTr(<);;JLB6$_?yk~|loU~f6U)xt_MzC;oWPOBrR6b_@UqdAS%)^p^D_eLfvr%N`R zL7Ge38&AYT3|*FhGZ3H24^nn}DZQW~FHZr1e>VLmW=FuWHihW{i^-=S#QFXW`ID&UXx_`W8*DnHl@#!A>OWkl z2Ho#34q&C{JQpWA>z)7N=oHr7BP*Fn#fhJ-WiYNx8ZI^UpS-fCu>wQkL~@16l^ zE(q##3RFvvG?(Wo1N4f06IY$0zYy|i+|)C-rjVtSSW@o8Dfl=vh}0K?+BfipHSu}| z5_ezhJ7%)x;>G@gh))~K0}aSK>vYwLufAgL?YCO5m{jjAUyQJ(OEA7<;o_mxmUz;j z*nz>7S|nq+OJzW+=$TiZ(B85ceJ(#=Q||)olA=ByZdt54P?lY^5gsV+{}7fv{%L*g z!kr~3a=lo@hd=2Azl2V2a02j#uzJKW(tLpocsdE?&}H8Kad+*ag~byq7y~RixG1YhX>!KLMuqwJMyfL8tzR_ zq{l7IR3~0sB`Jha84wCvt(>Vc+%d63WM(Q`PibO>MPQ}D^twX0eR#_8b_RaWOtK9! zn5*`AP8%Ke+P>iWeDhKi^u|k(7aPV_d{VCau<(M$SIfGaoy$76`BOIpq3t$Fh4++y zL@05QmG>xum)VAD7+ySkse|pL1sX>X#`iKun84#L!F@H~*cUZQMY$NKeCQrz8iUohM{16`t~U(<3Bdd7BBe45QlY|jr{a(FD=YnTDfDNjvPS4gNqT#dvYrlvrze}oGN)p&+~x`I zIo!9$i6JNi~T>*wdceX zPflc=3wm!Z5!9ErPw#7RCG$tE#~zXKisX<&NVb(!y`&43E6 zpdONsufhHMZJCTsNG0V7gy7X3%{!wy<5Gs~c>z5T5wulsALQ9^hNww2_jlYu2OzM- z=lOMzRZ6Ec#f+vVk&n0|F?%#J{49@eVr+(PY>+?VWcjCqZ44o)mhY~d`#D*iYU&R7 zMGQ%2bpjYmfFkTd$;KqbW3)Qw$sD?}LjBjPJe4&`o=d0OD>ddY9&fQ~B|5d@ZKvQ@ z6A4ZQuNl0d?K5loo7UzP20H;fbpMU8McKSI?^t8GtheVD{|S6k7Oz=)eS=cq!Z5iK zB3FMruoOA@@RR|&I;LFuoBI3Vr&i4&q;$ih^wVa6#ZWh}gAp+ul>Ij-BqTS-Wk4!0Z1(nl=6jUqcIBz&M7qDD| zCaXh5!2}4FC-}h;_+oMI>+|)8{I_x)#(he!`jz^$4Sjhg8HpWWDnG=L8#Z}*X<)db z<`razXXhSve)Eq`bz-T6lx6`*5z82SI*8F9M29nC3rq2gp>0=|*_!SPD4sU6j^0g5 z)%+O72hXsnRu1au{&uv$F;36aaqXGSf=@3&&C~XB1QVBAVi+nF{cXYfRte*i^w9>z z@w=@5#ck;pTb+#mOTGj#{4?LL7~3l=+WJ`Y_n{4+ZYCCRH>q#o<84C8Z%?>Aq6(a)ULIT?(-^2uO*01%Vl zz)Z0*O9OyTK%jP^p|D>_`PtkR-R2X_)Bz*Xuc1{C5*JeUWM-NkImF;rey+6h-vd+*_{Z|L%^t$N%Wc%8b7JI>Z+L*U1JQxf_4;U86x!7OxsJZol5i z`lD?+ihhdz;9P;L;raNk^Y_hd#(8ZTs)D3$SN)n)1m3_)mJy1sH~mqa&IGp9 zKKX^Q9AMy%sd!?2;0nxvpLO<~RWjT}ex*kmf2%S8{-#s8RPw_L98`V!5e;BEmaIu) z41#7QpoEP@cUkF7=)bB??xs(caR$$* zb01OE+(3e|FwhH*G3?tC6rAfkZ-Q5sP4ejUxOkE{XFW0PolA@%3J=eOrtgOMOneZO z5?$F*?txxoE}j3Yd5wFp`FKQDwOiv0${6DGS@D^0o09y6H(l*_LzY(~W%@;%+uEsy z7s!*c^RE}$pM2f=fVzqa3zE6a3rn(l18q7j=Izwm$_eR?PI1 z;ztp>qPAa4mf*_2AUR6* z8w^Y>^{m86!k?L@0MB88rWjB1T2J-5@K=iobK8@#2M9+!>C#0$WizDnIA3rv^A-ib z-q#nWJRiS&>W_8~iVp4>JbNUA9_H+xntsOohvDAzSIz*X?rF{Adt8$!;#|XdaKC#! zXt~SB$oJ3?iTPcFZ8O81HC3dG6gjE2#OZx31WroMCd51Jt6fQ8OqqCh!#{MQ?ukW0 zm>Wr^XHI}D0QmVT=s&KjhyN@iza>aKliIlh`9387lrG`cH#&>6QUeaeC{VziD8RQ-jHj^SgkaXR92o|(VL4ZEyXUh)nC8fThsJe9nkJB*MFA!`PiWN zR}{qW#7{#Bs)9E%V(eO4Z?yIQoea@zWdBg{C*yd zz~O&3W!~nmRKg)~oT@xj;ET=f%*3tqFxRk*`mCL24PHVS-l#9u=(Q=$OeBicb|&Ei z6#zoQ>BRd-)g)|qR~Z+0->Kuz|6GYJWu5ulTselU_$njM_m4Ln!cryChKbU@*sgK; zT&!|k`;WTVHVhlSXlCvht11Bxg(kVvoxT7afMosui2!BJX^AyZM3D8i;2a!?(Z?i! z(8JMgsI&x5&ifyRFW|q~u3u+XV0!K@5w739cFmrXHoX0pMdEod#Vl93jz<@41LmokxYckG+LX2nCBJhng~TL=0?2cs>F?lRV{m#H|MXY7AcnfP0KWWdoappI9IRf zrmO1x$HO7gPVwDR4eMw}B7M|>e5Lw*k4ezVb7qq|okyk^l~}81?7~8yv^pUIG?p_kH}`3!;3kV`A`(nK)c#@g;EWs^8I>j=nWIbJv*_90Oy7-PVr)RCq@D+39>w<56s=n!yPp> zKiQG8h@3I3pTv-i0bgXr7Oxbx!Uo*K;lbmm4AnmoPdVf4RLzsg!|B*gGnYEnQC%lX z(yyT^g%?+%-;j-DIBoc6rDF`Ea<9i36doQJIlVh1|A(O_O5ZceIL<@%NK4=GZ~2(M zK)3y|8J8SJ*5d*2Sbv2GUsNwE@|d~1Ep@u=KX#poasoA+L;tXCpFKKp;n0i zW@12}MxNzLxYV5`vK&55XmX2DY_>(x;+DtL`Oi9ua@8Q{1@(rx=*?#dn0##4t{!?f z?^(a9sr-NLjUFou16t0xyd5X+U4`nfz3>m%zyxQ=xBcd>fIY#Ge+!E2T4r6w-F}xJ z<#~P+7Q33MP{~9Fs{4C8lTHS18obWRolArA2Y99`N+a}f_V!iUO{wj?RdpNjV}ccu%d0<)u~&rZZzp1M54Quqi4}V`H>m7C_ z4cXC&x1WC{UH2raWwV#GtD275)n7DIdV+w#k{&3gyZiY?$#&&liYXH|Gn>d3q8|z0 z5_BsUy#FrAyttudl-_2l!^jSKRY6$ba97V|IkI?Bee%uC{tiD*SI>F?3FsHjqV9Xm zgk=7C{oSW5w*3x({NI|U78Us@PrR??Fa4|SS@#<6FJ=&|SEJWz$VQWHhAVG>0wdn? zakn*BNp~6L&IPf7v6eP|Ht%7L)oyBMHCa1nA{coEYo+r^*(P7g_}RB&H}GC$iX!73 zX(MF2`kcT;bBNl&8CWrh@UQ&!?{4TK7^G7E{u)hy!L9Br;3V78`fX5+THOHfW|p*} zg4||bPskw5L&jw$aJf#QG7O7K3-&Qtc^oi)*5*agbEQm|{9~e-3F*%Fndr~nJGQ^6 z6Hh8*KYX~`&N~rAV`Qy3v6|Hfy)609Ck*$i?()0rC|Sh@-z_9L@1(`mlz#3O^9d1xg2LNrguDox^9rw2H-y(1&|J zUI6R(tw#cuS(*g#fxgk%sr(K*DF}u=QT|H-)@pnJs1N|4tUD)J=b7;DyMc2Of3y=^ zh22pEaOn(0%!Km?ih66DZ9{x`p!u~5@UNZ&oE%SnjQSR;)D7vt@iBAkPnR5C>G;WQ z8p_*5`?;ovxsk0=CZZ8~L4Tk6HU!h^eFuX}44Q%5GEP0NK-gz-+c0`uN4Zq`Y~+GY zqjjs38hwCwkv_k{ubifMSpvZDc$`Fu&ad)$Q4f|;2{dZXq;U%1C9=kn<32tG&qssj zjP6A1M-Vt7Pi~cL-2eSD6`o+qvi->#%u0{A{q%`qt+8E;0YaZQ`Z}?KK)~{(Rag*= z($e@jxgElqW4%hvDjwN`L=^q%jnu_qqIk#8l6eFGTZ#zbRCq}w(@db{K<7cskL6-} zla?VtxUjvV9rCjRiIwB8;-R4CKM|YK*iheBeMp!x!j*OmpVl!wpp^0Az62)N#h;0k z!|a?e)7E6^GJt-1wmr51_E&XE=Q+ziHgG&4sKR($Y`6t~%7x9l4iZ~(GeSzIFakTH zk6R6{I*DE}m9D=Pd=ok!)z$^qz!8;V*z7D+h9qh9)%O9*EP~dmLcaFz7}&3K^iM_i zymVx!j|SnJB6rmKA-fy?zHrsLd(Kw3S?5drY9oq0`19(0@}P(b=0W;mUjsN9y;h%V zAuc152fef9Q;~Z;#oB++HM-Lm89!k-a+TuzDBwuj`N_B@Iszal+gmX#?Vj~BmHzHi z2p!(8;XRrO|EVaTHZ?j5s}IAm{8%v43GGQG=!r_}!W%4ar57YZFL5fJS1M!`ee7Q9 zum|KgR_cpwr(w9?&gD5a$c8=+vTD1rz&^5<#V2#oia|&T4;CUzif0yC9~g4hLq~wK zJTQAWon%tQknV#Cm@_q!cn?)`Ve64e0rwOQ_c4?fRw7IIuL*dNd(sqo-PoQHVa~olb>ekI`yQ;2T}& z5LdkhU;v-VhKm%N8ZEq@KQw|>VlE?cf8>S()y_$LK@Ih2 z(fNQ+n41|aXyaVZg#12z>{3coVj+GuLC$aLn5U*luO!Pv*lu4zDc50iMgQ|ddmG)* zM+wW)6Ogg&?8c8nD-o)H+r6o?w}F-q8oB;Wg=>3MAP(-$isR+O47 z13x`b7C%^Ht1VHqG&VYwTe9E1k^7!o-}aXucL8C0cBu(iV4c&06*HOK=135X*zhK zXR-%X1Pz;D8}&Hw^=hS@p<=Ls@j+R5t?`7LxGTIg0oJHg+d8i&C4qhol7W%HZtVFL zODwe*he-=@E?amk-J)v$J5w)P(e^dzAQdm6-5mxE2M)B;wN;931D8-pgM)`jUtVv~ znCL?o{tDmsP5d=+@DwnHz zp_X$S1UwpnG0{*vUS;sWCgX?#_9p8oKCk+euoaicxN zF7byDh!id>VeaBamn*{@J&Tv?x^U&jjzddA8u`M>LwaGKgcq}g*qkTr1Ba<2^R`yD ztbg!Nq#T`$4ukuA`Sd`N-V7g6g7(W6rPpFMQ@FOF8Lh+`fk-xA$?_uqp}XoC6}*W6 zK0)IacC*R=qQxMzCSN+zT6um5+Jcy=qc>rHj!RN}tml+X29!7Q(MzfEBh zQWo4`LML$+b~2F~+2NaNGJQUDUD`cnjCXD6Q*aHxFGd^dZm1nxvzJmvV=YuE7JLId z%>Z&m$ND2Z0>>2l#>G4)d{XIjl5wnNjCo_`-0+)$hZZRTp+f=!qw8)py{W?OzM&jr z@?d|XbH(iJn_LL8q#ZYykn3A-+acYx(wkwvPFFsLtN>}e zG6N9#s1HR!nv%M{7qyD@$UPi2p=@D~xL$a^7SYZKAMc9HAIf=T{ew$KFW=;>fj1}> zTb@L$<+N`s;x#Sg1uXL}*2FhEsEkvDdmDiVskfi^dh$lZc-yI7IFycjA98;|RnSm3 zbWt~Q@iVEiNN37eW{I!n|OO4<#5D9Dc zn^0r-+ClV*u`_dtK*JdWk3}7p(aTLB$F5s;9iWdtZ%Oz9cg7$XZ&)S8_C3uqOr>mB@EvMAO(6(uh(+4i?|Vnn8x#3~z@A1} zH6;44+>E(%>Hsy1vc+MJ#y?6zTBCo+H5VbjWCb+0l6S~`AtL&zoI_U zM$m8*5kHdnZ|tfv5;Mi_owayXafOL*(~TCM#6Qo;q{P#LhzLkPKsvw*J8A*&q#BiE z-%T;D?>zPiaP4bm4-3M9zF=`%@djmUUot}5J`G8V;3EQ%uQcxtUHpZ&Atc3VH z!TMg2h6xHr34jOIz-x!8ws^5sHG20sH&<%;riqor;M;uyLX(`G{?h5Cr*v;4~)i zkj*$xxSh6z-9*?W5D9Jy=EBheV+E=ko9qv5&ByvUbN-W&-Mp~z!E3=jJL5lm!m654 zjd${pjQz@0m8Dn&9vw=8u>FFc_T|_FB%dzlm{B+`h!{Z7O1mZ8a$*rNUANumNi+pY zD>{zWagiV4=i`SE@kh@7M}Z#rF@%HG=na(7uJCz16+LZnqSZ}mqN5Gm!I8_6w&@nx5c_o-TOI8QSxNW2>{+r0F;wBT;_77+FJTvWFqxX`6}yjEd%u1DBGIln}ee(T{oN zoY}3dYlZz{lw5nbYCC;pl_J!LDf|OvX}~-IT~zlfg+m1;)S{_02n?)`4DHI3cmR5> zcI_gLyeVZ=lzT12Ge3RTF4Yje@{&uQi`(IYztmB{HWwsLK?6>a74KlcDsbudVO|~} zM`KwBG=!fG6H|nVZR)1kbkRW{iAA+n3TVO^@ERDX&%`AS{z#m_w&_whsZVc&0AQu7 z_7N<82yvm9b9nN!eZo}}z*VYa=n!XUH$VSR?%QdYAG%HtgWBx-V$?t2SBC|qj%!RE z)mg1s_()-;17X)pc^?kkULLv>W6OW@BOsuf^9Dlir&Fl_5Y+h!;h~^iI)Ba%3KQEx z!%}l4Op%6EgGM7Z-dcBOuvVlk@Ihh3f5xf)J+3{0uK2Auz}$WD{?yIb+r*-VL{!Cv zex5~#{EM#?ZM(#4fKjWyW_;Je>1?dy=0Ght2?l`mEnBp^XRSqSk!LOQm^T}}De+e_ z+}S8NQ<&JKYATswiahLMJ19J$Ef$KJ-4y0^{JYxC*=7dQW%I6a|1r)t_>+zP5h3!l zD1gJ3Wv5l-=YNt9)lF-%j=<`Lmt)NwAN(^CBp0^jee~M)t0`WTnb~R*-AwlKLi`%N zgz7Z=)ffNkP+g`q+*E@-T7ip$$Yl*fXb5qK5H#Vv+_a5$cB7Cipi+)sM4L0^ia7fN zjZ}*u8w24yk=XL^qP!=2R)fTbHxkSPtNu108Z<9IN;6iBn9uj;sq{?`Z&Jx*ECib> zSi9~gW38KZuWRK9<$qD9Z}#Mnq^6(0hv4yO1CAC$WtHzzL|u@$h_G)+mzh*oyNGU) zxjjy2l8cUDRiDsT_Z!zhYh}O1Wj?pu8Yz~=ugJVZ6_D?|d}Pnb9aNLztDd!(_{>!O ztVmrzx_vn)Z1*QmHns_vEMB7G^KZzLRiSrgfeCiLeVcywgyF`m3^)dG1Cy+~u&Mtv z_oka->0a4A?)iHHhYax5GPokefKlW}5M|(fJ?DFbU!!Yevup5&`?ildFT~!KYx0+m zW&P+^!OwA<>G8Gn09zk1J4kN8ytCznexyr*I{_02;*t=l^{A8p2ipgfxujKfioL|K zvMB`pGO#DxUa9iY3GKa-vPBmt;aQJuVO6?W#i3gNUPJp+H_e-$K&W(aNDlu|9q_&9 z>YVi5{Z^DdM-8^-1%m9h^P=L!zMz^HJ9tSSr?gm%;*Mp89g799ON`O> zK*=sSc#{5y%Hkwqa0BpCQ|{UyXC$z2Y%w;^fYZa1qrxlPb|F0B0=u$G9}*#^0Yk9h zC?TU|rd^uU-Z)vAaxsY^CAZYU|3p~;9q(SDNzD!Y?Z7q?@?G7vEs5LjykYA7pxxA5 z_TqqGd@fQKng5ebBe%R@lm2RML6%ASUo|1ePK*=q(Lr66fKc=O<@A|P>An%shp-kT zCFydF%R~FIGHLdzc@_7UT!)gG5fVLDeG%arx>CT)chjE<&j^sD@R1-+L|Imzo6{0! z*x=xU+Yipc?OQVCYj}ZU!P|ArA3aBmpxPb*ytOve*RC3B5rWJWIX{g96?qb+Y^RVS zyzx73VOT8&BRGc;&+D7DB8pMafomSi+~aW(u*K=AVv*4%P0?2o1+Ft{Zh^qyYnPSC ztXsE;eGT}}^dZ%t%~W!LVu0x93N4;Wv3w57fDw`e3xG>ZUhhSc_~wN8od{foF|tdX zK@3G#^gux~<;2>hD;blc4G_eU6v?Am@sW9^h%U)dxAW^?71mQWHKG!|PtRla1sb3j zRbalQ$iaQAdUrv0=fF!bLQQ$V`}NAg!A;Io@3xDVyk&f1%R*{=J_&xsBF#KyKdr>( zg6TGBdR%_{vR#!to&!-CL)2?;sA@KW3)1>JMGsZMi2gNZeqphMn@Vw{d3f(TjMg-b z=#NQ+k3A{-F*qF+ARmnE&uw#EdLsYt#tiRN>Re&c^pCKbPyU*zWDif<^yXI=F#+lt zzz9X;eE)2!qh*c|TJH@a;tcOi9Hm-R<0LT zuOk|FqCx&Zxb6I95+9QiyX}GwJH_!`9#5T+bS+*QHP0@kX#TFI`rhjvHC@)5#TTgT z&)uwFP7Derzt1ugf9Mj7keQUh>poPs2s_Zdt4c`Xl$q3^%y0jj6~f-NF3H)wbXSkI zgC6nCEQC5Ze8oGy{oDMF_=+SXX4t{ceZ|1q|20!KA)K$1QB5H*A0lX{ zNWY#o^2+u<&YRl}hdWtybbJ&5TW*&CrMcd3&fqKdU?RQtZdZ7S-mOMi2GSP~hwz30 zozXt-{+2Y?P@$$gfgOa`i^hbMH3WSbW%l0rKH{R0l^>7=b z1$v72B|gJR&DN@Vl!GYdbkHy|PkpVSEkeu#evildmSEK}VV}OT17JrbzrXy5#KM!R z`Z?)~3knqlrGQGPf)hV@+%e!)VObJ*UPbLovoo?7YkGgVFpVcjDFZH->scY%T(CKJ zI|KgDt-@?mgI*2ZZ1;9A>QZUUw-Y9ZMj0Z%1 z9tNz0)2D_*Rje7@;f;cC6*X~=pR_(RJjk~_8m7U2ma1!9#{YC;hSR{T=cChi{iLTi zRNh2bu4vFj^f7NA<|Ge*xf#TX2l6B;m>!CkJm|z3CDIw3{rP#-*OWAag;rofJiAM1 zH(+a0;eIMCIFg(qF1L!0#NhaEyDsTPI|)KwP{v(_am&#@KQ$?JXv1;QivEgx+@m*;1OL6n_kW zWg$KQ`hmaE`Q!81p`vQUB~|HxagfBy-j*-jER2E1^`&9Jk7kc`$h4=lyIuTs;}wH(|>nu8&AAh_{AMO}dInQyP0_00jy`t!?Itv87X!rn_-Y1g(@H_Yyd z`CdZv16F?IGGE!^bDEI8jHh?PPPBpoCulw#!~&UD@PZ{{ccr=<@=yVH<8jrxlz2Y7 z4}7KC9ET6%#i<8(g22BIgh=E4Uxl-^rFTW3c;Ru*9MgoBaRO$v}D=~+pRUTR`Ho9e6Ez7`Yb`MG4nxfb^k(j(tw=Xs_3VuzXV~SRflpx$djFM|{|BQMQ99y<}8L~RQurZyTh_rlq)b>@= ztBv>S+F9aWsd$`p_^NESy$Xoc`}45JQib)Gs6U;7a^_TJK7523wjW*4EZaFObUY=%g2M zSqlbSZ#TABxXvY*a&zE6AIpS)bWd&)|NZUUh#MfB<+&_VIkO3R>$M^Cauz_~~nUs23K;bO?R7MO|0g z@1c!cmCw8NDJsjmJDCWD-TNtWE4vAA-m=9GlOR97_`=k(cU}#UPEH&!JU>y;ns^`6 zHSs6qy1RPJB8eIi`Ip{~i{xlG+ISb6ySR%MGbOyJUcrVr{N*W}VeAs^+{sjzvY|_a zoX^pPvdn&N&@DlgGV8hYgTUiuLu!YF-MFQjCrV8azlXh{NW4xB!<=bO25>4mK9)?k70vUG2upm%!2ch2`l#}tVT(!Ax+?h0yoj0YW zZiSQENij7vtc1`nmk)!)a+2byAZL4DzoLAztoE#07Pk0>?-WVCLC~yz`^*ak`7UUi zl=wXJ;(KY67gc8RmM)rQhJ2fFa#jIxEGBn5$tzAZsd)?ORiMy+q|018K`q(2s?&4K z$`pxoDb>)9&V%;AEZl{vMXjXSj4pO@JS71k6h@16O46GsT`R8(Qgz{*Bfl0WWq7$W z9+kaK7Mh>SL~+8@4ifdF=~OVB0zA;}@d^RQs2o~wh%Y9jF6)PSzWXqA{gsAK1?^8N;Z}PMoDK)+QCVDE#7gAKr&4M-R0Geg9ja=l|WuzlWq|HG7(9f*JOb zTT`kvl1eF7L%PklfL)z1hN&!Q*HL>zKMY~QoD4{4*Anc{a832qdEt7`{{r*&8$G|S z#I`=#CD!d|XA4Ba5$9xed%x%rj-|JCymG7)INL6_AB0Vlh1f!uglq=~WQC{Hnk?T9 zg>%^E^tc(79_|S*l}ESvki+IVLupZI{N4n$B+SbhZWDP$&$z6SGNY~CVj1p;SS29h zi^0;1fFa3ZBFGb?gB;-ri7gSpmIVZP+>BS`W^NUHii=*logT4ck`v8&&5-H4|4v32 z!TCFYsG=rKwq3%sjRReLwj~z&nIQtMyXcFnMhR9_)zFet>2S{+P;LVzR+eqnc-)#^ zF$~ha@C7oMG#3u>bwoIO7?Ulg2L6GKPYWPE7x3LlMu85DtcMuiJ3d5Hd;Re9gQ>Sy zCR|5blF!nJrM>vJww7g~k|p>zs*_!y0*_bcP8yU-P_3+7kMOM6h6nxeK`zLxMoFoX zJ;eWH8kqOrN>(%^;vc0{;+;IiTT%!iXOibBvzAy{IfW;W`@i4#QXzE3hlSuU9CTia zOjAL$FZcxD$YmkI6ye934m|oI{poZT>i}rS3+i1absI%WkZpkO=Nu-A*1G2Fz>o5- z(4j|N9j}VRjHSl2`b4C^yokKnlSU7|v8ni5_k}L<OqG}@qubXfol8NY%l072x-K=e$P(S9<%uOQSq(HqZK=^!A zTB#oIeX(nzgjbOcHEHT4>g4^qS5;RD4J#}O;4dD8US{hYn}IZ5CO#^Yo!1iXoSj0K zTWIdh6do#BVIRSSTV1|V`H-Q2x+o5Su8Z#FRvJfOY9aA3*0Q-K$cj=Tyy^UR`#A6^2pQ4Mo{Ub?fG+A+Mypb)($4UX(xzZV;`Ju)?;^^ zvO;DLnt!*_0c0ZO{@2s-gQe!rgKOjl|{DJX;P)jeb2}1^dD}B^324l;ls&Hl3)tKNbtAO_vvs zzI4~f>~5Jhd{9^f1qfOK?btGZma+IEbOal6!8|b{ixo_UMy@ByPBFK0SioZN@g+z{ zkXi9i-hg>hmjZ0}xR#-$dO;M7fsq2mf$wGOJ`d_r^zM3uJHX&rot#a<{54GA8|Dqw zm#lZmIA*H`=hSVNiIixh2NSvY!isp4qHyJ;{qZH_y-4}KAWcX zih%j${K$Avc9Yt)OreGhT}!YYx2|lMqj+Q$}(+gR*kU>kKe3x!3gn zX=i&l5t&*t`{U53bW}VNVC*en88R)U;BIjp?|ZkY&U$2XtXmUWtkcM>W)V01aHk%4aYNT~8Xu8(2T8nzgH&--!w8 zJh0Db*w$r*r=bch!1U&()Uew$TgmF~z?R{H;=;M)?)H8A>!6RDbQIP>mtX4_xEmbg zHq&Ph&c;~J78oc?u@5)b(6_+OY{YJn&0jBNH<^fjMr-9e1QDX)F}ZSb*VeW(b_cMCkYk|hG; z2+*n^?Gbeo_S|>mjpVd*GBBBfyU{MmZ4>Hf55s-e(;{oSXtPp?7lt>xMGCXZZu62~ z>T*seU=r{W8>X)L5f^{s`0e6fGq}35qnx? zfoTKWRWYWQSk-{Z3Ga@xw1A|ZEx3~h>`q19nla|>ybXiUhx1MKX&#G@Vl)HthS0+f zwOX=%Vy-tGl&QI{ew{~j*|kcr4JrF|Cy`N#IARIZV3kP5v^`3LXm9Ni8<0PBwTG|n zhev^oQMCDOIxveKM27kfy5F^gqVZ6%Zx10q%Iyk3A#?iB4j}J}!&_we)4xgLCmN@+ zkf$k?T_7ohq`bN>s(=iTK@Cv;cu->MmC|&xvJ4AoDhgYY`z{=a)|O%v6y~xu)o0|f zj(TM6+K>cSV-MiVvO@_U=#04*yy}CG#e?VnC`feQj(Q2!!{Upd{Xv0H+Q{wZb>WGsiYG8 za1HjnMsI)Rh1S_EurNpUAj}=T$NW$eBx{&)Yyy0z_UI374JydSk^uyOj<6v?Tfmbq zcqiu*>u&;&EDigk2*=$aU?jAR<|s@_)gC;kR4-DwBl}GOcJ}Ys+Sjg^NOk#OvEn9| zsQGc_i-v!+54TRBR87(KxZK_B^jvfGA7C5RUPF;&xLd7g1z>QvATK2+MGIXFPf#4ixxG1zV)U=7al7i!wyTvkOb|d|I6E4bm@9+|0W&`Wiwl*k$smWq zzIU9l+;ti3U{_)o?sin(CKXMZH(V$J_+UAOfV>mS`^rY(s|xJjO63bk_LsreZhyUx z)Z6@1%FkuX32%YH+ip-kL}QQ!oTBZ)^-gT&J*&icj6>v}F&JbT$!6Le2Oa@{9_|SH z)^OVvpqQ-vR4b@25_Y$UVSqQPb!Uf^NQ}NX5}uac)rqiIgB?W5AmbRffYVQx#86WO zQC%(!9y{ulWXuC~IfjU|7HaKw!najJ9;^RDQ~FiYySGDq%L-D|X_%DRV=hw%5PyUl zvlPzvq|nuAZ8aYyCk}B@P)Va#g2Zp5sqm9C-lffs4`M~lpLk@`|4jeq3R2H%y`!7| z?48;ZRrd02+%ck5*WVM&CfcvP?FrK0Fj{tJl)f#K9j5dwtkv61N%_yb$r-eEwrGB! zHj!>rLjoZz9N2j=lRRwwu4Mpc_Di+)|rS+Pp4zEnSzxT0jS>wRNCx)^o zjlO+UkHK2b9OB`v^@b>;gA@UR#-6;2TkJ|d7?dK)v%8V^dZ2yZ|K_shWi*ss)ldI) z*-J~-L~yQtWBF~st!dU(w7~e%a^)$lQ-vjS)LgeGHkre2TmPjQ8+E#9us7S~I{u!5 zhCYno9Yvz(l%Is=lB3Q4l4RLTr}pPf3y||uG^asm=Q(Fz+C$?dhF{2uzy|uQ4f%p6 za@&IpCeBju#bYI_TzJvf(pMPj}46Y-Rxke(hSkQ$*~uKU3Sy|Q_C&Zm1IYI z7Gbjc14igtM#UR4?%50-yv9n8moO10(c=G~21T#G=%r`c$2T9R#ZJn+4?KXl{S>c1 zCyh&6NPZG^TUl304B085F=c;WOIwkJVaX;6=Gg;HZIS1oVoM2yhrH`rQqNv&y&9sG zKsnVoOLG6AHD!pd?(<$*GMJ-?T?;D~;=Vcd@aQS=6Azm`} z3;1oWP=9@fH8lbLh6*J-~ zEL4mp4_}&{gLe0(5cWR|~#aW|SyJrTot;`@dfr;vg@Brf9|AMJE3O&!@CA%Lhly zmek8upP3scwdGa~Q#_YPa>)s5oSYvV;nkj?_4<>qsNXeOC=ivGIBeL<(TsXafv|uNAdrz)U3VtPB34q%KaROt*x=5HAgX?4AK<~Xr|n~PlXdfcBp^=?I^UH9V= zVwgd(gKoBm2OYhel*0Eo?xq`g1+#ie4`qq9hW-6D`C+?*^q|9F zbDn5Z)^>2q9UAgKOA`d3{dD-5i9WN~wdBhy3ET&iyRmhz=id!umE>K;XKt*~muh|= zwG>pRU2qE;uI}=-RfipV1c~JW0$ZutQ3<7TCmw)(+7tm|Ss-s0dtW}&6`v^FNO*Jz zO8G#Ou+0xCf^iIE3r9-ltwoL0@O&b|lEufrIzM%gW zb97JjSHWtb5SqEb^@|_Yn!BX@V&x8T&!G}>Q#2}4M#kUL-4otEd91ssVbJUU?!hHO z_Nc*vo>8xVUGm`h{h_A*Ua_-jv9`Alh3S+Grmm(Ih;O&6%&4ilJ38*<3TKz54LqU` z97sL-Wg`{6FKBGu>BwqvvR{Ys(oI{DUUYb#!+VkPq@Jxc+J&Wp&jp{{mXYkWI)LDF zTw6ShVbxAIB;OIc2-3zwsSyjJ8vPMenuR$SiDx(qZ^rW>##uDksDye4e9>WNGQYON z^n=h+!k*l<1^dJGOO>(8>v36rqEQf!q7fZsuNQy28UdA$UQa2Qc?5;Y2%u6fcpl$| z36qdgqF{*hL_z02>6Yrf5M}|P01uqKJCFs@ciB1T@w{LV37V=WAU=i1GcvJPXX( zN#OAZg27$g4?Nqnp}?LSc27$ymPVe-TYU{iN8IUP!U2Igo+aeZ@H^CE_)4dU(gU zFcIv!;UQn<8+9u&z;*o%Ua*ew0D-W~1frO1A2Jm&L~$oUSX&Z^Yp`rd7&vazgN&(( zoq!xZzSphVv&5GQxA_;$mGYjj%REgR+VB!0XqWDt@7%Wq5(V*1&omNo=i!i!)L^A@D&{~lDng7}Nd5bO0r-gvl`YRkU$@OUG8mzwnNjZ8^HcBaC% z9>sP4$!|2d=`-llxrtHT_BG%{amX?MyNQ((4jLnMA9VbG@hTf3`lVC;X^y^M3HcAN z7M7QkybrDum>AbP>_vTL%Ael>--OoGR!vnFN-=e7W!IqF&7+RqtE=O<%Lk($y%N_K zmW?eujH-S;dqV%oXBrIoC;CCMVK(iyOUK`m7o#w6R!pLd=OD&T4Ni6!(R7JYb#9*y zj_LRy)H(r;gtu|b^Z{(PhW0J+mt5CC7WIJ;z=;6E- z%}o@{txTh!_MfVV6PHvw*a{(5hUq}K?>)=DL+>*e5OGBbcUFVj?34nMqwQ}9l()#ri=wAZl>A++ zLoB@C=#Wgfg+f*9@4J zT@set173xcLX$IvaBc~*HMBwA8*))$HD z`k|@d^3I0BG0MBRu4!#~#{L%)_g}Z98vW*kBcR$AcqGj`-fx5u846J41v!^1vDJ=w zsDs^;^}+&ECOIJ4#^O08D~jEFUKfPQ@IgM)Y zJ^gI{()MAaf&(Gzpx{8L3KRa!PX0L8U``wiK`J>wrCL@{(+J2t4y)OE=bvYN}u^K-axxilaTmsF|f(+xXHo_15n(#Ax_Mu!FtdoP*DX_VMW zZbvQ49t@zHQnGYdRRyYNpH?1{HiqctVSGS3Pz z1_@9UhT#f$b&9)l#JaPIcD;f6BG~M$As+M0;x!e11XRSVAWaJ22{)?PcxT^90FPC;)m_7sM8EZp@o#JPzR2<$pjeE_-+>HhOfzF`^|4wl>L%%@bX)U1@1s3A_CQ9< zDp?9aDf9F%%8@%=%$T~YbIC&l?p3hZD_&Bw3LRU|G;Rn0n+_gUQql{!AD68!SG8WM zCuo@M%0AIcx>&wsA~hKg3BnM({aA_*Ub)zLPcD==n&QAlmxtT2*9%AdGaPP%y)k|D z(}=Ik8OsCan46J!P-DY|W4Z?lEqDQPHxDNd2VptG2^~b$#jFw6Vk~$SZ9Lp-ycise zk!qZWO6L^1{EA9Al+Es=4HNE2j?jU*vaL4&u8Y5-RH=~1pT$F-Cng4j#+GcrcL^cD z3sqO!!C1nTj;t)PHZn$4)i;%(e`)E@gTuD&%v|d-;}XPtf5q_$^MkK)u77#lRc=gh zE!GhRL;7J3p`)+kv?L9qTNUQ1Hz{hyLiP=%(j`kh5E&2qAn`yUp|w7MT$Z)q=DHO9 zUGa>IieG-QxD)!q&~287;2YxBhJkdPol_`P_-=9>20Nx&_~%z`?fV6z-mThwwU9se zKAq5zIw;%aQI_p}fFxWV!*DImuf?wigtv~<&qdaw*_u~o(QoTXR!0`nr6GAwHxRIV6TnOWSWM@MEaTCV*?BR+-!!}clkSRM5&kJ&{s0z_n#p1`Hb5o~zStrx_6eXD` zB$Q<8;O!y1eiZdNG;uvl97|^nY$_5>>_ThaJ8ZJf@NOTMBx$@$DL3)AQ^MxaJ#YHw zZhd=0c-`}-gQz@0H1Ma(G$pljiuKc9v;mGkZhf(PZ@_SBz$VJh2tWkAHVB~W%3>i| zb_EI|y9}IPpZ!Q0@f{yGi^+^C0CcsneQGcsEl`&TJ&KV z>q7cr(PELax%9glIiF8rx&3bvG<~=#HT1zD!jIR*`pLcp_Ahr$C*4LSuH?QnRO`iH zeAG!jLA6PGtFwWlQh3X5?CPLO*Kw$=e;s+GZ!?pq-6LX;l$Zinv#9VQzNwSDZ80Y$ z_86#vnhs|oa`>4As-vOu9|{S7n1Gk$WX6s_y_do#%zuEtft(8-Qd&+C|3@l7(Tvt< za~ExuR`XkAISU=0)~yQyEaQCLf)eS3tTtO{KG~UcN5SOeok5uoR0oAiERIqNX9Q8Y ziume_L6{fqb&IQP(g5&%?mzdDMuR`^>^sM$y!yvk7%9)iAeDrUxy+YrnlktOH*#*i zKl>lJPvnzoNU=XX$IB^soAudCoCLlesP*UY4?U`Wp4dN@FRwCCw@6(&tuScelo1Jp zx3>lLpMIQ%mfitN2Q#F1dX;v-iY|ndS?C$s9l*cmSIz7M>q0-mBqy!Oud zI}f?uEl-Sf6oRuPSGlV0*<39cY#qJeA7C`^iSA1{Z%}c*(O1p=cEWQXYm<}mRv%;$ z@N+B4eNi6{-`VGueBx7Rf&Y?siZ|m-95CZbz)?CyXQ%gD$E)M7)jkhCUk)pn;G&vE zW?f>J!-~(`>eQ@NLNsP+BZuDnEE!gLJq+Zh?EA00(TVg&GSrT88m!g5fd^=kdj9Ik z1^I>!i+nt0CbvK59kXrLV6$7o+g4>gP`ApDl_CM%>BUabMVpBNQu48Yeupxi;!k#k zdf6l~*F*yXCub4*qyxnTOrhE|p^N1rJ=EY!x2zqRFUUjhgk*>?Vx34XF&laPQZ4p4 z9}eW5p6dH#pK+s%cr9;P^~flqou~voiauqcvu8m+wkMV08c_U5^_h~&|d zJJqcP_fYE4;oMqJ@$ZhzT5?}K6GrX4+4qxIIZw)2=1!G$S8{-!m+pG6t+*Ad)UrSK zpPT;kk)}Kk)&vmM;2;jsEy$vV2Q2!pqL|ea=(k(g5xi%d^%$(;40h1c-l6|Zt7zf@ zI1_UBj@#S!0!5-0n2+p}39f!(eT!J^GRQp(dGjjBtcsMy3C{3LGwB;iOSGSlT>Hb$ zEC5(W->Qoy{pI@G1#m)f=~fxH?wRM2=F1z`-ES$;BxCg=8_|0-#9pOFrh$9DPVdEY zhRkZuAg)(5cnRPh@M|(6a%@)5KvDK$&#^5%p7n59QT9jFutKd)mn+eZoV zl_Y`Vv(v{NPxa@RyXEKxZvDo9q)r7q7O3?Xyl7`+|9*Jjwkbqsf!>tSjZUNKmJ}Z_uiv59SiC(P|-H^QTut8?_RZ9tGX{4$ztZdXS|IpZ@wJqbEh{mi!}(J>z(W z9@C*UdKA`OvB|!flSsJOX9IIwuY2zpTHS3m#9TdLypzqHX|D1HHs;i_m1*)yJw^KS zqHq%SHxb=ur-or*>sKXEy&9Wz+m!PQh#n}M5NP@kzQeeU?F}-lh>Pl#btRq|^j6TR z!DceB{8Z8EJhlHCC zUDMJvSTmfa!#PR@JL?2UvxWRes0o>=Gzw`aMv`XzVJ5Za2sMc>~L6p;r#LCQ<1ztyd)SySmj$KpEO>e+whY^Q7?Y1(c zlxT{W9xRohvPm44GXnJNp{y2xxi(}PB#mpMXRamOzrYd^Hb_z zVEifQ$zF>5DokFA35l$3u90*BBpE>9u@xq0rtGIfpke*JbuZFd&EwfxVyotd?U6 z@mQ}=+T3!CD(Os+nfULn6qi&AQX-<+SU+7K!4@(sC5-~NF$8ZOIz>Q>&;AgcA+Y|`O|t9gGGULU7?S&r z;k+aS-~XlX$_8WCB>dR@K*hoW!zioNe`obbc15+Q{-HKegfcJN-%05D8N3qB;i3JB zSBYq$s4`C6l@5yy{eJ3s&1)HbGN{$>?}(vY+5Hqj;6)DclW(Hp^y7VD2a#ca0|c_c z@)pBd+giS-QB2}#)gBPJJJ;(ByW0h> zk^iZ^QoyH)ZD)#aO%Vny6hx4xP~M>;6loNuQtHbkd&(em{0e9`9w{{(0;A zyTEfDtlb_%+Lc);$gn9hDNwNU1V8lS0dM+c=su^$@I;@zH>PR@ov8O?;4s+JLC%Z% z>hb!w+P|wkHlelfF?ON@pzlt+NhoaRQBq=7yl~i#^u*~ITWK(^h)wS8$%vQ6?Mj;w z=Xy>mn{Fy}L?ugJ4XtbPV=aEBvky$i0&_}p;J$-$8qRXfFEvDrcCmK(5m^V&V5@Jo zZPLDNvWn0_GBlV66#=Vx=ipQYo+`md9_agZbG#m$9Nx7+E^=-$*fB}}6erI*e(c2A z-+=`5IQOyx<%azNFVEmjo~%utwu(t+bZ*Lt{=FP|_`$HIe0bBeWbA;5NfnOQ z6s((fO*&K*cp1YVf)SU5s{KcO~CvRLm`K55@pxdsMZvm=8 zEX(Y8DCiqAdQQbG9MhEuc&snc5p42iv$SE+7JhkVLC&VHwiS znOu44xX6B%RC{yZQYht~pfs^li_H<0SQ&&M<&0vmVt6TiT!vL$KLD}2(Qqs0#@_*e zD)qRK`7F+opJ{-iR-{$k_`W<6pF<}lsN@)~+$Fa7Izg+`jKWz?Y01GXYp5?)>K>H8 zl5|imrcNtzkrI@UJ)I-gNdBgNF0eL%m5>}-@nr4K8Ik@)+aOd&)%23m ze)9=ElY(8eW5!3zmXQ{bG3{@M5AH4pigGoYI4$u$$!v+w`Q{^SZ0e6uHh9ZDLBcv1 zVG6oGH+-SSUOq>`V;7c@y6 zihud{xy{Ml?gc>?YgaZ)M4JEzOAui;)6zw{U_-c#cBz!ZF3@%~*k`FWv+q>#9qe42J}%xtC3czDTf)#ey#K8-k+O>FSr z#hzu!e%~llcrhJ#7E6{+CEw>copVTv-xbx*%Sg1)yYH!ZX0@q*VQFxQ&1{x!RVNGU ze`i<4)sz_!*2~J@)-5g3p5t^LR%ExDT*3+`JwR{qiMQ82WIP=Oe~j;HqJ9L*ydxy8 zXVe}L+`a}>yZ>aTL1OCJHL;M{Cx3qB*^7$d^5YV0wA%O*0cq{nT;JVsl*S`DDVNgt zqS}yBMA$E$z4k$m!|u#7kJdqZz^Z$y6v0^XO-+b-!dN(4MxlF^jI*lGqVDZ)sqRd2 zU!YV?m5lms7zf>P`;Dy@SV_LI?9z|=Ay}>_R6Cd>Ar=5B?#iZ{JvoOQs$oKmXlAX- z>QWzl=^n4!K#}hDAWsV2>w2@;K}3Qg+?_Nd^Y*#pXt;NhFSgc&#ffL*)dX9w^A-o% zJz1jrb93G_=mi0%E+$&UEl)x|@mNVb?lFy}1d&y0(!m?cDnkjB4dO<-6 zBPWX-_IzFi0eIWC+dcFJK*e%m$J&eRhG*#x8V8#otCObL#$CGT>6(*f1<1Ps{EYmw zCiyGhRiFNaMc8~Xoa)|fzPtQSRmz`g7s4!+{MJ;JK9(S&fEmhvvl<|Zor8x`9gO$& zY2gh1+)aj&jbS&~Cy>RGkZfi}iZ>6kRn8~e$R%Fxe4zRTS+ZLfOsmX;u#VQ{pdQWN zey^Nc6xl?RF&|H&h{VT(OO~fS|3Usi(7a2VAfG}M|0hRf?$z0=MjNpUwf8vv0b1H z=#z?NS)n$qQ4HD4sJetnvxppqJ=T_GIN<7o{@ICyrjx4`c#g|)ITmGUY(G1@VUGk| zTt&oJn@FmN^Iik5@90V=Wtg_%?M7H6W*WYEb%>^4acU>r%}_qDX>%|^5rrxik6o8E z+v$}$^2Aeo{x_!lMiurNRarn_q(q4$6V%`HQgE}}3)W4fMYwB3VNmvvBjoPyR867F z-qVLhUNOM*m9@OL|tZSdgxUCN~kn^zoGQ%CoB{{T|9VE&P2i&L4q#pej3S;DBw?!mjVDM z!U(lS$s0C0j;~paF!YIreBr)Hnkv$Oil*cQt=kM8z%-qHooaMba0 zTN6s!9YmDpakh~Qxb>9jZxt_cd2z>C=HWGH4t{>ahUVwA{Q9A)!#?#`7qr3=Pg4qV znMB&fCmK%CD+yU|e_j&t{ol55ix^SDfcyJV%c|O`BT*oz1J(LibNBO;TfJsyx<8)e zJKeRt3KoD+lCl+Wx`~*isJPY#z}O&NLC0hK+zDfQ-K*nK)}FiqM)&NEhQlgfK`#;Dk@N+PpoE1#TOM_>Dq{%Y1pwEp!?XiXEX%2@1rA1a7bk|M8S$?|hEMN< zv6Yiek?9Bwz8~#?5yI`NaldBS(-vB_0@ESgb>Yr#{|l`3-S+;*3CaL|GIwwqT~WIw z{g18MBUgL!1z>ZlI5{=Xgthfo<>~i4d6c(Mp^NcvO6Mcsor?{7?-&DOjy)X=tF9R! z)l%wz^V&DzjaZxF`^p_ZY-W+d1x%bw+4>e}1)m_h?s>g!By+f9rYBzBKRv2qsaulM3n6K#x$}y68`L z0|9AR2s;qW4)JdUK+O2?f}{r;0MkxBT!tR_`ZU0dDzx-fI7Uk+9~fDIOSPQ&(uRn> z5$Ua@I<1tJ7FVa5+t(RaeIO?s`#SyOus$wf3{-^uR3R#SdnJ}&_t|<*={jkZ-6z%s zkXTMPUOe&#S0-BmmsY`+;HP+QMm%BF3hCYi2B_4 z`)I7onWBvQ>93!|c_e6AsL&nW^*!*iC`yOzAdEZjzE6W?o{BhGE2c*sHFsX8aa7bX zQfu>8AU(yVP!9!r^kk3PrQvS5Rkh6;#I>1vJ$_xad3`{vyVcfYF4(NUbf};6_wV^II}Q6X2glr zaOun-nbHw4D(>EQn%OXX&)@Elxg^2?fVB~aw=;1jO zo(~s%N(&@oart4`8^Y2dM1vTf((PV56Vl5vL$IcoJw%K4)RBkLvfra}C>S7xiX}*! zoU3psaB#BHXzyfA#3Y)k+P+%F1mkS=`OUvS8U9>MdL4ZFGUXfGhY<0H|35P>lv*QmHkrR&ap)rCHa$AaU^k;W{FE z;)kJiOtfWn8mAoTN)@hIp(u@u2Tgd+!hTvMs4CdrtypP@n(!%O5^)Xlm_`3>GP-Y-tz#37{gpxlIFDJDS=ROH<#FdasX)O=5j}q8 zE_2yBEm>|r*g7Xvx|lDn#&sW7tz=7n(OOK?;~P)ZU7jBRPIG}){g^*rPD`UOa) zo9B*J1V6s2Y8KTb-*^*PVGDcI1Nt;(`8EbT3BX~i#lB-zItjui9OcXFdv_5JO+btu z71$Lh=;_g##9DoxckSwaUqy>Y1%22TiBd*UU(u6JVkI)kIU@L^cNRlZDU?@W`uPT}0& z_We4y8DJg5=)i?=aw}=bQ{?r2Nnxsl8b<~Iz3Fb|~8BQ(UMWVI)BGm7P9~pQ3Em!6v2p{QRPkUnq^3kIP5a%*30R}LA zfY8aZ5S?+GydjX-S%aQbs{gOxe7Y!6W#o z9hS)YchpX#{H72pFBx@{DKn*sivgHKatfs`Nyy9~24@g`%fUS&(5=;nP*;-K&9<`3 z!QM=I)K>?Rv?Zq$LE(cgapd8f!I>nltMw-ez7R-DN-(_1tmMn@=hT>HZen2JHyGsY=MaBK#otlFvEChz;QLCJuh0!c8WtEikc4yFch>+cYYmRa;IU(jr(vV%8 zsc;#jFo;s5s&=yNo99wk)x{f9byx-rP2(z}IM7MVXFZGx5(WDnfXLUpI7*Axekk2^ zLA;G8@|FgKrz#f1&OP->5WEG#Yh5G4X{wjPtDTHk0rxliY9cq_W7HnNVyMl)xpVR1 zm`iyU*MEojYT>NEWXCp9!X~hw48|DwyvJ4q*)W%?5-dDmZZmGP&z^te(uAEAAr2T`zugyPq8d~6^}pw2=XO$}1{E0Ekze==*$@@~@`d6e)lxYI zFam(#X`Y2w!~}vPq9`i&`i11z!Ft?RDZv5ClUQy$5N3(}q@-%AY=?NfZ5Vk$6%wS; zq0Tzo;}exg;mi5%()WF`(A`RWtIEoC3B^2y0_@7Em3bAITUT#*Vg|AW^ z_vj#~vE5$x;qy3aNxKpq@wEo|Nd9F7g5P7I&#^vF`JM}Fs(Zi5wY8RVK3{wm3l8oU zF((4v5e`mKFW=H^mB+z%u`;xyk}|d^a-TwFqf_4H;365CjBAeiJG-4PhEI|oN%3uqrBKTra+cbJR(iswqXcja*J!yEx<70)DR223v?fg07{iS{Cj{Ms zfA2mZcplQX{KvclB-buS2MV1s<`)Vet~g2&n3<4iJ5}L==G*RfVfpx*su@;KZy_&v zv-a`O`KQ%0Z0Gjh5iE;FN-ekC)#m3d%M%tDd{&B)m5y6>H=Rb%;oyi2h%&Bv8WUpGDEWm-*}X1tq3IL;b1*2z~C@7Gx{ zTTpJji4ne0-C8)8o$^M?^NVp2_O2|B!r39(#+}#PREa%k!gji8B=5)iYx^wkW=4<; zD;zu6aZ7R9^8<^GKxS8%T5 zfSOEYC;pmKu7K?R1OI+(UtwZ$d_G(E75FIhk&|+XwHXe&gN7>Il($uA1%kXe^6$sf z#zM{iH$cXNR6Vay0xw{W51A@j;h5X1maf8|$G@HbLZ!bIufV*!l5n!8S5uHure&oQ z^(Rv|<{a_{MlL}=0(SeP-j;UHgOuVP{c10==7MTc>4&L=Phan~7)SWF4Q2GVs^tr6Is;vc#Y1^bn+{N4h&s7eFaxuu)&gfgw_Ot4Nmu-x=|mS6XNHAN zu6}05%jkL*$88j|wB-ba>#!UgJ5%nmcVApf88pRtn$yhgPZ!dDC&+q7Y74Y)2^O^6 z`@l+@#iR_iBmNljg^}ny+&|~Uh%b#rrspR&AD+F{P%?yySTZWTWz_scC*zyxNZ`AN za<`gRA3`GEr+n$sDi7sz|8%U()a?@eYD($%zur_sP2bd+>e#QMq2(a9>hWI}Ri+xHLERQ88{nQRe9W z#nBIK-!f!(mE#=8e>4ZR><*P>e8gSU=n5YCa}jR!Gv1}Ppzfkc|t9f9R~ zX0Gg4-ZHahuiZD{vJ-@Ln*Aup#DnyO)A!ECG{&bqO1AsUtkWn=TJn^FUNciN6t$d) zkBGpNk1|NlrQHM#Kis_C*ZN%5HprOg8n>fN-xwSX$s6?^3>A`bA8O#|iA0dpM({x& z-#Gimu020?C$v&%%7*jv;#lbB=62!T8wT0OLfN~oP%uV%|6w7Eod+oy0#tU5czSaw z!#@3qAq$!EIy6CE$(LWKD5!Z~U|)6ADf?_{rKjZiZjm#(!p|;30H7xzz;sN#)a6r= z!_n@k_mr_$xHcAP0gl$w-rmag1C+z}F1O@f{$|+a4ml~f@?l>uQySiMhAwnziYN(` zG5wv182iMPwD&5$;2VjDQO1+BC}fVk*se2`CStNBAtCE&lEM&Nhhgo-f1Bd$Ri#=+ zZC(`@;Ou!j7wFXjQto+wlLAX2$1Qzm4?y)|Y=%AF6Y{0*NobT>>=T!$eoRZvwzpB> zT8oRlyM3*fktf+b8IE4}UVLeDH+jVJX@i$^;Xzd zwaFZ@uKyK}L_1tNN#m|Te$_NN3Dv$nJT5I$n043aP1nZDc)5+Qr|7_5{wez!3bTg> z`GI%p6FO7ygn`!Jt@laXtcm`&DNjO}zTLxjfo?msbil5isu}K$%~YPEbXMPK12Em* zU0pIDcKGO-AE}C01JqL5XCz9f+?OpIxec)NLz$9o0e!VGVbaj!-z)MTY@9~%mf1iPeqp@UqT9JeZK>I-K8kkZac0s6tj?3g3eP;YC;ljQbVx_h;v?4FDJ zt8JHUfJGmH;}n@9*tfx=sCMDoePa8%dW}(Ewbk{;d_$+okD?AVFi1kcF|8+zyWZAZ zK@FLnXh+bDoGfm5{_z2w4~C_CAP8bVM3YWrfgf&uNk+(YPCw6)n3ZxISDNSylG+w! zk7uYNTOpY#`D(Y}!=X*r9b#K!v_9X;4bj_6k#GdFCU%U%PUX2PXK;Pm3rq_MbE)yi zG?HnzZ)vlRw&ym6N0CgDc;YtuoE1&xuVB8YkXJV27!g_sMmli;AgP#wGN%!+{w2_@4!T{lv+;21eX&Z#Z&%Yo>0Ga2ACi)WJi978&` ze{f#c%EIrV+q01EXqmNQp|ShSEW5(JHu}Oc7!v%Q{A<+-SDXFqi1tN&X~-+;bGQfg zR`KT6_mO?}i>u6+S>||IBtF|-hO|ixn=uYYm%d01oJ6sWAVxhX_zORh5i`e@@N5i2 zh8?~x-7r^| zR&L=&=c|mYspq9Epgg~Xr5(>{y*UUelpxI{PqU$GU!4T#!?w;Np!7qrkRadj|rGf0e*dVva8}|wp<#{288xJ4&M&9GDDa5{) z3rdF(npQ7qJni9q0s}TnY-50`ZyyC@RR7m0+Ir~sTR z7(*WPyX*4riuyMMDi5&lyefiUvo-El>D}s)K=HJ}zo0-8pYJt+Ob@AW@Drf!K3chHfEK0xaj|w;H9X7yaj@ylhzsx7O%YD4BK(xti`P5ZJddRWofMs~K`AQg zTOlDpGk=f|*8*=;4gS^vUDm0IPimaQV}Uo#?;4j$m?AJ5^^@a8kzhK3FB)MjfV-?~ zjts}Ta08Lg_w}B-A-%=0jh!5rAJ8Bd{hlSKSB}U=f8@`DaABZ$YI&DxdV}NVmtE^WA*3nGG3Ye5y%O>B7F#qr!oAjR)0%TU)pAP z1Arv&v-0jcBGr-Wsk&veyeuw3vQW{DXAUR^DFJP%hyxCT8AeGew_Bb};GvhVDSF97 z+cQeWX)ETRJKfZ~Gh~BTbd}LetbrvuI(Aqur72E#d@^*;{2D>rT&XitsnbXC5#Qwi zE8`!0i4?tHOJ}XVxPOf>?`oJl$mL$GWAZ$|`l3CzBhY##{KgwzGnkN>9O+xI{EM?O z`w{@!;lh;*=Eh2CYa4PA+@8(2J(8p-O)#%HDmxtsKcyk9HgOw^`g!E_w!5ZH$24IJ*QCuf8+!Eh82Nf;53|7Jv+~3>SZ} zdr@*jn{zXeo^=46SSd~pVdsTq->I{e!`OX@$)g|KEHy`Z>97*`?0;g|HUF@a^(3TC zSr>k=h&m%@p-4jp4iJ{X437QT!hb)|VOI+%Z>d>lqVW>L)0?6>B4!cXx&)tU{kM4$XN8F4ADTdI(42MZl8zl=FxBgvWB^fZgIqL@omDjG=jMW z%G`&>$cB|Fsg;bz^QD^*4FUZ1CUIozI5RVi;N`4!E2N1zk|c>tbJkylDdrxs-`=vM zS6{E$Dy{r}z0p&Vdq!ye7yIQ!rQ|wLON(KIt0B}HSsP1P_Z3#qxf`+CxP8c8P6PkJ zGDK3H|LG(Y{9v!$XWwto*y@g?=jIwE^PlL5G1RJslLMg6$X__yUx+-$7;(|O^Q@Y7 z`8F`l-|QqcR@F3ht^?ptbbfh*I?p5Z3>f4+6|sm)Gm$S?bg$}8px+)rhVc@T6l!97 zWlZA47-e0&f;bZYvovjsN54BL9MeW)IvFH!-ph=c!|}Wc2N~}ldN)jDPM7euG-i6b zXWlr-RPrsBFU!oRQ3%EY=7Oy%tdhz{>a_#24EEEwqS&1GFDLC-2;i`RyykT#`cMo| zI25CJHMgh2tP7 zF>P6?wlM?l1qLo-PV^H@jqw5JBR=?Dh-g(@>};sx2BXsz5N}{^C~Z$Rj`LcCdU4vl zYfe3VVD#vQFN$=9AUnY(u>}Qax9yv5!IWg>_q++ndhDuSFE4UghmhH?0(#Fp8*Y zb*}5aD;JU}lB>D2=G<}7xzf&Zr(bFtb!r)ZQ`qxtuayXL5tCrkvUrV6}#T9Y%t@1)_sv)f)kQn3GPxi=5 zNOyQ*WCn}7yv#Wumq%ys3Z}@w0{K+Wk&bZ;RyV_6lDjh}>-sx1{m?x9Qf8utdEO=S zOu=5cRFj=p^FpA@{;d4Uq&!)lKf5OOzJf<$DAMbhoc;59-5tQkP~^*BjAv2OjxhNr z8&*kXk1wYC&MzswjLw|_4o_g*HV)X`Cd>LHZ6epK6(%S{k}6ImtUG>9T4LZKYk(`t zpoBWc6BWSY*sSyT+<=)ld)LrTnAlA#q(hO~4oG~LGjI|tUUCuX%X5VUxWePiP*v8` zB#>Wkz?vRR79F)$K*{~%zh`P)r3`w(MWCbF!PWGY8i<0vqdag=R^~yU^#d4%%a_w_ zC0&Qm<~Xd4?R+Gn5E|UV8||m#0OfmI)_$YEcq+J9cF`w#tB*$<8pluM*=VS#d(a|1 zX2Vb)U>29gX)b_ci`;i62${DYT^wpLe@vs(Q7hg0b@Gg{PD4882+?f@OL|V`-hHF7ifWZG(j2gns=aV-r1)qIDX8;zClJK^v((NrpzPr#QF6W`2rmu{3;V9)d98mRvyPoCHo@dQ^8EX z*!LN(LO+i)e_nYcewCc7Pe%#ugaZk}4AOCi;j4{;Z#(&Hleb4G(~583Zz$6NP??+F z-T)Ao#!>9CL|n5CX-=u5r1Wo>uXZ=DbFN?46C0&@6mW|H{lq?@do2D{R0up(vEFTJ zRj=I36UBk+6!IIq)VYcF5@HNty?ow=K}XgF)+g-ZfENmqL7!bNZ3Xpm&cGeSr9Hfd zbu9ckS73s7h=5 zI*W5P0ci*jP#XYmTtw>q)g8%ph}*Z&_CJZry+kzwRQl^&$IlO_DRV+z@pq;vCTj z3-^+B<8cJywHkD0h~1AnE{As0KZyXE7zyN9;RQ`%HHM*UTk#f~>`4m^7@ zGdAT_G$pW=S9S3z_v~)gsV8^M(>K*j@%!xf8I|CkQLNxS(G67Pc>n-8GWL`}S2{*F z(>E?_ro2q}Ifo5lC33G&r2ok>Z#?xDI`_@AmszqaaY`l6s5E>W!pw7)SlYi(YUler zlMek>JWrrMJ z&(u}*d|GnsKaw$}>htW5u=D-3qZk>xa@^+t-{)M*9{x;!WWf```*rjhDibIJk07<)S@t1e!O`k$z&jb0MUlugM5MS_{gnUJzeAT==Q;~>G%`+qD8 z6VD%60)wBx?Mg$-hC{29bOeI&Kph5OJP#c>obf~^*rG(>ZcC#8E6YrE23n0O$M4xC z-%{5>$z?1J*VvaLCQ_!V+4CF6Xe`1$t0pB%SDhw+<)kPqUsD3pbHIZ*jZN8>N`_Fy z_VSAitu0BRt#Sss3f*#&lQyC9k}PU~7af~grfm0E0-0vBzAR5>Bq~L5iCbOk2v89g z`o~dQS@wy!=4;%{0;?~V>26LU$G?v`!fSe1E#T>mYju!Qc~a7!H-YXRXjuBERGV2NCs<*4Sq#5*m&9%W|#N#*s@I{aFE8Rt8qACnc9 zUHmaSY9viNINqNr&?=IXCI%YKR~I>GN1WjP4O zY5G}iaWBv47+~d=PW7p*T5A~Y4&av#Jga*yRTi7SQuoCw31%@fBNH2VZiQY;tQ42G6XXo(eQ zFy3G>6|?nBI!~>EovRaO85Bzya3n{1;h9-{bKwvtt)y6AfQ$z+6BCfi*I%!u_uMgC zib zbl1MS-)ydIv*VCjx9M@zD+^)?l(cP`<=W9IR3NJ>x$DC`_!fBv#m1NoI$T<`#1!mV zKZxG`l(c)AYfv7H;!Et~^0&CIKq`rnEL)kpr-kO9FKNp2K(aoFPF1w~79mM7nLM!I zZ9&8WFY?5T;0CV=T|H?`Xgy|C>EN!|)(g`A$dt|bp(7<`EyLL%4X!Z9S(Nza-faiZ z*H>0SpyTZarGkzoi5O}X`OinczfqJ4o}OIBJ8gJYE>8yCf-d+~FuUeIOJKbtS*HUa zzV5i;md6KI9VV1p>=RDUX$w&Iv3UID9wHvLbIr2IUQ4THhWo#e&o;5Hkij~yW zNd%@xil=y_jw*D`C)_OoKdYTfrt(>@RmX7#9%G7aYC@%Ef^7X)U>M)LA#wI#*h z^j!c9U~HHvrc8@S2VDHt-K!1Sl0AcN5!L?pX|alWJ6#Uv`(m=Me4(*EpMK+$?m3+` z9a(bn{Y3(}S1mOwwG3wc!8jhT4wRnkQb3F8a>t{ZZUNZoeOS`BgG^XRun=8s++yG$ z$8^8?DsvhfmTqC1pwg6z= zyW=1Nf40RtI;}lEq;MBiMx{$EzO`KJ(^XB+b?_?vwDG~BsI>CBmWs5b=l0A*>qREAn9WqFXADqz+tXExFlkxGn;QHH;Z&86lfE( zhA4kC;Z~56lJ7}>{RLadzY7MN5us@I>}XXMEU~X&=}y zr+!>5zr1FsXRO1AjrP9zD*@9GC=Tfv^cKj~_}_osd{e z6>Ry%WqP_eDEg+dC3MK$&$Ak8kIDsF)smT4)iqDKsEl!OAB|ILB%7w2RX>)X=Dg>e zr}zV)XZ<$tD{+>xIBnb~3{QCqNp~7!7Ktekbn3vXwW@Qo!niBXePXm3 z-Lx`{>ePPcJs-#V@saZRKn0>=xyw<{dS`jDQmSsogV%idD2sH~$bB6qi?unU_re52UOSPtvtofoL%OIVbrDQ>kmF|yLCEkaHA=Ze9c{8a$@7H->1EZ)vvW3T7Z>~((GK!>FdlI3l7JII znZuL(M6W7KPh;*z2DBU?yx(*Sk(bTa-1ZhGWzw99$n&58#Ew#T_J~saB<>OwonXiz z6H{P^kx_r`B}I8sb^3s_eL!QuV$#Ez8n__U*53{ zRP7*E`L@3N^I+OEDWY=+3kgQNs>l=b zPk1G4=%NYh#gL&D!%CBA7G^a<5S);RxTjM6Xp2%QFrU@8$#dsB&-Ume{8r^x{wM2` ztRxyFg?{JCKtk@OSTCe564+q3JZnv$YqNx+6E+@C9x=_9;1f~faXyc#~ z%hBYUVzNdvl!#!c0+qtRN9_;;^r;K@?s9>@Wa#RVjq3;r8o=W&z=x1N_%5kqB{|G1 zB~eeVJ)?{j%qd~o${JCUQ=PN-4bTlZL zPd${#>Z;Do^eEa`#A$esP*4YJm|BtXoVWiQi zMemN(!{oCC!p>jg*W2}AyK{+c8&GxmY ze}Extraji}fA(a!cKpV$#wyu(R=adS+ru_J5tZ&PY5LBnMRU{sol;#FO$A;0AhF|t zPX}HF$JMc)MC=pN!4ieBpuw38*A*CgRkDsj;oMuKqk7>ReupMN>zGvqi0;Ao+$yPg z513yswbrjt%*BJ7e^P2!;iv&ze=)VPe~$MB?6#tw``@WdYEe>t@>X{dXsGC7LiPte zNCZ$+mWtQ*7`Q^_wcQ-M^@q3JsPXJC{(Y?yIkfQ~MC1Nw30c0hSE)406vnnfUhwPY zq>evc>kpxVcxNc5nvBkPfZ^w-W>$cpcz`_$#zO;!d`_G4wQEE4a3aYFigI}<+(I&K z(@BoPry>R9RryNZOee7{GxEzNv&b1Xd!_+66&~#0&6}(3du#GE?BVr*e8QlFUb0Ef zUVaTJg3dS${T7S(S`a8z${|8EJvvxe>O2Vb@$ZPfEIt8|PB23uc z1D3X|p6oTVfxPckbMeM_W?%DUOZEqt#lr(j81AfKeyOv|&iADbr_&E?j``Rllx>ys zHU16f;8tOeNQjtqasm!$gnJRv@gi(FB@4sIYt?Rc1XVz_3oGLz@c2a5i9{@cfmW48 zLs!G=tMtVQIOIxb8c+`$A*ZsrH=R5;C&hnXSR>DSmuMv?Ckq6OAd)lD3CWi~Z((56*mCG-UoJS?hjb)RI;;&zPH*!`- zy^~UlKU5&$Gga(_HEZjcf-ks9XSK~0Vmn)nq@)s<>J!y!PDob8N9T44>W??=u*sdh z!b^dn08>&uts0p*en_-fpj-EzXwP)e+vhhiY@k!95R45|T=g2{zS~2w{(sDt?D8*Cg+yuQ4rU@ zZd@f(IO0>-muKTo@K^g%==t3dIN;1nO@->DC=cbtlT)3KrzeVoMbO4`d3QE=dAiYYAQ~Q$~AL*UJ`n&x@ zru+Dn^=Y4WnlpOIhjQrCSv@65k4>NalX{IMV>i>(@bE#Ppj6G_B=oK!>#j@vMb77x z%wzjPC8eRJtA$K7kb4}{+hJ~OP2l~nYRgefTDy$i=S4KYWhVhh%NuF1HGnL_?an-z z?{ij_(G{FLBwGLerk)vzj0s1&eO{LS>fy`dCfWw`8rjpky58j&(0E+qD`I6X^ou!G zWI~5IBc$X_**_&`D#+W8KNQtIv~BC!m$GQnzL5SV@yM7olLEX2FdSv)p>32@*G-zi zG%RY!5gtwe0;>n%{n5m`qq$v)`+3a*6giF;E@{Y;q}<#2*t*(2B=dEtbl&Y+{vIE# zwBhTdPoIZ;N=`W6mOr$hi)<5&thT+FtL-1o;<$A1R@ODlxkh$bO5ANYV(l(mcNeaO zdn7_(xgabB6`P{(fTVXrTURq!GtPBRNHwWVrZ($X!ryW;*Vo)&E}_=>@z}WiwRShn z4pf0u9a~4u!frn1iBiuNy~h@%pB*;^JuFSK-@Y=s`2oN2{8`aPcth7q4Df~K8=iH9 z&cD>4=G28%M-uhpEuCcHosY3h-j9!=q16mVF_54c$aPb1QXhbe3}T=)BN@sPmqDG$ z!MvRSI6!XwtN7YavWoP#tbIF4I6R;P)MG2KW*2fkqF%dDVkm6o?Y4v^&nx|??R7KG z2S@qk>z0-8K7FURwj6LfTsT$i9N$ij<%-{W0DUW|G*qH*hm7du#b<^& zuhOqfBDO8)tK@D}WYY}3K^_03guxugBpW;OXrICAM#Z_Ltn}E-y2XWNhx{RO#l0s$ zNj;G*Yhf)tyr3Sy#b>{*eUyFvyyb|g(yf7qmHS*8us1D}aI1!1R<>S=8G+B!tPtMt zSy(ji3xxYi<6s!48%ehsD6l;CMKb3{a1~Wq=|ZNjb6ewNQd@A|L+N01wif8|X!_N7 zeioCfH@`w|Rjw`UL7tj@&BA zjCxCou_RvE8gf4wsJCTFXU#EQ)-ii?~X>{)*pjCk9t zthYtJ7y0QX$@UUtC*^ZIZ$uN&UYGs$!B`@q@fUDb06P2k2A^|}#?&EmiX$+(#2LOp zfhYX#C@Gz>fC&##5|?eCy0o*bTz|dvCTzz2)(M2CuQa(Iuk?|?Osirv%6)gfh6gAo zVItSIMNYGid4IbsTrHyg-@1Mf{zpXuuN}GWzj=+?2#{24rE+ZP?U0c_FTDio^4$VT z>EG;88}kPrR34PXc6)G^FBd$3)yw14h}eIrw+b#;-u<%^-KNHZ_c&zFLg~_5U5?(+ zL`PFt3(wBdW_;0FEbMAtN9-nOHkNY|G-|GdbxOoc->1NiVl`E|UF;uS)D^oFp3^V5 z7)h6A!MH>9d5j_LuhFRH5y`hx+wW+bF??)b>;2qUy-X% z+3|p^e?d0g8DXtjm+KDusaIaRcpWR!GB=1&diEAhO5yAVHL4ax(oNOrsem$$_#E+KpTtVL*D%gcAGx@r_kR7I@uR&)x&k2nU5Td*L|LFkU)zx!)IJW<1{rx$S6Z zt@p8t$1L`B#di~U4Uah`%^Qle-jn_?e5nHdWG$&2KK}FKGV#NBQx%a**h(C|kF+=# z;cop%x$()DTqEQf;L({0G}JP;m(>WcL@Ut!r@;ETvUaNKx}^EZh(2Et6L#Ypf!@}~ zG4*CVJETkbtRY}|Yrrit$2>{R?L4$PftGALQqV%;@Ri$uNYGJVeDT411m!*}zVZ!TfF%i!_ zFX#aaq~?-RJJ@dTd$vli&9jNnz0iM+Ut@+59qY&WF!ZEMmw@@aL&gZ>BTlt;&`T|% za>*cpdKD+LozJTKM*oM`wWVBE@4+sNuJHBO;+hq@isimrx3)`*qefYfE>02`nE!(r)wrlbiWR)Gj|Dz!j`I$B@L=l*xla8@3-5G|5k^>MMLpi0vRL`_va$ z_>e56G+TU$B^aEsA88{6pEG?`FWqL{CTXK+U06~uOh*#LWHf>K3#T=l(%XbE?|Ww` zgI1B=%QOd@GzX`5X546V#)weKj8)nKqLyN=#1h58$?;ImoSKZ242LNQF%WG7TccP_ zh8y~h6}eEAz~xUHTc6LNGKSk%;QuqsSmc)#m;fPwMO339|KGO z8D;h_Ni~w&VAsZ(Iwf%w-T#76L=TU9jF}etVay|ZhRxC6cz6<pXC`7zK<(NGTGE<&~xZd_C4jqWM<-?5uEoxlMhRMW)W~xLpqqh^6cidfgHS*hx#Gbcc!{I{7?7k63=dRn6$|Fl1rPWO?xpd%Y%_-Bk)^OHWo!Z( zK;7v{)cL~?x=*Ka738-?eR0Z$!c>P9jd~tP6``E?YZXu(m0$Ptrw4HHf?1U_!GLV!rBZhezk-o4xUKRE~3TiZ+R~fd0-=_+TFL zTrKD6+(*P{xa3p1hkRla@E+i4EESaE3U|OL25ANe^n{WfMrRR0%_-tWGZray69DQI zwAL$5Ws|8UHZ@uyHw<3ISRHcR@BKHv6m?+SD(lvjS17360lv`eYSZw?Fe|N~+V`cvcORv5PbB}I9-U`! zzkKO)4E_A?Dc%mj5rnUrOJMfP|L$;f=iCsvf@G}GF;USdJHnbxQr>QhGWA$5S_OPa zT%Jt!_-uZ6-U#^GZA_exYs2D}&WITIlsNJJi`TYiTL0xG<1w?hg{2z$Ww>M#ZpT>| zH=@4F+O(eLtd$(w`7-w08^%0Vce&YCZLpIV($lQMbDCfd7eiCT%&m}HGppP;xR{q1 z`$95j4qUgjEduKu1%xl55qx;~GSukZrurCsZ4_=qMErGc*8=kuuxX))I!Z5$xfswh z4t*Q1*p?^Q;`w?hNdvwBUCPDSdY^P(Mt5oGXGf^a#@&98MWKaZQLJhH^oSXLQeE4O zo_XX$Wwla{D#~qmt-!s8jn7FbxRB2}S?IVT?%;_Iri#y@6A@s-;95M(bT;^m7DlG) zk%Y0F=wLurz&yBTEGvF#-fM=j6dLN8R+tH{bR475s=~4`g}Xx5UjUl&ro0|5Q~G9e z=PnkI^XpGc&hO{9%8#$&Xt9Cg&O$COdSGe(bfqKfmNS!s_^i9u?3w%YeA_yFz}aFL zpwNa*7+%%2DLrO{1v!qrH}TmPez&1b+F&gqO!;@85D}-KUA?j0zX&^_7PfWcJ|M?4#d)`K)*gaoX9so>acg@VFtq!^U==!H32lAx_ z{T$ZcQt=(HZ)w{-GZQ+YrT#;>*%^Qtzk1yMVX3NzzxrqKch-BZaw6~9{IUa9wlOA3 zftI8}9pZ;Y5qUP?>6?>c=43Oa`$ctG82Zai!y%yNhTj5ajCH%pitJo(GFGMs^i(px zcv09zYRhdBZt6(8+n45IR}4z`Zt)yn7cy@-8TC|1GP1U*2Y>LAa=1Cf*yNIqzQrWQ zO5`g|-zm>~LbkMLd`shh$L_}_zDC^WU)UTjz-y1)-q_l@P1VMLv@wwuwk3FpwN2X@zyhB&g&fGRng6DZ>N#PJ z%DVWrEyIrGI%g(UG79E7Sr@f#qz`Yiw5!Q6>&1{nv)Y zOWsA?7a5>rxtg$lHw!3jwUu&&>Sm&N!?$y*!3h(obfHP^2Q}LwY?&w_p*!(XHdpMv zhw$t2XHvbUPx*>|S;LZHLe#d>t5d#Dk3mOSlSkTSe=1;tRxizFWF2;?Y+kJ8EqTmv zaCWaV-GqstL27v=Wj+INHz2XqS5;&Mo>?h@$0owK(bk5*l~_ji;AW3tnupbROzt>1 zls7H9IPFln21kSSeoWABnnS#54ELZPK?UM3#G_u`X1v?U%13~g5Ex5*#eA&wF2RjJ z;&!9A+ZEA95G^_J2ge}ClTqE^QQZyz`>x{E2^pXrmCI^a2sa9S&bnfhT+;gP^F!wE zCCo8aPCD`E^O*#5A%G}$gjEP(4d%&Vh0qn%*N*w#OC`LnI$ZoN{(iI?N~9>!0pwbV z_qPyEIcn3tq?{8@MRr_fje9<7<&tGug>*JvWMW^w@L+fXZnxeod?5lpkQo%HoKrtb1D#U`7w`hb$6^^ZoRR)s*sy1GmEa-Ew5fr zAMoypSnvu@e|0*lHf?#-!A{pg1O7Jq$_WPFQ52_2Zsv_`Ep8&1TDc5-!r9P$tYp zn`sa|X7zp=zJ@Ymd8too-4v=XiA1xMB~OaIJptWNAFB%ds;X0Qp<}AjZk^3D*_nLx z4C6PwG49qUy>#`>UGTUEuDE7;s`YjONn((NF76Z#s6x{D;ZNvGB3I z9fr0_+7PkmYrU6i@l8uBrSm{;$Mu``S!l+JXzW7;ZHE>4po9YCfrG>PXM*Nf@s|F# zpLxFU`kDXe@c1Ft8P|q7OKO@OkDgg7l=!fR$f`u6AnMD!n=A_ZdmK*z3J0}N(M^tc z5v%LJ=0?lwEG;5$1mkbnwbRwM$L!KAxz+PE586QEL9?%{RMmEj2U%)%GJZ@|QLtIMBB# z8X#UKUAgTYW%@(+pR40tU!Ef8-q3*s1IuF9VnsjPh4_Rx^mEAgE7el6&dX|NpGpoukK1q zg9LRhjv?|c&$fVD*cA)stlx217j1dL{YF@M1b%<>`t%xW%|Ai2%tWlqw>(?$t9jvS zt1X8Xa+!UVG?iYyLVxYVY~F*=EFHUv`k*gyXk7mvS4GdfopNBI*{LI3n(im!aVV-p zWfF4q8G*mWZ@7~W(b5GR-sUC;Jsj#_li{gVjn6AujH^GhZk%H}R(NSy^QPTAok@Eh`3E%UV18Pbw*IZ@!Er)8vzvG@61BNA!hdHq;EJ*=@&{E3JZsX6; zgy}bT>g$h>CjTDK>6C7cFSnrWR1@-=GJ^O1d-+U1e8>2xexZlr-Y8mra&Xmp#=j6M zfLjAn9zbd$=@J*B6`6J?G1<1Urf;=>9G}^xnO0f*hQZ+gh}if_=1cX=m&*%Tjx3B7 zR>Em{7v=wPYX0h>r)PnD1Q={3SBJZdkZdkb8GS!!cvESSvX_r~UD1dN0?}`;`lMTKqU-RC9r*qjw3(Fk)=Rr-KHeX< za_k(MW@juozmC&OwI(q^PjAWvr4`>vjx+Pfq^;XZq4%$x@4h(zE8=8G=2$5Dbrrh) zGGJA_c+&jxd|aq2nw?_-kf(YOA4o6e|7K{0W-$omF{_F zb5?M=ouxpOHHonU?x=^AP5rH|=5jsNewitGS+AfeYkbMRcvWp-rQ?zDN<(z1`T3Hw zx2;{6FVcEj$rWTlpTstw|7h$oY9s1vP5!*e9fB8bA#jesC$|QAubul=Kzyv7Xg%SO z4ASZt@w$Hh$u~t{_@@hi-oCy|ZZ9hhBvTDuzl)j_)DVISX!N4T^p4C*FNaJ&WAhrZ zpVoJqo-q1vN@dT-7o+qumB=?n_3+Gg0GBX!Wy8=844KcYKU@iHLxH7P2G4p8!#7)} ze{D9+M5hVkdon3J0woeu=Sf^$Xo=vh3dgb2Kfk+|NN_dISJm@Mt7}$d&UTwl!FV7Y zWL6Ouxw@X2g*r%j_C56ao2<&m@R}cI&mh*fEYHa)T^{A@Om+)gUrdNh4oplk*89l~ z&G_yZZS%}@qJVsGc9@aEyDoc+E2n6B^|{0oB20_4(@9+llD`_; zcR4kKS0QV!^a%Ml%dcvqPw5vj=f$U*_hUkUXWlbjp1C>i_h!oFa|)T|W)OFfQQG9y^~V;E%|MPXz*hFW@xxqJS!!mP zYm)YVDqGjtyus%*Gzt6$+E>eUD+G5@vMzEJxYY_~7dI`-Nl?NHi+OwEzTjAcLxjdV!7?M#Fszvcam+%Qd{Q>JWW z6gTe^*}BF56@S6@rXh7N9{&zn$|71I2274#Q*Dar2@kyvBd@5ILHzD(VX;%w(iTND zGyWg4j+N+3BIVXGa?cYTd>ORyjX|oCZpEkbINV4T+|bJae$YxS&>XMLaN@T7#fRx^Bpcr(6lMs!8SHNUPzJKAe>F47Q7^D>Qhlbg#JUd5d&8*)K%479%hxTgqm` zRMQYD5ZWLblKxy_1Tn&)K#JQwn*dr&F4DL8{7aU8RYn?mgNpM+%VZIG|p zs1-j+>^0ft5550sA~{hESHl|A^Cu-4sshW!3&-jCXLEMT!=7;m)!FlS#!bm=W!^pq zAyr7&`*frV=SIi=9B>H-3y4y8aKRyXjxqId#wG&J0riLB$2YE zy!>w@lO(22<)OvBE;@&q?vq5w*njU58mCUg zOe;&-hp#&5xv$?6B_RYt>XFahGcX;cpxISR{LGBVv-Fhr@72#*EAnp$7A zbENjGN^C(Z`!LhVGWVc)LQN{-sc4a~_Aw*Z zP*3J$$1`9K4Z=lDA@M0fd`0507Z=;GRC1(Oje>1qbsB*l`Y>KEIG445F4~adq zUs&OP8gSJL+lh*@vF7;HsF?aelXytFPe`gL}$zL>oT%!ny3{b0P{t}gDv(Swi zoCeFHL!I)gxrU*pY1XcDGGF$19f~L+Nj%4*pj-`9@h*+^Z4}<)tZ`m;JX*R}enp!$ z$H*6t2!qvZEGpFz3<()kH#|2OU-tIOJE>zLa3J z9oGR~$pOG@6CmQbO{9GfohtBw3NN19m3};Eby-xRPEdOEwouKa@zVboIuC!U|1XZ; z#l4q%x%PFfd+qJoGw!wbCPLXEbV*X*%3Zkj$fmkRvZ`w&XV9 z{0E=M=kflW_v4(;c|D&(y3tK1Gw{SbgAnh=jyXlL%YEjHc0K(w+M6D*;u0<@$MXgY zrCT1d=+$Fgc}x1Mi#9GVVD8EQ%@=pA>s^jsGp`DN8ZlFR3KQD7nqhS@CEId=g3PEB zu90qvhZWab!|zUI$Ju*NO*|E@+o`J9$j`ZM3OKnPo{ed@&l~HP)^Xj-HTRVlMw~~s z`Ont;@fI)}-W|UEO`|PLX&pOS?9Ph+#jj!#hBUC1S$}>;b_;#C$ku!e>Ax6QlXePJ z4SYvzO9zQ$m4zm(&uI4NnED@IL}Scc^u*;P8l)ou#EXT|YX< zFJ>=>do?vdKU!kpaeE~groQzEB6^*}^CGRmVoLwVEACRd<9F#=gp{z;|Kmd4BbT@TV1mgP!3 zuWhCt1r1p-EMJgO*3DtNFc~`EQcRoC;XkG{?uSaA#KBXA)w-WEfY*iW`_PTjt`cyf7v=vPJBl7F#-xc0ojIyt>GO(q~LBJ^#cfuH=%~$9CS~ zBwwiBlnYnGZRD{&TO9!+U!m;H7gJD|j}3szO|}Jkn<)I(DyEp65}xdQuur@{sF^p^|zE zUXj8~cK8FxJb`Izru1dQl>*YXhFO3O`xCbL_s}sqL1=2w-1oCZvvTbeSrAqKqP9L& z-8IgCpm-C4V`p3uK9qRz;GSm?)VWT4TM?L^FibP0w3!%U@Es!$LG)2BEIbQc%IA^A z2|Q%}hv($6p*jO9oa2i2W}r)Xli_);0%}WWdnP}dSJxGUTy2`5f?Q@c(xaEC3ro(e zpmasos}~1qw40TqL2hK~k$8~CL|*zuYl=zpN}qDfPcP{}fmpjnuJea?-@ zMZ=h{{_;54NX}?L!a6#1d@h1yHJ^hP#)@#X9mWu<|l)jHNK(oT? zjc9^qgNoJBN(G*A-CP*_!xFX{*E$IrZUUy^TL$T)N@nAhUp_*biTqn^p4ML>WbGn$K8J^s!;_2h6f)p`uM;h^p=;?`d?o3)}O zCAxcqzqLVm5@aQUqN(u2({0CPf9+jVdc6yFNG-kfjcOELZqyyCcFe^(>uL9zYC@)3JGJ`)$Sq!3Z3fYu z`spd7Ny3-#T;6S-<<{yupji%@$F%*uO!A(b`4_jk${q=7VrzZZNfqv3eEXr7$UZKs9DaN*@;Qt%hF$s$ydDVY2&eeP*#3MSJRrq03ExO;UJBQ3zww9e>Dt&jpp11r0Sd0}2ULe)h2*b{e1&gu-28|p zXGRIzv=H$jr3F3^U#NnhyifoK>!0muI&{Id#O4$Lj3+;wf#H&WV+T|&MnWPCjC3o&y?bu-PPMAeb0FKTWgXH zfj8E3(9f8WLZ`LWbzx`fOzgZYZU7x=NF%b8eXHSdu(uFY;9(s?5i1Ge8ksFPLXJ4x z+&`rsoA4SBJwuqqHG&HZl+3orKqbHAu0`=ReU#&i%^>`e+f35GX00zAs~e|}y`_Z} z1PPAZDro{<31(XP0{OXhGL`^qv)C(XpRvINj}xxa%q)eV*HK>CW!<><6L^6pz#UJm zeAwqk>ZCbsT%J(>>-FcDx1(bzbX*ZpArknfkF80ksAJ<36Pal=YhUmU$0<;BPq+q3 z*V*-~<`muUA5^RHtIA)fMrr3CcF$A9;pLH+_$HioTTmn#sN0CVrb!FKM<62Ss!9Mj zwD0MA3g&6I|Gl^G?QPNtPvje6dr3QM-_z*3*2XA&uJrKiDlhlv>~9ZUSr#Y{5ORO< zb$ILX5Ov83CZYs)vm^qS8nyoXxl)Y!*9$GuXOM+yzaLFKHS4VtQ6`|dl(@?hX)V#p zpOlSuIT8^fN||MF2?cE#C%CZ_;qg5yy*0NpCl?<6`A<;(M7uWT}9u%_1XdP7aX3$)LCg7OOE z6=jhdz}nfCpdP^7glF66C6DiNzF?)0@L!>j*ay|Wj+e;mF$Gsfals{0SfEe}=Wq09 zB|U~p#tW4kTdhw{em-ZbxT9L#RQ1*+^}Vj$p1h#BJbDsu%tAXYFkCBV2NaF#lI1&C zBHun(6cQK!zhr}#*ou$W0d&s{N8f)Y-PbRU32SmGw)RV~+&2F1EW^8so&$bpWqgxX z4tL<}gPmw*158JN18`Re`FwOeIto{^h+xkpN7g}3L|~5E@cx%^!t3anOa;@J3BB`Q zQi-#0PoHxhn6Ox~h36tN6X%)VCnQ(>14h+X)elL~A0cQOghC(F zI|p~=P<4!cQ@(?Kumx810z_osv{Lf1e;Zu3?LA)G#UBkEs^O6s#7<|G(2_*pw$c}P z@~r*ofxF2#V^3vN-3x542(54-BCP$ zu8%xutSTXq=B&LO+mr2Rf>w?TxBAsr>EyoKd^HMkUah<&UhkAk%-xDR5GC@j^p{{& zw7sS@R={>q$but>3H$P8To2O^j_2au+tgiN&D;~HCxz5A=K$CGhc${&ntVmm0bTKH z***2m;+D&60`g7kpL&Q9_WG^Krct_>jKq;f35a^*(l^5Zn-3uABd)blycZQa^?tAW>s-5w7qM~8`(5@2Oakm`G-1qEx|znNYL zM+E?BeOk=Lxpfs?alCY5ei4F&G+VKU$M{eOVbTKOPi_z)jl|Z&r`m#nTIFH?#ILqw&@>2-Gc?S=Fc_7wFnDUJk^?`#T(C2 zzExMPk5)mqmj!74uWaV`xz+0PAkR$dk%!n$?xM^=*oQxtex2dSBojofiJ}Mnzh5!O zf}R`#y30`@yfr&vgWP?oSx=mHkajy!?3|welDFn9akS^lUj7El{K>Nqs$|%j(#}w` z{*)-^rhw!AWbAW48$awXiK$b@c|4zrKbq{HYN%>xa%SoMZ$FhdYPx^-^R6T8q6D^S z|L?u!4=hSoqN*u=^8-Z+!Tlwy-0_x9k$`rpF*@pa3ekoL1IlMPSwC*m5h;_eHrbSy z;fa6$Yz*o1aOW?fW4aazF*65FXMPu|^4AN>vH~W;N3R>6sg*W2DuVl6=SD^i?tTCA zB;*0_1YeEex^`gnG&g#un7IBVKJ3c4wb5^M5sw+&OZ0VHg-|>1YSORRP)Tgg*YtHXVd_GY@ z@fks+6krSoc>{jZ$9>vxAP9GJ`Ey_TlYjjIb}N}@z;%mcDKkyhfDUpiIZsny!deU^ zT-u+n*@>s{>$vU@Jk>#5-oc3s`(`|~QW4nB)l@R_MbI>}{VV*f7GR_pmoViC6x@OI8-HN2Y4{N70l12f8xJ!exXut2aKcd3 zKOiPHWARu*wxL=`V3NLrO8lx{Xe@U>!#*9BdY9eGgGz~5K(a-_qOeZc3q%N z&GZC1ykU3vSh!_N4+QhzQyc_y<7~7>l>K%mO;xseivb;n9}e6yyQi$7GW#Nl(Dyx? zK^k-Ff7}&}O;B$iRJf@X$snhrvm{aj24v2;2W~nLMLhp_od@TS3OVFz=g2`}cmy*Y z$rbc>kqz*&2t>a{$(x)VQwtR7ay` zY{+UjhT;DL``Sd|DVAH(z#~3|9TLU6>hh>xPx2#{2-H!I*?21F`IT`mLIwy1kxs}e z?vSsw%gf0Wr0mP~?%xEROMsgTv(4T!CW|S{C$C=;AGQW27~BQ>E-3bO9Yx~Y#g+ox zj`~0$#OOS z%CLdH5wkn#39mIer&>Q{cGp|{c#*_qS%XMR0;2s^FSxU|km)R`1*>`4KMYlKZeBDX zP9$*3P9haS&`CYc+RA!tne;MGPY$=VZFNNcq3WQ3ApObZ-Oq2V{E_?|D~^7V6kt`* zZ7)k;1CSjNa6`i<43v2Q%!;ffg4AdzokV5+;DtO10kdh{(BJaHx-f|&SKem(|IU>_ zx<%_#QBP~hg+i4aX&)1h!UM1Tbmk(jqqnzBCEf4~D$?us&ukgj(5yTEmgU3r#;ts} zC?`E5RJf!=Wye-e31d7VVD~V)i~|Y9Z^>03ZctyFQmF^z@O)s<@Th(OLa~9A8&N7N zSLy&De(@qXJ@qdk-bVyK_llrh7}B7C^%l%f`m;n?#E@~AD4fV&ADu8Bo8echDnVji&p$8&sX9@SKGnac1nBL+{lGIp0uuP2$COc3eUTcW_NFjGsttTyb^Bm3JI=$fTL|!|f zeIsn1dGSls)|qGRDKe(57?}oc*FM966!zykD{hnI>LvZ8hjMQ_PDLrrS3!eT`U_Hh z?X*^6rtBdzj%k#}5SXwa<(HtVYyJ(e*MR!PA6!#aN!_qu{eDO5P%5wz08vPQ)nW#k zPKFleF7;;W57B)rECzG*vqfa7W(DcnEdcQJtkuh%LZj|QDcTQeT`#cz&KIv!1b3If zr5`lCZJ_oQGycGXe&p$PoNwR^`+;w)bGzqHRt8ct`od?Me!kZ#%-XVu0JXMZ`=+Bl z+MZFWXVjxxnj|6^Hg|c$`NK-;a6+(zxa>`8=Jt8)rGuYw!>_1Ih#_!c8uiwJys+w3 z!F$`SYgTiitnUH!xZm$!p+^`w03w4zJiEn5QevQYuVpfZcs9g$%0sl$|fqn6NWUZ>MI_0>s(2n~13 zCRSBgUJ9RxR=;vlY1{1jNfSv)V_*M(7SOX!Gt-Aw!GQClVHu}4^JtQdzvXmRf5vo_ z%s0NkO6W5PBA6diAEk9fZ|9af&Jnz?*M6QZ{iIB~^y7}zN)#x4Y)0;4I6D)?O5gqY zB1PsC9(Wm}A<-}+Ij;yQaIfbMm=%auz#<3B7;VMrX zbCFbgm!bZ$;r51MlgzwYJXQOmtz%;{tOQltm=*s_{`v#qLtG7y%7wpjoP!?v3y1uQ z#>Rx?Q#F-y`8Vq;2Soq)-}AVv-jo>dE6@MgETLPdS;m#g>q?>)ekd7*Ub^ee2{GAu z_M_0!7bf|hvEDf1ErEzR@Xi~YIp-_MIz0>YfHuAqlXoRLB@ zCii0ElwBUpY9Oa!mPD<=mlmEJ5vNhHma{3Bey4CrDKA8{Qg8(10jj)he zL$RVW6Cy?e`*Cdavcv$(BA(lV5=N+yqW=IhK1u{+e1tG*De(>Va4-PWW~$i!LM#GQ zf=Xfk^Mq|Q8DCgJ==~P_K)6KH<3X??Ava7EUZNP?q==6u6iBi9$9)*E4xXf5IT4PFZ!pLebc5)!5QZv5L&t^i9)d z93p+H&>?^amMQks&!cWcZfo00CqpBkBLu>BlPW?6m*E7uiLUp|5 z$axB8-KFbBs6U^40=9f>(GeaW!+B7u5=DMzHpOM?O>k?fG5sb) zWr>qs;vqoz$R>E4#h=I{9^obaiG)CVf-!IS5!qssRP?Z8pvW-k97^0n=2?vSM`C3hxJnlcTe+v#VF2lZ66*2z`2qvR zxB~j~32~t-@eRIgJJlWu^aFbVY=t6%&ik2V0^b6zFMf>3TNaBpMaSO1%#A&d&llX=;&T$_GX^k^HPHU)p4;npUtf`fPF2|0Hrtefc?N4Pk#im>m4e|@J{CQ&yBk}*XW>aniN!qRSJ9?PU-t*{s=M__DU(~Jm^1CqXl z;>%}C|8E(C!2`pgpkgEUKcHavgyn}VC^Q-1BrhlWQ_#5xx{bT~jo$o1k{&>$Cx$|3 ze9U`fX}~er2!fC@9&}Vl&wWm;7!N4pAcd*8qjSgW_5`Ju^kjdu$c*E{=dS5^%a2D} zkFmw=2I1b!BrvWex04UPEhwmbGq@w@Q%V?j>`N9BQ_3ZTaW{eb6zGvMhC$M;;1!U9 zGIHV#$}yvEepB$OhG@nV>Qbj@QYi2g8Mr{m$yLC8-1B>%m80-i4Eaa=#1YbM4FMSEmd7+lO$8?)4nA?O+F z{_>K0e!u&x0eeq`p4n<8dgbWgPl>uvdU~G+Pybmdj$e&W+9(?!j0F;k#58F=3F}z9 zGy2-Jp6e4hwR&9gsS`0kJ+wE1yNMFqQM-ViYDkWw-=7e>4Rs2nC0v|Cxu>*`&q9|faA>Efsc3Sch&oB)NkU1#$Z)mvrh8U$pQh+5uftf4V<$V&8`??x>L|R zb-W5=_fGWj1OPg@96q5U{^fTl`3I^LUaT8oeCDTMBQwiv=_q;2?@;E)odlCRS+Hp!? z1){OZm;2{<;4~fSt-y^lNBIYc5q#uT^b`huw_uZ@!W@2$WCbY>k1_k+&!j8xoJqY2W+$GMe3s=^a!D*GR(GH8}bj z^`eimy#jdBW?!y0_iSt5jD!qK!4ynTS2l0Ns`G-dO+k1F=dVRYUdktQK|pcJv44UQ z&PQ%VmM1wd}+NsPLx@K zZxtam!B8|=U>4NRWzK{9q4|dv%u>VOHcIS1+lPT+_n{yhsTbMtF1RBsUjMYyMdV8W zFGNOEzQ^uD=fY)>9}yVw9Hq+T6g1@mL!+i^KZ^v_!lSG^_ z3pCDpAJ`!#vMnBz1WQG!OH&f+z~-0V2Cx}<8FdwfH!57?B+X1QTF`g?MhWc@=bT~< zL`*`rF6s-B*A3sqMInqN5&dLH(59d_^6`Car~RKr&vOO$nXhz;_)UuVy^o-Riki6M z&!I-+uiht1my%x_CRp`aC(JaE;(xksCWH_S-U?+7qWRs? zS64(NgYr6-0}8g*y=EmIJV#$6YK;7-yhY=k1UW-@1mEolULesI)*pfUQCug|m2iBM z#^q!04+KT=kVQ+dDE_gh@%U{rPf#d0+*7c>2y&6dxAmCM*ojwWif;(-K9CVRIPO;T zNu->hv(WFhx1PE!s_Xd8YI4}>5JqQfi30SkEPMp`Sfk$j5;9}=O9y_;UV;?i0f)Wi z#mEe4xO|A0!ET@dPEEf|{(2ZgTp0Nj(s>`77wh3gx|huI`M0UdDgD zDq(z>p$>i#{SM^f6iSVkH!l425`dRL6rKMfqM#mOp9DPg<|4?dU!2RWdirkXl6Lw= z0)g)rk4SJ?0>a)6Rve@!OrpHLyl4=)JH>OJf*;CAaH-C#2unZYpJ*r_O$NP}2s~HW zD6CoGYu>eFli?NE{9breDH+D?d>wMda+#oe8mql3j_{2_wm-wy@-J-(}hTxzbd)RXVnJWX^YG&+|TYACj8p(?rwb03ml6(_$hws z&}jOgGfJbOt>%NVeF4jER2 z+oQ6{FG18Twbk99>Qmey1Kj|d7gmBiT)N@TDD}p&_}p9vXSjgqa=hN-UgX+HJ|B-M zcJ6UV`{DZPLlFeG@W1mk(}De`Nr!NO^u&~X@HvE+Lecs!s;s#I`O2Y6TQV8s7YlgNC1nH zjChoe*-@3fg@(Ak(~CR}=N6hbAS0T3#BPXNbXq!@$EYP!$B5Z{AHF&u5PdU)Qmpj%p`d;EqKob0mdtlx%WvTiS^+B{`!CRiTblzLp z>NN%ZOkM^;IJ3P2Ry}$9@xj~Nj>oT$X=ry{&>iq`(Ynzl(RZ-eaYxBrCQqwtUVVe* z`_|f~W5D+7Ev7vzhjU$?Dugt+5D)$em>(kN>nrx4uyE=zsinK=(aSagFZ^}G#V#aA z{Jt)r9IIGFh-xD& zhCMZ!lG4sfjk$eeUwH~h7XmekG)I^ehXXun=dvq!RZ5E_cyRkCZ4gQ+sKNBg9n!~Q zJ7Lp4cdhJiuwFHSe9tKvB-eQUR4E9l#rjxFiY z<;=o}PUjKz)}p!`Y@&Yv?XeuC_NO26xLq5zAOEDyr%!o!ea6#^XVSX|pn%XCK-UqnGg9#DKcd6Vzm&k%V{ zc_PN{R~B1>tLR}V&bF$NGPJi@W=)+Z2zYDRILQph?aq>@W^30&s-hmbuwpR+lIjWS zS7*}Ki5|FO{V`EkIOxXOdW@aVn5ibIAXl_e=17C6R;-fyXWB8y8>cSfbmml zQ2v83qXZK$h?_xCljo%_SadI)*5` zZFuAY!ya*a$AKSFpXKqR7(2K-*pCUfT&3$=Ivxs6)+9<(eX%EgzH2%^W^--JRuSsF zGx~h;hw2i2nE%=RLKo2yUep*NLf`4Fhq`WWY0{*1C!mUfDXlAwMcKHnqC{*XRgl1K zn`GLcavlXy^Jo`?@iUaV~<%G-VJU1 zCsw@8`ZjK86>R&4L47J5Jgo)i|2ilvV0mc_d%u@$RH5XqoKR7UanR-~tDPe7{APdI zkS3@r{%2J`v*g_CQ_pGx7%Zz*j-UuZ?g4YRXjL@w($Cp9MuVm|rS=nb8*_XE{Q2&l z{d5v~8N%^^VX!7+1d#U_%1UNikaxm53Vw@u)%ka4h1iOO{KUAsx=1`HvOJ(~YA`4H0S zoiXhj$Qh`h?nj-Cy8rZq^XFE7(-FVxd@{F6Cs)!_O0b;@3_w~0YN*oL@0!zqmO;?R z4aG&6oeA~iFo6>(Sj?ETW*l4*lj` zYMUxuyQ$?HuEJQ|li8~~hJ9Ei*Ym_FKnwjV z`Nq00`tnPG@_*Y<0i_0!!_Y>y+&1vRKD}xTv+}=UFV#(j0naw1^G<1Yj7@wgF*ke! z{`;F*jaa}ia!CVm-(b06uCb04ffk}0j@t{>zh77oIn`=MM+mS z*h!TtkB8XAP(yIaR4n_GnzGFtNE54ElglM5A2z~Fu#_jjf(_x?XjwYu!EU#)HE`f$COtkR3}8kfFR5Z z-6?1|KS)cvenlOILX~{lY=+}2`0=nf80%z>nnn_^mJ0B}vI?eP@h&hm^_p}~Am|W+ zwo0`DSYG(Ty1^PcRJ%DZNPUv(=xl{YvUB2BY5IpaR!-)*!JNjqduGRgYH1Sx(ir5Q z4GvX^$-z>3uE||M=cE@m@5y{yCrST7XxYn_Hjl;P0d>S!0LXX_D9Fki-b030<^`!U z6gjjY6QC1|B9EnsuL4MUfTN@g$uXXqQ&fRDvFfg?>6H}foLE{n0ij4aG#Oc<^7VWi z^k4<)FnY`ml*9mK5Qr5!%?zy3t)L3AJVxS=0$UBn&t@L!72>oB;5be2uqV4~e*15z zO3#0#YD{JRw8w2QRAtM}PCd!>&g_CU6uG1(L$6tD8-WL~ zTXohg=4PH|*4ysz#x~1~b0})75MGl^tsnK~{X;j3L};oCE6Y+Vv<^pnrXE1S^J0g| zUY6J#fQo`ukYLaM6a5Y!@eH)`)m3x`c48kWKANeWDi(FYlpQC~#!2AEFimQ6o=14Q zhaOdNj+(A|!%#~9bR#=u9}15jO&{Y4@q$TXM}yNS!BcM1t8S3)s{&YX{ez4kv@ESR zLl>KQ8KGp0@82()h~ncvOnLt-E@%ZCWJo_cHB*5R6_ir+xaP#z;7Cd*$u;|^xkQ-v z{AYew;n7V~no8b1Z*UbC+nmvt^VQpenug`;5D`@41rHCLh%xm2i;^q?N+b#COLKub zj)TcR@|+}d;hwzWdO-7?hxC|AOTnYa0Sr9J*FL{Lv8nQ68)hu1i(bsADz67g269UCeVH zueN0m22%9r1nQ{f+`-ICSJ#wu#euMPsbn7|hMX zN#-O3N@ehVRl6Ugs`SQ$eOeKwiiI6z1F310bn;6{l7qwWvvkr+N%Bi7)A#IxSgP&w z^c~ac+#4fbM$N%lW`yh7*lzM)+HsQLBHG~F`GUF*`qrYtZGFb(CbuF%z3^Dt9r_q8 zSJ>aR7bq8hGrzripJi~)Y(QC(>>V>@*_*?w>(p_<2{GVAUgD|QL1{sX!G@j(lm3lt z)Yov3`=J*Vsr^(6(TU9!{%EnsM=^P*qp~>V7c$u?1PrO98%$9(fUQ~-qd4ZY^o2|k zVXi6;pFibHtz;RIGNGhQqZpdWCaAIquDw@ZqiRGY&pcX?Y75PAMgtrfIUUCyM-mQ& z!GMTLGX5%{<=q|1@|_?2SMTdF`dj(^vSBlkWzQb=I_l+3O5TwNjNHrJkEwBgQ{EF%(9ruBGL;C0D6mYoj&GAWeJ7(Q6ppoDvxOIa~zH4 z?%eDktF2P?n<0{8WJ}oaC6FW%#|URLVLt}g8aD7)NwC|L)`InnUlmkKQes-Wwu-ur z&g<`$c$&a4CNU_4k{94<)2cdc7EKKei|QpCO=YAz(hMBEZZ~4^hY=1WRPIOJTYZM2 zFe{PMlAZ>F6=j~Qtfw--{Agnk&a#8$LYtdY<#}6F#%DI8AgEqDnEkjTXXbK4$9Z7x zJYNZYt?S&Y|8=45@3E!LpGiLoZ*&yqiVflsGr@TlN@Dy4cX~+n>a((x$-_FwSZseu z#g2B}(04QpdN;|VNXqy8H@~nk?}U7ZtE9Y_cBheiMGv6F9jNE3!^km!U)iu4jDLE? zYzKhJXg4GN*YsP>oZ)M5`it`PT>o$a+5Rb5sh6_I0xYkNt;7k)!G&#C>FxnE%vhFV zD7j?eLEOJW!TVU`t_q4+m47}0<~#)xelwGgExSKX=^Kw2lQfPw`cwoFl~#R*SKG{D5`>SW6?_H0c=lB4j1Ve^BEW?Q<K=F80MO4z<$$7)lW7lQTh^ z7;${foB!mgy>o_a@^*FhuoJ9#o>lN~Zskh6>PI>lF4{Tq;o(rLg&DYc(Qs~qB5nXf|kN@9lR7Z>{b@)eUrs{I`;kD;SADr`q=Tj=HpK?AL%7!+M1|~<1_r;KT=@^#1gKguR@IG0K?a6 zl(md@4L@1G%?Qz!hNGp!-QV6|ez;xVanNC7l`L9Q_Q`cOS2ki+{->+yGY$5t=ws_+ zgPwZ3{O$a1Wjoc+k%_16#1`GozC20MRGe}wV09i|9ycw7il;f!^3~+DAvme0Bfny^5X3-z{RgkA=pTmvhHX zoL#s%SU4^QGg_sZO|1b{Na5TyttJ0;Z;N2omFCn4;k9u-J;|b76&kjK&HvEx=hL^%-Hf))E25@QF^_(e22p-^^vL}peq$D$GewKp$W`W@}A zIhF63;49snLS<9ypL}k<)rliN^Sl~HX#&ip`O?yWx2{uyJ1M%V_tSm3jAlh)mjm$a zpMA2r70YQAKh&;gi1L49d=D7?g$Y~;eDZzW^T8=I?XR;7hLyDI%A40eK~%`JV-K=3 zIz@Pngh)6^ejOn7fmL~qNgmi%ZRKE3|IZ_M>NiDi-)0jex81OphHG$Q*lADG9Jfl2 ztvC77)0q^h*33hww+aD#awwlVyG)6j^3!>gSt6N(a3n} zPNw@NsJHlsTKb6&@=Tj>+hfhr0bMYh0qHX+b$^&``eR0UH_P*$pxmx1KQ?y}l_}RI z{osPw(ZGaWRWWPpk^FfRUS5V0cWPU>7m{B?dnDYa#`yH%i7uDBy_(JI<$)ALafRN( zg!pVVK?m8ynUxPHKGZNF50KeMjyy)IuN@BgDEKdL zO`;+s%zYjr_RleiT8uy`0-|ec#9pV|qLUIcIwMzzGG*DZd&Gl#1Ah887u_UYdnlH; zzq=fjl>rw?Nm=ua5Xek-7dH?4Q3#hFb1jhh`-fjbJMc+PlD6{t*5{|?>05ihP8keE zJn=pCIv(>TX7))XC)~Owdt;_6_g`}IST3F+qFKo^J-iGMk&4|{I4Il&6e|DTf2U9I zs#rFtR(rriF0}`y9o*P;0M(bLzdJbcvo~SBD|Yt1$4{=Rz`Rwg6u*haCPW^>Wo?FB zPT2qX%qofO3Fa*w_kL?t=7h#&$X;qHKlUIvZP7~NTe7R3a4B%e>TOJOou-;~X}Hls z9v%SWR)#`hzW?+oB=gd{(~y3iKOQs?1b5RAxz}GPv?E4c;EJlAX2ok z3{@BANrdZ4M%pBL@;tYbHB1KB-k+SBcDNniUxE%AqYpWXK8zJ{0->o87e`SX85yu2 zF^fWZ6+N}r%T8MLlzMb{5GiTCS@~SFM1w^72FuGIeQ+h~Cd$k3wN->uX}JTEbvI5+ zdh*6mS!Z_jDAIU?MU#kMjUh@H;_=XkJX0?3+gQ|FUMcO0f60r{)kj9&#dW^oeHYtz z=q5cqm!teHUhTPZOH*uHu4#?Mp(+^~+qyp#fe3w!^j(H2L|=c>baSlKNpNEK793WCxoe z;yo7DTzC2R4{fRQ;B4I*#Vc+(rt7s!&otrzR380rl#YSQ3wXm1DY1T~N}g3IQ4R~b=yBQvO@@@msY^f30c zX8c3f%$g`vV)w?q;eb!`_j*mQZ^VzfrX!>m+QYK(HD*U+Vaj9t(InmR?6b&ZNo)*n%chl0}WYioKLo8t& zMMKF_x9U(>xpY{**bY?AsKmi?Mc19i$NRpB!*{f(4%Y9futM(?a)e_G(*btKQoUk~ zCfjtg3;fw)>3%wid?lE|ZyRpcr(2{g-?Qg%%=0V_{L;?vR|yfa>!EU`3|{T=!Lw zimET{o3?;iT(1a=H@zc3E-^owO5wYf%sZl3&fs@sMa<7(4VYex^h5M2cE$d=v*Uos z1?O6s`LRrslsp7ORkCb6Fza}^Jo5aqT_bxD8Qb`r%?bmNg?J074PfK_F@}x7*vtA% z#ZKZg05?FmL}2MG5Q)$=U5f8{d~YFp9#I9oe&1 z`{fXeg9x!Uma~`ui?6?4t?)u81)a&tNRJQWH-e4G2<-~iJv__bU%76jDc|BLlNB?7 z54maaX59T?`AXdQ;HrgpTY9_rIo1^ytxazUB~=Q#M|2o%!J&lAw^oQ(4q(V)?Gx;N zZa`a-YxmII$yt-1daj#@M7_fr>hDoCV~+gg-yjv3(pA%?_AK&pKb*I=UpOj^#uH

DB!Y$p=844}m}Ap6x&d?Y{^SP@8g1o`6hPCNz#r!N`#^)q+V#Q0_8jY1Q7C zZ?#xOk;;=@RB3{hN5*t7Qn;!^3LC6Uv07KVI7-p4gL^{AmSLN5#xNj~(k? zNxs>bGsoPNYR)JXQcdQbXIk#2wmm zp}f)GsW_@N+PB;!n7347tm`CEPQDcKuB!yB&V%h2ypXY?gG*QM+_>J-r^|0gsrA0P zrQnu9NEH@iw!n${BA&h@#JeTEyB?|L^OOMIAsaaksvT{4YG-w)@>$8WS}CkzpYv6g zH$O1&R^fpIF*+!T*?OE#9K=|Ao@u=LRpRrPibT1Fu?3ma)5SG~kzKz!tE|4zdQ&{j zc)ultPnM>YE@&Rgw%zk}_YCC%@@_({;5PAkne*ipP?{>qC?RL%(a)@0@OC(7UAWUw z^QOvs(er~t)x<#x)v4;J66m%XZX$8WtZ^QVh?-Rc)+au^!UZKIr1UGCA?hj5XIZfc zSJl+R3t_my3@Y8G${Uq%~O zhwjh1F3+5BW}C`C3XA1U+yOf+)08K>PWCCA$Xa%+PN`-T73xDtjlL{oZgZvAe_u~r z7~nU3CvEbE?*dQ0@;Gwx4Z;x%#tTLe-*S614L^c|HrvN<1b|CI{6=~N9+OA9e%U^A z^_y+dDU4Rmls}YFOld~S{tWSxUsIC8=&?ckQy;SwQsV(rbrZb|JEK+Y4A>Y_S}Y`+ zpFRe1&}!M+zwJZy+dTaqn9oVq&^AVW`-_y)6Gb{(X!&$@{!!hv4TsRd%kJ4) z>LOy~(Sf!h6{E?_ofusCCq6_KaSaNnf#Rjdkl;si`%JMl*9~4`EV&rRU@6+3GAeEg zqEvw#eG!*OEs!qPA8<&Zvk5?HCt8e~yOSrxl9&;|a)h9n;_5s)&2g@^M7)<1&?{G4 zN0VL^=*}??^5n3vX`mwDtzZ%;(X)*^60c3!vVh~d?ZK>3ME8xXC{``-Gy?48nyUutV7Q#r%U_`y}?<)lfjyAt9 zYlpa;jJt>&c;TF~-w4aK+bkC8zjv->^vwHY_q?$y=cd|#2@IYYe?M5`N;+`lQH?!m z3l4s{|M8Joa8~AK-g`TnK7u{~42bws_a0Ffl;v3achnQM#g$;sqSsz~{10S8bQt;P ziP<0&AS$djN}GvtrEg_@kwcK~3$RtHqD=<48z^b!Q{p&Y?j)BT7r>Wh`PI?NS%uSa zj0$$1lfUCzCtyIrTe|{?B&7e~zn3#NE(IVaLV>EbLKWbVGOeuXTX~S-vYyQ2sQ5BX6uv zMPVJRyd5cynaUJU$W-|5#LV^|L&kfu;}gSJ`18@8Pfv{6AEBR*iE?f~kgK8lX1ySL z!^ZC@U4oced@pS#>Z5}!`XvTg*rc9w_F>h@RVJiY){#S+WGR%j05TFB<+T}xHekk` zVP}>Dw|tl~vS4b7Cgk=%%yVG4dMiX#Lugzm2E`~Upn_0?5CV*AY)7a@(n0jE&Hdta|GHmdz!xqzV znc0{S=-bP@x(Wz#oR*bry2_s3KL8EnLJ#L)ERrB{A$vtn(;~5t)j1%e$=)Nb9 zvc8SFCU<>yR&TybgNXMs_r`Ojx`6trEd8Sp290IhJe*n!h>_%H?m_d6**|T8@-|G< zAWtxpXDNW6BPdez*@!M?pU8kKHa`{%xSC0L#%c^Q$HvBwP&GyXPnYqxH2UovGawA^ z-e3TK&in62Z@T)PolOtl=vdpY3=H6l$^ej*Y>(HTQ&Gf~5J#t6Ybud$6=tIWH?$ar z5E&rSE(D~mqvS}}r~w=}SaRSXjm6B*wCt_Hq{d11)#!NIi{GWf2^s3iR53-Scrrjv zk!RP%RsYD%>XVzXb(9urT>`FlqmGl^o~o2(kBf7 z0(>2bX{!M%JdG`;(iCH#A?%b>wp7bI@;|U~z|bw9vP1q-cjsUUEsJ;3VB?qHg>HxB z`*>76X|r@X-z9P_lOi)9fMz8=1LNdUz=(KQULWG^a`Ckk+wGMUzuAQ_WmgMeY_z(> zHH%SM0NQ{#URRp`CX=`HNL&#@P-Dcg!Ac``QPq@O6-w@vJ{=I4Q~odS!$6I0HnCrG zi>kt5Tap~q2QneGJh?XBhs#WecAscb*H$LDScvi6D5dl3G>D*OQeQ_AlRR51`L_1< z%A9lgOBm`v6`iA?KU&&NBh*#6K1fN&o_4?>u)S}jQKYok0KS}a$cAcy! zqb}}@SHfVWcHsr>AbU!E0MW63d)>&X5uh*yY)=H$SOFC&&QWmr&JwMyclxsys>zn9D|z(p^Jj^=|%P0*PJ3A?R^kLHKmS2 z@qOy9OJ_De>!%JpsLSc+UX01kG;|Jn>yCzaj-}i1(%S*&^sxlTv0q16%oM(yFy`3N zT#wfG#^t(yb%dfHOszR){_2Gjr(DfBE1G16{H>(jt~^*RP0KNhPH}kN{ecb5#ze)Q zI(JlwJq1dkY7s#t8a}0tdD956JfWs44#{^rJdH+<0}t?9$75c;1r-3LMdFUqd~lCF z1ayZX)@HArV3K|lr1_B{&i!(Q4wNC%B0D-&6KHy}K)ES8oDbBb+s{t+JKGOrz6NOi zFx0wrl>gs#btlOpOP6;vmgANKG}(9H)I>;cR@`JuLU9sDXIOV_w(2=yu+08WNPVpS)67lJZ7lqT zy<cM9D8{bm)t046Fp*;)F)`EU1b9y*7c~Y1 z7f^S%(PTZD(6Pw&pSc)Mu0KHU+cHCA*&DCVuslmu(hmK6ia0K}UVi6j*;Xk2q+4+C zXepM3Hy;de_m$R!@@YP^t%qmiL5|6cM;I1&i6Mh$inD2ZM%J-tniN{{!vHJ7He6R0 z_)Eq}p5O1FNX0lTXEp1|6Sy;n7z$CK?5Kf4j?tDje21GHb?4UKK>37zC$pK1Fm0N- z3k{_Yuv|5A{AzS;zXPg~DzpUtCbmAk*%m?1wYsVdn{g&C;p1juM0cI&P!cInn0#_1 zY9RaWs&lg0{(QkK4BvHsD--h;`+$Ct#MQYdQr65S!;>IzARc7@(gJV>f>fN+U%J=F zap^tc;A+<&>x(xVSTeR}Xc8K@uQbWd1q{Rpga#1nbx_kt#6DF>0PnjbuZGS(?%mRY@PPJk5mLWDqU% zAkAOzn7CD;*={H5y;ta_r#ZFwB0JDAXP?1<=yZwweiv;$H}kkv-%+v*#5o`9J3vVYKDL zFjDio+p7E2j>6U(Q_b&W#}tI7LYpxiF937JBx-UpQBzn_fA>)M2?7U3)pT68KNCkw zEzhAr*|$HntEWIV%*I|F9f-X2WoCP zsG8Ni6#(!8#Boum%u z+@5$}82I--YWC^fP{E-78Gt1NRP4)Y+-GH(LsbIsfKtLG+o7v_VV?|IO?(iRZl{TV zR8QNsYWVJ~dXW#rUp4_o#8Aaf7>IS*xT@6hYG8tbxNrnD%h=PvQR=}1oB!SGs7I6! zUy1qQ%%%crT;yHt&(yj|RJriH9nK=U9RsH9_;2jC#2_||!+#XW+h7;qi1Cvy*<>DK z)8NnVn4Bo%?8MDpxS{9C5tEUOhu=K&y5AeOq;PL

fOJh%aAXUgWDAf9Cg1@(c- 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%0A~zj|sVb>1{N)9s(vN`%GJ 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 Q8h%3>v_TrAAs_$%JC2a~PXGV_ diff --git a/vendor/github.com/bitrise-io/bitrise/_docs/step-development-guideline.md b/vendor/github.com/bitrise-io/bitrise/_docs/step-development-guideline.md deleted file mode 100644 index 7ff9d458..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_docs/step-development-guideline.md +++ /dev/null @@ -1,118 +0,0 @@ -# Step Development Guideline - -## Never depend on Environment Variables in your Step - -You should expose every outside variable as an input of your step, -and just set the default value to the Environment Variable you want to use in the `step.yml`. - -An example: - -The Xcode Archive step generates a `$BITRISE_IPA_PATH` output environment variable. -**You should not** use this environment variable in your Step's code directly, -instead you should declare an input for your Step in `step.yml` and just set the default -value to `$BITRISE_IPA_PATH`. Example: - -``` -- ipa_path: "$BITRISE_IPA_PATH" - opts: - title: "IPA path" -``` - -After this, in your Step's code you can expect that the `$ipa_path` Environment Variable will -contain the value of the IPA path. - -By declaring every option as an input you make it easier to test your Step, -and you also let the user of your Step to easily declare these inputs, -instead of searching in the code for the required Environment Variable. - - -## Do not use submodules, or require any other resource downloaded on-demand - -As a Step runs frequently in a CI / automation environment you should try to make your Step as stable as possible. -This includes the resources / tools used by your Step as well, not just the core code. - -If your Step depends on another tool, which have to be downloaded on-demand, during the execution -of your Step, there's a chance that even your Step was retrieved correctly but the -resource it tries to download just fails because of a network, authorization or other error. - -You should try to include everything what's required for your Step into the Step's repository. -In case of submodules, you should rather include the content of the other repository, -instead of actually using it as a submodule. - -The only exception is the dependencies you can fetch from an OS dependency manager, -on Debian systems you can use `apt-get` and on OS X you can use `brew`. -You can declare these dependencies in your `step.yml`, with the `deps` property, -and `bitrise` will manage to call the dependency manager to install the dependency, -and will fail before the Step execution in case it can't retrieve the dependency. - - -## Step id naming convention - -Use hyphen (`-`) separated words for you step id, like: `set-ios-bundle-identifier`, `xcode-archive-mac`, ... - - -## Input naming convention - -Use lower case [snake case](https://en.wikipedia.org/wiki/Snake_case) style input IDs, e.g. `input_path`. - -### Inputs which can accept a list of values - -You should postfix the input ID with `_list` (e.g. `input_path_list`), and expect the values to be provided as a pipe character separated list (e.g. `first value|second value`). This is not a hard requirement, but a strong suggestion. This means that you should prefer this solution unless you really need to use another character for separating values. Based on our experience the pipe character (`|`) works really well as a universal separator character, as it's quite rare in input values (compared to `,`, `;`, `=` or other more common separator characters). - -**As a best practice you should filter out empty items**, so that `first value||second value` or even - -``` -first value| |second value -``` - -is treated the same way as `first value|second value`. Again, not a hard requirement, but based on our experience this is the most reliable long term solution. - - -## Output naming convention - -Use all-upper-case [snake case](https://en.wikipedia.org/wiki/Snake_case) style output IDs, e.g. `OUTPUT_PATH`. - -### List of values in outputs - -You should postfix the output ID with `_LIST` (e.g. `OUTPUT_PATH_LIST`), and provide the values as a pipe separated list (e.g. `first value|second value`). This is not a hard requirement, but a strong suggestion. This means that you should prefer this solution unless you really need to use another character for separating values. Based on our experience the pipe character (`|`) works really well as a universal separator character, as it's quite rare in output values (compared to `,`, `;`, `=` or other more common separator characters). - - -## Version naming convention - -You should use [semantic versioning](http://semver.org/) (MAJOR.MINOR.PATCH) for your step. For example: `1.2.3`. - - -## Step Grouping convention - -You can use `project_type_tags` and `type_tags` to group/categorise your steps. - -`project_type_tags` are used to control if the step is available/useful for the given project type. - -Available `project_type_tags`: - -- ios -- macos -- android -- xamarin -- react-native -- cordova -- ionic - -_If step is available for all project types, do not specify project_type_tags, otherwise specify every project types, with which the step can work._ - -`type_tags` are used to categorise the steps based on it's functionality. - -Available `type_tags`: - -- access-control -- artifact-info -- installer -- deploy -- utility -- dependency -- code-sign -- build -- test -- notification - -_Every step should have at least one type_tag, if you feel you would need a new one, or update an existing's name, please [create a github issue](https://github.com/bitrise-io/bitrise/issues/new), with your suggestion._ diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/README.md b/vendor/github.com/bitrise-io/bitrise/_examples/README.md deleted file mode 100644 index 26d66221..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Bitrise CLI Examples - -* If you're just starting with Bitrise CLI head over to the `tutorials` folder/section. -* The `experimentals` folder contains concepts / experimental examples which might or might not work with the current release version (most likely won't), but might provide you a hint of the future ;) diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/before-after/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/before-after/bitrise.yml deleted file mode 100644 index 8156bb48..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/before-after/bitrise.yml +++ /dev/null @@ -1,73 +0,0 @@ -format_version: 0.9.8 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -app: - envs: - - BITRISE_PROJECT_TITLE: MyTit1 - - BITRISE_DEV_BRANCH: develop - opts: - is_expand: false -workflows: - after: - steps: - - script@0.9.1: - title: Running the After script - inputs: - - content: | - #!/bin/bash - echo "STEPLIB_BUILD_STATUS: ${STEPLIB_BUILD_STATUS}" - echo "BITRISE_BUILD_STATUS: ${BITRISE_BUILD_STATUS}" - exit 1 - before: - envs: - - BITRISE_PROJECT: MyTit1 - steps: - - script: - title: Running the Before script - inputs: - - content: | - #!/bin/bash - set -v - echo 'This is a before workflow' - echo "MY_TEST_ENV: ${MY_TEST_ENV}" - echo "STEPLIB_BUILD_STATUS: ${STEPLIB_BUILD_STATUS}" - echo "BITRISE_BUILD_STATUS: ${BITRISE_BUILD_STATUS}" - before2: - steps: - - script: - title: Running the Before2 script - inputs: - - content: | - #!/bin/bash - set -v - echo 'This is a before2 workflow' - target: - title: test title - summary: | - This is a workflow summary. - Check this out! - before_run: - - before - - before2 - after_run: - - after - envs: - - MY_TEST_ENV: My test value - opts: - is_expand: false - steps: - - script: - title: Running the target script - inputs: - - content: | - #!/bin/bash - set -v - echo 'This is a before workflow' - - https://github.com/bitrise-io/bitrise-steplib.git::timestamp: - outputs: - - UNIX_TIMESTAMP: null - opts: - title: unix style - - ISO_DATETIME: null - opts: - title: iso 8601 (RFC3339Nano) - - script: {} diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/create-new-step/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/create-new-step/bitrise.yml deleted file mode 100644 index 946ae64f..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/create-new-step/bitrise.yml +++ /dev/null @@ -1,18 +0,0 @@ -format_version: 0.9.8 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -app: - envs: - - STEP_DIR_PATH: ~/develop/ - opts: - is_expand: true - is_required: true -workflows: - create: - envs: [] - steps: - - script: - title: Hello Bitrise! - inputs: - - content: |- - #!/bin/bash - echo "Welcome to Bitrise!" diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/dependencies/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/dependencies/bitrise.yml deleted file mode 100644 index f2c89629..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/dependencies/bitrise.yml +++ /dev/null @@ -1,103 +0,0 @@ -format_version: 1.1.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -app: - envs: - - BITRISE_PROJECT_TITLE: MyTit1 - - BITRISE_DEV_BRANCH: develop - opts: - is_expand: no - -workflows: - _cleanup: - title: Cleanup workflow - steps: - - script: - inputs: - - content: |- - #!/bin/bash - - OUTPUT="$(which apt-get)" - if [ "$OUTPUT" != "" ]; then - echo "apt-get remove cmake" - sudo apt-get -y remove cmake - fi - - OUTPUT="$(which brew)" - if [ "$OUTPUT" != "" ]; then - echo "brew remove cmake" - brew uninstall cmake - fi - - echo $(which cmake) - - test: - before_run: - - _cleanup - title: Test dependencies workflow - steps: - - script: - deps: - brew: - - name: cmake - apt_get: - - name: cmake - inputs: - - content: |- - #!/bin/bash - set -v - - OUTPUT="$(which cmake)" - echo "$OUTPUT" - if [ "$OUTPUT" == "" ]; then - exit 1 - fi - - dependencies_test: - title: "test title" - summary: | - This is a workflow summary. - Check this out! - envs: - - MY_TEST_ENV: My test value - opts: - is_expand: false - before_run: - - before - steps: - - script: - title: Running the target script - dependencies: - - manager: brew - name: cmake - inputs: - - content: | - #!/bin/bash - set -v - echo 'This is a target workflow' - - https://github.com/bitrise-io/bitrise-steplib.git::timestamp@0.9.0: - is_always_run: true - - before: - envs: - - BITRISE_PROJECT: MyTit1 - steps: - - script: - title: Running the Before script - inputs: - - content: | - #!/bin/bash - set -v - echo 'This is a before workflow' - OUTPUT="$(which cmake)" - echo "$OUTPUT" - if [ "$OUTPUT" != "" ]; then - brew uninstall cmake - fi - - dependencies_xcode: - steps: - - script: - dependencies: - - manager: _ - name: xcode diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/bitrise.yml deleted file mode 100644 index 2a74c522..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/bitrise.yml +++ /dev/null @@ -1,24 +0,0 @@ -format_version: 0.9.8 -workflows: - build: - steps: - # - https://github.com/bitrise-io/bitrise-steplib.git::git-clone: - # run_if: "{{ IS_TOOL == true }}" - - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: - inputs: - - content: | - bundle install - middleman build - - https://github.com/bitrise-io/bitrise-steplib.git::slack: - - deploy: - envs: - - S3_BUCKET: middleman-prod - steps: - - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: - inputs: - - content: | - bundle install - middleman build - - https://github.com/bitrise-io/bitrise-steplib.git::slack: - - https://github.com/bitrise-io/bitrise-steplib.git::amazon-s3-bucket-sync: diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/v2.bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/v2.bitrise.yml deleted file mode 100644 index 29145adb..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/middleman/v2.bitrise.yml +++ /dev/null @@ -1,40 +0,0 @@ -format_version: 1.3.0 -workflows: - - git_clone: - steps: - - https://github.com/bitrise-io/bitrise-steplib.git::git-clone: - run_if: "{{ IS_TOOL == true }}" - - build: - steps: - - https://github.com/bitrise-io/bitrise-steplib.git::bash-script-runner@1.1.1: - inputs: - - __INPUT_FILE__: | - bundle install - middleman build - - stage: - envs: - - S3_BUCKET: middleman-stage - before_run: - - build - after_run: - - notifications - steps: - - https://github.com/bitrise-io/bitrise-steplib.git::amazon-s3-bucket-sync: - - deploy: - envs: - - S3_BUCKET: middleman-prod - before_run: - - build - after_run: - - notifications - steps: - - https://github.com/bitrise-io/bitrise-steplib.git::amazon-s3-bucket-sync: - - notifications: - steps: - - https://github.com/bitrise-io/bitrise-steplib.git::send-hipchat-msg: - - https://github.com/bitrise-io/bitrise-steplib.git::send-slack-msg: diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/templates/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/templates/bitrise.yml deleted file mode 100644 index ca16d4e6..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/templates/bitrise.yml +++ /dev/null @@ -1,131 +0,0 @@ -# -# Demonstrates the Run-If template expressions. -# The templates / expressions you can use are the official -# Go template expressions, you can find the full documentation -# on Go's text/template doc page: https://golang.org/pkg/text/template/ -format_version: 0.9.8 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -workflows: - primary: - steps: - # - # Get and compare envs - - script: - title: Run-If expression - run_if: |- - {{getenv "TEST_KEY" | eq "test value"}} - inputs: - - content: |- - #!/bin/bash - echo "RunIf expression was true" - # - # Or if that's all you want to do just use the enveq function - - script: - title: Run-If expression - run_if: '{{enveq "TEST_KEY" "test value"}}' - inputs: - - content: |- - #!/bin/bash - echo "RunIf expression was true" - - # - # Env not empty - - script: - title: Run-If expression - run_if: '{{getenv "TEST_KEY" | ne ""}}' - inputs: - - content: |- - #!/bin/bash - echo "RunIf expression was true" - - # - # Another env not empty - - script: - title: Run-If expression - run_if: '{{getenv "TEST_KEY" | eq "" | not}}' - inputs: - - content: |- - #!/bin/bash - echo "RunIf expression was true" - - # - # Env equal to env - - script: - title: Run-If expression - run_if: '{{getenv "TEST_KEY_1" | eq (getenv "TEST_KEY_2")}}' - inputs: - - content: |- - #!/bin/bash - echo "RunIf expression was true" - - # - # Use the available expression data properties - # like IsCI or IsBuildFailed directly - - script: - title: Run-If expression - run_if: |- - {{.IsCI}} - inputs: - - content: |- - #!/bin/bash - echo "RunIf expression was true" - # - # You don't have to wrap the expression in {{...}} if it's a simple - # oneliner - - script: - title: Run-If expression - run_if: $.IsCI - inputs: - - content: |- - #!/bin/bash - echo "RunIf expression was true" - # - # You can even remove the $ sign, it's optional in a simple - # expression like this - - script: - title: Run-If expression - run_if: .IsCI - inputs: - - content: |- - #!/bin/bash - echo "RunIf expression was true" - # - # If-Else condition - - script: - title: Run-If expression - run_if: |- - {{if .IsCI}} - true - {{else}} - false - {{end}} - inputs: - - content: |- - #!/bin/bash - echo "RunIf expression was true" - # - # Multi condition - - script: - title: CI and Not Failed - run_if: |- - {{.IsCI | and (not .IsBuildFailed)}} - inputs: - - content: |- - #!/bin/bash - echo "RunIf expression was true" - # - # Pull Request condition - - script: - title: Only if NOT a Pull Request - run_if: not .IsPR - inputs: - - content: |- - #!/bin/bash - echo "RunIf expression was true" - - script: - title: Only if it was a Pull Request - run_if: .IsPR - inputs: - - content: |- - #!/bin/bash - echo "Pull Request ID: ${PULL_REQUEST_ID}" diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/timestamp-gen/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/timestamp-gen/bitrise.yml deleted file mode 100644 index 4eca75f4..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/timestamp-gen/bitrise.yml +++ /dev/null @@ -1,22 +0,0 @@ -format_version: 0.9.8 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" -app: - envs: - - BITRISE_PROJECT_TITLE: MyTit1 - opts: - is_expand: no - - BITRISE_DEV_BRANCH: develop - opts: - is_expand: no -workflows: - _: - envs: [] - steps: - - timestamp@0.9.0: - - script: - inputs: - - content: | - #!/bin/bash - set -v - echo ${UNIX_TIMESTAMP} - echo ${ISO_DATETIME} diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/trigger-map/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/trigger-map/bitrise.yml deleted file mode 100644 index 82eefbab..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/trigger-map/bitrise.yml +++ /dev/null @@ -1,58 +0,0 @@ -format_version: 0.9.8 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - -trigger_map: -- pattern: master - is_pull_request_allowed: false - workflow: master -- pattern: feature/* - is_pull_request_allowed: true - workflow: feature -- pattern: "*" - is_pull_request_allowed: true - workflow: primary - -workflows: - master: - title: Master workflow - summary: Shuld triggered by master branches - steps: - - script: - inputs: - - content: | - #!/bin/bash - echo 'This the master workflow' - - feature: - title: Feature workflow - summary: Shoould triggered by feature branches - steps: - - script: - inputs: - - content: | - #!/bin/bash - echo 'This is a feature workflow' - - primary: - title: Primary workflow - before_run: - - before - steps: - - script: - inputs: - - content: | - #!/bin/bash - echo 'This is the primary workflow' - echo 'time: ${ISO_DATETIME}' - - - script: - inputs: - - content: | - #!/bin/bash - echo 'This is the primary workflow' - echo 'time: ${ISO_DATETIME}' - - before: - title: Primary workflow - steps: - - https://github.com/bitrise-io/bitrise-steplib.git::timestamp: diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/.gitignore b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/.gitignore deleted file mode 100644 index f94187cd..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.bitrise* -tmp-bitrise.yml diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/bitrise.yml deleted file mode 100644 index c00de59d..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/experimentals/upload_download_bitrise_io/bitrise.yml +++ /dev/null @@ -1,32 +0,0 @@ -# -# This experimental upload&download will only work with the new bitrise.io API! -# -format_version: 1.1.0 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - -app: - envs: - - BITRISE_YML_PATH: ./tmp-bitrise.yml - # define these in your .bitrise.secrets.yml - - BITRISE_APP_SLUG: $BITRISE_APP_SLUG - - BITRISE_APP_API_TOKEN: $BITRISE_APP_API_TOKEN - -workflows: - download_from_bitrise_io: - steps: - - script: - title: Downloading bitrise.yml ... - inputs: - - content: |- - #!/bin/bash - set -e - ret_content=$(curl --fail https://www.bitrise.io/api/app/${BITRISE_APP_SLUG}/config/download.yml?api_token=${BITRISE_APP_API_TOKEN}) - echo "${ret_content}" > ${BITRISE_YML_PATH} - upload_to_bitrise_io: - steps: - - script: - title: Uploading bitrise.yml ... - inputs: - - content: |- - #!/bin/bash - curl --fail -X POST --data-urlencode "app_config_datastore_yaml=$(cat ${BITRISE_YML_PATH})" https://www.bitrise.io/api/app/${BITRISE_APP_SLUG}/config/upload.yml?api_token=${BITRISE_APP_API_TOKEN} diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/README.md b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/README.md deleted file mode 100644 index e9c9ed3d..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Tutorial Bitrise.yml configurations - -This folder contains examples for those who are just getting -started with Bitrise CLI. - - -## steps-and-workflows - -You should start your experimenting with the `steps_and_workflows` examples. -You can find an annotated `bitrise.yml` in the folder which will guide you -through the basic concepts of bitrise CLI and the `bitrise.yml` config -file format. - -## inputs-outputs-envs - -This folder contains examples of how inputs, outputs and environment -variables are handled. - -A quick note: every input and output is actually an environment -variable, you can handle those in your scripts just like you would any -regular environment variable. diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/errors-force-run-and-skippable/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/errors-force-run-and-skippable/bitrise.yml deleted file mode 100644 index d3c4510f..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/errors-force-run-and-skippable/bitrise.yml +++ /dev/null @@ -1,46 +0,0 @@ -format_version: 0.9.8 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - -workflows: - fail: - # this workflow is a test for failed steps/workflows, - # it'll fail - steps: - - script: - title: "ok" - - script: - title: "ls" - inputs: - - content: | - #/bin/bash - ls -alh - - script: - title: "fail" - is_skippable: true - inputs: - - content: | - #/bin/bash - set -v - exit 1 - - script: - title: "fail 2" - is_skippable: false - inputs: - - content: | - #/bin/bash - set -v - exit 1 - - script: - title: "ok" - inputs: - - content: | - #/bin/bash - echo "-----> This should NOT be printed!!" - is_always_run: false - - script: - title: "ok" - inputs: - - content: | - #/bin/bash - echo "-----> This should be printed!!" - is_always_run: true diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/inputs-outputs-envs/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/inputs-outputs-envs/bitrise.yml deleted file mode 100644 index 39629401..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/inputs-outputs-envs/bitrise.yml +++ /dev/null @@ -1,43 +0,0 @@ -format_version: 0.9.8 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - -app: - envs: - - BITRISE_PROJECT_TITLE: EnvTest $HOME - opts: - is_expand: no - - BITRISE_DEV_BRANCH: develop - opts: - is_expand: no - -workflows: - example-envs: - # You can run this workflow with: - # bitrise run example-envs - envs: [] - steps: - - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: - inputs: - - content: | - #!/bin/bash - echo "Hello world!" - echo "BITRISE_PROJECT_TITLE (HOME should NOT be expanded): ${BITRISE_PROJECT_TITLE}" - export EXP_TEST='Exported value' - echo "EXP_TEST: ${EXP_TEST}" - - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: - inputs: - - content: | - #!/bin/bash - echo "Second script/step" - echo "BITRISE_DEV_BRANCH: ${BITRISE_DEV_BRANCH}" - echo "EXP_TEST (should be empty): ${EXP_TEST}" - - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: - inputs: - - content: | - #!/bin/bash - echo "Read from .bitrise.secrets.yml: ${BITRISE_SECRET_TEST1}" - - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: - inputs: - - content: | - #!/bin/bash - echo 'This ENV should NOT be expanded: ${BITRISE_PROJECT_TITLE}' diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/react-native/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/react-native/bitrise.yml deleted file mode 100644 index 40c2cad3..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/react-native/bitrise.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- -format_version: 1.1.0 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -app: - envs: - - BITRISE_PROJECT_PATH: ios/ReactNativeSample.xcodeproj - opts: - is_expand: false - - BITRISE_SCHEME: ReactNativeSample - opts: - is_expand: false - -trigger_map: -- pattern: "*" - is_pull_request_allowed: true - workflow: build-react-app - -workflows: - build-react-app: - steps: - - install-react-native: {} - - npm: - inputs: - - command: install - - react-native-bundle: {} - - xcode-archive: - title: 'Xcode: Create Archive' - inputs: - - output_dir: "${BITRISE_DEPLOY_DIR}" - outputs: - - BITRISE_IPA_PATH: - opts: - title: The created .ipa file's path - - BITRISE_DSYM_PATH: - opts: - title: The created .dSYM.zip file's path diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/bitrise.yml deleted file mode 100644 index af518108..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/bitrise.yml +++ /dev/null @@ -1,139 +0,0 @@ -format_version: 0.9.8 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - -workflows: - - basics: - # You can run this workflow with: - # bitrise run basics - # - # All of the following steps in this workflow will do exactly the - # same thing, it demonstrates how you can define a step's ID - # in different ways. - steps: - # If you use a step from a step collection / library then - # a step's ID consists of three parts: - # 1. The step-lib source - # 2. The step's ID in the step-lib - # 3. The step's version, registered in the step-lib - # A full ID looks like this: - # step-lib-source::step-id@version - - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: - title: "Full ID" - inputs: - - content: | - #/bin/bash - echo "Welcome to Bitrise!" - # If you define a default_step_lib_source (just like you can see it - # at the top of this bitrise.yml file) then you don't have to - # specify it again for the steps if you want to use - # the default_step_lib_source - # You can include the :: separator or if you want to you can remove it - # completely. - - ::script@0.9.0: - title: "Using default_step_lib_source" - inputs: - - content: | - #/bin/bash - echo "Welcome to Bitrise!" - - script@0.9.0: - title: "Using default_step_lib_source, without ::" - inputs: - - content: | - #/bin/bash - echo "Welcome to Bitrise!" - # If you want to use the latest version of the step - # you can even remove the version from the ID. - # Once again you can include the separator (@ for the version) - # but you can remove it completely. - # Note that the trailing colon is still required, even - # if you don't specify the version! - - script@: - title: "Using default_step_lib_source, without ::" - inputs: - - content: | - #/bin/bash - echo "Welcome to Bitrise!" - - script: - title: "Using default_step_lib_source, without ::" - inputs: - - content: | - #/bin/bash - echo "Welcome to Bitrise!" - - - direct-url: - # You can run this workflow with: - # bitrise run direct-url - # - # This workflow shows how to use steps with specifying the - # step's git clone URL directly. - # This way the step will always be git cloned from the specified - # URL and not used from a step library/collection. - # To do this you have to construct the ID in this way: - # git::git-clone-url-of-the-step-repo@branch-or-tag - steps: - - script: - title: "ok" - - git::https://github.com/bitrise-io/steps-timestamp.git@master: - title: "remote_git-stamp-test" - - git::git@github.com:bitrise-io/steps-timestamp.git@master: - title: "remote_git-stamp-test" - - script: - title: "print time" - inputs: - - content: | - #/bin/bash - set -e - echo "ISO_DATETIME: ${ISO_DATETIME}" - - relative: - # You can run this workflow with: - # bitrise run relative - # - # You can specify local path for a step as well. - # The path can be any kind of path (even absolute path) - # but the best way is to use relative paths - # if you want to run your workflow on a Continuous Integration - # service or want to share with someone else. Absolute paths - # and relative-to-home paths most likely won't work anywhere - # else except on your machine. - # To do this you have to construct the ID in this way: - # path::local-path-of-the-step-folder - steps: - - script: - title: "ok" - - path::./steps-timestamp: - title: "relative_pth-stamp-test" - - script: - title: "print time" - inputs: - - content: | - #/bin/bash - set -e - echo "ISO_DATETIME: ${ISO_DATETIME}" - - local: - # You can run this workflow with: - # bitrise run local - # - # This is the same as the 'relative' workflow example - # just demonstrating that you can use all the common - # patterns to define the path of the step. - # You can define even absolute paths but keep in mind - # that if you do it most likely won't work at someone, - # or on your Continuous Integration service (like your favorite Bitrise.io) - steps: - - script: - title: "ok" - - path::~/develop/go/src/github.com/bitrise-io/steps-timestamp: - title: "local_time-stamp-test" - - path::$HOME/develop/go/src/github.com/bitrise-io/steps-timestamp: - title: "local_time-stamp-test" - - script: - title: "print time" - inputs: - - content: | - #/bin/bash - set -e - echo "ISO_DATETIME: ${ISO_DATETIME}" diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/README.md b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/README.md deleted file mode 100644 index a77de2b2..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# steps-timestamp -Generates a timestamp and stores it into env diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/_scripts/ci.sh b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/_scripts/ci.sh deleted file mode 100644 index 490966c5..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/_scripts/ci.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -set -e - -THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -cd "${THIS_SCRIPT_DIR}/.." - -export PATH="$PATH:$GOPATH/bin" - -# -# Script for Continuous Integration -# - -set -v - -# Check for unhandled errors -go get github.com/kisielk/errcheck -go install github.com/kisielk/errcheck -errcheck -asserts=true -blank=true ./... - -go test -v ./... - -# -# ==> DONE - OK -# \ No newline at end of file diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.go b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.go deleted file mode 100644 index 43b38aaa..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/exec" - "time" -) - -func EnvmanAdd(key, value string) error { - args := []string{"add", "-k", key, "-v", value} - return RunCommand("envman", args...) -} -func RunCommand(name string, args ...string) error { - cmd := exec.Command(name, args...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} - -func main() { - now := time.Now() - - // unix timestamp - // ex: 1436279645 - timestamp := now.Unix() - timestampString := fmt.Sprintf("%d", timestamp) - if err := EnvmanAdd("UNIX_TIMESTAMP", timestampString); err != nil { - fmt.Println("Failed to store UNIX_TIMESTAMP:", err) - os.Exit(1) - } - - // iso8601 time format (timezone: RFC3339Nano) - // ex: 2015-07-07T16:34:05.51843664+02:00 - timeString := fmt.Sprintf("%v", now.Format(time.RFC3339Nano)) - if err := EnvmanAdd("ISO_DATETIME", timeString); err != nil { - fmt.Println("Failed to store ISO_DATETIME:", err) - os.Exit(1) - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.sh b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.sh deleted file mode 100755 index 4d43276a..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -e - -THIS_SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -# Start go program -cd "${THIS_SCRIPTDIR}" - -set -v - -go run ./step.go diff --git a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.yml b/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.yml deleted file mode 100644 index 851fea2f..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_examples/tutorials/steps-and-workflows/steps-timestamp/step.yml +++ /dev/null @@ -1,17 +0,0 @@ -title: Generate time -summary: Generates current timestamp -description: | - Generates current timestamp -website: https://github.com/bitrise-io/steps-timestamp -fork_url: https://github.com/bitrise-io/steps-timestamp -source: - git: https://github.com/bitrise-io/steps-timestamp.git -is_requires_admin_user: false -is_always_run: false -outputs: - - UNIX_TIMESTAMP: - opts: - title: unix style - - ISO_DATETIME: - opts: - title: iso 8601 (RFC3339Nano) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/README.md deleted file mode 100644 index 2b134024..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Welcome to Bitrise CLI - -[Lesson 1 - The first Steps](./lesson1_steps) - -Captain's log, bitdate 110912.5. -We begin our mission discovering a new universe, full of new opportunities and new ways to improve ourselves and our day-to-day routines. We’ve prepared for this day for a long time. - -- The first step was a simple command to make sure we have what it takes to start our adventures: `curl -L https://github.com/bitrise-io/bitrise/releases/download/VERSION/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise` - -- Next we made sure that we are in the right mode `chmod +x /usr/local/bin/bitrise` - -- And finally we checked that everything is present and we are using the latest technologies by running the `chmod +x /usr/local/bin/bitrise` command and after it `bitrise setup` - -A journey has never had such an easy start. We’ve just traveled to the planet (or if you prefer folder) typed `bitrise init` in our computer and a new Workflow was created that we could use right away to automate a part of our day-to-day routine. We learned a lots of things on our voyage and we are here to help you get started in this automated universe. The lessons section is all about getting familiar with the how-to’s of the [Bitrise CLI](https://github.com/bitrise-io/bitrise). Every lesson folder contains a README.md that gives you an overview of the topic and a bitrise.yml that has a complete Workflow ready to run. - -- Explore the Steps (including the Steps in our [StepLib](https://github.com/bitrise-io/bitrise-steplib)) in [lesson1](./lesson1_steps) - Look, the final frontier! -- Create an army of Steps by adding them to your Workflow to conquer your automation needs in [lesson2](./lesson2_workflow) - Battle formation! -- Make sure your army of Steps get and pass on to each other in the right order in [lesson3](./lesson3_input_output_env) - Set phasers to stun! -- Stay in control even in hard times when the engines are on fire (due to errors) in [lesson4](./lesson4_errors) - Scotty, where are you?! -- And take a look at one of our journeys through a complete Workflow in [lesson5](./lesson5_complex_wf) - Are you ready for the Kobayashi Maru? - -[Lesson 1 - The first Steps](./lesson1_steps) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/.gitignore b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/.gitignore deleted file mode 100644 index 9e7527f5..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ - -.bitrise* diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/README.md deleted file mode 100644 index e188b0ec..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Lesson 1 - The first Steps - -[Back to the CLI Lessons Introduction page](../) - -[Lesson 2 - The flow of work in your Workflow](../lesson2_workflow) - -First of all let's talk about Steps. Steps are the building blocks of a [Bitrise](https://bitrise.io) Workflow. At [Bitrise](https://bitrise.io) we know how important it is to have plenty of opportunities to customize the automation process as we also worked as a mobile app development agency. The need for a wide variety of customization in the automation of the development and deployment workflows is what leads to the creation of [Bitrise](https://bitrise.io). We are eager to provide you with Steps that can help you with automation throughout the application lifecycle and this is where our Step Library comes into view. We created the open source [StepLib](https://github.com/bitrise-io/bitrise-steplib) to give you the basic Steps that you need in creating a Workflow to boost your productivity. Yes, it is open source, you can fork it, add your own Steps or even use another public fork of it! Also when you add a useful Step to your fork and you think other developers could make good use of it, don’t hesitate to send us a pull request! - -Now that you created your first local project (by calling the `bitrise setup` and after it the `bitrise init`) we can have some fun with the Steps! Open the bitrise.yml and let's add some steps! - -## StepID -SetpID is a unique identifier of a step. In your Workflow you have to include this ID to tell [Bitrise](https://bitrise.io) which Step you'd like to run. In our [StepLib](https://github.com/bitrise-io/bitrise-steplib) if you open the [steps folder](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps) you can see that every Step folder's name is the StepID. - -### StepID format in the .yml - -For Steps from the [StepLib](https://github.com/bitrise-io/bitrise-steplib): - - You can use the full StepID format. (step-lib-source::StepID@version:) - - `https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.1:` - - If the `default_step_lib_source` is defined (by default it is and refers to our [StepLib](https://github.com/bitrise-io/bitrise-steplib)), you can simply omit the step-lib-source and even the `::` separator. (::StepID@version:) - - `::script@0.9.0:` and `script@0.9.0:` - - If there is only one version of a step or if you always want to use the latest version you can even remove the version and the `@` separator, too. And if you take a look at the generated bitrise.yml you can see that this is the format it uses (StepID@: - the only step in the generated Workflow is `- script:`) - - `script@:` and `script:` -For Steps that are not in the [StepLib](https://github.com/bitrise-io/bitrise-steplib) and are stored online: - - The format to download and run a step is git::clone-url@branch - - `git::https://github.com/bitrise-io/steps-timestamp.git@master` - - In this case we are using the HTTPS clone url to clone the master branch in the Step's repository - - `git::git@github.com:bitrise-io/steps-timestamp.git@master` - - In this case we are using the SSH clone url to clone the master branch in the Step's repository -For Steps on your machine: - - In this case the Step is already stored on your computer and we only need to know the exact path to the step.sh - - relative-path::./steps-timestamp - - path::~/develop/go/src/github.com/bitrise-io/steps-timestamp - - path::$HOME/develop/go/src/github.com/bitrise-io/steps-timestamp - -[Back to the CLI Lessons Introduction page](../) - -[Lesson 2 - The flow of work in your Workflow](../lesson2_workflow) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/bitrise.yml deleted file mode 100644 index f6aa8a0b..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson1_steps/bitrise.yml +++ /dev/null @@ -1,91 +0,0 @@ -format_version: 1.3.0 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -app: - envs: - - BITRISE_PROJECT_TITLE: lesson_1 - opts: - is_expand: true - - BITRISE_DEV_BRANCH: master - opts: - is_expand: true -workflows: - steplib_steps: - steps: - - https://github.com/bitrise-io/bitrise-steplib.git::script@0.9.0: - title: "Full ID" - inputs: - - content: | - #/bin/bash - echo "Welcome to Bitrise!" - - ::script@0.9.0: - title: "Using default_step_lib_source" - inputs: - - content: | - #/bin/bash - echo "Welcome to Bitrise!" - - script@0.9.0: - title: "Using default_step_lib_source, without ::" - inputs: - - content: | - #/bin/bash - echo "Welcome to Bitrise!" - - script@: - title: "Using default_step_lib_source, without ::" - inputs: - - content: | - #/bin/bash - echo "Welcome to Bitrise!" - - script: - title: "Using default_step_lib_source, without ::" - inputs: - - content: | - #/bin/bash - echo "Welcome to Bitrise!" - - script: - title: "ok" - - git::https://github.com/bitrise-io/steps-timestamp.git@master: - title: "remote_git-stamp-test" - - script: - title: "print time" - inputs: - - content: | - #/bin/bash - set -e - echo "ISO_DATETIME: ${ISO_DATETIME}" - - script: - title: "ok" - - script: - title: "print time" - inputs: - - content: | - #/bin/bash - - - git-clone: - title: "Clone timestamp repo for local use" - run_if: true - inputs: - - repository_url: https://github.com/bitrise-io/steps-timestamp - - clone_into_dir: steps-timestamp - - branch: master - - path::./steps-timestamp: - title: "relative_pth-stamp-test" - - script: - title: "print time" - inputs: - - content: | - #/bin/bash - set -e - echo "ISO_DATETIME: ${ISO_DATETIME}" - - script: - title: "ok" - - path::~/Documents/Development/bitrise-cli/_lessons/lesson1_steps/steps-timestamp: - title: "local_time-stamp-test" - - path::$HOME/Documents/Development/bitrise-cli/_lessons/lesson1_steps/steps-timestamp: - title: "local_time-stamp-test" - - script: - title: "print time" - inputs: - - content: | - #/bin/bash - set -e - echo "ISO_DATETIME: ${ISO_DATETIME}" diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/.gitignore b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/.gitignore deleted file mode 100644 index 9e7527f5..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ - -.bitrise* diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/README.md deleted file mode 100644 index 5345fcf0..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/README.md +++ /dev/null @@ -1,88 +0,0 @@ -# Lesson 2 - The flow of work in your Workflow - -[Back to Lesson 1](../lesson1_steps) - -[Lesson 3 - The ins and outs with environment variables](../lesson3_input_output_env) - -Basically Workflows are groups of steps. There are main Workflows, that contain the Steps which provide the main functionality. There are utility Workflows that we use to prepare everything for the main Workflow, to clean up or to send notification containing the build status. The utility Workflows begin with '_' and these Workflows can't be run using the `bitrise run ` command. - -What could be a better example to show how Workflows work, than to create an iOS Unit Test Workflow? Let's get down to business! -First of all, what do we need in the Unit Test Workflow? -- Xcode: Test is all that we need to run - -And what are the needed setup steps to accomplish these objectives, what should be added to the utility Workflows? -- The project should be on the machine running the Workflow, so there should be a git-clone Step -- There should be a notification Step to make sure you don't have to sit in front of your computer and watch the terminal the whole time - -So let's create our first utility Workflow called _setup to make sure that the project is present on the current machine and is up-to-date. -We'll use a simple bash script to achieve this (just for the fun of it ;) ) The _setup Workflow should look something like this: - -``` -_setup: - description: Clone repo - steps: - - script: - title: clone - run_if: |- - {{enveq "XCODE_PROJECT_PATH" ""}} - inputs: - - content: |- - #!/bin/bash - echo $XCODE_PROJECT_PATH - if [ ! -d $PROJECT_FOLDER ] ; then - git clone ${REPO_URL} - else - cd $PROJECT_FOLDER - git pull - fi -``` - -Great! Now let's jump to the main Workflow. It will only contain an Xcode: Test step so let's keep it simple and call it `test`. You can add Workflows to the after_run and before_run of Workflow. This will run the given Workflow just before or after the given Workflow. So here is the main Workflow with the before_run and after_run sections: - -``` -test: - before_run: - - _setup - after_run: - - _cleanup - steps: - - xcode-test: - title: Run Xcode test - inputs: - - project_path: ${XCODE_PROJECT_PATH} - - scheme: ${XCODE_PROJECT_SCHEME} - - simulator_device: iPhone 6 - - simulator_os_version: latest - - is_clean_build: "no" -``` - -Awesome! Now we are almost done! only one more Workflow to create! _cleanup should contain simply be another bash script that just delete's the directory. - -``` -_cleanup: - description: |- - This is a utility workflow. It runs a script to delete the folders created in the setup. - steps: - - script: - title: Cleanup folder - description: |- - A script step to delete the downloaded Step folder. - inputs: - - content: |- - #!/bin/bash - rm -rf $PROJECT_TITLE -``` - -Wow! We're done! Weeell not quite. If you try to run the Workflow you can see, that it fails. Currently the environment variables aren't added that are needed. Add these environment variables to your .bitrise.secrets.yml: - -- REPO_URL: -- PROJECT_TITLE: -- PROJECT_FOLDER: -- XCODE_PROJECT_PATH: -- XCODE_PROJECT_SCHEME: ${PROJECT_TITLE} - -Aaaaand yeah! All done! Great job! *Drop mic* - -[Back to Lesson 1](../lesson1_steps) - -[Lesson 3 - The ins and outs with environment variables](../lesson3_input_output_env) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/bitrise.yml deleted file mode 100644 index 5cfbe8e4..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson2_workflow/bitrise.yml +++ /dev/null @@ -1,53 +0,0 @@ -format_version: 1.3.0 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -app: - envs: - - BITRISE_PROJECT_TITLE: lesson_2 - opts: - is_expand: true - - BITRISE_DEV_BRANCH: master - opts: - is_expand: true -workflows: - _setup: - description: Clone repo - steps: - - script: - title: clone - inputs: - - content: |- - #!/bin/bash - if [ ! -d $PROJECT_FOLDER ] ; then - git clone ${REPO_URL} - envman add --key XCODE_PROJECT_PATH --value $BITRISE_SOURCE_DIR/$PROJECT_FOLDER/${PROJECT_TITLE}.xcodeproj - else - cd $PROJECT_FOLDER - git pull - envman add --key XCODE_PROJECT_PATH --value $BITRISE_SOURCE_DIR/$PROJECT_FOLDER/${PROJECT_TITLE}.xcodeproj - fi - test: - before_run: - - _setup - after_run: - - _cleanup - steps: - - xcode-test: - title: Run Xcode test - inputs: - - project_path: ${XCODE_PROJECT_PATH} - - scheme: ${XCODE_PROJECT_SCHEME} - - simulator_device: iPhone 6 - - simulator_os_version: latest - - is_clean_build: "no" - _cleanup: - description: |- - This is a utility workflow. It runs a script to delete the folders created in the setup. - steps: - - script: - title: Cleanup folder - description: |- - A script step to delete the downloaded Step folder. - inputs: - - content: |- - #!/bin/bash - rm -rf $PROJECT_TITLE diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/.gitignore b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/.gitignore deleted file mode 100644 index 9e7527f5..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ - -.bitrise* diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/README.md deleted file mode 100644 index 6dc2d586..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Lesson 3 - The ins and outs with environment variables - -[Back to Lesson 2](../lesson2_workflow) - -[Lesson 4 - Keeping the control even when the engines are on fire](../lesson4_errors) - -You are probably familiar with environment variables. These are crucial part of [Bitrise](https://bitrise.io), because our Steps communicate using Environment Variables. We created [envman](https://github.com/bitrise-io/envman) to make Environment Variable management a whole lot easier. Also for security reasons we added a .bitrise.secrets.yml to store all your secret passwords and any other local machine- or user related data. At every `bitrise init` we create a .gitignore file to make sure that the top secret data you are storing in this file is not added to git. - -There are multiple ways to create Environment Variables - -- You can add them to the `.bitrise.secrets.yml` - these variables will be accessible throughout the whole app (every Workflow). -- You can add them to the envs section of the app, just like the BITRISE_PROJECT_TITLE and BITRES_DEV_BRANCH - these variables will be accessible throughout the whole app (every Workflow). -- You can add them to the envs section of the given Workflow you would like to use it in - these variables will be accessible throughout the Workflow. -- You can export them in your own Workflow by using the [script step from the StepLib](https://github.com/bitrise-io/bitrise-steplib/tree/master/steps/script) - - - or to make it visible in the whole Workflow you can use [envman](https://github.com/bitrise-io/envman) (`envman add --key SOME_KEY --value 'some value'`) - -[Back to Lesson 2](../lesson2_workflow) - -[Lesson 4 - Keeping the control even when the engines are on fire](../lesson4_errors) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/bitrise.yml deleted file mode 100644 index 83e86dcc..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson3_input_output_env/bitrise.yml +++ /dev/null @@ -1,39 +0,0 @@ -format_version: 1.3.0 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -app: - envs: - - BITRISE_PROJECT_TITLE: lesson_3 $HOME - opts: - is_expand: true - - BITRISE_DEV_BRANCH: master - opts: - is_expand: true -workflows: - example-envs: - envs: [] - steps: - - script: - inputs: - - content: | - #!/bin/bash - echo "Hello world!" - echo "BITRISE_PROJECT_TITLE (HOME should NOT be expanded): ${BITRISE_PROJECT_TITLE}" - export EXP_TEST='Exported value' - echo "EXP_TEST: ${EXP_TEST}" - - script: - inputs: - - content: | - #!/bin/bash - echo "Second script/step" - echo "BITRISE_DEV_BRANCH: ${BITRISE_DEV_BRANCH}" - echo "EXP_TEST (should be empty): ${EXP_TEST}" - - script: - inputs: - - content: | - #!/bin/bash - echo "Read from .bitrise.secrets.yml: ${BITRISE_SECRET_TEST1}" - - script: - inputs: - - content: | - #!/bin/bash - echo 'This ENV should NOT be expanded: ${BITRISE_PROJECT_TITLE}' diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/.gitignore b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/.gitignore deleted file mode 100644 index 9e7527f5..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ - -.bitrise* diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/README.md deleted file mode 100644 index ec79ec7a..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Lesson 4 - Keeping the control even when the engines are on fire a.k.a. Error management - -[Back to Lesson 3](../lesson3_input_output_env) - -[Lesson 5 - A complex Workflow](../lesson5_complex_wf) - -So here's one of the most common part of development - Errors. Yeah we all know that guy who configures everything and writes every line of code flawlessly at the first time... Of course we are not that guy. When working on a complex workflow, it happens at least once that an old code stays in the project making the tests fail or that one little option in the configuration that messes up the whole thing and makes it fail. - -We are following the bash conventions about error handling. Every Step that runs successfully exits with the error code 0 and if the exit code is different the Step fails and (if the Step wasn't skippable the whole Workflow fails). - -There are two ways to keep the Workflow up and running even after a failed Step. -- If the Step was marked skippable, the following Steps will also run. This is great if you want to notify the team that the build started but the used service is currently offline. -- If the Step is marked always run the given Step will be run even if the build fails. This can be used to notify the team of errors. - -[Back to Lesson 3](../lesson3_input_output_env) - -[Lesson 5 - A complex Workflow](../lesson5_complex_wf) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/bitrise.yml deleted file mode 100644 index 6d09d161..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson4_errors/bitrise.yml +++ /dev/null @@ -1,51 +0,0 @@ -format_version: 1.3.0 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -app: - envs: - - BITRISE_PROJECT_TITLE: lesson_4 - opts: - is_expand: true - - BITRISE_DEV_BRANCH: master - opts: - is_expand: true -workflows: - fail: - steps: - - script: - title: "ok" - - script: - title: "ls" - inputs: - - content: | - #/bin/bash - ls -alh - - script: - title: "fail" - is_skippable: true - inputs: - - content: | - #/bin/bash - set -v - exit 1 - - script: - title: "fail 2" - is_skippable: false - inputs: - - content: | - #/bin/bash - set -v - exit 1 - - script: - title: "ok" - inputs: - - content: | - #/bin/bash - echo "-----> This should NOT be printed!!" - is_always_run: false - - script: - title: "ok" - inputs: - - content: | - #/bin/bash - echo "-----> This should be printed!!" - is_always_run: true diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/.gitignore b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/.gitignore deleted file mode 100644 index 9e7527f5..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ - -.bitrise* diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/README.md deleted file mode 100644 index b15de51f..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/README.md +++ /dev/null @@ -1,133 +0,0 @@ -# Lesson 5 - A complex Workflow - -[Back to Lesson 4](../lesson4_errors) - -[Lesson 6 - Pull the trigger on the Workflow](../lesson6_triggers) - -Let's spice things up a little bit with a more complex bitrise.yml. We will create a Workflow for an iOS project just like in [lesson2](../lesson2_workflow), but this time we'll prepare it for running on our local machine and also on [Bitrise](https://bitrise.io) (Yeah, just for fun we'll run different Steps locally and on the CI server), also we'll add some more main Workflows so that we can use the Archive, Analyze and Test features of Xcode and combine these into a single Workflow by using the before_run / after_run fields. - -First of all let's summarize what we want. -- Utility - - _setup - - git clone or pull to get the source code on the local machine - - _cleanup - - remove source from the local machine - - _download_certs - - to download the needed certificates on the CI Server -- Main Workflows - - analyze - - archive - - test - - master - to create the archive, deploy it and notify the Users about it - -Move the Workflow from [lesson2](../lesson2_workflow) to the current bitrise.yml. Now we have a *_setup*, *_cleanup* and a *test* Workflow. - -Let's add the _download_certs Workflow. It will only have one step, the certificate-and-profile-installer. We have to pass two inputs to it - keychain_path and keychain_password. These are the only two parameters that we'll need. We also want to set it to run only on the CI server so we have to set the run_if to .IsCI. -The Workflow should look something like this: - - _download_certs: - description: This is a utility workflow, used by other workflows. - summary: This workflow downloads the needed certificates on the CI server and adds them to the keychain. - steps: - - git::https://github.com/bitrise-io/steps-certificate-and-profile-installer.git@master: - description: |- - This step will only be used in CI mode, on continuous integration - servers / services (because of the `run_if` statement), - but **NOT** when you run it on your own machine. - run_if: .IsCI - inputs: - - keychain_path: $BITRISE_KEYCHAIN_PATH - - keychain_password: $BITRISE_KEYCHAIN_PASSWORD - -Now we should add the remaining two Xcode Workflows. For both Workflows the _setup and _download_certs Workflows have to be added to the before_run section, to make sure the source is on the machine and the needed signing tools are also present. The only difference between these two Workflows is that before the archive is created we want to run a Unit Tests to make sure nothing went wrong since the previous deployed version. - - analyze: - before_run: - - _setup - - _download_certs - description: |- - This workflow will run Xcode analyze on this project, - but first it'll run the workflows listed in - the `before_run` section. - steps: - - script: - title: Run Xcode analyze - inputs: - - content: xcodebuild -project "${XCODE_PROJECT_PATH}" -scheme "${XCODE_PROJECT_SCHEME}" - analyze - archive: - description: |- - This workflow will run Xcode archive on this project, - but first it'll run the workflows listed in - the `before_run` section. - before_run: - - _setup - - test - - _download_certs - steps: - - xcode-archive: - title: Run Xcode archive - inputs: - - project_path: ${XCODE_PROJECT_PATH} - - scheme: ${XCODE_PROJECT_SCHEME} - - output_dir: $output_dir - outputs: - - BITRISE_IPA_PATH: null - opts: - title: The created .ipa file's path - -And now the master Workflow. This Workflow will deploy the created archive, clean up and send a notification to slack. So the before_run section should contain the archive and the after_run should contain the _cleanup Workflow. And just to make sure no one uploads a broken version, we will set the run_if to only run the Steps if the build is running on a CI server. By adding the correct input variables the Workflow should look like this: - -master: - description: |- - This workflow is meant to be used on a CI server (like bitrise.io), for continuous - deployment, but of course you can run it on your own Mac as well, - except the Step which deploys to Bitrise.io - that's marked with - a Run-If statement to be skipped, unless you run bitrise in --ci mode. - before_run: - - archive - after_run: - - _cleanup - steps: - - script: - inputs: - - content: |- - #!/bin/bash - echo "-> BITRISE_IPA_PATH: ${BITRISE_IPA_PATH}" - - bitrise-ios-deploy: - description: |- - The long `run_if` here is a workaround. At the moment Bitrise.io - defines the BITRISE_PULL_REQUEST environment - in case the build was started by a Pull Request, and not the - required PULL_REQUEST_ID - so we'll check for that instead. - run_if: enveq "BITRISE_PULL_REQUEST" "" | and .IsCI - inputs: - - notify_user_groups: none - - is_enable_public_page: "yes" - outputs: - - BITRISE_PUBLIC_INSTALL_PAGE_URL: null - opts: - title: Public Install Page URL - description: |- - Public Install Page's URL, if the - *Enable public page for the App?* option was *enabled*. - - slack: - run_if: .IsCI - inputs: - - webhook_url: ${SLACK_WEBHOOK_URL} - - channel: ${SLACK_CHANNEL} - - from_username: ${PROJECT_TITLE} - OK - - from_username_on_error: ${PROJECT_TITLE} - ERROR - - message: |- - CI check - OK - PULL_REQUEST_ID : ${PULL_REQUEST_ID} - BITRISE_PUBLIC_INSTALL_PAGE_URL: ${BITRISE_PUBLIC_INSTALL_PAGE_URL} - - message_on_error: |- - CI check - FAILED - PULL_REQUEST_ID : ${PULL_REQUEST_ID} - -This lesson showed you how to handle the local and the CI server Workflows. Move on to the next lesson and see how you can define triggers to run a Workflow using the given trigger. For example trigger a build using the `test` Workflow when a push comes any feature branch. - -[Back to Lesson 4](../lesson4_errors) - -[Lesson 6 - Pull the trigger on the Workflow](../lesson6_triggers) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/bitrise.yml deleted file mode 100644 index cbce92c0..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson5_complex_wf/bitrise.yml +++ /dev/null @@ -1,148 +0,0 @@ -format_version: 1.3.0 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -app: - envs: - - BITRISE_PROJECT_TITLE: lesson_5 - opts: - is_expand: true - - BITRISE_DEV_BRANCH: master - opts: - is_expand: true - - PROJECT_TITLE: BitriseSampleWithYML - - XCODE_PROJECT_PATH: ./sample-apps-ios-with-bitrise-yml/${PROJECT_TITLE}.xcodeproj - - XCODE_PROJECT_SCHEME: ${PROJECT_TITLE} - - BITRISE_KEYCHAIN_PATH: $HOME/Library/Keychains/login.keychain - - BITRISE_KEYCHAIN_PASSWORD: vagrant -workflows: - _download_certs: - description: This is a utility workflow, used by other workflows. - summary: This workflow downloads the needed certificates on the CI server and adds them to the keychain. - steps: - - git::https://github.com/bitrise-io/steps-certificate-and-profile-installer.git@master: - description: |- - This step will only be used in CI mode, on continuous integration - servers / services (because of the `run_if` statement), - but **NOT** when you run it on your own machine. - run_if: .IsCI - inputs: - - keychain_path: $BITRISE_KEYCHAIN_PATH - - keychain_password: $BITRISE_KEYCHAIN_PASSWORD - _setup: - description: Clone repo - steps: - - script: - title: clone - inputs: - - content: |- - #!/bin/bash - echo $XCODE_PROJECT_PATH - if [ ! -d $PROJECT_FOLDER ] ; then - git clone ${REPO_URL} - else - cd $PROJECT_FOLDER - git pull - fi - _cleanup: - description: |- - This is a utility workflow. It runs a script to delete the folders created in the setup. - steps: - - script: - title: Cleanup folder - description: |- - A script step to delete the downloaded Step folder. - run_if: not .IsCI - inputs: - - content: |- - #!/bin/bash - rm -rf $PROJECT_TITLE - analyze: - before_run: - - _setup - - _download_certs - description: |- - This workflow will run Xcode analyze on this project, - but first it'll run the workflows listed in - the `before_run` section. - steps: - - script: - title: Run Xcode analyze - inputs: - - content: xcodebuild -project "${XCODE_PROJECT_PATH}" -scheme "${XCODE_PROJECT_SCHEME}" - analyze - archive: - description: |- - This workflow will run Xcode archive on this project, - but first it'll run the workflows listed in - the `before_run` section. - before_run: - - _setup - - test - - _download_certs - steps: - - xcode-archive: - title: Run Xcode archive - inputs: - - project_path: ${XCODE_PROJECT_PATH} - - scheme: ${XCODE_PROJECT_SCHEME} - - output_dir: $output_dir - outputs: - - BITRISE_IPA_PATH: null - opts: - title: The created .ipa file's path - test: - steps: - - xcode-test: - title: Run Xcode test - inputs: - - project_path: ${XCODE_PROJECT_PATH} - - scheme: ${XCODE_PROJECT_SCHEME} - - simulator_device: iPhone 6 - - simulator_os_version: latest - - is_clean_build: "no" - master: - description: |- - This workflow is meant to be used on a CI server (like bitrise.io), for continuous - deployment, but of course you can run it on your own Mac as well, - except the Step which deploys to Bitrise.io - that's marked with - a Run-If statement to be skipped, unless you run bitrise in --ci mode. - before_run: - - archive - after_run: - - _cleanup - steps: - - script: - inputs: - - content: |- - #!/bin/bash - echo "-> BITRISE_IPA_PATH: ${BITRISE_IPA_PATH}" - - bitrise-ios-deploy: - description: |- - The long `run_if` here is a workaround. At the moment Bitrise.io - defines the BITRISE_PULL_REQUEST environment - in case the build was started by a Pull Request, and not the - required PULL_REQUEST_ID - so we'll check for that instead. - run_if: enveq "BITRISE_PULL_REQUEST" "" | and .IsCI - inputs: - - notify_user_groups: none - - is_enable_public_page: "yes" - outputs: - - BITRISE_PUBLIC_INSTALL_PAGE_URL: null - opts: - title: Public Install Page URL - description: |- - Public Install Page's URL, if the - *Enable public page for the App?* option was *enabled*. - - slack: - run_if: .IsCI - inputs: - - webhook_url: ${SLACK_WEBHOOK_URL} - - channel: ${SLACK_CHANNEL} - - from_username: ${PROJECT_TITLE} - OK - - from_username_on_error: ${PROJECT_TITLE} - ERROR - - message: |- - CI check - OK - PULL_REQUEST_ID : ${PULL_REQUEST_ID} - BITRISE_PUBLIC_INSTALL_PAGE_URL: ${BITRISE_PUBLIC_INSTALL_PAGE_URL} - - message_on_error: |- - CI check - FAILED - PULL_REQUEST_ID : ${PULL_REQUEST_ID} diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/.gitignore b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/.gitignore deleted file mode 100644 index 9e7527f5..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ - -.bitrise* diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/README.md b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/README.md deleted file mode 100644 index e6a7ad2a..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Lesson 6 - Pull the trigger on the Workflow - -[Back to Lesson 5](../lesson5_complex_wf) - -Using Git Flow you have multiple branches and need to do different things according to these branch types. Let's try the triggers with an example: -There are some developers working on your project. Each one of them works on a different feature branch developing different features. When a developer finishes a feature and merges the given branch, you want to notify the lead developer that it's time for a code review. When a feature set is merged on the development branch you may want to add the changes to the master branch, deploy the application and send notification emails to some employees of the client. Triggers can be added to the trigger map section in your bitrise.yml. You set a pattern and which workflow should the given pattern trigger. Here is a sample trigger map for the example development process: - - trigger_map: - - pattern: test** - is_pull_request_allowed: true - workflow: test - - pattern: "**feature**" - is_pull_request_allowed: true - workflow: feature - - pattern: "**develop" - is_pull_request_allowed: true - workflow: develop - - pattern: master - is_pull_request_allowed: true - workflow: master - - pattern: "*" - is_pull_request_allowed: true - workflow: fallback - -You can notice that there is a fallback workflow at the end of the trigger map. This Workflow runs if the trigger expression didn't match any of the defined trigger patterns. For example if a developer creates a new branch with the name `develop_awesome_important_change` it wouldn't match the `**develop` trigger pattern. In this case the fallback Workflow would run. You can use this Workflow to get notified about the wrong branch name. As you can see you can add wildcard to your pattern but make sure to add the `""` if you want to start the pattern with the wildcard (in yml the value can't start with *). - -You can notice on the [Bitrise website](https://bitrise.io) that the triggers there are the names of the branch that received the push or pull request. - -You can try the samples in the bitrise.yml. Just run the `bitrise trigger` command to view the full list of triggers in the .yml and try running the given workflow with the `bitrise trigger ` command. - -[Back to Lesson 5](../lesson5_complex_wf) diff --git a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/bitrise.yml b/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/bitrise.yml deleted file mode 100644 index 5e2d46ef..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_lessons/lesson6_triggers/bitrise.yml +++ /dev/null @@ -1,97 +0,0 @@ -format_version: 1.3.0 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - -title: Template configuration. -summary: |- - Template 'bitrise.yml', generated by 'bitrise init'. -description: |- - Configuration (environments) specified in 'app' will be available - for every workflow. - - The Trigger Map ('trigger_map') defines mapping between trigger patterns - and workflows. - You can run workflows directly with bitrise: bitrise run workflow-name - Or you can 'trigger' a build: bitrise trigger some-pattern - - With this example 'trigger_map' if you 'bitrise trigger test' - or 'bitrise trigger test-1' or specify any other pattern - which starts with 'test' then the 'test' workflow will be used. - In any other case (ex: 'bitrise trigger something-else') the - workflow called 'fallback' will be used. - - Workflows ('workflows') are where you can define different, separate scenarios, - which you can then 'bitrise run' or 'bitrise trigger'. - -app: - envs: - - BITRISE_APP_TITLE: "lesson_6" - - BITRISE_DEV_BRANCH: "master" - -trigger_map: -- pattern: test** - is_pull_request_allowed: true - workflow: test -- pattern: "**feature**" - is_pull_request_allowed: true - workflow: feature -- pattern: "**develop" - is_pull_request_allowed: true - workflow: develop -- pattern: master - is_pull_request_allowed: true - workflow: master -- pattern: "*" - is_pull_request_allowed: true - workflow: fallback - -workflows: - test: - steps: - - script: - title: Fallback - inputs: - - content: |- - #!/bin/bash - echo "This is the test workflow, used" - echo " if you 'bitrise trigger' a build and the pattern" - echo " starts with "test"" - feature: - steps: - - script: - title: Fallback - inputs: - - content: |- - #!/bin/bash - echo "This is the feature workflow, used" - echo " if you 'bitrise trigger' a build and the pattern" - echo " contains the "feature" expression" - develop: - steps: - - script: - title: Fallback - inputs: - - content: |- - #!/bin/bash - echo "This is a the develop workflow, used" - echo " if you 'bitrise trigger' a build and the pattern" - echo " ends with the "develop" expression" - master: - steps: - - script: - title: Fallback - inputs: - - content: |- - #!/bin/bash - echo "This is a the master workflow, used" - echo " if you 'bitrise trigger' a build and the pattern" - echo " matches the "master" pattern in the trigger_map" - fallback: - steps: - - script: - title: Fallback - inputs: - - content: |- - #!/bin/bash - echo "This is a the fallback workflow, used" - echo " if you 'bitrise trigger' a build but the pattern" - echo " does not match any other pattern in the trigger_map" diff --git a/vendor/github.com/bitrise-io/bitrise/_scripts/build_tools_in_docker.sh b/vendor/github.com/bitrise-io/bitrise/_scripts/build_tools_in_docker.sh deleted file mode 100644 index c8ff3959..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_scripts/build_tools_in_docker.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -set -e - -THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -export REPO_ROOT_DIR="${THIS_SCRIPT_DIR}/.." -cd "${REPO_ROOT_DIR}" - -CONFIG_tool_bin_path="${REPO_ROOT_DIR}/_temp/bin" -echo " (i) CONFIG_tool_bin_path: ${CONFIG_tool_bin_path}" - -if [ ! -d "${ENVMAN_REPO_DIR_PATH}" ] ; then - echo "[!] ENVMAN_REPO_DIR_PATH not defined or not a dir - required!" - exit 1 -fi - -if [ ! -d "${STEPMAN_REPO_DIR_PATH}" ] ; then - echo "[!] STEPMAN_REPO_DIR_PATH not defined or not a dir - required!" - exit 1 -fi - -set -v - -mkdir -p "${CONFIG_tool_bin_path}" - -# build envman -cd "${ENVMAN_REPO_DIR_PATH}" -docker-compose run --rm app go build -o bin-envman -mv ./bin-envman "${CONFIG_tool_bin_path}/envman" - -# build stepman -cd "${STEPMAN_REPO_DIR_PATH}" -docker-compose run --rm app go build -o bin-stepman -mv ./bin-stepman "${CONFIG_tool_bin_path}/stepman" - -# => DONE [OK] diff --git a/vendor/github.com/bitrise-io/bitrise/_scripts/ci.sh b/vendor/github.com/bitrise-io/bitrise/_scripts/ci.sh deleted file mode 100644 index fca661e4..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_scripts/ci.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -set -e - -THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -export REPO_ROOT_DIR="${THIS_SCRIPT_DIR}/.." - -set -v - -# Install dependencies -go get -u github.com/tools/godep -go install github.com/tools/godep -godep restore - -# Build a test version -go build -o tmpbin -./tmpbin setup -rm ./tmpbin - -bash "${THIS_SCRIPT_DIR}/common/ci.sh" - -# ===> DONE diff --git a/vendor/github.com/bitrise-io/bitrise/_scripts/create_release_with_docker_compose.sh b/vendor/github.com/bitrise-io/bitrise/_scripts/create_release_with_docker_compose.sh deleted file mode 100644 index efaf8933..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_scripts/create_release_with_docker_compose.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -e - -THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -export REPO_ROOT_DIR="${THIS_SCRIPT_DIR}/.." -cd "${REPO_ROOT_DIR}" - -set -v - -docker-compose build --no-cache app - -docker-compose run --rm app bitrise run create-release diff --git a/vendor/github.com/bitrise-io/bitrise/_scripts/get_version.go b/vendor/github.com/bitrise-io/bitrise/_scripts/get_version.go deleted file mode 100644 index 7f3b0a09..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_scripts/get_version.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "log" - "regexp" -) - -func main() { - // Inputs - var ( - versionFilePathParam = flag.String("file", "", `Version file path`) - ) - - flag.Parse() - - if versionFilePathParam == nil || *versionFilePathParam == "" { - log.Fatalf(" [!] No version file parameter specified") - } - versionFilePath := *versionFilePathParam - - // Main - versionFileBytes, err := ioutil.ReadFile(versionFilePath) - if err != nil { - log.Fatalf("Failed to read version file: %s", err) - } - versionFileContent := string(versionFileBytes) - - re := regexp.MustCompile(`const VERSION = "(?P[0-9]+\.[0-9-]+\.[0-9-]+)"`) - results := re.FindAllStringSubmatch(versionFileContent, -1) - versionStr := "" - for _, v := range results { - versionStr = v[1] - } - if versionStr == "" { - log.Fatalf("Failed to determine version") - } - - fmt.Println(versionStr) -} diff --git a/vendor/github.com/bitrise-io/bitrise/_scripts/go_install_tools.sh b/vendor/github.com/bitrise-io/bitrise/_scripts/go_install_tools.sh deleted file mode 100644 index f50825c2..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_scripts/go_install_tools.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -set -e - -THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -export REPO_ROOT_DIR="${THIS_SCRIPT_DIR}/.." -cd "${REPO_ROOT_DIR}" - -if [ ! -d "${ENVMAN_REPO_DIR_PATH}" ] ; then - echo "[!] ENVMAN_REPO_DIR_PATH not defined or not a dir - required!" - exit 1 -fi - -if [ ! -d "${STEPMAN_REPO_DIR_PATH}" ] ; then - echo "[!] STEPMAN_REPO_DIR_PATH not defined or not a dir - required!" - exit 1 -fi - -set -v - -# go install envman -cd "${ENVMAN_REPO_DIR_PATH}" -godep restore -go install - -# go install stepman -cd "${STEPMAN_REPO_DIR_PATH}" -godep restore -go install - -# godep restore for bitrise -cd "${REPO_ROOT_DIR}" -godep restore - -# => DONE [OK] diff --git a/vendor/github.com/bitrise-io/bitrise/_scripts/set_version.sh b/vendor/github.com/bitrise-io/bitrise/_scripts/set_version.sh deleted file mode 100644 index 901c1438..00000000 --- a/vendor/github.com/bitrise-io/bitrise/_scripts/set_version.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -x - -version_file_path="$1" -if [ ! -f "$version_file_path" ] ; then - echo " [!] version_file_path not provided, or file doesn't exist at path: $version_file_path" - exit 1 -fi -versionNumber=$next_version -if [[ "$versionNumber" == "" ]] ; then - echo " [!] versionNumber not provided" - exit 1 -fi - -cat >"${version_file_path}" < Linting: $line" - golint_out="$(golint $line)" - if [[ "${golint_out}" != "" ]] ; then - echo "=> Golint issues found:" - echo "${golint_out}" - exit 1 - fi - done <<< "$GOLIST_WITHOUT_VENDOR" - - script: - title: Go test - inputs: - - content: go test ./... - - _bitrise-run-setup: - steps: - - script: - title: Setup system bitrise - run_if: ".IsCI" - inputs: - - content: |- - #!/bin/bash - - bitrise setup - - _prepare-and-setup: - title: Prepare bitrise and install testing tools - description: | - Prepares the environment for testing - steps: - - script: - is_skippable: true - inputs: - - content: brew update - - script: - title: Install testing tools - inputs: - - content: |- - #!/bin/bash - set -ex - - # Check for unhandled errors - go get -u -v github.com/kisielk/errcheck - - # Go lint - go get -u -v github.com/golang/lint/golint - - script: - title: Install bitrise tools - run_if: ".IsCI" - inputs: - - content: |- - #!/bin/bash - set -e - set -x - - # Install envman - envman -v - curl -fL https://github.com/bitrise-io/envman/releases/download/1.1.0/envman-$(uname -s)-$(uname -m) > /usr/local/bin/envman - chmod +x /usr/local/bin/envman - envman -v - - # Install stepman - stepman -v - curl -fL https://github.com/bitrise-io/stepman/releases/download/0.9.18/stepman-$(uname -s)-$(uname -m) > /usr/local/bin/stepman - chmod +x /usr/local/bin/stepman - stepman -v - - # ---------------------------------------------------------------- - # --- workflows for Releasing - create-release: - title: Create Release version - description: |- - Creates new version with specified RELEASE_VERSION environment - - 1, Create CHANGELOG and git release - 2, Export RELEASE_VERSION - 3, Create binaries - after_run: - - create-binaries - steps: - - script: - title: Create CHANGELOG and git release - inputs: - - content: |- - #!/bin/bash - set -ex - - go get github.com/bitrise-tools/releaseman - - export CI=true - - releaseman create-changelog \ - --version $RELEASE_VERSION \ - --set-version-script "bash _scripts/set_version.sh version/version.go" - - announce-release: - title: Announce Release - description: |- - Notifying about new version of bitrise - - Send Slack notifications - steps: - - slack: - title: Announce on Internal Slack channel - inputs: - - webhook_url: "$INTERNAL_DEV_SLACK_WEBHOOK_URL" - - channel: "$INTERNAL_DEV_SLACK_CHANNEL" - - from_username: ${BIN_NAME} - - message: | - Release v${RELEASE_VERSION} was just published! :tada: - - You can find it at ${GITHUB_RELEASES_URL} - - emoji: ":rocket:" - - slack: - title: Announce on Public Slack channel - inputs: - - webhook_url: "$PUBLIC_SLACK_WEBHOOK_URL" - - channel: "$PUBLIC_SLACK_CHANNEL" - - from_username: ${BIN_NAME} - - message: | - Release v${RELEASE_VERSION} was just published! :tada: - - You can find it at ${GITHUB_RELEASES_URL} - - emoji: ":rocket:" - - create-binaries: - title: Create binaries - description: | - Creates Linux and Darwin binaries - steps: - - script: - title: Create binaries - inputs: - - content: | - #!/bin/bash - set -e - set -x - - echo - echo "Create final binaries" - echo " Build number: $BITRISE_BUILD_NUMBER" - - export ARCH=x86_64 - export GOARCH=amd64 - - # Create Darwin bin - export OS=Darwin - export GOOS=darwin - - DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" - echo " Create final Darwin binary at: $DEPLOY_PATH" - - version_package="github.com/bitrise-io/bitrise/version" - - go build \ - -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ - -o "$DEPLOY_PATH" - - envman add --key OSX_DEPLOY_PATH --value $DEPLOY_PATH - cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH - echo " Copy final Darwin binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" - - - # Create Linux binary - export OS=Linux - export GOOS=linux - - DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" - echo " Create final Linux binary at: $DEPLOY_PATH" - - go build \ - -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ - -o "$DEPLOY_PATH" - - envman add --key LINUX_DEPLOY_PATH --value $DEPLOY_PATH - cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH - echo " Copy final Linux binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" - - # ---------------------------------------------------------------- - # --- workflows for Utility - godeps-update: - title: Godeps update - description: | - Used for updating bitrise dependencies with godep - steps: - - script: - title: Dependency update - inputs: - - content: | - #!/bin/bash - set -ex - go get -u -v github.com/tools/godep - - rm -rf ./Godeps - rm -rf ./vendor - - go get -t -d ./... - go get golang.org/x/sys/unix - go get github.com/davecgh/go-spew/spew - go get github.com/pmezard/go-difflib/difflib - godep save ./... - - noop: - title: Noop - description: Empty workflow for quick testing - - fail-test: - title: Fails - description: Workflow will fail - steps: - - script: - title: Success - inputs: - - content: exit 0 - - script: - title: Fail wit exit code 2 - inputs: - - content: exit 2 - - script: - title: Skippable fail with exit code 2 - is_always_run: true - is_skippable: true - inputs: - - content: exit 2 - - script: - title: Skipping success - is_always_run: false - inputs: - - content: exit 0 diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/dependencies.go b/vendor/github.com/bitrise-io/bitrise/bitrise/dependencies.go deleted file mode 100644 index a6bdb231..00000000 --- a/vendor/github.com/bitrise-io/bitrise/bitrise/dependencies.go +++ /dev/null @@ -1,420 +0,0 @@ -package bitrise - -import ( - "errors" - "fmt" - "strings" - "time" - - "github.com/bitrise-io/bitrise/plugins" - "github.com/bitrise-io/bitrise/tools" - "github.com/bitrise-io/bitrise/utils" - "github.com/bitrise-io/go-utils/colorstring" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/progress" - "github.com/bitrise-io/go-utils/retry" - "github.com/bitrise-io/go-utils/versions" - "github.com/bitrise-io/goinp/goinp" - stepmanModels "github.com/bitrise-io/stepman/models" - ver "github.com/hashicorp/go-version" -) - -func removeEmptyNewLines(text string) string { - split := strings.Split(text, "\n") - cleanedLines := []string{} - for _, line := range split { - if strings.TrimSpace(line) != "" { - cleanedLines = append(cleanedLines, line) - } - } - return strings.Join(cleanedLines, "\n") -} - -// CheckIsPluginInstalled ... -func CheckIsPluginInstalled(name string, dependency PluginDependency) error { - _, found, err := plugins.LoadPlugin(name) - if err != nil { - return err - } - - currentVersion := "" - installOrUpdate := false - - if !found { - log.Warnf("Default plugin (%s) NOT found, installing...", name) - installOrUpdate = true - currentVersion = dependency.MinVersion - } else { - installedVersion, err := plugins.GetPluginVersion(name) - if err != nil { - return err - } - - if installedVersion == nil { - log.Warnf("Default plugin (%s) is not installed from git, no version info available.", name) - currentVersion = "" - } else { - currentVersion = installedVersion.String() - - minVersion, err := ver.NewVersion(dependency.MinVersion) - if err != nil { - return err - } - - if installedVersion.LessThan(minVersion) { - log.Warnf("Default plugin (%s) version (%s) is lower than required (%s), updating...", name, installedVersion.String(), minVersion.String()) - installOrUpdate = true - currentVersion = dependency.MinVersion - } - } - } - - if installOrUpdate { - var plugin plugins.Plugin - err := retry.Times(2).Wait(5 * time.Second).Try(func(attempt uint) error { - if attempt > 0 { - log.Warnf("Download failed, retrying ...") - } - p, _, err := plugins.InstallPlugin(dependency.Source, dependency.MinVersion) - plugin = p - return err - }) - if err != nil { - return fmt.Errorf("Failed to install plugin, error: %s", err) - } - - if len(plugin.Description) > 0 { - fmt.Println(removeEmptyNewLines(plugin.Description)) - } - } - - pluginDir := plugins.GetPluginDir(name) - - log.Printf("%s Plugin %s (%s): %s", colorstring.Green("[OK]"), name, currentVersion, pluginDir) - - return nil -} - -// CheckIsHomebrewInstalled ... -func CheckIsHomebrewInstalled(isFullSetupMode bool) error { - brewRubyInstallCmdString := `$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"` - officialSiteURL := "http://brew.sh/" - - progInstallPth, err := utils.CheckProgramInstalledPath("brew") - if err != nil { - fmt.Println() - log.Warnf("It seems that Homebrew is not installed on your system.") - log.Infof("Homebrew (short: brew) is required in order to be able to auto-install all the bitrise dependencies.") - log.Infof("You should be able to install brew by copying this command and running it in your Terminal:") - log.Infof(brewRubyInstallCmdString) - log.Infof("You can find more information about Homebrew on its official site at:", officialSiteURL) - log.Warnf("Once the installation of brew is finished you should call the bitrise setup again.") - return err - } - verStr, err := command.RunCommandAndReturnStdout("brew", "--version") - if err != nil { - log.Infof("") - return errors.New("Failed to get version") - } - - if isFullSetupMode { - // brew doctor - doctorOutput := "" - var err error - progress.NewDefaultWrapper("brew doctor").WrapAction(func() { - doctorOutput, err = command.RunCommandAndReturnCombinedStdoutAndStderr("brew", "doctor") - }) - if err != nil { - fmt.Println("") - log.Warnf("brew doctor returned an error:") - log.Warnf("%s", doctorOutput) - return errors.New("command failed: brew doctor") - } - } - - verSplit := strings.Split(verStr, "\n") - if len(verSplit) == 2 { - log.Printf("%s %s: %s", colorstring.Green("[OK]"), verSplit[0], progInstallPth) - log.Printf("%s %s", colorstring.Green("[OK]"), verSplit[1]) - } else { - log.Printf("%s %s: %s", colorstring.Green("[OK]"), verStr, progInstallPth) - } - - return nil -} - -// PrintInstalledXcodeInfos ... -func PrintInstalledXcodeInfos() error { - xcodeSelectPth, err := command.RunCommandAndReturnStdout("xcode-select", "--print-path") - if err != nil { - xcodeSelectPth = "xcode-select --print-path failed to detect the location of activate Xcode Command Line Tools path" - } - - progInstallPth, err := utils.CheckProgramInstalledPath("xcodebuild") - if err != nil { - return errors.New("xcodebuild is not installed") - } - - isFullXcodeAvailable := false - verStr, err := command.RunCommandAndReturnCombinedStdoutAndStderr("xcodebuild", "-version") - if err != nil { - // No full Xcode available, only the Command Line Tools - // verStr is something like "xcode-select: error: tool 'xcodebuild' requires Xcode, but active developer directory '/Library/Developer/CommandLineTools' is a command line tools instance" - isFullXcodeAvailable = false - } else { - // version OK - full Xcode available - // we'll just format it a bit to fit into one line - isFullXcodeAvailable = true - verStr = strings.Join(strings.Split(verStr, "\n"), " | ") - } - - if !isFullXcodeAvailable { - log.Printf("%s xcodebuild (%s): %s", colorstring.Green("[OK]"), colorstring.Yellow(verStr), progInstallPth) - } else { - log.Printf("%s xcodebuild (%s): %s", colorstring.Green("[OK]"), verStr, progInstallPth) - } - - log.Printf("%s active Xcode (Command Line Tools) path (xcode-select --print-path): %s", colorstring.Green("[OK]"), xcodeSelectPth) - - if !isFullXcodeAvailable { - log.Warnf("No Xcode found, only the Xcode Command Line Tools are available!") - log.Warnf("Full Xcode is required to build, test and archive iOS apps!") - } - - return nil -} - -func checkIsBitriseToolInstalled(toolname, minVersion string, isInstall bool) error { - doInstall := func() error { - officialGithub := "https://github.com/bitrise-io/" + toolname - log.Warnf("No supported %s version found", toolname) - log.Printf("You can find more information about %s on its official GitHub page: %s", toolname, officialGithub) - - // Install - var err error - progress.NewDefaultWrapper("Installing").WrapAction(func() { - err = retry.Times(2).Wait(5 * time.Second).Try(func(attempt uint) error { - if attempt > 0 { - log.Warnf("Download failed, retrying ...") - } - return tools.InstallToolFromGitHub(toolname, "bitrise-io", minVersion) - }) - }) - - if err != nil { - return err - } - - // check again - return checkIsBitriseToolInstalled(toolname, minVersion, false) - } - - // check whether installed - progInstallPth, err := utils.CheckProgramInstalledPath(toolname) - if err != nil { - if !isInstall { - return err - } - return doInstall() - } - verStr, err := command.RunCommandAndReturnStdout(toolname, "-version") - if err != nil { - log.Infof("") - return errors.New("Failed to get version") - } - - // version check - isVersionOk, err := versions.IsVersionGreaterOrEqual(verStr, minVersion) - if err != nil { - log.Errorf("Failed to validate installed version") - return err - } - if !isVersionOk { - if !isInstall { - log.Warnf("Installed %s found, but not a supported version (%s)", toolname, verStr) - return errors.New("Failed to install required version") - } - return doInstall() - } - - log.Printf("%s %s (%s): %s", colorstring.Green("[OK]"), toolname, verStr, progInstallPth) - return nil -} - -// CheckIsEnvmanInstalled ... -func CheckIsEnvmanInstalled(minEnvmanVersion string) error { - toolname := "envman" - minVersion := minEnvmanVersion - if err := checkIsBitriseToolInstalled(toolname, minVersion, true); err != nil { - return err - } - return nil -} - -// CheckIsStepmanInstalled ... -func CheckIsStepmanInstalled(minStepmanVersion string) error { - toolname := "stepman" - minVersion := minStepmanVersion - if err := checkIsBitriseToolInstalled(toolname, minVersion, true); err != nil { - return err - } - return nil -} - -func checkIfBrewPackageInstalled(packageName string) bool { - out, err := command.New("brew", "list", packageName).RunAndReturnTrimmedCombinedOutput() - if err != nil { - return false - } - return len(out) > 0 -} - -func checkIfAptPackageInstalled(packageName string) bool { - err := command.New("dpkg", "-s", packageName).Run() - return (err == nil) -} - -// DependencyTryCheckTool ... -func DependencyTryCheckTool(tool string) error { - var cmd *command.Model - errMsg := "" - - switch tool { - case "xcode": - cmd = command.New("xcodebuild", "-version") - errMsg = "The full Xcode app is not installed, required for this step. You can install it from the App Store." - break - default: - cmdFields := strings.Fields(tool) - if len(cmdFields) >= 2 { - cmd = command.New(cmdFields[0], cmdFields[1:]...) - } else if len(cmdFields) == 1 { - cmd = command.New(cmdFields[0]) - } else { - return fmt.Errorf("Invalid tool name (%s)", tool) - } - } - - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - if err != nil { - if errMsg != "" { - return errors.New(errMsg) - } - log.Infof("Output was: %s", out) - return fmt.Errorf("Dependency check failed for: %s", tool) - } - - return nil -} - -// InstallWithBrewIfNeeded ... -func InstallWithBrewIfNeeded(brewDep stepmanModels.BrewDepModel, isCIMode bool) error { - isDepInstalled := false - // First do a "which", to see if the binary is available. - // Can be available from another source, not just from brew, - // e.g. it's common to use NVM or similar to install and manage the Node.js version. - { - if out, err := command.RunCommandAndReturnCombinedStdoutAndStderr("which", brewDep.GetBinaryName()); err != nil { - if err.Error() == "exit status 1" && out == "" { - isDepInstalled = false - } else { - // unexpected `which` error - return fmt.Errorf("which (%s) failed -- out: (%s) err: (%s)", brewDep.Name, out, err) - } - } else if out != "" { - isDepInstalled = true - } else { - // no error but which's output was empty - return fmt.Errorf("which (%s) failed -- no error (exit code 0) but output was empty", brewDep.Name) - } - } - - // then do a package manager specific lookup - { - if !isDepInstalled { - // which did not find the binary, also check in brew, - // whether the package is installed - isDepInstalled = checkIfBrewPackageInstalled(brewDep.Name) - } - } - - if !isDepInstalled { - // Tool isn't installed -- install it... - if !isCIMode { - log.Infof(`This step requires "%s" to be available, but it is not installed.`, brewDep.GetBinaryName()) - allow, err := goinp.AskForBoolWithDefault(`Would you like to install the "`+brewDep.Name+`" package with brew?`, true) - if err != nil { - return err - } - if !allow { - return errors.New("(" + brewDep.Name + ") is required for step") - } - } - - log.Infof("(%s) isn't installed, installing...", brewDep.Name) - if cmdOut, err := command.RunCommandAndReturnCombinedStdoutAndStderr("brew", "install", brewDep.Name); err != nil { - log.Errorf("brew install %s failed -- out: (%s) err: (%s)", brewDep.Name, cmdOut, err) - return err - } - log.Infof(" * "+colorstring.Green("[OK]")+" %s installed", brewDep.Name) - } - - return nil -} - -// InstallWithAptGetIfNeeded ... -func InstallWithAptGetIfNeeded(aptGetDep stepmanModels.AptGetDepModel, isCIMode bool) error { - isDepInstalled := false - // First do a "which", to see if the binary is available. - // Can be available from another source, not just from brew, - // e.g. it's common to use NVM or similar to install and manage the Node.js version. - { - if out, err := command.RunCommandAndReturnCombinedStdoutAndStderr("which", aptGetDep.GetBinaryName()); err != nil { - if err.Error() == "exit status 1" && out == "" { - isDepInstalled = false - } else { - // unexpected `which` error - return fmt.Errorf("which (%s) failed -- out: (%s) err: (%s)", aptGetDep.Name, out, err) - } - } else if out != "" { - isDepInstalled = true - } else { - // no error but which's output was empty - return fmt.Errorf("which (%s) failed -- no error (exit code 0) but output was empty", aptGetDep.Name) - } - } - - // then do a package manager specific lookup - { - if !isDepInstalled { - // which did not find the binary, also check in brew, - // whether the package is installed - isDepInstalled = checkIfAptPackageInstalled(aptGetDep.Name) - } - } - - if !isDepInstalled { - // Tool isn't installed -- install it... - if !isCIMode { - log.Infof(`This step requires "%s" to be available, but it is not installed.`, aptGetDep.GetBinaryName()) - allow, err := goinp.AskForBoolWithDefault(`Would you like to install the "`+aptGetDep.Name+`" package with apt-get?`, true) - if err != nil { - return err - } - if !allow { - return errors.New("(" + aptGetDep.Name + ") is required for step") - } - } - - log.Infof("(%s) isn't installed, installing...", aptGetDep.Name) - if cmdOut, err := command.RunCommandAndReturnCombinedStdoutAndStderr("sudo", "apt-get", "-y", "install", aptGetDep.Name); err != nil { - log.Errorf("sudo apt-get -y install %s failed -- out: (%s) err: (%s)", aptGetDep.Name, cmdOut, err) - return err - } - - log.Infof(" * "+colorstring.Green("[OK]")+" %s installed", aptGetDep.Name) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/print.go b/vendor/github.com/bitrise-io/bitrise/bitrise/print.go deleted file mode 100644 index 539e381b..00000000 --- a/vendor/github.com/bitrise-io/bitrise/bitrise/print.go +++ /dev/null @@ -1,614 +0,0 @@ -package bitrise - -import ( - "fmt" - "strings" - "time" - "unicode/utf8" - - "github.com/bitrise-io/bitrise/models" - "github.com/bitrise-io/bitrise/toolkits" - "github.com/bitrise-io/go-utils/colorstring" - log "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/stringutil" - "github.com/bitrise-io/go-utils/versions" - stepmanModels "github.com/bitrise-io/stepman/models" -) - -const ( - // should not be under ~45 - stepRunSummaryBoxWidthInChars = 80 -) - -//------------------------------ -// Util methods -//------------------------------ - -func isUpdateAvailable(stepInfo stepmanModels.StepInfoModel) bool { - if stepInfo.LatestVersion == "" { - return false - } - - res, err := versions.CompareVersions(stepInfo.Version, stepInfo.LatestVersion) - if err != nil { - log.Errorf("Failed to compare versions, err: %s", err) - } - - return (res == 1) -} - -func getTrimmedStepName(stepRunResult models.StepRunResultsModel) string { - iconBoxWidth := len(" ") - timeBoxWidth := len(" time (s) ") - titleBoxWidth := stepRunSummaryBoxWidthInChars - 4 - iconBoxWidth - timeBoxWidth - 1 - - stepInfo := stepRunResult.StepInfo - - title := "" - if stepInfo.Step.Title != nil && *stepInfo.Step.Title != "" { - title = *stepInfo.Step.Title - } - - if stepInfo.GroupInfo.RemovalDate != "" { - title = fmt.Sprintf("[Deprecated] %s", title) - } - - titleBox := "" - switch stepRunResult.Status { - case models.StepRunStatusCodeSuccess, models.StepRunStatusCodeSkipped, models.StepRunStatusCodeSkippedWithRunIf: - titleBox = fmt.Sprintf("%s", title) - if len(titleBox) > titleBoxWidth { - dif := len(titleBox) - titleBoxWidth - title = stringutil.MaxFirstCharsWithDots(title, len(title)-dif) - titleBox = fmt.Sprintf("%s", title) - } - break - case models.StepRunStatusCodeFailed, models.StepRunStatusCodeFailedSkippable: - titleBox = fmt.Sprintf("%s (exit code: %d)", title, stepRunResult.ExitCode) - if len(titleBox) > titleBoxWidth { - dif := len(titleBox) - titleBoxWidth - title = stringutil.MaxFirstCharsWithDots(title, len(title)-dif) - titleBox = fmt.Sprintf("%s (exit code: %d)", title, stepRunResult.ExitCode) - } - break - default: - log.Errorf("Unkown result code") - return "" - } - - return titleBox -} - -func getRunningStepHeaderMainSection(stepInfo stepmanModels.StepInfoModel, idx int) string { - title := "" - if stepInfo.Step.Title != nil && *stepInfo.Step.Title != "" { - title = *stepInfo.Step.Title - } - - content := fmt.Sprintf("| (%d) %s |", idx, title) - charDiff := len(content) - stepRunSummaryBoxWidthInChars - - if charDiff < 0 { - // shorter than desired - fill with space - content = fmt.Sprintf("| (%d) %s%s |", idx, title, strings.Repeat(" ", -charDiff)) - } else if charDiff > 0 { - // longer than desired - trim title - trimmedTitleWidth := len(title) - charDiff - if trimmedTitleWidth < 4 { - log.Errorf("Step title too long, can't present title at all! : %s", title) - } else { - content = fmt.Sprintf("| (%d) %s |", idx, stringutil.MaxFirstCharsWithDots(title, trimmedTitleWidth)) - } - } - return content -} - -func getRunningStepHeaderSubSection(step stepmanModels.StepModel, stepInfo stepmanModels.StepInfoModel) string { - - idRow := "" - { - id := stepInfo.ID - idRow = fmt.Sprintf("| id: %s |", id) - charDiff := len(idRow) - stepRunSummaryBoxWidthInChars - if charDiff < 0 { - // shorter than desired - fill with space - idRow = fmt.Sprintf("| id: %s%s |", id, strings.Repeat(" ", -charDiff)) - } else if charDiff > 0 { - // longer than desired - trim title - trimmedWidth := len(id) - charDiff - if trimmedWidth < 4 { - log.Errorf("Step id too long, can't present id at all! : %s", id) - } else { - idRow = fmt.Sprintf("| id: %s |", stringutil.MaxFirstCharsWithDots(id, trimmedWidth)) - } - } - } - - versionRow := "" - { - version := stepInfo.Version - versionRow = fmt.Sprintf("| version: %s |", version) - charDiff := len(versionRow) - stepRunSummaryBoxWidthInChars - if charDiff < 0 { - // shorter than desired - fill with space - versionRow = fmt.Sprintf("| version: %s%s |", version, strings.Repeat(" ", -charDiff)) - } else if charDiff > 0 { - // longer than desired - trim title - trimmedWidth := len(version) - charDiff - if trimmedWidth < 4 { - log.Errorf("Step version too long, can't present version at all! : %s", version) - } else { - versionRow = fmt.Sprintf("| id: %s |", stringutil.MaxFirstCharsWithDots(version, trimmedWidth)) - } - } - } - - collectionRow := "" - { - collection := stepInfo.Library - collectionRow = fmt.Sprintf("| collection: %s |", collection) - charDiff := len(collectionRow) - stepRunSummaryBoxWidthInChars - if charDiff < 0 { - // shorter than desired - fill with space - collectionRow = fmt.Sprintf("| collection: %s%s |", collection, strings.Repeat(" ", -charDiff)) - } else if charDiff > 0 { - // longer than desired - trim title - trimmedWidth := len(collection) - charDiff - if trimmedWidth < 4 { - log.Errorf("Step collection too long, can't present collection at all! : %s", collection) - } else { - collectionRow = fmt.Sprintf("| collection: %s |", stringutil.MaxLastCharsWithDots(collection, trimmedWidth)) - } - } - } - - toolkitRow := "" - { - toolkitForStep := toolkits.ToolkitForStep(step) - toolkitName := toolkitForStep.ToolkitName() - toolkitRow = fmt.Sprintf("| toolkit: %s |", toolkitName) - charDiff := len(toolkitRow) - stepRunSummaryBoxWidthInChars - if charDiff < 0 { - // shorter than desired - fill with space - toolkitRow = fmt.Sprintf("| toolkit: %s%s |", toolkitName, strings.Repeat(" ", -charDiff)) - } else if charDiff > 0 { - // longer than desired - trim title - trimmedWidth := len(toolkitName) - charDiff - if trimmedWidth < 4 { - log.Errorf("Step toolkitName too long, can't present toolkitName at all! : %s", toolkitName) - } else { - toolkitRow = fmt.Sprintf("| toolkit: %s |", stringutil.MaxLastCharsWithDots(toolkitName, trimmedWidth)) - } - } - } - - timeRow := "" - { - logTime := time.Now().Format(time.RFC3339) - timeRow = fmt.Sprintf("| time: %s |", logTime) - charDiff := len(timeRow) - stepRunSummaryBoxWidthInChars - if charDiff < 0 { - // shorter than desired - fill with space - timeRow = fmt.Sprintf("| time: %s%s |", logTime, strings.Repeat(" ", -charDiff)) - } else if charDiff > 0 { - // longer than desired - trim title - trimmedWidth := len(logTime) - charDiff - if trimmedWidth < 4 { - log.Errorf("Time too long, can't present time at all! : %s", logTime) - } else { - timeRow = fmt.Sprintf("| time: %s |", stringutil.MaxFirstCharsWithDots(logTime, trimmedWidth)) - } - } - } - - return fmt.Sprintf("%s\n%s\n%s\n%s\n%s", idRow, versionRow, collectionRow, toolkitRow, timeRow) -} - -func getRunningStepFooterMainSection(stepRunResult models.StepRunResultsModel) string { - iconBoxWidth := len(" ") - timeBoxWidth := len(" time (s) ") - titleBoxWidth := stepRunSummaryBoxWidthInChars - 4 - iconBoxWidth - timeBoxWidth - 1 - - icon := "" - title := getTrimmedStepName(stepRunResult) - coloringFunc := colorstring.Green - - switch stepRunResult.Status { - case models.StepRunStatusCodeSuccess: - icon = "✓" - coloringFunc = colorstring.Green - break - case models.StepRunStatusCodeFailed: - icon = "x" - coloringFunc = colorstring.Red - break - case models.StepRunStatusCodeFailedSkippable: - icon = "!" - coloringFunc = colorstring.Yellow - break - case models.StepRunStatusCodeSkipped, models.StepRunStatusCodeSkippedWithRunIf: - icon = "-" - coloringFunc = colorstring.Blue - break - default: - log.Errorf("Unkown result code") - return "" - } - - iconBox := fmt.Sprintf(" %s ", coloringFunc(icon)) - - titleWhiteSpaceWidth := titleBoxWidth - len(title) - coloredTitle := title - if strings.HasPrefix(title, "[Deprecated]") { - title := strings.TrimPrefix(title, "[Deprecated]") - coloredTitle = fmt.Sprintf("%s%s", colorstring.Red("[Deprecated]"), coloringFunc(title)) - } else { - coloredTitle = coloringFunc(title) - } - - titleBox := fmt.Sprintf(" %s%s", coloredTitle, strings.Repeat(" ", titleWhiteSpaceWidth)) - - runTimeStr, err := FormattedSecondsToMax8Chars(stepRunResult.RunTime) - if err != nil { - log.Errorf("Failed to format time, error: %s", err) - runTimeStr = "999+ hour" - } - - timeWhiteSpaceWidth := timeBoxWidth - len(runTimeStr) - 1 - if timeWhiteSpaceWidth < 0 { - log.Errorf("Invalid time box size for RunTime: %#v", stepRunResult.RunTime) - timeWhiteSpaceWidth = 0 - } - timeBox := fmt.Sprintf(" %s%s", runTimeStr, strings.Repeat(" ", timeWhiteSpaceWidth)) - - return fmt.Sprintf("|%s|%s|%s|", iconBox, titleBox, timeBox) -} - -func getDeprecateNotesRows(notes string) string { - colorDeprecateNote := func(line string) string { - if strings.HasPrefix(line, "Removal notes:") { - line = strings.TrimPrefix(line, "Removal notes:") - line = fmt.Sprintf("%s%s", colorstring.Red("Removal notes:"), line) - } - return line - } - - boxContentWidth := stepRunSummaryBoxWidthInChars - 4 - - notesWithoutNewLine := strings.Replace(notes, "\n", " ", -1) - words := strings.Split(notesWithoutNewLine, " ") - if len(words) == 0 { - return "" - } - - formattedNote := "" - line := "" - - for i, word := range words { - isLastLine := (i == len(words)-1) - - expectedLine := "" - if line == "" { - expectedLine = word - } else { - expectedLine = line + " " + word - } - - if utf8.RuneCountInString(expectedLine) > boxContentWidth { - // expected line would be to long, so print the previous line, and start a new with the last word. - noteRow := fmt.Sprintf("| %s |", line) - charDiff := len(noteRow) - stepRunSummaryBoxWidthInChars - if charDiff <= 0 { - // shorter than desired - fill with space - line = colorDeprecateNote(line) - noteRow = fmt.Sprintf("| %s%s |", line, strings.Repeat(" ", -charDiff)) - } else if charDiff > 0 { - // longer than desired - should not - log.Errorf("Should not be longer then expected") - } - - if formattedNote == "" { - formattedNote = noteRow - } else { - formattedNote = fmt.Sprintf("%s\n%s", formattedNote, noteRow) - } - - line = word - - if isLastLine { - noteRow := fmt.Sprintf("| %s |", line) - charDiff := len(noteRow) - stepRunSummaryBoxWidthInChars - if charDiff < 0 { - // shorter than desired - fill with space - line = colorDeprecateNote(line) - noteRow = fmt.Sprintf("| %s%s |", line, strings.Repeat(" ", -charDiff)) - } else if charDiff > 0 { - // longer than desired - should not - log.Errorf("Should not be longer then expected") - } - - if formattedNote == "" { - formattedNote = noteRow - } else { - formattedNote = fmt.Sprintf("%s\n%s", formattedNote, noteRow) - } - } - } else { - // expected line is not to long, just keep growing the line - line = expectedLine - - if isLastLine { - noteRow := fmt.Sprintf("| %s |", line) - charDiff := len(noteRow) - stepRunSummaryBoxWidthInChars - if charDiff <= 0 { - // shorter than desired - fill with space - line = colorDeprecateNote(line) - noteRow = fmt.Sprintf("| %s%s |", line, strings.Repeat(" ", -charDiff)) - } else if charDiff > 0 { - // longer than desired - should not - log.Errorf("Should not be longer then expected") - } - - if formattedNote == "" { - formattedNote = noteRow - } else { - formattedNote = fmt.Sprintf("%s\n%s", formattedNote, noteRow) - } - } - } - } - - return formattedNote -} - -func getRunningStepFooterSubSection(stepRunResult models.StepRunResultsModel) string { - stepInfo := stepRunResult.StepInfo - - removalDate := stepInfo.GroupInfo.RemovalDate - deprecateNotes := stepInfo.GroupInfo.DeprecateNotes - removalDateRow := "" - deprecateNotesRow := "" - if removalDate != "" { - removalDateValue := removalDate - removalDateKey := colorstring.Red("Removal date:") - - removalDateRow = fmt.Sprintf("| Removal date: %s |", removalDateValue) - charDiff := len(removalDateRow) - stepRunSummaryBoxWidthInChars - removalDateRow = fmt.Sprintf("| %s %s%s |", removalDateKey, removalDateValue, strings.Repeat(" ", -charDiff)) - - if deprecateNotes != "" { - deprecateNotesStr := fmt.Sprintf("Removal notes: %s", deprecateNotes) - deprecateNotesRow = getDeprecateNotesRows(deprecateNotesStr) - } - } - - isUpdateAvailable := isUpdateAvailable(stepRunResult.StepInfo) - updateRow := "" - if isUpdateAvailable { - updateRow = fmt.Sprintf("| Update available: %s -> %s |", stepInfo.Version, stepInfo.LatestVersion) - charDiff := len(updateRow) - stepRunSummaryBoxWidthInChars - if charDiff < 0 { - // shorter than desired - fill with space - updateRow = fmt.Sprintf("| Update available: %s -> %s%s |", stepInfo.Version, stepInfo.LatestVersion, strings.Repeat(" ", -charDiff)) - } else if charDiff > 0 { - // longer than desired - trim title - if charDiff > 6 { - updateRow = fmt.Sprintf("| Update available!%s |", strings.Repeat(" ", -len("| Update available! |")-stepRunSummaryBoxWidthInChars)) - } else { - updateRow = fmt.Sprintf("| Update available: -> %s%s |", stepInfo.LatestVersion, strings.Repeat(" ", -len("| Update available: -> %s |")-stepRunSummaryBoxWidthInChars)) - } - } - } - - issueRow := "" - sourceRow := "" - if stepRunResult.ErrorStr != "" { - // Support URL - var coloringFunc func(...interface{}) string - supportURL := "" - if stepInfo.Step.SupportURL != nil && *stepInfo.Step.SupportURL != "" { - supportURL = *stepInfo.Step.SupportURL - } - if supportURL == "" { - coloringFunc = colorstring.Yellow - supportURL = "Not provided" - } - - issueRow = fmt.Sprintf("| Issue tracker: %s |", supportURL) - - charDiff := len(issueRow) - stepRunSummaryBoxWidthInChars - if charDiff <= 0 { - // shorter than desired - fill with space - - if coloringFunc != nil { - // We need to do this after charDiff calculation, - // because of coloring characters increase the text length, but they do not printed - supportURL = coloringFunc("Not provided") - } - - issueRow = fmt.Sprintf("| Issue tracker: %s%s |", supportURL, strings.Repeat(" ", -charDiff)) - } else if charDiff > 0 { - // longer than desired - trim title - trimmedWidth := len(supportURL) - charDiff - if trimmedWidth < 4 { - log.Errorf("Support url too long, can't present support url at all! : %s", supportURL) - } else { - issueRow = fmt.Sprintf("| Issue tracker: %s |", stringutil.MaxLastCharsWithDots(supportURL, trimmedWidth)) - } - } - - // Source Code URL - coloringFunc = nil - sourceCodeURL := "" - if stepInfo.Step.SourceCodeURL != nil && *stepInfo.Step.SourceCodeURL != "" { - sourceCodeURL = *stepInfo.Step.SourceCodeURL - } - if sourceCodeURL == "" { - coloringFunc = colorstring.Yellow - sourceCodeURL = "Not provided" - } - - sourceRow = fmt.Sprintf("| Source: %s |", sourceCodeURL) - - charDiff = len(sourceRow) - stepRunSummaryBoxWidthInChars - if charDiff <= 0 { - // shorter than desired - fill with space - - if coloringFunc != nil { - // We need to do this after charDiff calculation, - // because of coloring characters increase the text length, but they do not printed - sourceCodeURL = coloringFunc("Not provided") - } - - sourceRow = fmt.Sprintf("| Source: %s%s |", sourceCodeURL, strings.Repeat(" ", -charDiff)) - } else if charDiff > 0 { - // longer than desired - trim title - trimmedWidth := len(sourceCodeURL) - charDiff - if trimmedWidth < 4 { - log.Errorf("Source url too long, can't present source url at all! : %s", sourceCodeURL) - } else { - sourceRow = fmt.Sprintf("| Source: %s |", stringutil.MaxLastCharsWithDots(sourceCodeURL, trimmedWidth)) - } - } - } - - // Update available - content := "" - if isUpdateAvailable { - content = fmt.Sprintf("%s", updateRow) - } - - // Support URL - if issueRow != "" { - if content != "" { - content = fmt.Sprintf("%s\n%s", content, issueRow) - } else { - content = fmt.Sprintf("%s", issueRow) - } - } - - // Source Code URL - if sourceRow != "" { - if content != "" { - content = fmt.Sprintf("%s\n%s", content, sourceRow) - } else { - content = fmt.Sprintf("%s", sourceRow) - } - } - - // Deprecation - if removalDate != "" { - if content != "" { - content = fmt.Sprintf("%s\n%s", content, removalDateRow) - } else { - content = fmt.Sprintf("%s", removalDateRow) - } - - if deprecateNotes != "" { - if content != "" { - content = fmt.Sprintf("%s\n%s", content, deprecateNotesRow) - } else { - content = fmt.Sprintf("%s", deprecateNotesRow) - } - } - } - - return content -} - -// PrintRunningStepHeader ... -func PrintRunningStepHeader(stepInfo stepmanModels.StepInfoModel, step stepmanModels.StepModel, idx int) { - sep := fmt.Sprintf("+%s+", strings.Repeat("-", stepRunSummaryBoxWidthInChars-2)) - - fmt.Println(sep) - fmt.Println(getRunningStepHeaderMainSection(stepInfo, idx)) - fmt.Println(sep) - fmt.Println(getRunningStepHeaderSubSection(step, stepInfo)) - fmt.Println(sep) - fmt.Println("|" + strings.Repeat(" ", stepRunSummaryBoxWidthInChars-2) + "|") -} - -// PrintRunningStepFooter .. -func PrintRunningStepFooter(stepRunResult models.StepRunResultsModel, isLastStepInWorkflow bool) { - iconBoxWidth := len(" ") - timeBoxWidth := len(" time (s) ") - titleBoxWidth := stepRunSummaryBoxWidthInChars - 4 - iconBoxWidth - timeBoxWidth - sep := fmt.Sprintf("+%s+%s+%s+", strings.Repeat("-", iconBoxWidth), strings.Repeat("-", titleBoxWidth), strings.Repeat("-", timeBoxWidth)) - - fmt.Println("|" + strings.Repeat(" ", stepRunSummaryBoxWidthInChars-2) + "|") - - fmt.Println(sep) - fmt.Println(getRunningStepFooterMainSection(stepRunResult)) - fmt.Println(sep) - if stepRunResult.ErrorStr != "" || stepRunResult.StepInfo.GroupInfo.RemovalDate != "" || isUpdateAvailable(stepRunResult.StepInfo) { - footerSubSection := getRunningStepFooterSubSection(stepRunResult) - if footerSubSection != "" { - fmt.Println(footerSubSection) - fmt.Println(sep) - } - } - - if !isLastStepInWorkflow { - fmt.Println() - fmt.Println(strings.Repeat(" ", 42) + "▼") - fmt.Println() - } -} - -// PrintRunningWorkflow ... -func PrintRunningWorkflow(title string) { - fmt.Println() - log.Printf("%s %s", colorstring.Blue("Switching to workflow:"), title) - fmt.Println() -} - -// PrintSummary ... -func PrintSummary(buildRunResults models.BuildRunResultsModel) { - iconBoxWidth := len(" ") - timeBoxWidth := len(" time (s) ") - titleBoxWidth := stepRunSummaryBoxWidthInChars - 4 - iconBoxWidth - timeBoxWidth - - fmt.Println() - fmt.Println() - fmt.Printf("+%s+\n", strings.Repeat("-", stepRunSummaryBoxWidthInChars-2)) - whitespaceWidth := (stepRunSummaryBoxWidthInChars - 2 - len("bitrise summary ")) / 2 - fmt.Printf("|%sbitrise summary %s|\n", strings.Repeat(" ", whitespaceWidth), strings.Repeat(" ", whitespaceWidth)) - fmt.Printf("+%s+%s+%s+\n", strings.Repeat("-", iconBoxWidth), strings.Repeat("-", titleBoxWidth), strings.Repeat("-", timeBoxWidth)) - - whitespaceWidth = stepRunSummaryBoxWidthInChars - len("| | title") - len("| time (s) |") - fmt.Printf("| | title%s| time (s) |\n", strings.Repeat(" ", whitespaceWidth)) - fmt.Printf("+%s+%s+%s+\n", strings.Repeat("-", iconBoxWidth), strings.Repeat("-", titleBoxWidth), strings.Repeat("-", timeBoxWidth)) - - orderedResults := buildRunResults.OrderedResults() - tmpTime := time.Time{} - for _, stepRunResult := range orderedResults { - tmpTime = tmpTime.Add(stepRunResult.RunTime) - fmt.Println(getRunningStepFooterMainSection(stepRunResult)) - fmt.Printf("+%s+%s+%s+\n", strings.Repeat("-", iconBoxWidth), strings.Repeat("-", titleBoxWidth), strings.Repeat("-", timeBoxWidth)) - if stepRunResult.ErrorStr != "" || stepRunResult.StepInfo.GroupInfo.RemovalDate != "" || isUpdateAvailable(stepRunResult.StepInfo) { - footerSubSection := getRunningStepFooterSubSection(stepRunResult) - if footerSubSection != "" { - fmt.Println(footerSubSection) - fmt.Printf("+%s+%s+%s+\n", strings.Repeat("-", iconBoxWidth), strings.Repeat("-", titleBoxWidth), strings.Repeat("-", timeBoxWidth)) - } - } - } - runtime := tmpTime.Sub(time.Time{}) - - runTimeStr, err := FormattedSecondsToMax8Chars(runtime) - if err != nil { - log.Errorf("Failed to format time, error: %s", err) - runTimeStr = "999+ hour" - } - - whitespaceWidth = stepRunSummaryBoxWidthInChars - len(fmt.Sprintf("| Total runtime: %s|", runTimeStr)) - if whitespaceWidth < 0 { - log.Errorf("Invalid time box size for RunTime: %#v", runtime) - whitespaceWidth = 0 - } - - fmt.Printf("| Total runtime: %s%s|\n", runTimeStr, strings.Repeat(" ", whitespaceWidth)) - fmt.Printf("+%s+\n", strings.Repeat("-", stepRunSummaryBoxWidthInChars-2)) - - fmt.Println() -} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/print_test.go b/vendor/github.com/bitrise-io/bitrise/bitrise/print_test.go deleted file mode 100644 index 6ac5001b..00000000 --- a/vendor/github.com/bitrise-io/bitrise/bitrise/print_test.go +++ /dev/null @@ -1,389 +0,0 @@ -package bitrise - -import ( - "strings" - "testing" - "time" - - "github.com/bitrise-io/bitrise/models" - "github.com/bitrise-io/go-utils/pointers" - stepmanModels "github.com/bitrise-io/stepman/models" - "github.com/stretchr/testify/require" -) - -const longStr = "This is a very long string, this is a very long string, " + - "this is a very long string, this is a very long string," + - "this is a very long string, this is a very long string." - -func TestIsUpdateAvailable(t *testing.T) { - t.Log("simple compare versions - ture") - { - stepInfo1 := stepmanModels.StepInfoModel{ - Version: "1.0.0", - LatestVersion: "1.1.0", - } - - require.Equal(t, true, isUpdateAvailable(stepInfo1)) - } - - t.Log("simple compare versions - false") - { - stepInfo1 := stepmanModels.StepInfoModel{ - Version: "1.0.0", - LatestVersion: "1.0.0", - } - - require.Equal(t, false, isUpdateAvailable(stepInfo1)) - } - - t.Log("issue - no latest - false") - { - stepInfo1 := stepmanModels.StepInfoModel{ - Version: "1.0.0", - LatestVersion: "", - } - - require.Equal(t, false, isUpdateAvailable(stepInfo1)) - } - - t.Log("issue - no current - false") - { - stepInfo1 := stepmanModels.StepInfoModel{ - Version: "", - LatestVersion: "1.0.0", - } - - require.Equal(t, false, isUpdateAvailable(stepInfo1)) - } -} - -func TestGetTrimmedStepName(t *testing.T) { - t.Log("successful step") - { - stepInfo := stepmanModels.StepInfoModel{ - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(longStr), - }, - Version: longStr, - } - - result := models.StepRunResultsModel{ - StepInfo: stepInfo, - Status: models.StepRunStatusCodeSuccess, - Idx: 0, - RunTime: 10000000, - ErrorStr: longStr, - ExitCode: 1, - } - - actual := getTrimmedStepName(result) - expected := "This is a very long string, this is a very long string, thi..." - require.Equal(t, expected, actual) - } - - t.Log("failed step") - { - stepInfo := stepmanModels.StepInfoModel{ - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(""), - }, - Version: longStr, - } - - result := models.StepRunResultsModel{ - StepInfo: stepInfo, - Status: models.StepRunStatusCodeSuccess, - Idx: 0, - RunTime: 0, - ErrorStr: "", - ExitCode: 0, - } - - actual := getTrimmedStepName(result) - expected := "" - require.Equal(t, expected, actual) - } -} - -func TestGetRunningStepHeaderMainSection(t *testing.T) { - stepInfo := stepmanModels.StepInfoModel{ - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(longStr), - }, - Version: longStr, - } - - actual := getRunningStepHeaderMainSection(stepInfo, 0) - expected := "| (0) This is a very long string, this is a very long string, this is a ver... |" - require.Equal(t, expected, actual) -} - -func TestGetRunningStepHeaderSubSection(t *testing.T) { - stepInfo := stepmanModels.StepInfoModel{ - ID: longStr, - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(longStr), - }, - Version: longStr, - } - - actual := getRunningStepHeaderSubSection(stepmanModels.StepModel{}, stepInfo) - require.NotEqual(t, "", actual) -} - -func TestGetRunningStepFooterMainSection(t *testing.T) { - t.Log("failed step") - { - stepInfo := stepmanModels.StepInfoModel{ - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(longStr), - }, - Version: longStr, - } - - result := models.StepRunResultsModel{ - StepInfo: stepInfo, - Status: models.StepRunStatusCodeFailed, - Idx: 0, - RunTime: 10000000, - ErrorStr: longStr, - ExitCode: 1, - } - - actual := getRunningStepFooterMainSection(result) - expected := "| \x1b[31;1mx\x1b[0m | \x1b[31;1mThis is a very long string, this is a very l... (exit code: 1)\x1b[0m| 0.01 sec |" - require.Equal(t, expected, actual) - } - - t.Log("successful step") - { - stepInfo := stepmanModels.StepInfoModel{ - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(""), - }, - Version: longStr, - } - result := models.StepRunResultsModel{ - StepInfo: stepInfo, - Status: models.StepRunStatusCodeSuccess, - Idx: 0, - RunTime: 0, - ErrorStr: "", - ExitCode: 0, - } - - actual := getRunningStepFooterMainSection(result) - expected := "| \x1b[32;1m✓\x1b[0m | \x1b[32;1m\x1b[0m | 0.00 sec |" - require.Equal(t, expected, actual) - } - - t.Log("long Runtime") - { - stepInfo := stepmanModels.StepInfoModel{ - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(""), - }, - Version: longStr, - } - result := models.StepRunResultsModel{ - StepInfo: stepInfo, - Status: models.StepRunStatusCodeSuccess, - Idx: 0, - RunTime: 100 * 1000 * 1e9, // 100 * 1000 * 10^9 nanosec = 100 000 sec - ErrorStr: "", - ExitCode: 0, - } - - actual := getRunningStepFooterMainSection(result) - expected := "| \x1b[32;1m✓\x1b[0m | \x1b[32;1m\x1b[0m | 28 hour |" - require.Equal(t, expected, actual) - } - - t.Log("long Runtime") - { - stepInfo := stepmanModels.StepInfoModel{ - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(""), - }, - Version: longStr, - } - result := models.StepRunResultsModel{ - StepInfo: stepInfo, - Status: models.StepRunStatusCodeSuccess, - Idx: 0, - RunTime: hourToDuration(1000), - ErrorStr: "", - ExitCode: 0, - } - - actual := getRunningStepFooterMainSection(result) - expected := "| \x1b[32;1m✓\x1b[0m | \x1b[32;1m\x1b[0m | 999+ hour|" - require.Equal(t, expected, actual) - } -} - -func TestGetDeprecateNotesRows(t *testing.T) { - notes := "Removal notes: " + longStr - actual := getDeprecateNotesRows(notes) - expected := "| \x1b[31;1mRemoval notes:\x1b[0m This is a very long string, this is a very long string, this |" + "\n" + - "| is a very long string, this is a very long string,this is a very long |" + "\n" + - "| string, this is a very long string. |" - require.Equal(t, expected, actual) -} - -func TestGetRunningStepFooterSubSection(t *testing.T) { - t.Log("Update available, no support_url, no source_code_url") - { - stepInfo := stepmanModels.StepInfoModel{ - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(longStr), - }, - Version: "1.0.0", - LatestVersion: "1.1.0", - } - - result := models.StepRunResultsModel{ - StepInfo: stepInfo, - Status: models.StepRunStatusCodeSuccess, - Idx: 0, - RunTime: 10000000, - ErrorStr: longStr, - ExitCode: 1, - } - - actual := getRunningStepFooterSubSection(result) - expected := "| Update available: 1.0.0 -> 1.1.0 |" + "\n" + - "| Issue tracker: \x1b[33;1mNot provided\x1b[0m |" + "\n" + - "| Source: \x1b[33;1mNot provided\x1b[0m |" - require.Equal(t, expected, actual) - } - - t.Log("support url row length's chardiff = 0") - { - paddingCharCnt := 4 - placeholderCharCnt := len("Issue tracker: ") - supportURLCharCnt := stepRunSummaryBoxWidthInChars - paddingCharCnt - placeholderCharCnt - supportURL := strings.Repeat("a", supportURLCharCnt) - - // supportURL := - stepInfo := stepmanModels.StepInfoModel{ - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(longStr), - SupportURL: pointers.NewStringPtr(supportURL), - }, - Version: "1.0.0", - LatestVersion: "1.0.0", - } - - result := models.StepRunResultsModel{ - StepInfo: stepInfo, - Status: models.StepRunStatusCodeSuccess, - Idx: 0, - RunTime: 10000000, - ErrorStr: longStr, - ExitCode: 1, - } - - actual := getRunningStepFooterSubSection(result) - expected := "| Issue tracker: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |" + "\n" + - "| Source: \x1b[33;1mNot provided\x1b[0m |" - require.Equal(t, expected, actual) - } -} - -func TestPrintRunningWorkflow(t *testing.T) { - PrintRunningWorkflow(longStr) -} - -func TestPrintRunningStepHeader(t *testing.T) { - stepInfo := stepmanModels.StepInfoModel{ - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(""), - }, - Version: "", - } - step := stepmanModels.StepModel{} - PrintRunningStepHeader(stepInfo, step, 0) - - stepInfo.Step.Title = pointers.NewStringPtr(longStr) - stepInfo.Version = "" - PrintRunningStepHeader(stepInfo, step, 0) - - stepInfo.Step.Title = pointers.NewStringPtr("") - stepInfo.Version = longStr - PrintRunningStepHeader(stepInfo, step, 0) - - stepInfo.Step.Title = pointers.NewStringPtr(longStr) - stepInfo.Version = longStr - PrintRunningStepHeader(stepInfo, step, 0) -} - -func TestPrintRunningStepFooter(t *testing.T) { - stepInfo := stepmanModels.StepInfoModel{ - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(longStr), - }, - Version: longStr, - } - - result := models.StepRunResultsModel{ - StepInfo: stepInfo, - Status: models.StepRunStatusCodeSuccess, - Idx: 0, - RunTime: 10000000, - ErrorStr: longStr, - ExitCode: 1, - } - PrintRunningStepFooter(result, true) - PrintRunningStepFooter(result, false) - - stepInfo.Step.Title = pointers.NewStringPtr("") - result = models.StepRunResultsModel{ - StepInfo: stepInfo, - Status: models.StepRunStatusCodeSuccess, - Idx: 0, - RunTime: 0, - ErrorStr: "", - ExitCode: 0, - } - PrintRunningStepFooter(result, true) - PrintRunningStepFooter(result, false) -} - -func TestPrintSummary(t *testing.T) { - PrintSummary(models.BuildRunResultsModel{}) - - stepInfo := stepmanModels.StepInfoModel{ - Step: stepmanModels.StepModel{ - Title: pointers.NewStringPtr(longStr), - }, - Version: longStr, - } - - result1 := models.StepRunResultsModel{ - StepInfo: stepInfo, - Status: models.StepRunStatusCodeSuccess, - Idx: 0, - RunTime: 10000000, - ErrorStr: longStr, - ExitCode: 1, - } - - stepInfo.Step.Title = pointers.NewStringPtr("") - result2 := models.StepRunResultsModel{ - StepInfo: stepInfo, - Status: models.StepRunStatusCodeSuccess, - Idx: 0, - RunTime: 0, - ErrorStr: "", - ExitCode: 0, - } - - buildResults := models.BuildRunResultsModel{ - StartTime: time.Now(), - StepmanUpdates: map[string]int{}, - SuccessSteps: []models.StepRunResultsModel{result1, result2}, - } - - PrintSummary(buildResults) -} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/setup.go b/vendor/github.com/bitrise-io/bitrise/bitrise/setup.go deleted file mode 100644 index d1bad775..00000000 --- a/vendor/github.com/bitrise-io/bitrise/bitrise/setup.go +++ /dev/null @@ -1,184 +0,0 @@ -package bitrise - -import ( - "errors" - "fmt" - "runtime" - - "github.com/bitrise-io/bitrise/configs" - "github.com/bitrise-io/bitrise/plugins" - "github.com/bitrise-io/bitrise/toolkits" - "github.com/bitrise-io/bitrise/version" - "github.com/bitrise-io/go-utils/colorstring" - "github.com/bitrise-io/go-utils/log" -) - -const ( - minEnvmanVersion = "1.1.6" - minStepmanVersion = "0.9.33" -) - -// PluginDependency .. -type PluginDependency struct { - Source string - MinVersion string -} - -// PluginDependencyMap ... -var PluginDependencyMap = map[string]PluginDependency{ - "init": PluginDependency{ - Source: "https://github.com/bitrise-core/bitrise-plugins-init.git", - MinVersion: "0.9.7", - }, - "step": PluginDependency{ - Source: "https://github.com/bitrise-core/bitrise-plugins-step.git", - MinVersion: "0.9.4", - }, - "workflow-editor": PluginDependency{ - Source: "https://github.com/bitrise-io/bitrise-workflow-editor.git", - MinVersion: "1.0.13", - }, - "analytics": PluginDependency{ - Source: "https://github.com/bitrise-core/bitrise-plugins-analytics.git", - MinVersion: "0.9.10", - }, -} - -// RunSetupIfNeeded ... -func RunSetupIfNeeded(appVersion string, isFullSetupMode bool) error { - if !configs.CheckIsSetupWasDoneForVersion(version.VERSION) { - log.Warnf(colorstring.Yellow("Setup was not performed for this version of bitrise, doing it now...")) - return RunSetup(version.VERSION, false, false) - } - return nil -} - -// RunSetup ... -func RunSetup(appVersion string, isFullSetupMode bool, isCleanSetupMode bool) error { - log.Infof("Setup") - log.Printf("Full setup: %v", isFullSetupMode) - log.Printf("Clean setup: %v", isCleanSetupMode) - log.Printf("Detected OS: %s", runtime.GOOS) - - if isCleanSetupMode { - if err := configs.DeleteBitriseConfigDir(); err != nil { - return err - } - - if err := configs.InitPaths(); err != nil { - return err - } - - if err := plugins.InitPaths(); err != nil { - return err - } - } - - if err := doSetupBitriseCoreTools(); err != nil { - return fmt.Errorf("Failed to do common/platform independent setup, error: %s", err) - } - - switch runtime.GOOS { - case "darwin": - if err := doSetupOnOSX(isFullSetupMode); err != nil { - return fmt.Errorf("Failed to do MacOS specific setup, error: %s", err) - } - case "linux": - default: - return errors.New("unsupported platform :(") - } - - if err := doSetupPlugins(); err != nil { - return fmt.Errorf("Failed to do Plugins setup, error: %s", err) - } - - if err := doSetupToolkits(); err != nil { - return fmt.Errorf("Failed to do Toolkits setup, error: %s", err) - } - - fmt.Println() - log.Donef("All the required tools are installed! We're ready to rock!!") - - if err := configs.SaveSetupSuccessForVersion(appVersion); err != nil { - return fmt.Errorf("failed to save setup-success into config file, error: %s", err) - } - - return nil -} - -func doSetupToolkits() error { - fmt.Println() - log.Infof("Checking Bitrise Toolkits...") - - coreToolkits := toolkits.AllSupportedToolkits() - - for _, aCoreTK := range coreToolkits { - toolkitName := aCoreTK.ToolkitName() - isInstallRequired, checkResult, err := aCoreTK.Check() - if err != nil { - return fmt.Errorf("Failed to perform toolkit check (%s), error: %s", toolkitName, err) - } - - if isInstallRequired { - log.Warnf("No installed/suitable %s found, installing toolkit ...", toolkitName) - if err := aCoreTK.Install(); err != nil { - return fmt.Errorf("Failed to install toolkit (%s), error: %s", toolkitName, err) - } - - isInstallRequired, checkResult, err = aCoreTK.Check() - if err != nil { - return fmt.Errorf("Failed to perform toolkit check (%s), error: %s", toolkitName, err) - } - } - if isInstallRequired { - return fmt.Errorf("Toolkit (%s) still reports that it isn't (properly) installed", toolkitName) - } - - log.Printf("%s %s (%s): %s", colorstring.Green("[OK]"), toolkitName, checkResult.Version, checkResult.Path) - } - - return nil -} - -func doSetupPlugins() error { - fmt.Println() - log.Infof("Checking Bitrise Plugins...") - - for pluginName, pluginDependency := range PluginDependencyMap { - if err := CheckIsPluginInstalled(pluginName, pluginDependency); err != nil { - return fmt.Errorf("Plugin (%s) failed to install: %s", pluginName, err) - } - } - - return nil -} - -func doSetupBitriseCoreTools() error { - fmt.Println() - log.Infof("Checking Bitrise Core tools...") - - if err := CheckIsEnvmanInstalled(minEnvmanVersion); err != nil { - return fmt.Errorf("Envman failed to install: %s", err) - } - - if err := CheckIsStepmanInstalled(minStepmanVersion); err != nil { - return fmt.Errorf("Stepman failed to install: %s", err) - } - - return nil -} - -func doSetupOnOSX(isMinimalSetupMode bool) error { - fmt.Println() - log.Infof("Doing OS X specific setup") - log.Printf("Checking required tools...") - - if err := CheckIsHomebrewInstalled(isMinimalSetupMode); err != nil { - return errors.New(fmt.Sprint("Homebrew not installed or has some issues. Please fix these before calling setup again. Err:", err)) - } - - if err := PrintInstalledXcodeInfos(); err != nil { - return errors.New(fmt.Sprint("Failed to detect installed Xcode and Xcode Command Line Tools infos. Err:", err)) - } - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/template_util.go b/vendor/github.com/bitrise-io/bitrise/bitrise/template_util.go deleted file mode 100644 index 8bb34ecc..00000000 --- a/vendor/github.com/bitrise-io/bitrise/bitrise/template_util.go +++ /dev/null @@ -1,90 +0,0 @@ -package bitrise - -import ( - "bytes" - "errors" - "os" - "strings" - "text/template" - - "github.com/bitrise-io/bitrise/models" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/goinp/goinp" -) - -// TemplateDataModel ... -type TemplateDataModel struct { - BuildResults models.BuildRunResultsModel - IsBuildFailed bool - IsBuildOK bool - IsCI bool - IsPR bool - PullRequestID string -} - -func getEnv(key string, envList envmanModels.EnvsJSONListModel) string { - if len(envList) > 0 { - for aKey, value := range envList { - if aKey == key { - return value - } - } - } - return os.Getenv(key) -} - -func createTemplateDataModel(isCI, isPR bool, buildResults models.BuildRunResultsModel) TemplateDataModel { - isBuildOK := !buildResults.IsBuildFailed() - - return TemplateDataModel{ - BuildResults: buildResults, - IsBuildFailed: !isBuildOK, - IsBuildOK: isBuildOK, - IsCI: isCI, - IsPR: isPR, - } -} - -// EvaluateTemplateToString ... -func EvaluateTemplateToString(expStr string, isCI, isPR bool, buildResults models.BuildRunResultsModel, envList envmanModels.EnvsJSONListModel) (string, error) { - if expStr == "" { - return "", errors.New("EvaluateTemplateToBool: Invalid, empty input: expStr") - } - - if !strings.Contains(expStr, "{{") { - expStr = "{{" + expStr + "}}" - } - - var templateFuncMap = template.FuncMap{ - "getenv": func(key string) string { - return getEnv(key, envList) - }, - "enveq": func(key, expectedValue string) bool { - return (getEnv(key, envList) == expectedValue) - }, - } - - tmpl := template.New("EvaluateTemplateToBool").Funcs(templateFuncMap) - tmpl, err := tmpl.Parse(expStr) - if err != nil { - return "", err - } - - templateData := createTemplateDataModel(isCI, isPR, buildResults) - var resBuffer bytes.Buffer - if err := tmpl.Execute(&resBuffer, templateData); err != nil { - return "", err - } - - return resBuffer.String(), nil -} - -// EvaluateTemplateToBool ... -func EvaluateTemplateToBool(expStr string, isCI, isPR bool, buildResults models.BuildRunResultsModel, envList envmanModels.EnvsJSONListModel) (bool, error) { - resString, err := EvaluateTemplateToString(expStr, isCI, isPR, buildResults, envList) - if err != nil { - return false, err - } - - return goinp.ParseBool(resString) -} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/template_utils_test.go b/vendor/github.com/bitrise-io/bitrise/bitrise/template_utils_test.go deleted file mode 100644 index 0762cabe..00000000 --- a/vendor/github.com/bitrise-io/bitrise/bitrise/template_utils_test.go +++ /dev/null @@ -1,408 +0,0 @@ -package bitrise - -import ( - "os" - "strings" - "testing" - - "github.com/bitrise-io/bitrise/configs" - "github.com/bitrise-io/bitrise/models" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/stretchr/testify/require" -) - -func TestEvaluateStepTemplateToBool(t *testing.T) { - buildRes := models.BuildRunResultsModel{} - - propTempCont := `{{eq 1 1}}` - isYes, err := EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, isYes) - - propTempCont = `{{eq 1 2}}` - isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, false, isYes) - - isYes, err = EvaluateTemplateToBool("", false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.NotEqual(t, nil, err) - - // these all should be `true` - for _, expStr := range []string{ - "true", - "1", - `"yes"`, - `"YES"`, - `"Yes"`, - `"YeS"`, - `"TRUE"`, - `"True"`, - `"TrUe"`, - `"y"`, - } { - isYes, err = EvaluateTemplateToBool(expStr, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.NoError(t, err) - require.Equal(t, true, isYes) - } - - // these all should be `true` - for _, expStr := range []string{ - "false", - "0", - `"no"`, - `"NO"`, - `"No"`, - `"FALSE"`, - `"False"`, - `"FaLse"`, - `"n"`, - } { - isYes, err = EvaluateTemplateToBool(expStr, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.NoError(t, err) - require.Equal(t, false, isYes) - } -} - -func TestRegisteredFunctions(t *testing.T) { - defer func() { - // env cleanup - require.Equal(t, nil, os.Unsetenv("TEST_KEY")) - }() - - buildRes := models.BuildRunResultsModel{} - - propTempCont := `{{getenv "TEST_KEY" | eq "Test value"}}` - require.Equal(t, nil, os.Setenv("TEST_KEY", "Test value")) - isYes, err := EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, isYes) - - propTempCont = `{{getenv "TEST_KEY" | eq "A different value"}}` - require.Equal(t, nil, os.Setenv("TEST_KEY", "Test value")) - isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, false, isYes) - - propTempCont = `{{enveq "TEST_KEY" "enveq value"}}` - require.Equal(t, nil, os.Setenv("TEST_KEY", "enveq value")) - isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, isYes) - - propTempCont = `{{enveq "TEST_KEY" "different enveq value"}}` - require.Equal(t, nil, os.Setenv("TEST_KEY", "enveq value")) - isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, false, isYes) -} - -func TestCIFlagsAndEnvs(t *testing.T) { - defer func() { - // env cleanup - if err := os.Unsetenv(configs.CIModeEnvKey); err != nil { - t.Error("Failed to unset environment: ", err) - } - }() - - buildRes := models.BuildRunResultsModel{} - - propTempCont := `{{.IsCI}}` - t.Log("IsCI=true; propTempCont: ", propTempCont) - if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { - t.Fatal("Failed to set test env!") - } - isYes, err := EvaluateTemplateToBool(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if !isYes { - t.Fatal("Invalid result") - } - - propTempCont = `{{.IsCI}}` - t.Log("IsCI=fase; propTempCont: ", propTempCont) - if err := os.Setenv(configs.CIModeEnvKey, "false"); err != nil { - t.Fatal("Failed to set test env!") - } - isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if isYes { - t.Fatal("Invalid result") - } - - propTempCont = `{{.IsCI}}` - t.Log("[unset] IsCI; propTempCont: ", propTempCont) - if err := os.Unsetenv(configs.CIModeEnvKey); err != nil { - t.Fatal("Failed to set test env!") - } - isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if isYes { - t.Fatal("Invalid result") - } - - propTempCont = `$.IsCI` - t.Log("IsCI=true; short with $; propTempCont: ", propTempCont) - if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { - t.Fatal("Failed to set test env!") - } - isYes, err = EvaluateTemplateToBool(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if !isYes { - t.Fatal("Invalid result") - } - - propTempCont = `.IsCI` - t.Log("IsCI=true; short, no $; propTempCont: ", propTempCont) - if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { - t.Fatal("Failed to set test env!") - } - isYes, err = EvaluateTemplateToBool(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if !isYes { - t.Fatal("Invalid result") - } - - propTempCont = `not .IsCI` - t.Log("IsCI=true; NOT; propTempCont: ", propTempCont) - if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { - t.Fatal("Failed to set test env! : ", err) - } - isYes, err = EvaluateTemplateToBool(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if isYes { - t.Fatal("Invalid result") - } - - propTempCont = `not .IsCI` - t.Log("IsCI=false; NOT; propTempCont: ", propTempCont) - if err := os.Setenv(configs.CIModeEnvKey, "false"); err != nil { - t.Fatal("Failed to set test env! : ", err) - } - isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if !isYes { - t.Fatal("Invalid result") - } -} - -func TestPullRequestFlagsAndEnvs(t *testing.T) { - defer func() { - // env cleanup - if err := os.Unsetenv(configs.PullRequestIDEnvKey); err != nil { - t.Error("Failed to unset environment: ", err) - } - }() - - // env cleanup - if err := os.Unsetenv(configs.PullRequestIDEnvKey); err != nil { - t.Error("Failed to unset environment: ", err) - } - - buildRes := models.BuildRunResultsModel{} - - propTempCont := `{{.IsPR}}` - t.Log("IsPR [undefined]; propTempCont: ", propTempCont) - isYes, err := EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if isYes { - t.Fatal("Invalid result") - } - - propTempCont = `{{.IsPR}}` - t.Log("IsPR=true; propTempCont: ", propTempCont) - if err := os.Setenv(configs.PullRequestIDEnvKey, "123"); err != nil { - t.Fatal("Failed to set test env! : ", err) - } - isYes, err = EvaluateTemplateToBool(propTempCont, false, true, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if !isYes { - t.Fatal("Invalid result") - } -} - -func TestPullRequestAndCIFlagsAndEnvs(t *testing.T) { - defer func() { - // env cleanup - if err := os.Unsetenv(configs.PullRequestIDEnvKey); err != nil { - t.Error("Failed to unset environment: ", err) - } - if err := os.Unsetenv(configs.CIModeEnvKey); err != nil { - t.Error("Failed to unset environment: ", err) - } - }() - - buildRes := models.BuildRunResultsModel{} - - propTempCont := `not .IsPR | and (not .IsCI)` - t.Log("IsPR [undefined] & IsCI [undefined]; propTempCont: ", propTempCont) - isYes, err := EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if !isYes { - t.Fatal("Invalid result") - } - - propTempCont = `not .IsPR | and .IsCI` - t.Log("IsPR [undefined] & IsCI [undefined]; propTempCont: ", propTempCont) - isYes, err = EvaluateTemplateToBool(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if isYes { - t.Fatal("Invalid result") - } - - propTempCont = `not .IsPR | and .IsCI` - t.Log("IsPR [undefined] & IsCI=true; propTempCont: ", propTempCont) - if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { - t.Fatal("Failed to set test env! : ", err) - } - isYes, err = EvaluateTemplateToBool(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if !isYes { - t.Fatal("Invalid result") - } - - propTempCont = `.IsPR | and .IsCI` - t.Log("IsPR [undefined] & IsCI=true; propTempCont: ", propTempCont) - if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { - t.Fatal("Failed to set test env! : ", err) - } - isYes, err = EvaluateTemplateToBool(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if isYes { - t.Fatal("Invalid result") - } - - propTempCont = `.IsPR | and .IsCI` - t.Log("IsPR=true & IsCI=true; propTempCont: ", propTempCont) - if err := os.Setenv(configs.PullRequestIDEnvKey, "123"); err != nil { - t.Fatal("Failed to set test env! : ", err) - } - if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { - t.Fatal("Failed to set test env! : ", err) - } - isYes, err = EvaluateTemplateToBool(propTempCont, true, true, buildRes, envmanModels.EnvsJSONListModel{}) - if err != nil { - t.Fatal("Unexpected error:", err) - } - if !isYes { - t.Fatal("Invalid result") - } -} - -func TestEvaluateTemplateToString(t *testing.T) { - buildRes := models.BuildRunResultsModel{} - - propTempCont := "" - value, err := EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.NotEqual(t, nil, err) - - propTempCont = ` -{{ if .IsCI }} -value in case of IsCI -{{ end }} -` - value, err = EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, strings.Contains(value, "value in case of IsCI")) - - propTempCont = ` -{{ if .IsCI }} -value in case of IsCI -{{ else }} -value in case of not IsCI -{{ end }} -` - value, err = EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, strings.Contains(value, "value in case of IsCI")) - - value, err = EvaluateTemplateToString(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, strings.Contains(value, "value in case of not IsCI")) - - propTempCont = ` -This is -{{ if .IsCI }} -value in case of IsCI -{{ end }} -` - value, err = EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "value in case of IsCI"))) - - value, err = EvaluateTemplateToString(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, (strings.Contains(value, "This is"))) - - propTempCont = ` -This is -{{ if .IsCI }} -value in case of IsCI -{{ end }} -after end -` - value, err = EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "value in case of IsCI"))) - - value, err = EvaluateTemplateToString(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "after end"))) - - propTempCont = ` -This is -{{ if .IsCI }} -value in case of IsCI -{{ else }} -value in case of not IsCI -{{ end }} -` - value, err = EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "value in case of IsCI"))) - - value, err = EvaluateTemplateToString(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "value in case of not IsCI"))) - - propTempCont = ` -This is -{{ if .IsCI }} -value in case of IsCI -{{ else }} -value in case of not IsCI -{{ end }} -mode -` - value, err = EvaluateTemplateToString(propTempCont, true, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "value in case of IsCI") && strings.Contains(value, "mode"))) - - value, err = EvaluateTemplateToString(propTempCont, false, false, buildRes, envmanModels.EnvsJSONListModel{}) - require.Equal(t, nil, err) - require.Equal(t, true, (strings.Contains(value, "This is") && strings.Contains(value, "value in case of not IsCI") && strings.Contains(value, "mode"))) -} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/util.go b/vendor/github.com/bitrise-io/bitrise/bitrise/util.go deleted file mode 100644 index 596b0a2f..00000000 --- a/vendor/github.com/bitrise-io/bitrise/bitrise/util.go +++ /dev/null @@ -1,675 +0,0 @@ -package bitrise - -import ( - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "strings" - "time" - - "gopkg.in/yaml.v2" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/bitrise/configs" - "github.com/bitrise-io/bitrise/models" - "github.com/bitrise-io/bitrise/tools" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/command/git" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - stepmanModels "github.com/bitrise-io/stepman/models" -) - -// InventoryModelFromYAMLBytes ... -func InventoryModelFromYAMLBytes(inventoryBytes []byte) (inventory envmanModels.EnvsSerializeModel, err error) { - if err = yaml.Unmarshal(inventoryBytes, &inventory); err != nil { - return - } - - for _, env := range inventory.Envs { - if err := env.Normalize(); err != nil { - return envmanModels.EnvsSerializeModel{}, fmt.Errorf("Failed to normalize bitrise inventory, error: %s", err) - } - if err := env.FillMissingDefaults(); err != nil { - return envmanModels.EnvsSerializeModel{}, fmt.Errorf("Failed to fill bitrise inventory, error: %s", err) - } - if err := env.Validate(); err != nil { - return envmanModels.EnvsSerializeModel{}, fmt.Errorf("Failed to validate bitrise inventory, error: %s", err) - } - } - - return -} - -func searchEnvInSlice(searchForEnvKey string, searchIn []envmanModels.EnvironmentItemModel) (envmanModels.EnvironmentItemModel, int, error) { - for idx, env := range searchIn { - key, _, err := env.GetKeyValuePair() - if err != nil { - return envmanModels.EnvironmentItemModel{}, -1, err - } - - if key == searchForEnvKey { - return env, idx, nil - } - } - return envmanModels.EnvironmentItemModel{}, -1, nil -} - -// ApplyOutputAliases ... -func ApplyOutputAliases(onEnvs, basedOnEnvs []envmanModels.EnvironmentItemModel) ([]envmanModels.EnvironmentItemModel, error) { - for _, basedOnEnv := range basedOnEnvs { - envKey, envKeyAlias, err := basedOnEnv.GetKeyValuePair() - if err != nil { - return []envmanModels.EnvironmentItemModel{}, err - } - - envToAlias, idx, err := searchEnvInSlice(envKey, onEnvs) - if err != nil { - return []envmanModels.EnvironmentItemModel{}, err - } - - if idx > -1 && envKeyAlias != "" { - _, origValue, err := envToAlias.GetKeyValuePair() - if err != nil { - return []envmanModels.EnvironmentItemModel{}, err - } - - origOptions, err := envToAlias.GetOptions() - if err != nil { - return []envmanModels.EnvironmentItemModel{}, err - } - - onEnvs[idx] = envmanModels.EnvironmentItemModel{ - envKeyAlias: origValue, - envmanModels.OptionsKey: origOptions, - } - } - } - return onEnvs, nil -} - -// CollectEnvironmentsFromFile ... -func CollectEnvironmentsFromFile(pth string) ([]envmanModels.EnvironmentItemModel, error) { - bytes, err := fileutil.ReadBytesFromFile(pth) - if err != nil { - return []envmanModels.EnvironmentItemModel{}, err - } - - return CollectEnvironmentsFromFileContent(bytes) -} - -// CollectEnvironmentsFromFileContent ... -func CollectEnvironmentsFromFileContent(bytes []byte) ([]envmanModels.EnvironmentItemModel, error) { - var envstore envmanModels.EnvsSerializeModel - if err := yaml.Unmarshal(bytes, &envstore); err != nil { - return []envmanModels.EnvironmentItemModel{}, err - } - - for _, env := range envstore.Envs { - if err := env.Normalize(); err != nil { - return []envmanModels.EnvironmentItemModel{}, err - } - if err := env.FillMissingDefaults(); err != nil { - return []envmanModels.EnvironmentItemModel{}, err - } - if err := env.Validate(); err != nil { - return []envmanModels.EnvironmentItemModel{}, err - } - } - - return envstore.Envs, nil -} - -// ExportEnvironmentsList ... -func ExportEnvironmentsList(envsList []envmanModels.EnvironmentItemModel) error { - log.Debugln("[BITRISE_CLI] - Exporting environments:", envsList) - - for _, env := range envsList { - key, value, err := env.GetKeyValuePair() - if err != nil { - return err - } - - opts, err := env.GetOptions() - if err != nil { - return err - } - - isExpand := envmanModels.DefaultIsExpand - if opts.IsExpand != nil { - isExpand = *opts.IsExpand - } - - skipIfEmpty := envmanModels.DefaultSkipIfEmpty - if opts.SkipIfEmpty != nil { - skipIfEmpty = *opts.SkipIfEmpty - } - - if err := tools.EnvmanAdd(configs.InputEnvstorePath, key, value, isExpand, skipIfEmpty); err != nil { - log.Errorln("[BITRISE_CLI] - Failed to run envman add") - return err - } - } - return nil -} - -// CleanupStepWorkDir ... -func CleanupStepWorkDir() error { - stepYMLPth := filepath.Join(configs.BitriseWorkDirPath, "current_step.yml") - if err := command.RemoveFile(stepYMLPth); err != nil { - return errors.New(fmt.Sprint("Failed to remove step yml: ", err)) - } - - stepDir := configs.BitriseWorkStepsDirPath - if err := command.RemoveDir(stepDir); err != nil { - return errors.New(fmt.Sprint("Failed to remove step work dir: ", err)) - } - return nil -} - -// GetBuildFailedEnvironments ... -func GetBuildFailedEnvironments(failed bool) []string { - statusStr := "0" - if failed { - statusStr = "1" - } - - environments := []string{} - steplibBuildStatusEnv := "STEPLIB_BUILD_STATUS" + "=" + statusStr - environments = append(environments, steplibBuildStatusEnv) - - bitriseBuildStatusEnv := "BITRISE_BUILD_STATUS" + "=" + statusStr - environments = append(environments, bitriseBuildStatusEnv) - return environments -} - -// SetBuildFailedEnv ... -func SetBuildFailedEnv(failed bool) error { - statusStr := "0" - if failed { - statusStr = "1" - } - - if err := os.Setenv("STEPLIB_BUILD_STATUS", statusStr); err != nil { - return err - } - - if err := os.Setenv("BITRISE_BUILD_STATUS", statusStr); err != nil { - return err - } - return nil -} - -// FormattedSecondsToMax8Chars ... -func FormattedSecondsToMax8Chars(t time.Duration) (string, error) { - sec := t.Seconds() - min := t.Minutes() - hour := t.Hours() - - if sec < 1.0 { - // 0.999999 sec -> 0.99 sec - return fmt.Sprintf("%.2f sec", sec), nil // 8 - } else if sec < 10.0 { - // 9.99999 sec -> 9.99 sec - return fmt.Sprintf("%.2f sec", sec), nil // 8 - } else if sec < 600 { - // 599,999 sec -> 599 sec - return fmt.Sprintf("%.f sec", sec), nil // 7 - } else if min < 60 { - // 59,999 min -> 59.9 min - return fmt.Sprintf("%.1f min", min), nil // 8 - } else if hour < 10 { - // 9.999 hour -> 9.9 hour - return fmt.Sprintf("%.1f hour", hour), nil // 8 - } else if hour < 1000 { - // 999,999 hour -> 999 hour - return fmt.Sprintf("%.f hour", hour), nil // 8 - } - - return "", fmt.Errorf("time (%f hour) greater than max allowed (999 hour)", hour) -} - -// SaveConfigToFile ... -func SaveConfigToFile(pth string, bitriseConf models.BitriseDataModel) error { - contBytes, err := generateYAML(bitriseConf) - if err != nil { - return err - } - if err := fileutil.WriteBytesToFile(pth, contBytes); err != nil { - return err - } - return nil -} - -func generateYAML(v interface{}) ([]byte, error) { - bytes, err := yaml.Marshal(v) - if err != nil { - return []byte{}, err - } - return bytes, nil -} - -func normalizeValidateFillMissingDefaults(bitriseData *models.BitriseDataModel) ([]string, error) { - if err := bitriseData.Normalize(); err != nil { - return []string{}, err - } - warnings, err := bitriseData.Validate() - if err != nil { - return warnings, err - } - if err := bitriseData.FillMissingDefaults(); err != nil { - return warnings, err - } - return warnings, nil -} - -// ConfigModelFromYAMLBytes ... -func ConfigModelFromYAMLBytes(configBytes []byte) (bitriseData models.BitriseDataModel, warnings []string, err error) { - if err = yaml.Unmarshal(configBytes, &bitriseData); err != nil { - return - } - - warnings, err = normalizeValidateFillMissingDefaults(&bitriseData) - if err != nil { - return - } - - return -} - -// ConfigModelFromJSONBytes ... -func ConfigModelFromJSONBytes(configBytes []byte) (bitriseData models.BitriseDataModel, warnings []string, err error) { - if err = json.Unmarshal(configBytes, &bitriseData); err != nil { - return - } - warnings, err = normalizeValidateFillMissingDefaults(&bitriseData) - if err != nil { - return - } - - return -} - -// ReadBitriseConfig ... -func ReadBitriseConfig(pth string) (models.BitriseDataModel, []string, error) { - if isExists, err := pathutil.IsPathExists(pth); err != nil { - return models.BitriseDataModel{}, []string{}, err - } else if !isExists { - return models.BitriseDataModel{}, []string{}, fmt.Errorf("No file found at path: %s", pth) - } - - bytes, err := fileutil.ReadBytesFromFile(pth) - if err != nil { - return models.BitriseDataModel{}, []string{}, err - } - - if len(bytes) == 0 { - return models.BitriseDataModel{}, []string{}, errors.New("empty config") - } - - if strings.HasSuffix(pth, ".json") { - log.Debugln("=> Using JSON parser for: ", pth) - return ConfigModelFromJSONBytes(bytes) - } - - log.Debugln("=> Using YAML parser for: ", pth) - return ConfigModelFromYAMLBytes(bytes) -} - -// ReadSpecStep ... -func ReadSpecStep(pth string) (stepmanModels.StepModel, error) { - if isExists, err := pathutil.IsPathExists(pth); err != nil { - return stepmanModels.StepModel{}, err - } else if !isExists { - return stepmanModels.StepModel{}, fmt.Errorf("No file found at path: %s", pth) - } - - bytes, err := fileutil.ReadBytesFromFile(pth) - if err != nil { - return stepmanModels.StepModel{}, err - } - - var stepModel stepmanModels.StepModel - if err := yaml.Unmarshal(bytes, &stepModel); err != nil { - return stepmanModels.StepModel{}, err - } - - if err := stepModel.Normalize(); err != nil { - return stepmanModels.StepModel{}, err - } - - if err := stepModel.ValidateInputAndOutputEnvs(false); err != nil { - return stepmanModels.StepModel{}, err - } - - if err := stepModel.FillMissingDefaults(); err != nil { - return stepmanModels.StepModel{}, err - } - - return stepModel, nil -} - -func getInputByKey(inputs []envmanModels.EnvironmentItemModel, key string) (envmanModels.EnvironmentItemModel, error) { - for _, input := range inputs { - aKey, _, err := input.GetKeyValuePair() - if err != nil { - return envmanModels.EnvironmentItemModel{}, err - } - if aKey == key { - return input, nil - } - } - return envmanModels.EnvironmentItemModel{}, fmt.Errorf("No Environmnet found for key (%s)", key) -} - -func isStringSliceWithSameElements(s1, s2 []string) bool { - if len(s1) != len(s2) { - return false - } - - m := make(map[string]bool, len(s1)) - for _, s := range s1 { - m[s] = true - } - - for _, s := range s2 { - v, found := m[s] - if !found || !v { - return false - } - delete(m, s) - } - return len(m) == 0 -} - -func isDependecyEqual(d1, d2 stepmanModels.DependencyModel) bool { - return (d1.Manager == d2.Manager && d1.Name == d2.Name) -} - -func containsDependecy(m map[stepmanModels.DependencyModel]bool, d1 stepmanModels.DependencyModel) bool { - for d2 := range m { - if isDependecyEqual(d1, d2) { - return true - } - } - return false -} - -func isDependencySliceWithSameElements(s1, s2 []stepmanModels.DependencyModel) bool { - if len(s1) != len(s2) { - return false - } - - m := make(map[stepmanModels.DependencyModel]bool, len(s1)) - for _, s := range s1 { - m[s] = true - } - - for _, d := range s2 { - if containsDependecy(m, d) == false { - return false - } - delete(m, d) - } - return len(m) == 0 -} - -func removeStepDefaultsAndFillStepOutputs(stepListItem *models.StepListItemModel, defaultStepLibSource string) error { - // Create stepIDData - compositeStepIDStr, workflowStep, err := models.GetStepIDStepDataPair(*stepListItem) - if err != nil { - return err - } - stepIDData, err := models.CreateStepIDDataFromString(compositeStepIDStr, defaultStepLibSource) - if err != nil { - return err - } - - // Activate step - get step.yml - tempStepCloneDirPath, err := pathutil.NormalizedOSTempDirPath("step_clone") - if err != nil { - return err - } - tempStepYMLDirPath, err := pathutil.NormalizedOSTempDirPath("step_yml") - if err != nil { - return err - } - tempStepYMLFilePath := filepath.Join(tempStepYMLDirPath, "step.yml") - - if stepIDData.SteplibSource == "path" { - stepAbsLocalPth, err := pathutil.AbsPath(stepIDData.IDorURI) - if err != nil { - return err - } - if err := command.CopyFile(filepath.Join(stepAbsLocalPth, "step.yml"), tempStepYMLFilePath); err != nil { - return err - } - } else if stepIDData.SteplibSource == "git" { - if err := git.CloneTagOrBranch(stepIDData.IDorURI, tempStepCloneDirPath, stepIDData.Version); err != nil { - return err - } - if err := command.CopyFile(filepath.Join(tempStepCloneDirPath, "step.yml"), tempStepYMLFilePath); err != nil { - return err - } - } else if stepIDData.SteplibSource == "_" { - // Steplib independent steps are completly defined in workflow - tempStepYMLFilePath = "" - } else if stepIDData.SteplibSource != "" { - if err := tools.StepmanSetup(stepIDData.SteplibSource); err != nil { - return err - } - if err := tools.StepmanActivate(stepIDData.SteplibSource, stepIDData.IDorURI, stepIDData.Version, tempStepCloneDirPath, tempStepYMLFilePath); err != nil { - return err - } - } else { - return errors.New("Failed to fill step ouputs: unkown SteplibSource") - } - - // Fill outputs - if tempStepYMLFilePath != "" { - specStep, err := ReadSpecStep(tempStepYMLFilePath) - if err != nil { - return err - } - - if workflowStep.Title != nil && specStep.Title != nil && *workflowStep.Title == *specStep.Title { - workflowStep.Title = nil - } - if workflowStep.Description != nil && specStep.Description != nil && *workflowStep.Description == *specStep.Description { - workflowStep.Description = nil - } - if workflowStep.Summary != nil && specStep.Summary != nil && *workflowStep.Summary == *specStep.Summary { - workflowStep.Summary = nil - } - if workflowStep.Website != nil && specStep.Website != nil && *workflowStep.Website == *specStep.Website { - workflowStep.Website = nil - } - if workflowStep.SourceCodeURL != nil && specStep.SourceCodeURL != nil && *workflowStep.SourceCodeURL == *specStep.SourceCodeURL { - workflowStep.SourceCodeURL = nil - } - if workflowStep.SupportURL != nil && specStep.SupportURL != nil && *workflowStep.SupportURL == *specStep.SupportURL { - workflowStep.SupportURL = nil - } - workflowStep.PublishedAt = nil - if workflowStep.Source != nil && specStep.Source != nil { - if workflowStep.Source.Git == specStep.Source.Git { - workflowStep.Source.Git = "" - } - if workflowStep.Source.Commit == specStep.Source.Commit { - workflowStep.Source.Commit = "" - } - } - if isStringSliceWithSameElements(workflowStep.HostOsTags, specStep.HostOsTags) { - workflowStep.HostOsTags = []string{} - } - if isStringSliceWithSameElements(workflowStep.ProjectTypeTags, specStep.ProjectTypeTags) { - workflowStep.ProjectTypeTags = []string{} - } - if isStringSliceWithSameElements(workflowStep.TypeTags, specStep.TypeTags) { - workflowStep.TypeTags = []string{} - } - if isDependencySliceWithSameElements(workflowStep.Dependencies, specStep.Dependencies) { - workflowStep.Dependencies = []stepmanModels.DependencyModel{} - } - if workflowStep.IsRequiresAdminUser != nil && specStep.IsRequiresAdminUser != nil && *workflowStep.IsRequiresAdminUser == *specStep.IsRequiresAdminUser { - workflowStep.IsRequiresAdminUser = nil - } - if workflowStep.IsAlwaysRun != nil && specStep.IsAlwaysRun != nil && *workflowStep.IsAlwaysRun == *specStep.IsAlwaysRun { - workflowStep.IsAlwaysRun = nil - } - if workflowStep.IsSkippable != nil && specStep.IsSkippable != nil && *workflowStep.IsSkippable == *specStep.IsSkippable { - workflowStep.IsSkippable = nil - } - if workflowStep.RunIf != nil && specStep.RunIf != nil && *workflowStep.RunIf == *specStep.RunIf { - workflowStep.RunIf = nil - } - - inputs := []envmanModels.EnvironmentItemModel{} - for _, input := range workflowStep.Inputs { - sameValue := false - - wfKey, wfValue, err := input.GetKeyValuePair() - if err != nil { - return err - } - - wfOptions, err := input.GetOptions() - if err != nil { - return err - } - - sInput, err := getInputByKey(specStep.Inputs, wfKey) - if err != nil { - return err - } - - _, sValue, err := sInput.GetKeyValuePair() - if err != nil { - return err - } - - if wfValue == sValue { - sameValue = true - } - - sOptions, err := sInput.GetOptions() - if err != nil { - return err - } - - hasOptions := false - - if wfOptions.Title != nil && sOptions.Title != nil && *wfOptions.Title == *sOptions.Title { - wfOptions.Title = nil - } else { - hasOptions = true - } - - if wfOptions.Description != nil && sOptions.Description != nil && *wfOptions.Description == *sOptions.Description { - wfOptions.Description = nil - } else { - hasOptions = true - } - - if wfOptions.Summary != nil && sOptions.Summary != nil && *wfOptions.Summary == *sOptions.Summary { - wfOptions.Summary = nil - } else { - hasOptions = true - } - - if isStringSliceWithSameElements(wfOptions.ValueOptions, sOptions.ValueOptions) { - wfOptions.ValueOptions = []string{} - } else { - hasOptions = true - } - - if wfOptions.IsRequired != nil && sOptions.IsRequired != nil && *wfOptions.IsRequired == *sOptions.IsRequired { - wfOptions.IsRequired = nil - } else { - hasOptions = true - } - - if wfOptions.IsExpand != nil && sOptions.IsExpand != nil && *wfOptions.IsExpand == *sOptions.IsExpand { - wfOptions.IsExpand = nil - } else { - hasOptions = true - } - - if wfOptions.IsDontChangeValue != nil && sOptions.IsDontChangeValue != nil && *wfOptions.IsDontChangeValue == *sOptions.IsDontChangeValue { - wfOptions.IsDontChangeValue = nil - } else { - hasOptions = true - } - - if !hasOptions && sameValue { - // default env - } else { - if hasOptions { - input[envmanModels.OptionsKey] = wfOptions - } else { - delete(input, envmanModels.OptionsKey) - } - - inputs = append(inputs, input) - } - } - - workflowStep.Inputs = inputs - - // We need only key-value and title from spec outputs - outputs := []envmanModels.EnvironmentItemModel{} - for _, output := range specStep.Outputs { - sKey, sValue, err := output.GetKeyValuePair() - if err != nil { - return err - } - - sOptions, err := output.GetOptions() - if err != nil { - return err - } - - newOutput := envmanModels.EnvironmentItemModel{ - sKey: sValue, - envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ - Title: sOptions.Title, - }, - } - - outputs = append(outputs, newOutput) - } - - workflowStep.Outputs = outputs - - (*stepListItem)[compositeStepIDStr] = workflowStep - } - - // Cleanup - if err := command.RemoveDir(tempStepCloneDirPath); err != nil { - return errors.New(fmt.Sprint("Failed to remove step clone dir: ", err)) - } - if err := command.RemoveDir(tempStepYMLDirPath); err != nil { - return errors.New(fmt.Sprint("Failed to remove step clone dir: ", err)) - } - - return nil -} - -// RemoveConfigRedundantFieldsAndFillStepOutputs ... -func RemoveConfigRedundantFieldsAndFillStepOutputs(config *models.BitriseDataModel) error { - for _, workflow := range config.Workflows { - for _, stepListItem := range workflow.Steps { - if err := removeStepDefaultsAndFillStepOutputs(&stepListItem, config.DefaultStepLibSource); err != nil { - return err - } - } - } - if err := config.RemoveRedundantFields(); err != nil { - return err - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/bitrise/util_test.go b/vendor/github.com/bitrise-io/bitrise/bitrise/util_test.go deleted file mode 100644 index 08432678..00000000 --- a/vendor/github.com/bitrise-io/bitrise/bitrise/util_test.go +++ /dev/null @@ -1,498 +0,0 @@ -package bitrise - -import ( - "encoding/json" - "testing" - "time" - - "github.com/bitrise-io/bitrise/configs" - envmanModels "github.com/bitrise-io/envman/models" - stepmanModels "github.com/bitrise-io/stepman/models" - "github.com/stretchr/testify/require" -) - -func secToDuration(sec float64) time.Duration { - return time.Duration(sec * 1e9) -} - -func minToDuration(min float64) time.Duration { - return secToDuration(min * 60) -} - -func hourToDuration(hour float64) time.Duration { - return minToDuration(hour * 60) -} - -func TestApplyOutputAliases(t *testing.T) { - t.Log("apply alias on signle env") - { - envs := []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "ORIGINAL_KEY": "value", - }, - } - - outputEnvs := []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "ORIGINAL_KEY": "ALIAS_KEY", - }, - } - - updatedEnvs, err := ApplyOutputAliases(envs, outputEnvs) - require.NoError(t, err) - require.Equal(t, 1, len(updatedEnvs)) - - updatedKey, value, err := updatedEnvs[0].GetKeyValuePair() - require.Equal(t, "ALIAS_KEY", updatedKey) - require.Equal(t, "value", value) - } - - t.Log("apply alias on env list") - { - envs := []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "ORIGINAL_KEY": "value", - }, - envmanModels.EnvironmentItemModel{ - "SHOULD_NOT_CHANGE_KEY": "value", - }, - } - - outputEnvs := []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "ORIGINAL_KEY": "ALIAS_KEY", - }, - } - - updatedEnvs, err := ApplyOutputAliases(envs, outputEnvs) - require.NoError(t, err) - require.Equal(t, 2, len(updatedEnvs)) - - { - // this env should be updated - updatedKey, value, err := updatedEnvs[0].GetKeyValuePair() - require.NoError(t, err) - require.Equal(t, "ALIAS_KEY", updatedKey) - require.Equal(t, "value", value) - } - - { - // this env should NOT be updated - key, value, err := updatedEnvs[1].GetKeyValuePair() - require.NoError(t, err) - require.Equal(t, "SHOULD_NOT_CHANGE_KEY", key) - require.Equal(t, "value", value) - } - } -} - -func TestTimeToFormattedSeconds(t *testing.T) { - t.Log("formatted print rounds") - { - timeStr, err := FormattedSecondsToMax8Chars(secToDuration(0.999)) - require.NoError(t, err) - require.Equal(t, "1.00 sec", timeStr) - } - - t.Log("sec < 1.0") - { - timeStr, err := FormattedSecondsToMax8Chars(secToDuration(0.111)) - require.NoError(t, err) - require.Equal(t, "0.11 sec", timeStr) - } - - t.Log("sec < 10.0") - { - timeStr, err := FormattedSecondsToMax8Chars(secToDuration(9.111)) - require.NoError(t, err) - require.Equal(t, "9.11 sec", timeStr) - } - - t.Log("sec < 600 | min < 10") - { - timeStr, err := FormattedSecondsToMax8Chars(secToDuration(599.111)) - require.NoError(t, err) - require.Equal(t, "599 sec", timeStr) - } - - t.Log("min < 60") - { - timeStr, err := FormattedSecondsToMax8Chars(minToDuration(59.111)) - require.NoError(t, err) - require.Equal(t, "59.1 min", timeStr) - } - - t.Log("hour < 10") - { - timeStr, err := FormattedSecondsToMax8Chars(hourToDuration(9.111)) - require.NoError(t, err) - require.Equal(t, "9.1 hour", timeStr) - } - - t.Log("hour < 1000") - { - timeStr, err := FormattedSecondsToMax8Chars(hourToDuration(999.111)) - require.NoError(t, err) - require.Equal(t, "999 hour", timeStr) - } - - t.Log("hour >= 1000") - { - timeStr, err := FormattedSecondsToMax8Chars(hourToDuration(1000)) - require.EqualError(t, err, "time (1000.000000 hour) greater than max allowed (999 hour)") - require.Equal(t, "", timeStr) - } -} - -func TestRemoveConfigRedundantFieldsAndFillStepOutputs(t *testing.T) { - // setup - require.NoError(t, configs.InitPaths()) - - configStr := ` - format_version: 1.3.0 - default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - - workflows: - remove_test: - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - exit 2 - opts: - is_expand: true - - timestamp: - title: Generate timestamps - ` - - config, warnings, err := ConfigModelFromYAMLBytes([]byte(configStr)) - require.Equal(t, nil, err) - require.Equal(t, 0, len(warnings)) - - require.Equal(t, nil, RemoveConfigRedundantFieldsAndFillStepOutputs(&config)) - - for workflowID, workflow := range config.Workflows { - if workflowID == "remove_test" { - for _, stepListItem := range workflow.Steps { - for stepID, step := range stepListItem { - if stepID == "script" { - for _, input := range step.Inputs { - key, _, err := input.GetKeyValuePair() - require.Equal(t, nil, err) - - if key == "content" { - opts, err := input.GetOptions() - require.Equal(t, nil, err) - - // script content should keep is_expand: true, becouse it's diffenet from spec default - require.Equal(t, true, *opts.IsExpand) - } - } - } else if stepID == "timestamp" { - // timestamp title should be nil, becouse it's the same as spec value - require.Equal(t, (*string)(nil), step.Title) - - for _, output := range step.Outputs { - key, _, err := output.GetKeyValuePair() - require.Equal(t, nil, err) - - if key == "UNIX_TIMESTAMP" { - // timestamp outputs should filled with key-value & opts.Title - opts, err := output.GetOptions() - require.Equal(t, nil, err) - - require.Equal(t, "unix style", *opts.Title) - require.Equal(t, (*bool)(nil), opts.IsExpand) - require.Equal(t, (*bool)(nil), opts.IsDontChangeValue) - require.Equal(t, (*bool)(nil), opts.IsRequired) - } - } - } - } - } - } - } - - // timestamp outputs should filled with key-value & opts.Title - -} - -func TestSsStringSliceWithSameElements(t *testing.T) { - s1 := []string{} - s2 := []string{} - require.Equal(t, true, isStringSliceWithSameElements(s1, s2)) - - s1 = []string{"1", "2", "3"} - s2 = []string{"2", "1"} - require.Equal(t, false, isStringSliceWithSameElements(s1, s2)) - - s2 = append(s2, "3") - require.Equal(t, true, isStringSliceWithSameElements(s1, s2)) - - s2 = []string{"1,", "1,", "1"} - require.Equal(t, false, isStringSliceWithSameElements(s1, s2)) -} - -func TestIsDependecyEqual(t *testing.T) { - d1 := stepmanModels.DependencyModel{Manager: "manager", Name: "dep"} - d2 := stepmanModels.DependencyModel{Manager: "manager", Name: "dep"} - - require.Equal(t, true, isDependecyEqual(d1, d2)) - - d1 = stepmanModels.DependencyModel{Manager: "manager", Name: "dep1"} - d2 = stepmanModels.DependencyModel{Manager: "manager", Name: "dep"} - - require.Equal(t, false, isDependecyEqual(d1, d2)) - - d1 = stepmanModels.DependencyModel{Manager: "manager", Name: "dep"} - d2 = stepmanModels.DependencyModel{Manager: "manager1", Name: "dep"} - - require.Equal(t, false, isDependecyEqual(d1, d2)) -} - -func TestContainsDependecy(t *testing.T) { - d1 := stepmanModels.DependencyModel{Manager: "manager", Name: "dep1"} - d2 := stepmanModels.DependencyModel{Manager: "manager", Name: "dep2"} - d3 := stepmanModels.DependencyModel{Manager: "manager1", Name: "dep3"} - - m := map[stepmanModels.DependencyModel]bool{ - d1: false, - d2: true, - } - - require.Equal(t, true, containsDependecy(m, d1)) - - require.Equal(t, false, containsDependecy(m, d3)) -} - -func TestIsDependencySliceWithSameElements(t *testing.T) { - s1 := []stepmanModels.DependencyModel{} - s2 := []stepmanModels.DependencyModel{} - require.Equal(t, true, isDependencySliceWithSameElements(s1, s2)) - - d1 := stepmanModels.DependencyModel{Manager: "manager", Name: "dep1"} - d2 := stepmanModels.DependencyModel{Manager: "manager", Name: "dep2"} - d3 := stepmanModels.DependencyModel{Manager: "manager1", Name: "dep3"} - - s1 = []stepmanModels.DependencyModel{d1, d2, d3} - s2 = []stepmanModels.DependencyModel{d2, d1} - require.Equal(t, false, isDependencySliceWithSameElements(s1, s2)) - - s2 = append(s2, d3) - require.Equal(t, true, isDependencySliceWithSameElements(s1, s2)) - - s2 = []stepmanModels.DependencyModel{d1, d1, d1} - require.Equal(t, false, isDependencySliceWithSameElements(s1, s2)) -} - -func TestConfigModelFromYAMLBytes(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - trivial_fail: - steps: - - script: - title: Should success - - script: - title: Should fail, but skippable - is_skippable: true - inputs: - - content: | - #!/bin/bash - set -v - exit 2 - - script: - title: Should success - - script: - title: Should fail - inputs: - - content: | - #!/bin/bash - set -v - exit 2 - - script: - title: Should success - is_always_run: true - - script: - title: Should skipped - ` - config, warnings, err := ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - workflow, found := config.Workflows["trivial_fail"] - require.Equal(t, true, found) - require.Equal(t, 6, len(workflow.Steps)) -} - -func TestConfigModelFromJSONBytes(t *testing.T) { - configStr := ` -{ - "format_version": "1.0.0", - "default_step_lib_source": "https://github.com/bitrise-io/bitrise-steplib.git", - "app": { - "envs": null - }, - "workflows": { - "trivial_fail": { - "title": "", - "summary": "", - "before_run": null, - "after_run": null, - "envs": null, - "steps": [ - { - "script": { - "title": "Should success", - "source": {} - } - }, - { - "script": { - "title": "Should fail, but skippable", - "source": {}, - "is_skippable": true, - "inputs": [ - { - "content": "#!/bin/bash\nset -v\nexit 2\n", - "opts": {} - } - ] - } - }, - { - "script": { - "title": "Should success", - "source": {} - } - }, - { - "script": { - "title": "Should fail", - "source": {}, - "inputs": [ - { - "content": "#!/bin/bash\nset -v\nexit 2\n", - "opts": {} - } - ] - } - }, - { - "script": { - "title": "Should success", - "source": {}, - "is_always_run": true - } - }, - { - "script": { - "title": "Should skipped", - "source": {} - } - } - ] - } - } -} - ` - config, warnings, err := ConfigModelFromJSONBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - workflow, found := config.Workflows["trivial_fail"] - require.Equal(t, true, found) - require.Equal(t, 6, len(workflow.Steps)) -} - -func TestConfigModelFromYAMLBytesNormalize(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -app: - envs: - - BITRISE_BIN_NAME: bitrise - opts: - is_expand: false - - GITHUB_RELEASES_URL: https://github.com/bitrise-io/bitrise/releases - opts: - is_expand: false - -workflows: - test: - steps: - - script: - title: Should fail, but skippable - is_skippable: true - inputs: - - content: echo "Hello World" - opts: - is_expand: no -` - config, warnings, err := ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - // should be able to serialize into JSON - _, err = json.MarshalIndent(config, "", "\t") - require.NoError(t, err) -} - -func TestConfigModelFromJSONBytesNormalize(t *testing.T) { - configStr := ` -{ - "format_version": "1.0.0", - "default_step_lib_source": "https://github.com/bitrise-io/bitrise-steplib.git", - "app": { - "envs": [ - { - "BITRISE_BIN_NAME": "bitrise", - "opts": { - "is_expand": false - } - }, - { - "GITHUB_RELEASES_URL": "https://github.com/bitrise-io/bitrise/releases", - "opts": { - "is_expand": false - } - } - ] - }, - "workflows": { - "test": { - "steps": [ - { - "script": { - "title": "Should fail, but skippable", - "is_skippable": true, - "inputs": [ - { - "content": "echo \"Hello World\"", - "opts": { - "is_expand": false - } - } - ] - } - } - ] - } - } -} -` - config, warnings, err := ConfigModelFromJSONBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - t.Log("The ConfigModelFromJSONBytes method should call the required Normalize methods, so that no map[interface{}]interface{} is left - which would prevent the JSON serialization.") - t.Logf("Config: %#v", config) - // should be able to serialize into JSON - _, err = json.MarshalIndent(config, "", "\t") - require.NoError(t, err) -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/cli.go b/vendor/github.com/bitrise-io/bitrise/cli/cli.go deleted file mode 100644 index c2106bd3..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/cli.go +++ /dev/null @@ -1,163 +0,0 @@ -package cli - -import ( - "errors" - "fmt" - "os" - "path" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/bitrise/bitrise" - "github.com/bitrise-io/bitrise/configs" - "github.com/bitrise-io/bitrise/plugins" - "github.com/bitrise-io/bitrise/version" - "github.com/urfave/cli" -) - -func initLogFormatter() { - log.SetFormatter(&log.TextFormatter{ - FullTimestamp: true, - ForceColors: true, - TimestampFormat: "15:04:05", - }) -} - -func before(c *cli.Context) error { - /* - return err will print app's help also, - use log.Fatal to avoid print help. - */ - - initLogFormatter() - initHelpAndVersionFlags() - - // Debug mode? - if c.Bool(DebugModeKey) { - // set for other tools, as an ENV - if err := os.Setenv(configs.DebugModeEnvKey, "true"); err != nil { - log.Fatalf("Failed to set DEBUG env, error: %s", err) - } - configs.IsDebugMode = true - log.Warn("=> Started in DEBUG mode") - } - - // Log level - // If log level defined - use it - logLevelStr := c.String(LogLevelKey) - if logLevelStr == "" && configs.IsDebugMode { - // if no Log Level defined and we're in Debug Mode - set loglevel to debug - logLevelStr = "debug" - log.Warn("=> LogLevel set to debug") - } - if logLevelStr == "" { - // if still empty: set the default - logLevelStr = "info" - } - - level, err := log.ParseLevel(logLevelStr) - if err != nil { - log.Fatalf("Failed parse log level, error: %s", err) - } - - if err := os.Setenv(configs.LogLevelEnvKey, level.String()); err != nil { - log.Fatalf("Failed to set LOGLEVEL env, error: %s", err) - } - log.SetLevel(level) - - // CI Mode check - if c.Bool(CIKey) { - // if CI mode indicated make sure we set the related env - // so all other tools we use will also get it - if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { - log.Fatalf("Failed to set CI env, error: %s", err) - } - configs.IsCIMode = true - } - - if err := configs.InitPaths(); err != nil { - log.Fatalf("Failed to initialize required paths, error: %s", err) - } - - // Pull Request Mode check - if c.Bool(PRKey) { - // if PR mode indicated make sure we set the related env - // so all other tools we use will also get it - if err := os.Setenv(configs.PRModeEnvKey, "true"); err != nil { - log.Fatalf("Failed to set PR env, error: %s", err) - } - configs.IsPullRequestMode = true - } - - pullReqID := os.Getenv(configs.PullRequestIDEnvKey) - if pullReqID != "" { - configs.IsPullRequestMode = true - } - - IsPR := os.Getenv(configs.PRModeEnvKey) - if IsPR == "true" { - configs.IsPullRequestMode = true - } - - return nil -} - -func printVersion(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "%v\n", c.App.Version) -} - -// Run ... -func Run() { - if err := plugins.InitPaths(); err != nil { - log.Fatalf("Failed to initialize plugin path, error: %s", err) - } - - initAppHelpTemplate() - - // Parse cl - cli.VersionPrinter = printVersion - - app := cli.NewApp() - app.Name = path.Base(os.Args[0]) - app.Usage = "Bitrise Automations Workflow Runner" - app.Version = version.VERSION - - app.Author = "" - app.Email = "" - - app.Before = before - - app.Flags = flags - app.Commands = commands - - app.Action = func(c *cli.Context) error { - pluginName, pluginArgs, isPlugin := plugins.ParseArgs(c.Args()) - if isPlugin { - plugin, found, err := plugins.LoadPlugin(pluginName) - if err != nil { - return fmt.Errorf("Failed to get plugin (%s), error: %s", pluginName, err) - } - if !found { - return fmt.Errorf("Plugin (%s) not installed", pluginName) - } - - if err := bitrise.RunSetupIfNeeded(version.VERSION, false); err != nil { - log.Fatalf("Setup failed, error: %s", err) - } - - if err := plugins.RunPluginByCommand(plugin, pluginArgs); err != nil { - return fmt.Errorf("Failed to run plugin (%s), error: %s", pluginName, err) - } - } else { - if err := cli.ShowAppHelp(c); err != nil { - return fmt.Errorf("Failed to show help, error: %s", err) - } - return errors.New("") - } - - return nil - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/commands.go b/vendor/github.com/bitrise-io/bitrise/cli/commands.go deleted file mode 100644 index 3ef7b848..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/commands.go +++ /dev/null @@ -1,237 +0,0 @@ -package cli - -import "github.com/urfave/cli" - -const ( - // JSONParamsKey ... - JSONParamsKey = "json-params" - // JSONParamsBase64Key ... - JSONParamsBase64Key = "json-params-base64" - - // WorkflowKey ... - WorkflowKey = "workflow" - - // PatternKey ... - PatternKey = "pattern" - // PushBranchKey ... - PushBranchKey = "push-branch" - // PRSourceBranchKey ... - PRSourceBranchKey = "pr-source-branch" - // PRTargetBranchKey ... - PRTargetBranchKey = "pr-target-branch" - - // ConfigKey ... - ConfigKey = "config" - // InventoryKey ... - InventoryKey = "inventory" - - // OuputFormatKey ... - OuputFormatKey = "format" -) - -var ( - commands = []cli.Command{ - initCmd, - setupCommand, - { - Name: "version", - Usage: "Prints the version", - Action: printVersionCmd, - Flags: []cli.Flag{ - flOutputFormat, - cli.BoolFlag{Name: "full", Usage: "Prints the build number as well."}, - }, - }, - { - Name: "validate", - Usage: "Validates a specified bitrise config.", - Action: validate, - Flags: []cli.Flag{ - flPath, - flConfig, - flConfigBase64, - flInventory, - flInventoryBase64, - flFormat, - }, - }, - { - Name: "run", - Aliases: []string{"r"}, - Usage: "Runs a specified Workflow.", - Action: run, - Flags: []cli.Flag{ - // cli params - cli.StringFlag{Name: WorkflowKey, Usage: "workflow id to run."}, - cli.StringFlag{Name: ConfigKey + ", " + configShortKey, Usage: "Path where the workflow config file is located."}, - cli.StringFlag{Name: InventoryKey + ", " + inventoryShortKey, Usage: "Path of the inventory file."}, - - // cli params used in CI mode - cli.StringFlag{Name: JSONParamsKey, Usage: "Specify command flags with json string-string hash."}, - cli.StringFlag{Name: JSONParamsBase64Key, Usage: "Specify command flags with base64 encoded json string-string hash."}, - - // deprecated - flPath, - - // should deprecate - cli.StringFlag{Name: ConfigBase64Key, Usage: "base64 encoded config data."}, - cli.StringFlag{Name: InventoryBase64Key, Usage: "base64 encoded inventory data."}, - }, - }, - { - Name: "trigger-check", - Usage: "Prints out which workflow will triggered by specified pattern.", - Action: triggerCheck, - Flags: []cli.Flag{ - // cli params - cli.StringFlag{Name: PatternKey, Usage: "trigger pattern."}, - cli.StringFlag{Name: ConfigKey + ", " + configShortKey, Usage: "Path where the workflow config file is located."}, - cli.StringFlag{Name: InventoryKey + ", " + inventoryShortKey, Usage: "Path of the inventory file."}, - - cli.StringFlag{Name: PushBranchKey, Usage: "Git push branch name."}, - cli.StringFlag{Name: PRSourceBranchKey, Usage: "Git pull request source branch name."}, - cli.StringFlag{Name: PRTargetBranchKey, Usage: "Git pull request target branch name."}, - cli.StringFlag{Name: TagKey, Usage: "Git tag name."}, - - cli.StringFlag{Name: OuputFormatKey, Usage: "Output format. Accepted: json, yml."}, - - // cli params used in CI mode - cli.StringFlag{Name: JSONParamsKey, Usage: "Specify command flags with json string-string hash."}, - cli.StringFlag{Name: JSONParamsBase64Key, Usage: "Specify command flags with base64 encoded json string-string hash."}, - - // deprecated - flPath, - - // should deprecate - cli.StringFlag{Name: ConfigBase64Key, Usage: "base64 encoded config data."}, - cli.StringFlag{Name: InventoryBase64Key, Usage: "base64 encoded inventory data."}, - }, - }, - { - Name: "trigger", - Aliases: []string{"t"}, - Usage: "Triggers a specified Workflow.", - Action: trigger, - Flags: []cli.Flag{ - // cli params - cli.StringFlag{Name: PatternKey, Usage: "trigger pattern."}, - cli.StringFlag{Name: ConfigKey + ", " + configShortKey, Usage: "Path where the workflow config file is located."}, - cli.StringFlag{Name: InventoryKey + ", " + inventoryShortKey, Usage: "Path of the inventory file."}, - - cli.StringFlag{Name: PushBranchKey, Usage: "Git push branch name."}, - cli.StringFlag{Name: PRSourceBranchKey, Usage: "Git pull request source branch name."}, - cli.StringFlag{Name: PRTargetBranchKey, Usage: "Git pull request target branch name."}, - cli.StringFlag{Name: TagKey, Usage: "Git tag name."}, - - // cli params used in CI mode - cli.StringFlag{Name: JSONParamsKey, Usage: "Specify command flags with json string-string hash."}, - cli.StringFlag{Name: JSONParamsBase64Key, Usage: "Specify command flags with base64 encoded json string-string hash."}, - - // deprecated - flPath, - - // should deprecate - cli.StringFlag{Name: ConfigBase64Key, Usage: "base64 encoded config data."}, - cli.StringFlag{Name: InventoryBase64Key, Usage: "base64 encoded inventory data."}, - }, - }, - { - Name: "export", - Usage: "Export the bitrise configuration.", - Action: export, - Flags: []cli.Flag{ - flPath, - flConfig, - flConfigBase64, - flFormat, - flOutputPath, - flPretty, - }, - }, - { - Name: "normalize", - Usage: "Normalize the bitrise configuration.", - Action: normalize, - Flags: []cli.Flag{ - flPath, - flConfig, - flConfigBase64, - }, - }, - { - Name: "step-list", - Usage: "List of available steps.", - Action: stepList, - Flags: []cli.Flag{ - flCollection, - flFormat, - }, - }, - { - Name: "step-info", - Aliases: []string{"i"}, - Usage: "Provides information (step ID, last version, given version) about specified step.", - Action: stepInfo, - Flags: []cli.Flag{ - flCollection, - flVersion, - flFormat, - flShort, - flStepYML, - }, - }, - { - Name: "workflows", - Usage: "List of available workflows in config.", - Action: workflowList, - Flags: []cli.Flag{ - flPath, - flConfig, - flConfigBase64, - flFormat, - cli.BoolFlag{ - Name: MinimalModeKey, - Usage: "Print only workflow summary.", - }, - }, - }, - { - Name: "share", - Usage: "Publish your step.", - Action: share, - Subcommands: []cli.Command{ - { - Name: "start", - Usage: "Preparations for publishing.", - Action: start, - Flags: []cli.Flag{ - flCollection, - }, - }, - { - Name: "create", - Usage: "Create your change - add it to your own copy of the collection.", - Action: create, - Flags: []cli.Flag{ - flTag, - flGit, - flStepID, - }, - }, - { - Name: "audit", - Usage: "Validates the step collection.", - Action: shareAudit, - }, - { - Name: "finish", - Usage: "Finish up.", - Action: finish, - }, - }, - }, - pluginCommand, - stepmanCommand, - envmanCommand, - } -) diff --git a/vendor/github.com/bitrise-io/bitrise/cli/export.go b/vendor/github.com/bitrise-io/bitrise/cli/export.go deleted file mode 100644 index f3a5a2e7..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/export.go +++ /dev/null @@ -1,78 +0,0 @@ -package cli - -import ( - "encoding/json" - - "gopkg.in/yaml.v2" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/bitrise/output" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/urfave/cli" -) - -func export(c *cli.Context) error { - // Expand cli.Context - bitriseConfigBase64Data := c.String(ConfigBase64Key) - - bitriseConfigPath := c.String(ConfigKey) - deprecatedBitriseConfigPath := c.String(PathKey) - if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { - log.Warn("'path' key is deprecated, use 'config' instead!") - bitriseConfigPath = deprecatedBitriseConfigPath - } - - outfilePth := c.String(OuputPathKey) - outFormat := c.String(OuputFormatKey) - prettyFormat := c.Bool(PrettyFormatKey) - // - - if outfilePth == "" { - showSubcommandHelp(c) - log.Fatal("No output file path specified!") - } - - if outFormat == "" { - showSubcommandHelp(c) - log.Fatal("No output format format specified!") - } - - // Config validation - bitriseConfig, warnings, err := CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath) - for _, warning := range warnings { - log.Warnf("warning: %s", warning) - } - if err != nil { - showSubcommandHelp(c) - log.Fatalf("Failed to create bitrise config, error: %s", err) - } - - // serialize - configBytes := []byte{} - if outFormat == output.FormatJSON { - if prettyFormat { - configBytes, err = json.MarshalIndent(bitriseConfig, "", "\t") - } else { - configBytes, err = json.Marshal(bitriseConfig) - } - if err != nil { - log.Fatalf("Failed to generate config JSON, error: %s", err) - } - } else if outFormat == output.FormatYML { - configBytes, err = yaml.Marshal(bitriseConfig) - if err != nil { - log.Fatalf("Failed to generate config YML, error: %s", err) - } - } else { - log.Fatalf("Invalid output format: %s", outFormat) - } - - // write to file - if err := fileutil.WriteBytesToFile(outfilePth, configBytes); err != nil { - log.Fatalf("Failed to write file (%s), error: %s", outfilePth, err) - } - - log.Infof("Done, saved to path: %s", outfilePth) - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/flags.go b/vendor/github.com/bitrise-io/bitrise/cli/flags.go deleted file mode 100644 index 3df8a580..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/flags.go +++ /dev/null @@ -1,192 +0,0 @@ -package cli - -import ( - "github.com/bitrise-io/bitrise/configs" - "github.com/urfave/cli" -) - -const ( - // CollectionPathEnvKey ... - CollectionPathEnvKey = "STEPMAN_COLLECTION" - // CIKey ... - CIKey = "ci" - // PRKey ... - PRKey = "pr" - // DebugModeKey ... - DebugModeKey = "debug" - - // LogLevelKey ... - LogLevelKey = "loglevel" - logLevelKeyShort = "l" - - // VersionKey ... - VersionKey = "version" - versionKeyShort = "v" - - // PathKey ... - PathKey = "path" - pathKeyShort = "p" - - // CollectionKey ... - CollectionKey = "collection" - collectionKeyShort = "c" - - inventoryShortKey = "i" - - // InventoryBase64Key ... - InventoryBase64Key = "inventory-base64" - - configShortKey = "c" - - // ConfigBase64Key ... - ConfigBase64Key = "config-base64" - - // HelpKey ... - HelpKey = "help" - helpKeyShort = "h" - - // MinimalModeKey ... - MinimalModeKey = "minimal" - // FullModeKey ... - FullModeKey = "full" - - ouputFormatKeyShort = "f" - // OuputPathKey ... - OuputPathKey = "outpath" - // PrettyFormatKey ... - PrettyFormatKey = "pretty" - - // IDKey ... - IDKey = "id" - idKeyShort = "i" - // ShortKey ... - ShortKey = "short" - - // StepYMLKey ... - StepYMLKey = "step-yml" - - // - // Stepman share - - // TagKey ... - TagKey = "tag" - // GitKey ... - GitKey = "git" - // StepIDKey ... - StepIDKey = "stepid" -) - -var ( - // App flags - flLogLevel = cli.StringFlag{ - Name: LogLevelKey + ", " + logLevelKeyShort, - Usage: "Log level (options: debug, info, warn, error, fatal, panic).", - EnvVar: configs.LogLevelEnvKey, - } - flDebugMode = cli.BoolFlag{ - Name: DebugModeKey, - Usage: "If true it enabled DEBUG mode. If no separate Log Level is specified this will also set the loglevel to debug.", - EnvVar: configs.DebugModeEnvKey, - } - flTool = cli.BoolFlag{ - Name: CIKey, - Usage: "If true it indicates that we're used by another tool so don't require any user input!", - EnvVar: configs.CIModeEnvKey, - } - flPRMode = cli.BoolFlag{ - Name: PRKey, - Usage: "If true bitrise runs in pull request mode.", - } - flags = []cli.Flag{ - flLogLevel, - flDebugMode, - flTool, - flPRMode, - } - // Command flags - flOutputFormat = cli.StringFlag{ - Name: OuputFormatKey + ", " + ouputFormatKeyShort, - Usage: "Output format. Accepted: raw (default), json, yml", - } - flPath = cli.StringFlag{ - Name: PathKey + ", " + pathKeyShort, - Usage: "[Deprecated!!! Use 'config'] Path where the workflow config file is located.", - } - flCollection = cli.StringFlag{ - Name: CollectionKey + ", " + collectionKeyShort, - Usage: "Collection of step.", - EnvVar: CollectionPathEnvKey, - } - flConfig = cli.StringFlag{ - Name: ConfigKey + ", " + configShortKey, - Usage: "Path where the workflow config file is located.", - } - flConfigBase64 = cli.StringFlag{ - Name: ConfigBase64Key, - Usage: "base64 decoded config data.", - } - flInventory = cli.StringFlag{ - Name: InventoryKey + ", " + inventoryShortKey, - Usage: "Path of the inventory file.", - } - flInventoryBase64 = cli.StringFlag{ - Name: InventoryBase64Key, - Usage: "base64 decoded inventory data.", - } - - // Export - flFormat = cli.StringFlag{ - Name: OuputFormatKey, - Usage: "Output format. Accepted: json, yml.", - } - flOutputPath = cli.StringFlag{ - Name: OuputPathKey, - Usage: "Output path, where the exported file will be saved.", - } - flPretty = cli.BoolFlag{ - Name: PrettyFormatKey, - Usage: "Pretty printed export?", - } - flID = cli.StringFlag{ - Name: IDKey + ", " + idKeyShort, - Usage: "Step id.", - } - flVersion = cli.StringFlag{ - Name: VersionKey + ", " + versionKeyShort, - Usage: "Step version.", - } - flShort = cli.BoolFlag{ - Name: ShortKey, - Usage: "Show short version of infos.", - } - flStepYML = cli.StringFlag{ - Name: StepYMLKey, - Usage: "Path of step.yml", - } - - // Stepman share - flTag = cli.StringFlag{ - Name: TagKey, - Usage: "Git (version) tag.", - } - flGit = cli.StringFlag{ - Name: GitKey, - Usage: "Git clone url of the step repository.", - } - flStepID = cli.StringFlag{ - Name: StepIDKey, - Usage: "ID of the step.", - } -) - -func initHelpAndVersionFlags() { - cli.HelpFlag = cli.BoolFlag{ - Name: HelpKey + ", " + helpKeyShort, - Usage: "Show help.", - } - - cli.VersionFlag = cli.BoolFlag{ - Name: VersionKey + ", " + versionKeyShort, - Usage: "Print the version.", - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/help.go b/vendor/github.com/bitrise-io/bitrise/cli/help.go deleted file mode 100644 index e0d1dc1f..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/help.go +++ /dev/null @@ -1,59 +0,0 @@ -package cli - -import ( - "log" - "strings" - - "fmt" - - "github.com/bitrise-io/bitrise/plugins" - "github.com/urfave/cli" -) - -const ( - helpTemplate = ` -NAME: {{.Name}} - {{.Usage}} - -USAGE: {{.Name}} {{if .Flags}}[OPTIONS] {{end}}COMMAND/PLUGIN [arg...] - -VERSION: {{.Version}}{{if or .Author .Email}} - -AUTHOR:{{if .Author}} - {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} - {{.Email}}{{end}}{{end}} -{{if .Flags}} -GLOBAL OPTIONS: - {{range .Flags}}{{.}} - {{end}}{{end}} -COMMANDS: - {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}} -%s -COMMAND HELP: {{.Name}} COMMAND --help/-h - -` -) - -func getPluginsList() string { - pluginListString := "PLUGINS:\n" - - pluginList, err := plugins.InstalledPluginList() - if err != nil { - log.Fatalf("Failed to list plugins, error: %s", err) - } - - if len(pluginList) > 0 { - plugins.SortByName(pluginList) - for _, plugin := range pluginList { - pluginListString += fmt.Sprintf(" :%s\t%s\n", plugin.Name, strings.Split(plugin.Description, "\n")[0]) - } - } else { - pluginListString += " No plugins installed\n" - } - - return pluginListString -} - -func initAppHelpTemplate() { - cli.AppHelpTemplate = fmt.Sprintf(helpTemplate, getPluginsList()) -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/init.go b/vendor/github.com/bitrise-io/bitrise/cli/init.go deleted file mode 100644 index c2852307..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/init.go +++ /dev/null @@ -1,50 +0,0 @@ -package cli - -import ( - "fmt" - - "github.com/Sirupsen/logrus" - "github.com/bitrise-io/bitrise/plugins" - "github.com/urfave/cli" -) - -var initCmd = cli.Command{ - Name: "init", - Aliases: []string{"i"}, - Usage: "Init bitrise config.", - Action: func(c *cli.Context) error { - if err := initConfig(c); err != nil { - logrus.Fatal(err) - } - return nil - }, - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "minimal", - Usage: "creates empty bitrise config and secrets", - }, - }, -} - -func initConfig(c *cli.Context) error { - minimal := c.Bool("minimal") - - pluginName := "init" - plugin, found, err := plugins.LoadPlugin(pluginName) - if err != nil { - return fmt.Errorf("Failed to get plugin (%s), error: %s", pluginName, err) - } - if !found { - return fmt.Errorf("Plugin (%s) not installed", pluginName) - } - - pluginArgs := []string{} - if minimal { - pluginArgs = []string{"--minimal"} - } - if err := plugins.RunPluginByCommand(plugin, pluginArgs); err != nil { - return fmt.Errorf("Failed to run plugin (%s), error: %s", pluginName, err) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/normalize.go b/vendor/github.com/bitrise-io/bitrise/cli/normalize.go deleted file mode 100644 index 8d13019f..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/normalize.go +++ /dev/null @@ -1,50 +0,0 @@ -package cli - -import ( - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/bitrise/bitrise" - "github.com/urfave/cli" -) - -func normalize(c *cli.Context) error { - // Expand cli.Context - bitriseConfigBase64Data := c.String(ConfigBase64Key) - - bitriseConfigPath := c.String(ConfigKey) - deprecatedBitriseConfigPath := c.String(PathKey) - if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { - log.Warn("'path' key is deprecated, use 'config' instead!") - bitriseConfigPath = deprecatedBitriseConfigPath - } - // - - // Input validation - bitriseConfigPath, err := GetBitriseConfigFilePath(bitriseConfigPath) - if err != nil { - log.Fatalf("Failed to get bitrise config path, error: %s", err) - } - if bitriseConfigPath == "" { - log.Fatal("No bitrise config path defined!") - } - - // Config validation - bitriseConfig, warnings, err := CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath) - for _, warning := range warnings { - log.Warnf("warning: %s", warning) - } - if err != nil { - log.Fatalf("Failed to create bitrise config, error: %s", err) - } - - // Normalize - if err := bitrise.RemoveConfigRedundantFieldsAndFillStepOutputs(&bitriseConfig); err != nil { - log.Fatalf("Failed to remove redundant fields, error: %s", err) - } - if err := bitrise.SaveConfigToFile(bitriseConfigPath, bitriseConfig); err != nil { - log.Fatalf("Failed to save config to file, error: %s", err) - } - - log.Info("Redundant fields removed") - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/plugin.go b/vendor/github.com/bitrise-io/bitrise/cli/plugin.go deleted file mode 100644 index d09b2831..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/plugin.go +++ /dev/null @@ -1,24 +0,0 @@ -package cli - -import ( - "github.com/bitrise-io/go-utils/log" - "github.com/urfave/cli" -) - -var pluginCommand = cli.Command{ - Name: "plugin", - Usage: "Plugin handling.", - Subcommands: []cli.Command{ - pluginInstallCommand, - pluginUpdateCommand, - pluginDeleteCommand, - pluginInfoCommand, - pluginListCommand, - }, -} - -func showSubcommandHelp(c *cli.Context) { - if err := cli.ShowSubcommandHelp(c); err != nil { - log.Warnf("Failed to show help, error: %s", err) - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/plugin_delete.go b/vendor/github.com/bitrise-io/bitrise/cli/plugin_delete.go deleted file mode 100644 index 93f42e24..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/plugin_delete.go +++ /dev/null @@ -1,58 +0,0 @@ -package cli - -import ( - "errors" - "fmt" - "os" - - "github.com/bitrise-io/bitrise/plugins" - "github.com/bitrise-io/go-utils/log" - "github.com/urfave/cli" -) - -var pluginDeleteCommand = cli.Command{ - Name: "delete", - Usage: "Delete bitrise plugin.", - Action: func(c *cli.Context) error { - if err := pluginDelete(c); err != nil { - log.Errorf("Plugin delete failed, error: %s", err) - os.Exit(1) - } - return nil - }, - ArgsUsage: "", -} - -func pluginDelete(c *cli.Context) error { - // Input validation - args := c.Args() - if len(args) == 0 { - showSubcommandHelp(c) - return errors.New("plugin_name not defined") - } - - name := args[0] - if name == "" { - showSubcommandHelp(c) - return errors.New("plugin_name not defined") - } - // --- - - // Delete - if _, found, err := plugins.LoadPlugin(name); err != nil { - return fmt.Errorf("failed to check if plugin installed, error: %s", err) - } else if !found { - log.Warnf("Plugin not installed") - return nil - } - - log.Infof("Deleting plugin") - if err := plugins.DeletePlugin(name); err != nil { - return fmt.Errorf("failed to delete plugin, error: %s", err) - } - - log.Donef("Plugin deleted") - // --- - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/plugin_info.go b/vendor/github.com/bitrise-io/bitrise/cli/plugin_info.go deleted file mode 100644 index 2c94559a..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/plugin_info.go +++ /dev/null @@ -1,106 +0,0 @@ -package cli - -import ( - "errors" - "fmt" - "os" - - "github.com/bitrise-io/bitrise/output" - "github.com/bitrise-io/bitrise/plugins" - "github.com/bitrise-io/go-utils/log" - "github.com/urfave/cli" -) - -var pluginInfoCommand = cli.Command{ - Name: "info", - Usage: "Installed bitrise plugin's info", - Action: func(c *cli.Context) error { - if err := pluginInfo(c); err != nil { - log.Errorf("Plugin info failed, error: %s", err) - os.Exit(1) - } - return nil - }, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: output.FormatKey, - Usage: "Output format. Accepted: raw, json.", - }, - }, - ArgsUsage: "", -} - -func createPluginInfo(name string) (plugins.PluginInfoModel, error) { - plugin, found, err := plugins.LoadPlugin(name) - if err != nil { - return plugins.PluginInfoModel{}, fmt.Errorf("failed to check if plugin installed, error: %s", err) - } else if !found { - return plugins.PluginInfoModel{}, fmt.Errorf("plugin is not installed") - } - - route, found, err := plugins.ReadPluginRoute(plugin.Name) - if err != nil { - return plugins.PluginInfoModel{}, fmt.Errorf("failed to read plugin route, error: %s", err) - } else if !found { - return plugins.PluginInfoModel{}, errors.New("no route found for loaded plugin") - } - - pluginVersionPtr, err := plugins.GetPluginVersion(plugin.Name) - if err != nil { - return plugins.PluginInfoModel{}, fmt.Errorf("failed to read plugin version, error: %s", err) - } - - pluginDefinitionPth := plugins.GetPluginDefinitionPath(plugin.Name) - - pluginInfo := plugins.PluginInfoModel{ - Name: plugin.Name, - Version: pluginVersionPtr.String(), - Source: route.Source, - Plugin: plugin, - DefinitionPth: pluginDefinitionPth, - } - - return pluginInfo, nil -} - -func pluginInfo(c *cli.Context) error { - // Input validation - args := c.Args() - if len(args) == 0 { - showSubcommandHelp(c) - return errors.New("plugin_name not defined") - } - - name := args[0] - if name == "" { - showSubcommandHelp(c) - return errors.New("plugin_name not defined") - } - - format := c.String(output.FormatKey) - if format == "" { - format = output.FormatRaw - } - if format != output.FormatRaw && format != output.FormatJSON { - showSubcommandHelp(c) - return fmt.Errorf("invalid format: %s", format) - } - - var logger log.Logger - logger = log.NewDefaultRawLogger() - if format == output.FormatJSON { - logger = log.NewDefaultJSONLoger() - } - // --- - - // Info - pluginInfo, err := createPluginInfo(name) - if err != nil { - return err - } - - logger.Print(pluginInfo) - // --- - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/plugin_install.go b/vendor/github.com/bitrise-io/bitrise/cli/plugin_install.go deleted file mode 100644 index e737b0c0..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/plugin_install.go +++ /dev/null @@ -1,75 +0,0 @@ -package cli - -import ( - "fmt" - "os" - - "github.com/bitrise-io/bitrise/plugins" - "github.com/bitrise-io/go-utils/log" - "github.com/urfave/cli" -) - -var pluginInstallCommand = cli.Command{ - Name: "install", - Usage: "Intsall bitrise plugin.", - Action: func(c *cli.Context) error { - if err := pluginInstall(c); err != nil { - log.Errorf("Plugin install failed, error: %s", err) - os.Exit(1) - } - return nil - }, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "version", - Usage: "Plugin version tag.", - }, - cli.StringFlag{ - Name: "source", - Usage: "Deprecated!!! Specify as arg instead - Plugin source url (can be local path or remote url).", - }, - }, - ArgsUsage: "", -} - -func pluginInstall(c *cli.Context) error { - // Input validation - pluginSource := "" - if args := c.Args(); len(args) > 0 { - pluginSource = args[0] - } else { - pluginSource = c.String("source") - } - - pluginVersionTag := c.String("version") - - if pluginSource == "" { - showSubcommandHelp(c) - return fmt.Errorf("plugin source not defined") - } - // --- - - // Install - log.Infof("Installing plugin") - - plugin, version, err := plugins.InstallPlugin(pluginSource, pluginVersionTag) - if err != nil { - return err - } - - if len(plugin.Description) > 0 { - fmt.Println() - log.Infof("Description:") - fmt.Println(plugin.Description) - } - - fmt.Println() - if version == "" { - log.Donef("Local plugin (%s) installed ", plugin.Name) - } else { - log.Donef("Plugin (%s) with version (%s) installed ", plugin.Name, version) - } - // --- - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/plugin_list.go b/vendor/github.com/bitrise-io/bitrise/cli/plugin_list.go deleted file mode 100644 index 2450c662..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/plugin_list.go +++ /dev/null @@ -1,77 +0,0 @@ -package cli - -import ( - "fmt" - "os" - - "github.com/bitrise-io/bitrise/output" - "github.com/bitrise-io/bitrise/plugins" - "github.com/bitrise-io/go-utils/log" - "github.com/urfave/cli" -) - -var pluginListCommand = cli.Command{ - Name: "list", - Usage: "List installed bitrise plugins.", - Action: func(c *cli.Context) error { - if err := pluginList(c); err != nil { - log.Errorf("Plugin list failed, error: %s", err) - os.Exit(1) - } - return nil - }, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: output.FormatKey, - Usage: "Output format. Accepted: raw, json.", - }, - }, - ArgsUsage: "", -} - -func pluginList(c *cli.Context) error { - // Input validation - format := c.String(output.FormatKey) - if format == "" { - format = output.FormatRaw - } - if format != output.FormatRaw && format != output.FormatJSON { - showSubcommandHelp(c) - return fmt.Errorf("invalid format: %s", format) - } - - var logger log.Logger - logger = log.NewDefaultRawLogger() - if format == output.FormatJSON { - logger = log.NewDefaultJSONLoger() - } - // --- - - // List - installedPlugins, err := plugins.InstalledPluginList() - if err != nil { - return fmt.Errorf("failed to list plugins, error: %s", err) - } - - if len(installedPlugins) == 0 { - log.Warnf("No installed plugin found") - return nil - } - - plugins.SortByName(installedPlugins) - - pluginInfos := plugins.PluginInfos{} - - for _, plugin := range installedPlugins { - pluginInfo, err := createPluginInfo(plugin.Name) - if err != nil { - return err - } - pluginInfos = append(pluginInfos, pluginInfo) - } - - logger.Print(pluginInfos) - // --- - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/plugin_update.go b/vendor/github.com/bitrise-io/bitrise/cli/plugin_update.go deleted file mode 100644 index bd49e7fc..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/plugin_update.go +++ /dev/null @@ -1,100 +0,0 @@ -package cli - -import ( - "errors" - "fmt" - "os" - - "github.com/bitrise-io/bitrise/plugins" - "github.com/bitrise-io/go-utils/log" - "github.com/urfave/cli" -) - -var pluginUpdateCommand = cli.Command{ - Name: "update", - Usage: "Update bitrise plugin. If not specified, every plugin will be updated.", - Action: func(c *cli.Context) error { - if err := pluginUpdate(c); err != nil { - log.Errorf("Plugin update failed, error: %s", err) - os.Exit(1) - } - return nil - }, - ArgsUsage: "[]", -} - -func pluginUpdate(c *cli.Context) error { - // Input validation - pluginNameToUpdate := "" - - args := c.Args() - if len(args) != 0 { - pluginNameToUpdate = args[0] - } - // --- - - // Update - pluginsToUpdate := []plugins.Plugin{} - - if pluginNameToUpdate != "" { - plugin, found, err := plugins.LoadPlugin(pluginNameToUpdate) - if err != nil { - return fmt.Errorf("failed to check if plugin installed, error: %s", err) - } else if !found { - return fmt.Errorf("plugin is not installed") - } - - pluginsToUpdate = append(pluginsToUpdate, plugin) - } else { - installedPlugins, err := plugins.InstalledPluginList() - if err != nil { - return fmt.Errorf("failed to list plugins, error: %s", err) - } - - if len(installedPlugins) == 0 { - log.Warnf("No installed plugin found") - return nil - } - - plugins.SortByName(installedPlugins) - - pluginsToUpdate = append(pluginsToUpdate, installedPlugins...) - } - - for _, plugin := range pluginsToUpdate { - log.Infof("Updating plugin %s", plugin.Name) - - if newVersion, err := plugins.CheckForNewVersion(plugin); err != nil { - return fmt.Errorf("failed to check for plugin new version, error: %s", err) - } else if newVersion != "" { - log.Printf("Installing new version (%s)", newVersion) - - route, found, err := plugins.ReadPluginRoute(plugin.Name) - if err != nil { - return fmt.Errorf("failed to read plugin route, error: %s", err) - } - if !found { - return errors.New("no route found for already loaded plugin") - } - - plugin, version, err := plugins.InstallPlugin(route.Source, newVersion) - if err != nil { - return fmt.Errorf("failed to install plugin from (%s), error: %s", route.Source, err) - } - - if len(plugin.Description) > 0 { - fmt.Println() - log.Infof("Description:") - fmt.Println(plugin.Description) - } - - fmt.Println() - log.Donef("Plugin (%s) with version (%s) installed ", plugin.Name, version) - } else { - log.Donef("No new version available") - } - } - // --- - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/run.go b/vendor/github.com/bitrise-io/bitrise/cli/run.go deleted file mode 100644 index e2620360..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/run.go +++ /dev/null @@ -1,237 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "sort" - "strings" - "time" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/bitrise/bitrise" - "github.com/bitrise-io/bitrise/models" - "github.com/bitrise-io/bitrise/version" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/colorstring" - "github.com/bitrise-io/go-utils/pointers" - "github.com/urfave/cli" -) - -const ( - // DefaultBitriseConfigFileName ... - DefaultBitriseConfigFileName = "bitrise.yml" - // DefaultSecretsFileName ... - DefaultSecretsFileName = ".bitrise.secrets.yml" - - depManagerBrew = "brew" - depManagerTryCheck = "_" -) - -// -------------------- -// Utility -// -------------------- - -func printAboutUtilityWorkflowsText() { - fmt.Println("Note about utility workflows:") - fmt.Println(" Utility workflow names start with '_' (example: _my_utility_workflow).") - fmt.Println(" These workflows can't be triggered directly, but can be used by other workflows") - fmt.Println(" in the before_run and after_run lists.") -} - -func printAvailableWorkflows(config models.BitriseDataModel) { - workflowNames := []string{} - utilityWorkflowNames := []string{} - - for wfName := range config.Workflows { - if strings.HasPrefix(wfName, "_") { - utilityWorkflowNames = append(utilityWorkflowNames, wfName) - } else { - workflowNames = append(workflowNames, wfName) - } - } - sort.Strings(workflowNames) - sort.Strings(utilityWorkflowNames) - - if len(workflowNames) > 0 { - fmt.Println("The following workflows are available:") - for _, wfName := range workflowNames { - fmt.Println(" * " + wfName) - } - - fmt.Println() - fmt.Println("You can run a selected workflow with:") - fmt.Println("$ bitrise run WORKFLOW-ID") - fmt.Println() - } else { - fmt.Println("No workflows are available!") - } - - if len(utilityWorkflowNames) > 0 { - fmt.Println() - fmt.Println("The following utility workflows are defined:") - for _, wfName := range utilityWorkflowNames { - fmt.Println(" * " + wfName) - } - - fmt.Println() - printAboutUtilityWorkflowsText() - fmt.Println() - } -} - -func runAndExit(bitriseConfig models.BitriseDataModel, inventoryEnvironments []envmanModels.EnvironmentItemModel, workflowToRunID string) { - if workflowToRunID == "" { - log.Fatal("No workflow id specified") - } - - if err := bitrise.RunSetupIfNeeded(version.VERSION, false); err != nil { - log.Fatalf("Setup failed, error: %s", err) - } - - startTime := time.Now() - - // Run selected configuration - if buildRunResults, err := runWorkflowWithConfiguration(startTime, workflowToRunID, bitriseConfig, inventoryEnvironments); err != nil { - log.Fatalf("Failed to run workflow, error: %s", err) - } else if buildRunResults.IsBuildFailed() { - os.Exit(1) - } - os.Exit(0) -} - -func printRunningWorkflow(bitriseConfig models.BitriseDataModel, targetWorkflowToRunID string) { - beforeWorkflowIDs := bitriseConfig.Workflows[targetWorkflowToRunID].BeforeRun - afterWorkflowIDs := bitriseConfig.Workflows[targetWorkflowToRunID].AfterRun - workflowsString := "" - if len(beforeWorkflowIDs) == 0 && len(afterWorkflowIDs) == 0 { - workflowsString = "Running workflow: " - } else { - workflowsString = "Running workflows: " - } - - if len(beforeWorkflowIDs) != 0 { - for _, workflowName := range beforeWorkflowIDs { - workflowsString = workflowsString + workflowName + " --> " - } - } - - workflowsString = workflowsString + colorstring.Green(targetWorkflowToRunID) - - if len(afterWorkflowIDs) != 0 { - for _, workflowName := range afterWorkflowIDs { - workflowsString = workflowsString + " --> " + workflowName - } - } - - log.Infof(workflowsString) -} - -// -------------------- -// CLI command -// -------------------- - -func run(c *cli.Context) error { - PrintBitriseHeaderASCIIArt(version.VERSION) - - // - // Expand cli.Context - var prGlobalFlagPtr *bool - if c.GlobalIsSet(PRKey) { - prGlobalFlagPtr = pointers.NewBoolPtr(c.GlobalBool(PRKey)) - } - - var ciGlobalFlagPtr *bool - if c.GlobalIsSet(CIKey) { - ciGlobalFlagPtr = pointers.NewBoolPtr(c.GlobalBool(CIKey)) - } - - workflowToRunID := c.String(WorkflowKey) - if workflowToRunID == "" && len(c.Args()) > 0 { - workflowToRunID = c.Args()[0] - } - - bitriseConfigBase64Data := c.String(ConfigBase64Key) - bitriseConfigPath := c.String(ConfigKey) - deprecatedBitriseConfigPath := c.String(PathKey) - if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { - log.Warn("'path' key is deprecated, use 'config' instead!") - bitriseConfigPath = deprecatedBitriseConfigPath - } - - inventoryBase64Data := c.String(InventoryBase64Key) - inventoryPath := c.String(InventoryKey) - - jsonParams := c.String(JSONParamsKey) - jsonParamsBase64 := c.String(JSONParamsBase64Key) - - runParams, err := parseRunParams( - workflowToRunID, - bitriseConfigPath, bitriseConfigBase64Data, - inventoryPath, inventoryBase64Data, - jsonParams, jsonParamsBase64) - if err != nil { - return fmt.Errorf("Failed to parse command params, error: %s", err) - } - // - - // Inventory validation - inventoryEnvironments, err := CreateInventoryFromCLIParams(runParams.InventoryBase64Data, runParams.InventoryPath) - if err != nil { - log.Fatalf("Failed to create inventory, error: %s", err) - } - - // Config validation - bitriseConfig, warnings, err := CreateBitriseConfigFromCLIParams(runParams.BitriseConfigBase64Data, runParams.BitriseConfigPath) - for _, warning := range warnings { - log.Warnf("warning: %s", warning) - } - if err != nil { - log.Fatalf("Failed to create bitrise config, error: %s", err) - } - - // Workflow id validation - if runParams.WorkflowToRunID == "" { - // no workflow specified - // list all the available ones and then exit - log.Error("No workfow specified!") - fmt.Println() - printAvailableWorkflows(bitriseConfig) - os.Exit(1) - } - if strings.HasPrefix(runParams.WorkflowToRunID, "_") { - // util workflow specified - // print about util workflows and then exit - log.Error("Utility workflows can't be triggered directly") - fmt.Println() - printAboutUtilityWorkflowsText() - os.Exit(1) - } - // - - // - // Main - isPRMode, err := isPRMode(prGlobalFlagPtr, inventoryEnvironments) - if err != nil { - log.Fatalf("Failed to check PR mode, error: %s", err) - } - - if err := registerPrMode(isPRMode); err != nil { - log.Fatalf("Failed to register PR mode, error: %s", err) - } - - isCIMode, err := isCIMode(ciGlobalFlagPtr, inventoryEnvironments) - if err != nil { - log.Fatalf("Failed to check CI mode, error: %s", err) - } - - if err := registerCIMode(isCIMode); err != nil { - log.Fatalf("Failed to register CI mode, error: %s", err) - } - - printRunningWorkflow(bitriseConfig, runParams.WorkflowToRunID) - - runAndExit(bitriseConfig, inventoryEnvironments, runParams.WorkflowToRunID) - // - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/run_test.go b/vendor/github.com/bitrise-io/bitrise/cli/run_test.go deleted file mode 100644 index b5e4af78..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/run_test.go +++ /dev/null @@ -1,1482 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "path/filepath" - "testing" - "time" - - "github.com/bitrise-io/bitrise/bitrise" - "github.com/bitrise-io/bitrise/configs" - "github.com/bitrise-io/bitrise/models" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestSkipIfEmpty(t *testing.T) { - t.Log("skip_if_empty=true && value=empty => should not add") - { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - skip_if_empty: - envs: - - TEST: test - - TEST: - opts: - skip_if_empty: true - steps: - - script: - is_skippable: true - title: "Envman add DELETE_TEST" - inputs: - - content: | - #!/bin/bash - if [ -z $TEST ] ; then - echo "TEST shuld exist" - exit 1 - fi -` - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "skip_if_empty", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 1, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) - } - - t.Log("skip_if_empty=false && value=empty => should add") - { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - skip_if_empty: - envs: - - TEST: test - - TEST: - opts: - skip_if_empty: false - steps: - - script: - is_skippable: true - title: "Envman add DELETE_TEST" - inputs: - - content: | - #!/bin/bash - if [ ! -z $TEST ] ; then - echo "TEST env shuld not exist" - exit 1 - fi -` - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "skip_if_empty", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 1, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) - } -} - -func TestDeleteEnvironment(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - test: - steps: - - script: - is_skippable: true - title: "Envman add DELETE_TEST" - inputs: - - content: | - #!/bin/bash - envman add --key DELETE_TEST --value "delete test" - - script: - title: "Test env DELETE_TEST" - inputs: - - content: | - #!/bin/bash - set -v - echo "DELETE_TEST: $DELETE_TEST" - if [ -z "$DELETE_TEST" ] ; then - exit 1 - fi - - script: - title: "Delete env DELETE_TEST" - inputs: - - content: | - #!/bin/bash - envman add --key DELETE_TEST --value "" - - script: - title: "Test env DELETE_TEST" - inputs: - - content: | - #!/bin/bash - set -v - echo "DELETE_TEST: $DELETE_TEST" - if [ ! -z "$DELETE_TEST" ] ; then - exit 1 - fi -` - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "test", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 4, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) -} - -func TestStepOutputsInTemplate(t *testing.T) { - inventoryStr := ` -envs: -- TEMPLATE_TEST0: "true" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -app: - envs: - - TEMPLATE_TEST1: "true" - -workflows: - test: - envs: - - TEMPLATE_TEST2: "true" - steps: - - script: - title: "Envman add" - inputs: - - content: | - #!/bin/bash - set -v - envman add --key TEMPLATE_TEST3 --value "true" - - script: - title: "TEMPLATE_TEST0" - run_if: |- - {{enveq "TEMPLATE_TEST0" "true"}} - - script: - title: "TEMPLATE_TEST1" - run_if: |- - {{enveq "TEMPLATE_TEST1" "true"}} - - script: - title: "TEMPLATE_TEST2" - run_if: |- - {{enveq "TEMPLATE_TEST2" "true"}} - - script: - title: "TEMPLATE_TEST3" - run_if: |- - {{enveq "TEMPLATE_TEST3" "true"}} - - script: - title: "TEMPLATE_TEST_NO_VALUE" - run_if: |- - {{enveq "TEMPLATE_TEST_NO_VALUE" "true"}} -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) - require.NoError(t, err) - require.Equal(t, 5, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 1, len(buildRunResults.SkippedSteps)) -} - -func TestFailedStepOutputs(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - test: - steps: - - script: - is_skippable: true - title: "Envman add" - inputs: - - content: | - #!/bin/bash - set -v - envman add --key FAILED_OUTPUT_TEST --value "failed step output" - exit 1 - - script: - title: "Test failed output" - inputs: - - content: | - #!/bin/bash - set -v - echo "FAILED_OUTPUT_TEST: $FAILED_OUTPUT_TEST" - if [[ "$FAILED_OUTPUT_TEST" != "failed step output" ]] ; then - exit 1 - fi -` - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "test", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 1, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 1, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) -} - -func TestBitriseSourceDir(t *testing.T) { - currPth, err := pathutil.NormalizedOSTempDirPath("bitrise_source_dir_test") - require.NoError(t, err) - - testPths := []string{} - for i := 0; i < 4; i++ { - testPth := filepath.Join(currPth, fmt.Sprintf("_test%d", i)) - require.NoError(t, os.RemoveAll(testPth)) - require.NoError(t, os.Mkdir(testPth, 0777)) - - // eval symlinks: the Go generated temp folder on OS X is a symlink - // from /var/ to /private/var/ - testPth, err = filepath.EvalSymlinks(testPth) - require.NoError(t, err) - - defer func() { require.NoError(t, os.RemoveAll(testPth)) }() - - testPths = append(testPths, testPth) - } - - t.Log("BITRISE_SOURCE_DIR defined in Secret") - { - inventoryStr := ` -envs: -- BITRISE_SOURCE_DIR: "` + testPths[0] + `" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - test: - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - echo "BITRISE_SOURCE_DIR: $BITRISE_SOURCE_DIR" - if [[ "$BITRISE_SOURCE_DIR" != "` + testPths[0] + `" ]] ; then - exit 1 - fi -` - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) - require.NoError(t, err) - } - - t.Log("BITRISE_SOURCE_DIR defined in Secret, and in App") - { - inventoryStr := ` -envs: -- BITRISE_SOURCE_DIR: "` + testPths[0] + `" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -app: - envs: - - BITRISE_SOURCE_DIR: "` + testPths[1] + `" - -workflows: - test: - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - echo "BITRISE_SOURCE_DIR: $BITRISE_SOURCE_DIR" - if [[ "$BITRISE_SOURCE_DIR" != "` + testPths[1] + `" ]] ; then - exit 1 - fi -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) - require.NoError(t, err) - } - - t.Log("BITRISE_SOURCE_DIR defined in Secret, App and Workflow") - { - inventoryStr := ` -envs: -- BITRISE_SOURCE_DIR: "` + testPths[0] + `" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -app: - envs: - - BITRISE_SOURCE_DIR: "` + testPths[1] + `" - -workflows: - test: - envs: - - BITRISE_SOURCE_DIR: "` + testPths[2] + `" - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - echo "BITRISE_SOURCE_DIR: $BITRISE_SOURCE_DIR" - if [[ "$BITRISE_SOURCE_DIR" != "` + testPths[2] + `" ]] ; then - exit 1 - fi -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) - require.NoError(t, err) - } - - t.Log("BITRISE_SOURCE_DIR defined in secret, App, Workflow and Step") - { - inventoryStr := ` -envs: -- BITRISE_SOURCE_DIR: "` + testPths[0] + `" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -app: - envs: - - BITRISE_SOURCE_DIR: "` + testPths[1] + `" - -workflows: - test: - envs: - - BITRISE_SOURCE_DIR: "` + testPths[2] + `" - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - envman add --key BITRISE_SOURCE_DIR --value ` + testPths[3] + ` - - script: - inputs: - - content: | - #!/bin/bash - set -v - echo "BITRISE_SOURCE_DIR: $BITRISE_SOURCE_DIR" - if [[ "$BITRISE_SOURCE_DIR" != "` + testPths[3] + `" ]] ; then - exit 1 - fi -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) - require.NoError(t, err) - } -} - -func TestEnvOrders(t *testing.T) { - t.Log("Only secret env - secret env should be use") - { - inventoryStr := ` -envs: -- ENV_ORDER_TEST: "should be the 1." -` - - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - test: - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - echo "ENV_ORDER_TEST: $ENV_ORDER_TEST" - if [[ "$ENV_ORDER_TEST" != "should be the 1." ]] ; then - exit 1 - fi -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) - require.NoError(t, err) - } - - t.Log("Secret env & app env also defined - app env should be use") - { - inventoryStr := ` -envs: -- ENV_ORDER_TEST: "should be the 1." -` - - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -app: - envs: - - ENV_ORDER_TEST: "should be the 2." - -workflows: - test: - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - echo "ENV_ORDER_TEST: $ENV_ORDER_TEST" - if [[ "$ENV_ORDER_TEST" != "should be the 2." ]] ; then - exit 1 - fi - -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) - require.NoError(t, err) - } - - t.Log("Secret env & app env && workflow env also defined - workflow env should be use") - { - inventoryStr := ` -envs: -- ENV_ORDER_TEST: "should be the 1." -` - - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -app: - envs: - - ENV_ORDER_TEST: "should be the 2." - -workflows: - test: - envs: - - ENV_ORDER_TEST: "should be the 3." - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - echo "ENV_ORDER_TEST: $ENV_ORDER_TEST" - if [[ "$ENV_ORDER_TEST" != "should be the 3." ]] ; then - exit 1 - fi -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) - require.NoError(t, err) - } - - t.Log("Secret env & app env && workflow env && step output env also defined - step output env should be use") - { - inventoryStr := ` -envs: -- ENV_ORDER_TEST: "should be the 1." -` - - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -app: - envs: - - ENV_ORDER_TEST: "should be the 2." - -workflows: - test: - envs: - - ENV_ORDER_TEST: "should be the 3." - steps: - - script: - inputs: - - content: envman add --key ENV_ORDER_TEST --value "should be the 4." - - script: - inputs: - - content: | - #!/bin/bash - set -v - echo "ENV_ORDER_TEST: $ENV_ORDER_TEST" - if [[ "$ENV_ORDER_TEST" != "should be the 4." ]] ; then - exit 1 - fi -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, err = runWorkflowWithConfiguration(time.Now(), "test", config, inventory.Envs) - require.NoError(t, err) - } -} - -// Test - Bitrise activateAndRunWorkflow -// If workflow contains no steps -func Test0Steps1Workflows(t *testing.T) { - workflow := models.WorkflowModel{} - - require.NoError(t, os.Setenv("BITRISE_BUILD_STATUS", "0")) - defer func() { require.NoError(t, os.Unsetenv("BITRISE_BUILD_STATUS")) }() - - require.NoError(t, os.Setenv("STEPLIB_BUILD_STATUS", "0")) - defer func() { require.NoError(t, os.Unsetenv("STEPLIB_BUILD_STATUS")) }() - - config := models.BitriseDataModel{ - FormatVersion: "1.0.0", - DefaultStepLibSource: "https://github.com/bitrise-io/bitrise-steplib.git", - Workflows: map[string]models.WorkflowModel{ - "zero_steps": workflow, - }, - } - - _, err := config.Validate() - require.NoError(t, err) - - buildRunResults := models.BuildRunResultsModel{ - StartTime: time.Now(), - StepmanUpdates: map[string]int{}, - } - - require.NoError(t, configs.InitPaths()) - - buildRunResults, err = runWorkflowWithConfiguration(time.Now(), "zero_steps", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 0, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) - - require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Test - Bitrise activateAndRunWorkflow -// Workflow contains before and after workflow, and no one contains steps -func Test0Steps3WorkflowsBeforeAfter(t *testing.T) { - require.NoError(t, os.Setenv("BITRISE_BUILD_STATUS", "0")) - defer func() { require.NoError(t, os.Unsetenv("BITRISE_BUILD_STATUS")) }() - - require.NoError(t, os.Setenv("STEPLIB_BUILD_STATUS", "0")) - defer func() { require.NoError(t, os.Unsetenv("STEPLIB_BUILD_STATUS")) }() - - beforeWorkflow := models.WorkflowModel{} - afterWorkflow := models.WorkflowModel{} - - workflow := models.WorkflowModel{ - BeforeRun: []string{"before"}, - AfterRun: []string{"after"}, - } - - config := models.BitriseDataModel{ - FormatVersion: "1.0.0", - DefaultStepLibSource: "https://github.com/bitrise-io/bitrise-steplib.git", - Workflows: map[string]models.WorkflowModel{ - "target": workflow, - "before": beforeWorkflow, - "after": afterWorkflow, - }, - } - - _, err := config.Validate() - require.NoError(t, err) - - buildRunResults := models.BuildRunResultsModel{ - StartTime: time.Now(), - StepmanUpdates: map[string]int{}, - } - - buildRunResults, err = activateAndRunWorkflow("target", workflow, config, buildRunResults, &[]envmanModels.EnvironmentItemModel{}, "") - require.NoError(t, err) - require.Equal(t, 0, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) - - require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Test - Bitrise Validate workflow -// Workflow contains before and after workflow, and no one contains steps, but circular wofklow dependecy exist, which should fail -func Test0Steps3WorkflowsCircularDependency(t *testing.T) { - require.NoError(t, os.Setenv("BITRISE_BUILD_STATUS", "0")) - defer func() { require.NoError(t, os.Unsetenv("BITRISE_BUILD_STATUS")) }() - - require.NoError(t, os.Setenv("STEPLIB_BUILD_STATUS", "0")) - defer func() { require.NoError(t, os.Unsetenv("STEPLIB_BUILD_STATUS")) }() - - beforeWorkflow := models.WorkflowModel{ - BeforeRun: []string{"target"}, - } - - afterWorkflow := models.WorkflowModel{} - - workflow := models.WorkflowModel{ - BeforeRun: []string{"before"}, - AfterRun: []string{"after"}, - } - - config := models.BitriseDataModel{ - FormatVersion: "1.0.0", - DefaultStepLibSource: "https://github.com/bitrise-io/bitrise-steplib.git", - Workflows: map[string]models.WorkflowModel{ - "target": workflow, - "before": beforeWorkflow, - "after": afterWorkflow, - }, - } - - _, err := config.Validate() - require.Error(t, err) - - require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Test - Bitrise activateAndRunWorkflow -// Trivial test with 1 workflow -func Test1Workflows(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - trivial_fail: - steps: - - script: - title: Should success - - script: - title: Should fail, but skippable - is_skippable: true - inputs: - - content: | - #!/bin/bash - set -v - exit 2 - - script: - title: Should success - - script: - title: Should fail - inputs: - - content: | - #!/bin/bash - set -v - exit 2 - - script: - title: Should success - is_always_run: true - - script: - title: Should skipped - ` - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - workflow, found := config.Workflows["trivial_fail"] - require.Equal(t, true, found) - - buildRunResults := models.BuildRunResultsModel{ - StartTime: time.Now(), - StepmanUpdates: map[string]int{}, - } - - buildRunResults, err = activateAndRunWorkflow("trivial_fail", workflow, config, buildRunResults, &[]envmanModels.EnvironmentItemModel{}, "") - require.NoError(t, err) - require.Equal(t, 3, len(buildRunResults.SuccessSteps)) - require.Equal(t, 1, len(buildRunResults.FailedSteps)) - require.Equal(t, 1, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 1, len(buildRunResults.SkippedSteps)) - - require.Equal(t, "1", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "1", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Test - Bitrise activateAndRunWorkflow -// Trivial test with before, after workflows -func Test3Workflows(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - before1: - steps: - - script: - title: Should success - - script: - title: Should fail, but skippable - is_skippable: true - inputs: - - content: | - #!/bin/bash - set -v - exit 2 - - script: - title: Should success - - before2: - steps: - - script: - title: Should success - - target: - before_run: - - before1 - - before2 - after_run: - - after1 - - after2 - steps: - - script: - title: Should fail - inputs: - - content: | - #!/bin/bash - set -v - exit 2 - - after1: - steps: - - script: - title: Should fail - is_always_run: true - inputs: - - content: | - #!/bin/bash - set -v - exit 2 - - after2: - steps: - - script: - title: Should be skipped - ` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, found := config.Workflows["target"] - require.Equal(t, true, found) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 3, len(buildRunResults.SuccessSteps)) - require.Equal(t, 2, len(buildRunResults.FailedSteps)) - require.Equal(t, 1, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 1, len(buildRunResults.SkippedSteps)) - - require.Equal(t, "1", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "1", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Test - Bitrise ConfigModelFromYAMLBytes -// Workflow contains before and after workflow, and no one contains steps, but circular wofklow dependecy exist, which should fail -func TestRefeneceCycle(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - before1: - before_run: - - before2 - - before2: - before_run: - - before1 - - target: - before_run: - - before1 - - before2 - ` - _, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.Error(t, err) - require.Equal(t, 0, len(warnings)) -} - -// Test - Bitrise BuildStatusEnv -// Checks if BuildStatusEnv is set correctly -func TestBuildStatusEnv(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - before1: - steps: - - script: - title: Should success - - script: - title: Should fail, but skippable - is_skippable: true - inputs: - - content: | - #!/bin/bash - set -v - exit 2 - - script: - title: Should success - - before2: - steps: - - script: - title: Should success - - target: - steps: - - script: - title: Should success - inputs: - - content: | - #!/bin/bash - set -v - if [[ "$BITRISE_BUILD_STATUS" != "0" ]] ; then - exit 1 - fi - if [[ "$STEPLIB_BUILD_STATUS" != "0" ]] ; then - exit 1 - fi - - script: - title: Should fail, but skippable - is_skippable: true - inputs: - - content: | - #!/bin/bash - set -v - echo 'This is a before workflow' - exit 2 - - script: - title: Should success - inputs: - - content: | - #!/bin/bash - set -v - if [[ "$BITRISE_BUILD_STATUS" != "0" ]] ; then - exit 1 - fi - if [[ "$STEPLIB_BUILD_STATUS" != "0" ]] ; then - exit 1 - fi - - script: - title: Should fail - inputs: - - content: | - #!/bin/bash - set -v - exit 1 - - script: - title: Should success - is_always_run: true - inputs: - - content: | - #!/bin/bash - set -v - if [[ "$BITRISE_BUILD_STATUS" != "1" ]] ; then - echo "should fail" - fi - if [[ "$STEPLIB_BUILD_STATUS" != "1" ]] ; then - echo "should fail" - fi - - script: - title: Should skipped - ` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, found := config.Workflows["target"] - require.Equal(t, true, found) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 3, len(buildRunResults.SuccessSteps)) - require.Equal(t, 1, len(buildRunResults.FailedSteps)) - require.Equal(t, 1, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 1, len(buildRunResults.SkippedSteps)) - - require.Equal(t, "1", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "1", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Test - Bitrise activateAndRunWorkflow -// Trivial fail test -func TestFail(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - target: - steps: - - script: - title: Should success - - script: - title: Should fail, but skippable - is_skippable: true - inputs: - - content: | - #!/bin/bash - set -v - exit 2 - - script: - title: Should success - - script: - title: Should fail - inputs: - - content: | - #!/bin/bash - set -v - exit 1 - - script: - title: Should skipped - - script: - title: Should success - is_always_run: true - ` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, found := config.Workflows["target"] - require.Equal(t, true, found) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 3, len(buildRunResults.SuccessSteps)) - require.Equal(t, 1, len(buildRunResults.FailedSteps)) - require.Equal(t, 1, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 1, len(buildRunResults.SkippedSteps)) - - require.Equal(t, "1", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "1", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Test - Bitrise activateAndRunWorkflow -// Trivial success test -func TestSuccess(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - target: - steps: - - script: - title: Should success - ` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, found := config.Workflows["target"] - require.Equal(t, true, found) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 1, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) - - require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Test - Bitrise BuildStatusEnv -// Checks if BuildStatusEnv is set correctly -func TestBuildFailedMode(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - before1: - title: before1 - steps: - - script: - title: Should success - - script: - title: Should fail - inputs: - - content: | - #!/bin/bash - set -v - exit 2 - - before2: - title: before2 - steps: - - script: - title: Should skipped - - target: - title: target - before_run: - - before1 - - before2 - steps: - - script: - title: Should skipped - ` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, found := config.Workflows["target"] - require.Equal(t, true, found) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 1, len(buildRunResults.SuccessSteps)) - require.Equal(t, 1, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 2, len(buildRunResults.SkippedSteps)) - - require.Equal(t, "1", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "1", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Test - Bitrise Environments -// Trivial test for workflow environment handling -// Before workflows env should be visible in target and after workflow -func TestWorkflowEnvironments(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - before: - envs: - - BEFORE_ENV: beforeenv - - target: - title: target - before_run: - - before - after_run: - - after - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - if [[ "$BEFORE_ENV" != "beforeenv" ]] ; then - exit 1 - fi - - after: - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - if [[ "$BEFORE_ENV" != "beforeenv" ]] ; then - exit 1 - fi - ` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, found := config.Workflows["target"] - require.Equal(t, true, found) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 2, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) - - require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Test - Bitrise Environments -// Test for same env in before and target workflow, actual workflow should overwrite environemnt and use own value -func TestWorkflowEnvironmentOverWrite(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - before: - envs: - - ENV: env1 - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - echo ${ENV} - if [[ "$ENV" != "env1" ]] ; then - exit 1 - fi - - target: - title: target - envs: - - ENV: env2 - before_run: - - before - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - echo ${ENV} - if [[ "$ENV" != "env2" ]] ; then - exit 1 - fi -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, found := config.Workflows["target"] - require.Equal(t, true, found) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 2, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) - - require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Test - Bitrise Environments -// Target workflows env should be visible in before and after workflow -func TestTargetDefinedWorkflowEnvironment(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - before: - steps: - - script: - inputs: - - content: | - #!/bin/bash - set -v - echo ${ENV} - if [[ "$ENV" != "targetenv" ]] ; then - exit 3 - fi - - target: - title: target - envs: - - ENV: targetenv - before_run: - - before -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, found := config.Workflows["target"] - require.Equal(t, true, found) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 1, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) - - require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Test - Bitrise Environments -// Step input should visible only for actual step and invisible for other steps -func TestStepInputEnvironment(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - before: - steps: - - script@1.1.3: - inputs: - - working_dir: $HOME - - target: - title: target - before_run: - - before - steps: - - script@1.1.3: - title: "${working_dir} should not exist" - inputs: - - content: | - #!/bin/bash - set -v - env - if [ ! -z "$working_dir" ] ; then - echo ${working_dir} - exit 3 - fi -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, found := config.Workflows["target"] - require.Equal(t, true, found) - - if os.Getenv("working_dir") != "" { - require.Equal(t, nil, os.Unsetenv("working_dir")) - } - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 2, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) - - require.Equal(t, "0", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "0", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -// Outputs exported with `envman add` should be accessible for subsequent Steps. -func TestStepOutputEnvironment(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - out-test: - title: Output Test - steps: - - script: - inputs: - - content: envman -l=debug add --key MY_TEST_1 --value 'Test value 1' - - script: - inputs: - - content: |- - if [[ "${MY_TEST_1}" != "Test value 1" ]] ; then - echo " [!] MY_TEST_1 invalid: ${MY_TEST_1}" - exit 1 - fi - - script: - title: Should fail - inputs: - - content: |- - envman add --key MY_TEST_2 --value 'Test value 2' - # exported output, but test fails - exit 22 - - script: - is_always_run: true - inputs: - - content: |- - if [[ "${MY_TEST_2}" != "Test value 2" ]] ; then - exit 1 - fi -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - _, found := config.Workflows["out-test"] - require.Equal(t, true, found) - - _, err = config.Validate() - require.NoError(t, err) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "out-test", config, []envmanModels.EnvironmentItemModel{}) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) - require.Equal(t, 3, len(buildRunResults.SuccessSteps)) - require.Equal(t, 1, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - - // the exported output envs should NOT be exposed here, should NOT be available! - require.Equal(t, "", os.Getenv("MY_TEST_1")) - require.Equal(t, "", os.Getenv("MY_TEST_2")) - - // standard, Build Status ENV test - require.Equal(t, "1", os.Getenv("BITRISE_BUILD_STATUS")) - require.Equal(t, "1", os.Getenv("STEPLIB_BUILD_STATUS")) -} - -func TestLastWorkflowIDInConfig(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - before: - - target: - title: target - before_run: - - before - after_run: - - after1 - - after1: - after_run: - - after2 - - after2: - ` - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - last, err := lastWorkflowIDInConfig("target", config) - require.NoError(t, err) - require.Equal(t, "after2", last) -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params.go b/vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params.go deleted file mode 100644 index 9d20a4e4..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params.go +++ /dev/null @@ -1,138 +0,0 @@ -package cli - -import ( - "encoding/base64" - "encoding/json" -) - -// -------------------- -// Models -// -------------------- - -// RunAndTriggerParamsModel ... -type RunAndTriggerParamsModel struct { - // Run Params - WorkflowToRunID string `json:"workflow"` - - // Trigger Params - TriggerPattern string `json:"pattern"` - - PushBranch string `json:"push-branch"` - PRSourceBranch string `json:"pr-source-branch"` - PRTargetBranch string `json:"pr-target-branch"` - Tag string `json:"tag"` - - // Trigger Check Params - Format string `json:"format"` - - // Bitrise Config Params - BitriseConfigPath string `json:"config"` - BitriseConfigBase64Data string `json:"config-base64"` - - InventoryPath string `json:"inventory"` - InventoryBase64Data string `json:"inventory-base64"` -} - -func parseRunAndTriggerJSONParams(jsonParams string) (RunAndTriggerParamsModel, error) { - params := RunAndTriggerParamsModel{} - if err := json.Unmarshal([]byte(jsonParams), ¶ms); err != nil { - return RunAndTriggerParamsModel{}, err - } - return params, nil -} - -func parseRunAndTriggerParams( - workflowToRunID, - triggerPattern, - pushBranch, prSourceBranch, prTargetBranch, tag, - format, - bitriseConfigPath, bitriseConfigBase64Data, - inventoryPath, inventoryBase64Data, - jsonParams, base64JSONParams string) (RunAndTriggerParamsModel, error) { - params := RunAndTriggerParamsModel{} - var err error - - // Parse json params if exist - if jsonParams == "" && base64JSONParams != "" { - jsonParamsBytes, err := base64.StdEncoding.DecodeString(base64JSONParams) - if err != nil { - return RunAndTriggerParamsModel{}, err - } - jsonParams = string(jsonParamsBytes) - } - - if jsonParams != "" { - params, err = parseRunAndTriggerJSONParams(jsonParams) - if err != nil { - return RunAndTriggerParamsModel{}, err - } - } - - // Owerride params - if workflowToRunID != "" { - params.WorkflowToRunID = workflowToRunID - } - - if triggerPattern != "" { - params.TriggerPattern = triggerPattern - } - - if pushBranch != "" { - params.PushBranch = pushBranch - } - if prSourceBranch != "" { - params.PRSourceBranch = prSourceBranch - } - if prTargetBranch != "" { - params.PRTargetBranch = prTargetBranch - } - if tag != "" { - params.Tag = tag - } - - if format != "" { - params.Format = format - } - - if bitriseConfigPath != "" { - params.BitriseConfigPath = bitriseConfigPath - } - if bitriseConfigBase64Data != "" { - params.BitriseConfigBase64Data = bitriseConfigBase64Data - } - if inventoryPath != "" { - params.InventoryPath = inventoryPath - } - if inventoryBase64Data != "" { - params.InventoryBase64Data = inventoryBase64Data - } - - return params, nil -} - -func parseRunParams( - workflowToRunID, - bitriseConfigPath, bitriseConfigBase64Data, - inventoryPath, inventoryBase64Data, - jsonParams, base64JSONParams string) (RunAndTriggerParamsModel, error) { - return parseRunAndTriggerParams(workflowToRunID, "", "", "", "", "", "", bitriseConfigPath, bitriseConfigBase64Data, inventoryPath, inventoryBase64Data, jsonParams, base64JSONParams) -} - -func parseTriggerParams( - triggerPattern, - pushBranch, prSourceBranch, prTargetBranch, tag, - bitriseConfigPath, bitriseConfigBase64Data, - inventoryPath, inventoryBase64Data, - jsonParams, base64JSONParams string) (RunAndTriggerParamsModel, error) { - return parseRunAndTriggerParams("", triggerPattern, pushBranch, prSourceBranch, prTargetBranch, tag, "", bitriseConfigPath, bitriseConfigBase64Data, inventoryPath, inventoryBase64Data, jsonParams, base64JSONParams) -} - -func parseTriggerCheckParams( - triggerPattern, - pushBranch, prSourceBranch, prTargetBranch, tag, - format, - bitriseConfigPath, bitriseConfigBase64Data, - inventoryPath, inventoryBase64Data, - jsonParams, base64JSONParams string) (RunAndTriggerParamsModel, error) { - return parseRunAndTriggerParams("", triggerPattern, pushBranch, prSourceBranch, prTargetBranch, tag, format, bitriseConfigPath, bitriseConfigBase64Data, inventoryPath, inventoryBase64Data, jsonParams, base64JSONParams) -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params_test.go b/vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params_test.go deleted file mode 100644 index 68eae2a4..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/run_trigger_params_test.go +++ /dev/null @@ -1,483 +0,0 @@ -package cli - -import ( - "encoding/base64" - "encoding/json" - "testing" - - "github.com/stretchr/testify/require" -) - -func toBase64(t *testing.T, str string) string { - bytes := base64.StdEncoding.EncodeToString([]byte(str)) - return string(bytes) -} - -func toJSON(t *testing.T, stringStringMap map[string]string) string { - bytes, err := json.Marshal(stringStringMap) - require.NoError(t, err) - return string(bytes) -} - -func TestParseRunAndTriggerJSONParams(t *testing.T) { - t.Log("it parses cli params") - { - paramsMap := map[string]string{ - WorkflowKey: "primary", - - PatternKey: "master", - PushBranchKey: "deploy", - PRSourceBranchKey: "development", - PRTargetBranchKey: "release", - TagKey: "0.9.0", - - OuputFormatKey: "json", - - ConfigKey: "bitrise.yml", - ConfigBase64Key: toBase64(t, "bitrise.yml"), - - InventoryKey: ".secrets.bitrise.yml", - InventoryBase64Key: toBase64(t, ".secrets.bitrise.yml"), - } - params, err := parseRunAndTriggerJSONParams(toJSON(t, paramsMap)) - require.NoError(t, err) - - require.Equal(t, "primary", params.WorkflowToRunID) - - require.Equal(t, "master", params.TriggerPattern) - require.Equal(t, "deploy", params.PushBranch) - require.Equal(t, "development", params.PRSourceBranch) - require.Equal(t, "release", params.PRTargetBranch) - require.Equal(t, "0.9.0", params.Tag) - - require.Equal(t, "json", params.Format) - - require.Equal(t, "bitrise.yml", params.BitriseConfigPath) - require.Equal(t, toBase64(t, "bitrise.yml"), params.BitriseConfigBase64Data) - - require.Equal(t, ".secrets.bitrise.yml", params.InventoryPath) - require.Equal(t, toBase64(t, ".secrets.bitrise.yml"), params.InventoryBase64Data) - } - - t.Log("it fails for invalid json") - { - params, err := parseRunAndTriggerJSONParams("master") - require.Error(t, err) - - require.Equal(t, "", params.WorkflowToRunID) - - require.Equal(t, "", params.TriggerPattern) - require.Equal(t, "", params.PushBranch) - require.Equal(t, "", params.PRSourceBranch) - require.Equal(t, "", params.PRTargetBranch) - - require.Equal(t, "", params.Format) - - require.Equal(t, "", params.BitriseConfigPath) - require.Equal(t, "", params.BitriseConfigBase64Data) - - require.Equal(t, "", params.InventoryPath) - require.Equal(t, "", params.InventoryBase64Data) - } -} - -func TestParseRunAndTriggerParams(t *testing.T) { - t.Log("it parses cli params") - { - workflow := "primary" - - pattern := "*" - pushBranch := "master" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "0.9.0" - format := "json" - - bitriseConfigPath := "bitrise.yml" - bitriseConfigBase64Data := toBase64(t, "bitrise.yml") - - inventoryPath := ".secrets.bitrise.yml" - inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") - - jsonParams := "" - base64JSONParams := "" - - params, err := parseRunAndTriggerParams( - workflow, - pattern, - pushBranch, prSourceBranch, prTargetBranch, tag, - format, - bitriseConfigPath, bitriseConfigBase64Data, - inventoryPath, inventoryBase64Data, - jsonParams, base64JSONParams, - ) - require.NoError(t, err) - - require.Equal(t, workflow, params.WorkflowToRunID) - - require.Equal(t, pattern, params.TriggerPattern) - require.Equal(t, pushBranch, params.PushBranch) - require.Equal(t, prSourceBranch, params.PRSourceBranch) - require.Equal(t, prTargetBranch, params.PRTargetBranch) - require.Equal(t, tag, params.Tag) - - require.Equal(t, format, params.Format) - - require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) - require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) - - require.Equal(t, inventoryPath, params.InventoryPath) - require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) - } - - t.Log("it parses json params") - { - workflow := "primary" - - pattern := "*" - pushBranch := "master" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "0.9.0" - format := "json" - - bitriseConfigPath := "bitrise.yml" - bitriseConfigBase64Data := toBase64(t, "bitrise.yml") - - inventoryPath := ".secrets.bitrise.yml" - inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") - - paramsMap := map[string]string{ - WorkflowKey: workflow, - - PatternKey: pattern, - PushBranchKey: pushBranch, - PRSourceBranchKey: prSourceBranch, - PRTargetBranchKey: prTargetBranch, - TagKey: tag, - OuputFormatKey: format, - - ConfigKey: bitriseConfigPath, - ConfigBase64Key: bitriseConfigBase64Data, - - InventoryKey: inventoryPath, - InventoryBase64Key: inventoryBase64Data, - } - - jsonParams := toJSON(t, paramsMap) - base64JSONParams := "" - - params, err := parseRunAndTriggerParams("", "", "", "", "", "", "", "", "", "", "", jsonParams, base64JSONParams) - require.NoError(t, err) - - require.Equal(t, workflow, params.WorkflowToRunID) - - require.Equal(t, pattern, params.TriggerPattern) - require.Equal(t, pushBranch, params.PushBranch) - require.Equal(t, prSourceBranch, params.PRSourceBranch) - require.Equal(t, prTargetBranch, params.PRTargetBranch) - require.Equal(t, tag, params.Tag) - - require.Equal(t, format, params.Format) - - require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) - require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) - - require.Equal(t, inventoryPath, params.InventoryPath) - require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) - } - - t.Log("it parses json params decoded in base64") - { - workflow := "primary" - - pattern := "*" - pushBranch := "master" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "0.9.0" - format := "json" - - bitriseConfigPath := "bitrise.yml" - bitriseConfigBase64Data := toBase64(t, "bitrise.yml") - - inventoryPath := ".secrets.bitrise.yml" - inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") - - paramsMap := map[string]string{ - WorkflowKey: workflow, - - PatternKey: pattern, - PushBranchKey: pushBranch, - PRSourceBranchKey: prSourceBranch, - PRTargetBranchKey: prTargetBranch, - TagKey: tag, - OuputFormatKey: format, - - ConfigKey: bitriseConfigPath, - ConfigBase64Key: bitriseConfigBase64Data, - - InventoryKey: inventoryPath, - InventoryBase64Key: inventoryBase64Data, - } - - jsonParams := "" - base64JSONParams := toBase64(t, toJSON(t, paramsMap)) - - params, err := parseRunAndTriggerParams("", "", "", "", "", "", "", "", "", "", "", jsonParams, base64JSONParams) - require.NoError(t, err) - - require.Equal(t, workflow, params.WorkflowToRunID) - - require.Equal(t, pattern, params.TriggerPattern) - require.Equal(t, pushBranch, params.PushBranch) - require.Equal(t, prSourceBranch, params.PRSourceBranch) - require.Equal(t, prTargetBranch, params.PRTargetBranch) - require.Equal(t, tag, params.Tag) - - require.Equal(t, format, params.Format) - - require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) - require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) - - require.Equal(t, inventoryPath, params.InventoryPath) - require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) - } - - t.Log("json params has priority over json params encoded in base 64") - { - workflow := "primary" - - pattern := "*" - pushBranch := "master" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "0.9.0" - format := "json" - - bitriseConfigPath := "bitrise.yml" - bitriseConfigBase64Data := toBase64(t, "bitrise.yml") - - inventoryPath := ".secrets.bitrise.yml" - inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") - - paramsMap := map[string]string{ - WorkflowKey: workflow, - - PatternKey: pattern, - PushBranchKey: pushBranch, - PRSourceBranchKey: prSourceBranch, - PRTargetBranchKey: prTargetBranch, - TagKey: tag, - OuputFormatKey: format, - - ConfigKey: bitriseConfigPath, - ConfigBase64Key: bitriseConfigBase64Data, - - InventoryKey: inventoryPath, - InventoryBase64Key: inventoryBase64Data, - } - - jsonParams := `{"workflow":"test"}` - base64JSONParams := toBase64(t, toJSON(t, paramsMap)) - - params, err := parseRunAndTriggerParams("", "", "", "", "", "", "", "", "", "", "", jsonParams, base64JSONParams) - require.NoError(t, err) - - require.Equal(t, "test", params.WorkflowToRunID) - - require.Equal(t, "", params.TriggerPattern) - require.Equal(t, "", params.PushBranch) - require.Equal(t, "", params.PRSourceBranch) - require.Equal(t, "", params.PRTargetBranch) - require.Equal(t, "", params.Tag) - - require.Equal(t, "", params.Format) - - require.Equal(t, "", params.BitriseConfigPath) - require.Equal(t, "", params.BitriseConfigBase64Data) - - require.Equal(t, "", params.InventoryPath) - require.Equal(t, "", params.InventoryBase64Data) - } - - t.Log("cli params can override json params") - { - workflow := "primary" - - pattern := "*" - pushBranch := "master" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "0.9.0" - format := "json" - - bitriseConfigPath := "bitrise.yml" - bitriseConfigBase64Data := toBase64(t, "bitrise.yml") - - inventoryPath := ".secrets.bitrise.yml" - inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") - - jsonParams := `{"workflow":"test","pattern":"feature/","config":"test.bitrise.yml","inventory":".test.secrets.bitrise.yml"}` - base64JSONParams := "" - - params, err := parseRunAndTriggerParams( - workflow, - pattern, - pushBranch, prSourceBranch, prTargetBranch, tag, - format, - bitriseConfigPath, bitriseConfigBase64Data, - inventoryPath, inventoryBase64Data, - jsonParams, base64JSONParams, - ) - require.NoError(t, err) - - require.Equal(t, workflow, params.WorkflowToRunID) - - require.Equal(t, pattern, params.TriggerPattern) - require.Equal(t, pushBranch, params.PushBranch) - require.Equal(t, prSourceBranch, params.PRSourceBranch) - require.Equal(t, prTargetBranch, params.PRTargetBranch) - require.Equal(t, tag, params.Tag) - - require.Equal(t, format, params.Format) - - require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) - require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) - - require.Equal(t, inventoryPath, params.InventoryPath) - require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) - } -} - -func TestParseRunParams(t *testing.T) { - t.Log("it parses cli params") - { - workflow := "primary" - - bitriseConfigPath := "bitrise.yml" - bitriseConfigBase64Data := toBase64(t, "bitrise.yml") - - inventoryPath := ".secrets.bitrise.yml" - inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") - - jsonParams := "" - base64JSONParams := "" - - params, err := parseRunParams( - workflow, - bitriseConfigPath, bitriseConfigBase64Data, - inventoryPath, inventoryBase64Data, - jsonParams, base64JSONParams, - ) - require.NoError(t, err) - - require.Equal(t, workflow, params.WorkflowToRunID) - - require.Equal(t, "", params.TriggerPattern) - require.Equal(t, "", params.PushBranch) - require.Equal(t, "", params.PRSourceBranch) - require.Equal(t, "", params.PRTargetBranch) - require.Equal(t, "", params.Tag) - - require.Equal(t, "", params.Format) - - require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) - require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) - - require.Equal(t, inventoryPath, params.InventoryPath) - require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) - } -} - -func TestParseTriggerParams(t *testing.T) { - t.Log("it parses cli params") - { - pattern := "*" - pushBranch := "master" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "0.9.0" - - bitriseConfigPath := "bitrise.yml" - bitriseConfigBase64Data := toBase64(t, "bitrise.yml") - - inventoryPath := ".secrets.bitrise.yml" - inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") - - jsonParams := "" - base64JSONParams := "" - - params, err := parseTriggerParams( - pattern, - pushBranch, prSourceBranch, prTargetBranch, tag, - bitriseConfigPath, bitriseConfigBase64Data, - inventoryPath, inventoryBase64Data, - jsonParams, base64JSONParams, - ) - require.NoError(t, err) - - require.Equal(t, "", params.WorkflowToRunID) - - require.Equal(t, pattern, params.TriggerPattern) - require.Equal(t, pushBranch, params.PushBranch) - require.Equal(t, prSourceBranch, params.PRSourceBranch) - require.Equal(t, prTargetBranch, params.PRTargetBranch) - require.Equal(t, tag, params.Tag) - - require.Equal(t, "", params.Format) - - require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) - require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) - - require.Equal(t, inventoryPath, params.InventoryPath) - require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) - } -} - -func TestParseTriggerCheckParams(t *testing.T) { - t.Log("it parses cli params") - { - pattern := "*" - pushBranch := "master" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "0.9.0" - format := "json" - - bitriseConfigPath := "bitrise.yml" - bitriseConfigBase64Data := toBase64(t, "bitrise.yml") - - inventoryPath := ".secrets.bitrise.yml" - inventoryBase64Data := toBase64(t, ".secrets.bitrise.yml") - - jsonParams := "" - base64JSONParams := "" - - params, err := parseTriggerCheckParams( - pattern, - pushBranch, prSourceBranch, prTargetBranch, tag, - format, - bitriseConfigPath, bitriseConfigBase64Data, - inventoryPath, inventoryBase64Data, - jsonParams, base64JSONParams, - ) - require.NoError(t, err) - - require.Equal(t, "", params.WorkflowToRunID) - - require.Equal(t, pattern, params.TriggerPattern) - require.Equal(t, pushBranch, params.PushBranch) - require.Equal(t, prSourceBranch, params.PRSourceBranch) - require.Equal(t, prTargetBranch, params.PRTargetBranch) - require.Equal(t, tag, params.Tag) - - require.Equal(t, format, params.Format) - - require.Equal(t, bitriseConfigPath, params.BitriseConfigPath) - require.Equal(t, bitriseConfigBase64Data, params.BitriseConfigBase64Data) - - require.Equal(t, inventoryPath, params.InventoryPath) - require.Equal(t, inventoryBase64Data, params.InventoryBase64Data) - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/run_util.go b/vendor/github.com/bitrise-io/bitrise/cli/run_util.go deleted file mode 100644 index 37cc8bd3..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/run_util.go +++ /dev/null @@ -1,981 +0,0 @@ -package cli - -import ( - "encoding/base64" - "errors" - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - "time" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/bitrise/bitrise" - "github.com/bitrise-io/bitrise/configs" - "github.com/bitrise-io/bitrise/models" - "github.com/bitrise-io/bitrise/plugins" - "github.com/bitrise-io/bitrise/toolkits" - "github.com/bitrise-io/bitrise/tools" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/colorstring" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/command/git" - "github.com/bitrise-io/go-utils/errorutil" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/go-utils/pointers" - "github.com/bitrise-io/go-utils/retry" - "github.com/bitrise-io/go-utils/versions" - stepmanModels "github.com/bitrise-io/stepman/models" -) - -func isPRMode(prGlobalFlagPtr *bool, inventoryEnvironments []envmanModels.EnvironmentItemModel) (bool, error) { - if prGlobalFlagPtr != nil { - return *prGlobalFlagPtr, nil - } - - prIDEnv := os.Getenv(configs.PullRequestIDEnvKey) - prModeEnv := os.Getenv(configs.PRModeEnvKey) - - if prIDEnv != "" || prModeEnv == "true" { - return true, nil - } - - for _, env := range inventoryEnvironments { - key, value, err := env.GetKeyValuePair() - if err != nil { - return false, err - } - - if key == configs.PullRequestIDEnvKey && value != "" { - return true, nil - } - if key == configs.PRModeEnvKey && value == "true" { - return true, nil - } - } - - return false, nil -} - -func registerPrMode(isPRMode bool) error { - configs.IsPullRequestMode = isPRMode - - if isPRMode { - log.Info(colorstring.Yellow("bitrise runs in PR mode")) - return os.Setenv(configs.PRModeEnvKey, "true") - } - return os.Setenv(configs.PRModeEnvKey, "false") -} - -func isCIMode(ciGlobalFlagPtr *bool, inventoryEnvironments []envmanModels.EnvironmentItemModel) (bool, error) { - if ciGlobalFlagPtr != nil { - return *ciGlobalFlagPtr, nil - } - - ciModeEnv := os.Getenv(configs.CIModeEnvKey) - - if ciModeEnv == "true" { - return true, nil - } - - for _, env := range inventoryEnvironments { - key, value, err := env.GetKeyValuePair() - if err != nil { - return false, err - } - - if key == configs.CIModeEnvKey && value == "true" { - return true, nil - } - } - - return false, nil -} - -func registerCIMode(isCIMode bool) error { - configs.IsCIMode = isCIMode - - if isCIMode { - log.Info(colorstring.Yellow("bitrise runs in CI mode")) - return os.Setenv(configs.CIModeEnvKey, "true") - } - return os.Setenv(configs.CIModeEnvKey, "false") -} - -// GetBitriseConfigFromBase64Data ... -func GetBitriseConfigFromBase64Data(configBase64Str string) (models.BitriseDataModel, []string, error) { - configBase64Bytes, err := base64.StdEncoding.DecodeString(configBase64Str) - if err != nil { - return models.BitriseDataModel{}, []string{}, fmt.Errorf("Failed to decode base 64 string, error: %s", err) - } - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes(configBase64Bytes) - if err != nil { - return models.BitriseDataModel{}, warnings, fmt.Errorf("Failed to parse bitrise config, error: %s", err) - } - - return config, warnings, nil -} - -// GetBitriseConfigFilePath ... -func GetBitriseConfigFilePath(bitriseConfigPath string) (string, error) { - if bitriseConfigPath == "" { - bitriseConfigPath = filepath.Join(configs.CurrentDir, DefaultBitriseConfigFileName) - - if exist, err := pathutil.IsPathExists(bitriseConfigPath); err != nil { - return "", err - } else if !exist { - return "", fmt.Errorf("bitrise.yml path not defined and not found on it's default path: %s", bitriseConfigPath) - } - } - - return bitriseConfigPath, nil -} - -// CreateBitriseConfigFromCLIParams ... -func CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath string) (models.BitriseDataModel, []string, error) { - bitriseConfig := models.BitriseDataModel{} - warnings := []string{} - - if bitriseConfigBase64Data != "" { - config, warns, err := GetBitriseConfigFromBase64Data(bitriseConfigBase64Data) - warnings = warns - if err != nil { - return models.BitriseDataModel{}, warnings, fmt.Errorf("Failed to get config (bitrise.yml) from base 64 data, err: %s", err) - } - bitriseConfig = config - } else { - bitriseConfigPath, err := GetBitriseConfigFilePath(bitriseConfigPath) - if err != nil { - return models.BitriseDataModel{}, []string{}, fmt.Errorf("Failed to get config (bitrise.yml) path: %s", err) - } - if bitriseConfigPath == "" { - return models.BitriseDataModel{}, []string{}, errors.New("Failed to get config (bitrise.yml) path: empty bitriseConfigPath") - } - - config, warns, err := bitrise.ReadBitriseConfig(bitriseConfigPath) - warnings = warns - if err != nil { - return models.BitriseDataModel{}, warnings, fmt.Errorf("Config (path:%s) is not valid: %s", bitriseConfigPath, err) - } - bitriseConfig = config - } - - isConfigVersionOK, err := versions.IsVersionGreaterOrEqual(models.Version, bitriseConfig.FormatVersion) - if err != nil { - log.Warn("bitrise CLI model version: ", models.Version) - log.Warn("bitrise.yml Format Version: ", bitriseConfig.FormatVersion) - return models.BitriseDataModel{}, warnings, fmt.Errorf("Failed to compare bitrise CLI models's version with the bitrise.yml FormatVersion: %s", err) - } - if !isConfigVersionOK { - log.Warnf("The bitrise.yml has a higher Format Version (%s) than the bitrise CLI model's version (%s).", bitriseConfig.FormatVersion, models.Version) - return models.BitriseDataModel{}, warnings, errors.New("This bitrise.yml was created with and for a newer version of bitrise CLI, please upgrade your bitrise CLI to use this bitrise.yml") - } - - return bitriseConfig, warnings, nil -} - -// GetInventoryFromBase64Data ... -func GetInventoryFromBase64Data(inventoryBase64Str string) ([]envmanModels.EnvironmentItemModel, error) { - inventoryBase64Bytes, err := base64.StdEncoding.DecodeString(inventoryBase64Str) - if err != nil { - return []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Failed to decode base 64 string, error: %s", err) - } - - inventory, err := bitrise.InventoryModelFromYAMLBytes(inventoryBase64Bytes) - if err != nil { - return []envmanModels.EnvironmentItemModel{}, err - } - - return inventory.Envs, nil -} - -// GetInventoryFilePath ... -func GetInventoryFilePath(inventoryPath string) (string, error) { - if inventoryPath == "" { - log.Debugln("[BITRISE_CLI] - Inventory path not defined, searching for " + DefaultSecretsFileName + " in current folder...") - inventoryPath = filepath.Join(configs.CurrentDir, DefaultSecretsFileName) - - if exist, err := pathutil.IsPathExists(inventoryPath); err != nil { - return "", err - } else if !exist { - inventoryPath = "" - } - } - - return inventoryPath, nil -} - -// CreateInventoryFromCLIParams ... -func CreateInventoryFromCLIParams(inventoryBase64Data, inventoryPath string) ([]envmanModels.EnvironmentItemModel, error) { - inventoryEnvironments := []envmanModels.EnvironmentItemModel{} - - if inventoryBase64Data != "" { - inventory, err := GetInventoryFromBase64Data(inventoryBase64Data) - if err != nil { - return []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Failed to get inventory from base 64 data, err: %s", err) - } - inventoryEnvironments = inventory - } else { - inventoryPath, err := GetInventoryFilePath(inventoryPath) - if err != nil { - return []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Failed to get inventory path: %s", err) - } - - if inventoryPath != "" { - bytes, err := fileutil.ReadBytesFromFile(inventoryPath) - if err != nil { - return []envmanModels.EnvironmentItemModel{}, err - } - - if len(bytes) == 0 { - return []envmanModels.EnvironmentItemModel{}, errors.New("empty config") - } - - inventory, err := bitrise.CollectEnvironmentsFromFile(inventoryPath) - if err != nil { - return []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Invalid invetory format: %s", err) - } - inventoryEnvironments = inventory - } - } - - return inventoryEnvironments, nil -} - -func getCurrentBitriseSourceDir(envlist []envmanModels.EnvironmentItemModel) (string, error) { - bitriseSourceDir := os.Getenv(configs.BitriseSourceDirEnvKey) - for i := len(envlist) - 1; i >= 0; i-- { - env := envlist[i] - - key, value, err := env.GetKeyValuePair() - if err != nil { - return bitriseSourceDir, err - } - - if key == configs.BitriseSourceDirEnvKey && value != "" { - return value, nil - } - } - return bitriseSourceDir, nil -} - -func checkAndInstallStepDependencies(step stepmanModels.StepModel) error { - if len(step.Dependencies) > 0 { - log.Warnf("step.dependencies is deprecated... Use step.deps instead.") - } - - if step.Deps != nil && (len(step.Deps.Brew) > 0 || len(step.Deps.AptGet) > 0 || len(step.Deps.CheckOnly) > 0) { - // - // New dependency handling - for _, checkOnlyDep := range step.Deps.CheckOnly { - if err := bitrise.DependencyTryCheckTool(checkOnlyDep.Name); err != nil { - return err - } - log.Infof(" * "+colorstring.Green("[OK]")+" Step dependency (%s) installed, available.", checkOnlyDep.Name) - } - - switch runtime.GOOS { - case "darwin": - for _, brewDep := range step.Deps.Brew { - if err := bitrise.InstallWithBrewIfNeeded(brewDep, configs.IsCIMode); err != nil { - log.Infof("Failed to install (%s) with brew", brewDep.Name) - return err - } - log.Infof(" * "+colorstring.Green("[OK]")+" Step dependency (%s) installed, available.", brewDep.GetBinaryName()) - } - case "linux": - for _, aptGetDep := range step.Deps.AptGet { - log.Infof("Start installing (%s) with apt-get", aptGetDep.Name) - if err := bitrise.InstallWithAptGetIfNeeded(aptGetDep, configs.IsCIMode); err != nil { - log.Infof("Failed to install (%s) with apt-get", aptGetDep.Name) - return err - } - log.Infof(" * "+colorstring.Green("[OK]")+" Step dependency (%s) installed, available.", aptGetDep.GetBinaryName()) - } - default: - return errors.New("Unsupported os") - } - } else if len(step.Dependencies) > 0 { - log.Info("Deprecated dependencies found") - // - // Deprecated dependency handling - for _, dep := range step.Dependencies { - isSkippedBecauseOfPlatform := false - switch dep.Manager { - case depManagerBrew: - if runtime.GOOS == "darwin" { - err := bitrise.InstallWithBrewIfNeeded(stepmanModels.BrewDepModel{Name: dep.Name}, configs.IsCIMode) - if err != nil { - return err - } - } else { - isSkippedBecauseOfPlatform = true - } - break - case depManagerTryCheck: - err := bitrise.DependencyTryCheckTool(dep.Name) - if err != nil { - return err - } - break - default: - return errors.New("Not supported dependency (" + dep.Manager + ") (" + dep.Name + ")") - } - - if isSkippedBecauseOfPlatform { - log.Debugf(" * Dependency (%s) skipped, manager (%s) not supported on this platform (%s)", dep.Name, dep.Manager, runtime.GOOS) - } else { - log.Infof(" * "+colorstring.Green("[OK]")+" Step dependency (%s) installed, available.", dep.Name) - } - } - } - - return nil -} - -func executeStep(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath, bitriseSourceDir string) (int, error) { - toolkitForStep := toolkits.ToolkitForStep(step) - toolkitName := toolkitForStep.ToolkitName() - - if err := toolkitForStep.PrepareForStepRun(step, sIDData, stepAbsDirPath); err != nil { - return 1, fmt.Errorf("Failed to prepare the step for execution through the required toolkit (%s), error: %s", - toolkitName, err) - } - - cmd, err := toolkitForStep.StepRunCommandArguments(step, sIDData, stepAbsDirPath) - if err != nil { - return 1, fmt.Errorf("Toolkit (%s) rejected the step, error: %s", - toolkitName, err) - } - - return tools.EnvmanRun(configs.InputEnvstorePath, bitriseSourceDir, cmd) -} - -func runStep(step stepmanModels.StepModel, stepIDData models.StepIDData, stepDir string, environments []envmanModels.EnvironmentItemModel, buildRunResults models.BuildRunResultsModel) (int, []envmanModels.EnvironmentItemModel, error) { - log.Debugf("[BITRISE_CLI] - Try running step: %s (%s)", stepIDData.IDorURI, stepIDData.Version) - - // Check & Install Step Dependencies - // [!] Make sure this happens BEFORE the Toolkit Bootstrap, - // so that if a Toolkit requires/allows the use of additional dependencies - // required for the step (e.g. a brew installed OpenSSH) it can be done - // with a Toolkit+Deps - if err := retry.Times(2).Try(func(attempt uint) error { - if attempt > 0 { - fmt.Println() - log.Warn("Installing Step dependency failed, retrying ...") - } - - return checkAndInstallStepDependencies(step) - }); err != nil { - return 1, []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Failed to install Step dependency, error: %s", err) - } - - // Collect step inputs - if err := tools.EnvmanInitAtPath(configs.InputEnvstorePath); err != nil { - return 1, []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Failed to init envman for the Step, error: %s", err) - } - - if err := bitrise.ExportEnvironmentsList(environments); err != nil { - return 1, []envmanModels.EnvironmentItemModel{}, fmt.Errorf("Failed to export environment list for the Step, error: %s", err) - } - - evaluatedInputs := []envmanModels.EnvironmentItemModel{} - for _, input := range step.Inputs { - key, value, err := input.GetKeyValuePair() - if err != nil { - return 1, []envmanModels.EnvironmentItemModel{}, err - } - - options, err := input.GetOptions() - if err != nil { - return 1, []envmanModels.EnvironmentItemModel{}, err - } - - if options.IsTemplate != nil && *options.IsTemplate { - outStr, err := tools.EnvmanJSONPrint(configs.InputEnvstorePath) - if err != nil { - return 1, []envmanModels.EnvironmentItemModel{}, fmt.Errorf("EnvmanJSONPrint failed, err: %s", err) - } - - envList, err := envmanModels.NewEnvJSONList(outStr) - if err != nil { - return 1, []envmanModels.EnvironmentItemModel{}, fmt.Errorf("CreateFromJSON failed, err: %s", err) - } - - evaluatedValue, err := bitrise.EvaluateTemplateToString(value, configs.IsCIMode, configs.IsPullRequestMode, buildRunResults, envList) - if err != nil { - return 1, []envmanModels.EnvironmentItemModel{}, err - } - - input[key] = evaluatedValue - } - - evaluatedInputs = append(evaluatedInputs, input) - } - environments = append(environments, evaluatedInputs...) - - if err := tools.EnvmanInitAtPath(configs.InputEnvstorePath); err != nil { - return 1, []envmanModels.EnvironmentItemModel{}, err - } - - if err := bitrise.ExportEnvironmentsList(environments); err != nil { - return 1, []envmanModels.EnvironmentItemModel{}, err - } - - // Run step - bitriseSourceDir, err := getCurrentBitriseSourceDir(environments) - if err != nil { - return 1, []envmanModels.EnvironmentItemModel{}, err - } - if bitriseSourceDir == "" { - bitriseSourceDir = configs.CurrentDir - } - - if exit, err := executeStep(step, stepIDData, stepDir, bitriseSourceDir); err != nil { - stepOutputs, envErr := bitrise.CollectEnvironmentsFromFile(configs.OutputEnvstorePath) - if envErr != nil { - return 1, []envmanModels.EnvironmentItemModel{}, envErr - } - - updatedStepOutputs, updateErr := bitrise.ApplyOutputAliases(stepOutputs, step.Outputs) - if updateErr != nil { - return 1, []envmanModels.EnvironmentItemModel{}, updateErr - } - - return exit, updatedStepOutputs, err - } - - stepOutputs, err := bitrise.CollectEnvironmentsFromFile(configs.OutputEnvstorePath) - if err != nil { - return 1, []envmanModels.EnvironmentItemModel{}, err - } - - updatedStepOutputs, updateErr := bitrise.ApplyOutputAliases(stepOutputs, step.Outputs) - if updateErr != nil { - return 1, []envmanModels.EnvironmentItemModel{}, updateErr - } - - log.Debugf("[BITRISE_CLI] - Step executed: %s (%s)", stepIDData.IDorURI, stepIDData.Version) - - return 0, updatedStepOutputs, nil -} - -func activateAndRunSteps(workflow models.WorkflowModel, defaultStepLibSource string, buildRunResults models.BuildRunResultsModel, environments *[]envmanModels.EnvironmentItemModel, isLastWorkflow bool) models.BuildRunResultsModel { - log.Debugln("[BITRISE_CLI] - Activating and running steps") - - // ------------------------------------------ - // In function global variables - These are global for easy use in local register step run result methods. - var stepStartTime time.Time - - // ------------------------------------------ - // In function method - Registration methods, for register step run results. - registerStepRunResults := func(step stepmanModels.StepModel, stepInfoPtr stepmanModels.StepInfoModel, - stepIdxPtr int, runIf string, resultCode, exitCode int, err error, isLastStep, printStepHeader bool) { - - if printStepHeader { - bitrise.PrintRunningStepHeader(stepInfoPtr, step, stepIdxPtr) - } - - stepInfoCopy := stepmanModels.StepInfoModel{ - Library: stepInfoPtr.Library, - ID: stepInfoPtr.ID, - Version: stepInfoPtr.Version, - LatestVersion: stepInfoPtr.LatestVersion, - GroupInfo: stepInfoPtr.GroupInfo, - Step: stepInfoPtr.Step, - DefinitionPth: stepInfoPtr.DefinitionPth, - } - - errStr := "" - if err != nil { - errStr = err.Error() - } - - stepResults := models.StepRunResultsModel{ - StepInfo: stepInfoCopy, - Status: resultCode, - Idx: buildRunResults.ResultsCount(), - RunTime: time.Now().Sub(stepStartTime), - ErrorStr: errStr, - ExitCode: exitCode, - } - - isExitStatusError := true - if err != nil { - isExitStatusError = errorutil.IsExitStatusError(err) - } - - switch resultCode { - case models.StepRunStatusCodeSuccess: - buildRunResults.SuccessSteps = append(buildRunResults.SuccessSteps, stepResults) - break - case models.StepRunStatusCodeFailed: - if !isExitStatusError { - log.Errorf("Step (%s) failed, error: %s", pointers.StringWithDefault(stepInfoCopy.Step.Title, "missing title"), err) - } - - buildRunResults.FailedSteps = append(buildRunResults.FailedSteps, stepResults) - break - case models.StepRunStatusCodeFailedSkippable: - if !isExitStatusError { - log.Warnf("Step (%s) failed, but was marked as skippable, error: %s", pointers.StringWithDefault(stepInfoCopy.Step.Title, "missing title"), err) - } else { - log.Warnf("Step (%s) failed, but was marked as skippable", pointers.StringWithDefault(stepInfoCopy.Step.Title, "missing title")) - } - - buildRunResults.FailedSkippableSteps = append(buildRunResults.FailedSkippableSteps, stepResults) - break - case models.StepRunStatusCodeSkipped: - log.Warnf("A previous step failed, and this step (%s) was not marked as IsAlwaysRun, skipped", pointers.StringWithDefault(stepInfoCopy.Step.Title, "missing title")) - - buildRunResults.SkippedSteps = append(buildRunResults.SkippedSteps, stepResults) - break - case models.StepRunStatusCodeSkippedWithRunIf: - log.Warn("The step's (" + pointers.StringWithDefault(stepInfoCopy.Step.Title, "missing title") + ") Run-If expression evaluated to false - skipping") - if runIf != "" { - log.Info("The Run-If expression was: ", colorstring.Blue(runIf)) - } - - buildRunResults.SkippedSteps = append(buildRunResults.SkippedSteps, stepResults) - break - default: - log.Error("Unkown result code") - return - } - - bitrise.PrintRunningStepFooter(stepResults, isLastStep) - } - - // ------------------------------------------ - // Main - Preparing & running the steps - for idx, stepListItm := range workflow.Steps { - // Per step variables - stepStartTime = time.Now() - isLastStep := isLastWorkflow && (idx == len(workflow.Steps)-1) - stepInfoPtr := stepmanModels.StepInfoModel{} - stepIdxPtr := idx - - // Per step cleanup - if err := bitrise.SetBuildFailedEnv(buildRunResults.IsBuildFailed()); err != nil { - log.Error("Failed to set Build Status envs") - } - - if err := bitrise.CleanupStepWorkDir(); err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - - // - // Preparing the step - if err := tools.EnvmanInitAtPath(configs.InputEnvstorePath); err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - - if err := bitrise.ExportEnvironmentsList(*environments); err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - - // Get step id & version data - compositeStepIDStr, workflowStep, err := models.GetStepIDStepDataPair(stepListItm) - if err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - stepInfoPtr.ID = compositeStepIDStr - if workflowStep.Title != nil && *workflowStep.Title != "" { - stepInfoPtr.Step.Title = pointers.NewStringPtr(*workflowStep.Title) - } else { - stepInfoPtr.Step.Title = pointers.NewStringPtr(compositeStepIDStr) - } - - stepIDData, err := models.CreateStepIDDataFromString(compositeStepIDStr, defaultStepLibSource) - if err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - stepInfoPtr.ID = stepIDData.IDorURI - if stepInfoPtr.Step.Title == nil || *stepInfoPtr.Step.Title == "" { - stepInfoPtr.Step.Title = pointers.NewStringPtr(stepIDData.IDorURI) - } - stepInfoPtr.Version = stepIDData.Version - stepInfoPtr.Library = stepIDData.SteplibSource - - // - // Activating the step - stepDir := configs.BitriseWorkStepsDirPath - stepYMLPth := filepath.Join(configs.BitriseWorkDirPath, "current_step.yml") - - if stepIDData.SteplibSource == "path" { - log.Debugf("[BITRISE_CLI] - Local step found: (path:%s)", stepIDData.IDorURI) - stepAbsLocalPth, err := pathutil.AbsPath(stepIDData.IDorURI) - if err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - - log.Debugln("stepAbsLocalPth:", stepAbsLocalPth, "|stepDir:", stepDir) - - if err := command.CopyDir(stepAbsLocalPth, stepDir, true); err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - - if err := command.CopyFile(filepath.Join(stepAbsLocalPth, "step.yml"), stepYMLPth); err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - } else if stepIDData.SteplibSource == "git" { - log.Debugf("[BITRISE_CLI] - Remote step, with direct git uri: (uri:%s) (tag-or-branch:%s)", stepIDData.IDorURI, stepIDData.Version) - if err := git.CloneTagOrBranch(stepIDData.IDorURI, stepDir, stepIDData.Version); err != nil { - if strings.HasPrefix(stepIDData.IDorURI, "git@") { - fmt.Println(colorstring.Yellow(`Note: if the step's repository is an open source one,`)) - fmt.Println(colorstring.Yellow(`you should probably use a "https://..." git clone URL,`)) - fmt.Println(colorstring.Yellow(`instead of the "git@..." git clone URL which usually requires authentication`)) - fmt.Println(colorstring.Yellow(`even if the repository is open source!`)) - } - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - - if err := command.CopyFile(filepath.Join(stepDir, "step.yml"), stepYMLPth); err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - } else if stepIDData.SteplibSource == "_" { - log.Debugf("[BITRISE_CLI] - Steplib independent step, with direct git uri: (uri:%s) (tag-or-branch:%s)", stepIDData.IDorURI, stepIDData.Version) - - // Steplib independent steps are completly defined in workflow - stepYMLPth = "" - if err := workflowStep.FillMissingDefaults(); err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - - if err := git.CloneTagOrBranch(stepIDData.IDorURI, stepDir, stepIDData.Version); err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - } else if stepIDData.SteplibSource != "" { - log.Debugf("[BITRISE_CLI] - Steplib (%s) step (id:%s) (version:%s) found, activating step", stepIDData.SteplibSource, stepIDData.IDorURI, stepIDData.Version) - if err := tools.StepmanSetup(stepIDData.SteplibSource); err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - - isLatestVersionOfStep := (stepIDData.Version == "") - if isLatestVersionOfStep && !buildRunResults.IsStepLibUpdated(stepIDData.SteplibSource) { - log.Infof("Step uses latest version -- Updating StepLib ...") - if err := tools.StepmanUpdate(stepIDData.SteplibSource); err != nil { - log.Warnf("Step uses latest version, but failed to update StepLib, err: %s", err) - } else { - buildRunResults.StepmanUpdates[stepIDData.SteplibSource]++ - } - } - - outStr, err := tools.StepmanJSONStepLibStepInfo(stepIDData.SteplibSource, stepIDData.IDorURI, stepIDData.Version) - if err != nil { - if buildRunResults.IsStepLibUpdated(stepIDData.SteplibSource) { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, fmt.Errorf("StepmanJSONStepLibStepInfo failed, err: %s", err), isLastStep, true) - continue - } - // May StepLib should be updated - log.Infof("Step info not found in StepLib (%s) -- Updating ...", stepIDData.SteplibSource) - if err := tools.StepmanUpdate(stepIDData.SteplibSource); err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - buildRunResults.StepmanUpdates[stepIDData.SteplibSource]++ - - outStr, err = tools.StepmanJSONStepLibStepInfo(stepIDData.SteplibSource, stepIDData.IDorURI, stepIDData.Version) - if err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, fmt.Errorf("StepmanJSONStepLibStepInfo failed, err: %s", err), isLastStep, true) - continue - } - } - - stepInfo, err := stepmanModels.StepInfoModel{}.CreateFromJSON(outStr) - if err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, fmt.Errorf("CreateFromJSON failed, err: %s", err), isLastStep, true) - continue - } - - stepInfoPtr.ID = stepInfo.ID - if stepInfoPtr.Step.Title == nil || *stepInfoPtr.Step.Title == "" { - stepInfoPtr.Step.Title = pointers.NewStringPtr(stepInfo.ID) - } - stepInfoPtr.Version = stepInfo.Version - stepInfoPtr.LatestVersion = stepInfo.LatestVersion - stepInfoPtr.GroupInfo = stepInfo.GroupInfo - - if err := tools.StepmanActivate(stepIDData.SteplibSource, stepIDData.IDorURI, stepIDData.Version, stepDir, stepYMLPth); err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } else { - log.Debugf("[BITRISE_CLI] - Step activated: (ID:%s) (version:%s)", stepIDData.IDorURI, stepIDData.Version) - } - } else { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, fmt.Errorf("Invalid stepIDData: No SteplibSource or LocalPath defined (%v)", stepIDData), isLastStep, true) - continue - } - - // Fill step info with default step info, if exist - mergedStep := workflowStep - if stepYMLPth != "" { - specStep, err := bitrise.ReadSpecStep(stepYMLPth) - log.Debugf("Spec read from YML: %#v\n", specStep) - if err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - - mergedStep, err = models.MergeStepWith(specStep, workflowStep) - if err != nil { - registerStepRunResults(stepmanModels.StepModel{}, stepInfoPtr, stepIdxPtr, - "", models.StepRunStatusCodeFailed, 1, err, isLastStep, true) - continue - } - } - - if mergedStep.SupportURL != nil { - stepInfoPtr.Step.SupportURL = pointers.NewStringPtr(*mergedStep.SupportURL) - } - if mergedStep.SourceCodeURL != nil { - stepInfoPtr.Step.SourceCodeURL = pointers.NewStringPtr(*mergedStep.SourceCodeURL) - } - - // - // Run step - bitrise.PrintRunningStepHeader(stepInfoPtr, mergedStep, idx) - if mergedStep.RunIf != nil && *mergedStep.RunIf != "" { - outStr, err := tools.EnvmanJSONPrint(configs.InputEnvstorePath) - if err != nil { - registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, - *mergedStep.RunIf, models.StepRunStatusCodeFailed, 1, fmt.Errorf("EnvmanJSONPrint failed, err: %s", err), isLastStep, false) - continue - } - - envList, err := envmanModels.NewEnvJSONList(outStr) - if err != nil { - registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, - *mergedStep.RunIf, models.StepRunStatusCodeFailed, 1, fmt.Errorf("CreateFromJSON failed, err: %s", err), isLastStep, false) - continue - } - - isRun, err := bitrise.EvaluateTemplateToBool(*mergedStep.RunIf, configs.IsCIMode, configs.IsPullRequestMode, buildRunResults, envList) - if err != nil { - registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, - *mergedStep.RunIf, models.StepRunStatusCodeFailed, 1, err, isLastStep, false) - continue - } - if !isRun { - registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, - *mergedStep.RunIf, models.StepRunStatusCodeSkippedWithRunIf, 0, err, isLastStep, false) - continue - } - } - - isAlwaysRun := stepmanModels.DefaultIsAlwaysRun - if mergedStep.IsAlwaysRun != nil { - isAlwaysRun = *mergedStep.IsAlwaysRun - } else { - log.Warn("Step (%s) mergedStep.IsAlwaysRun is nil, should not!", stepIDData.IDorURI) - } - - if buildRunResults.IsBuildFailed() && !isAlwaysRun { - registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, - *mergedStep.RunIf, models.StepRunStatusCodeSkipped, 0, err, isLastStep, false) - } else { - exit, outEnvironments, err := runStep(mergedStep, stepIDData, stepDir, *environments, buildRunResults) - - if err := tools.EnvmanClear(configs.OutputEnvstorePath); err != nil { - log.Errorf("Failed to clear output envstore, error: %s", err) - } - - *environments = append(*environments, outEnvironments...) - if err != nil { - if *mergedStep.IsSkippable { - registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, - *mergedStep.RunIf, models.StepRunStatusCodeFailedSkippable, exit, err, isLastStep, false) - } else { - registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, - *mergedStep.RunIf, models.StepRunStatusCodeFailed, exit, err, isLastStep, false) - } - } else { - registerStepRunResults(mergedStep, stepInfoPtr, stepIdxPtr, - *mergedStep.RunIf, models.StepRunStatusCodeSuccess, 0, nil, isLastStep, false) - } - } - } - - return buildRunResults -} - -func runWorkflow(workflow models.WorkflowModel, steplibSource string, buildRunResults models.BuildRunResultsModel, environments *[]envmanModels.EnvironmentItemModel, isLastWorkflow bool) models.BuildRunResultsModel { - bitrise.PrintRunningWorkflow(workflow.Title) - - *environments = append(*environments, workflow.Environments...) - return activateAndRunSteps(workflow, steplibSource, buildRunResults, environments, isLastWorkflow) -} - -func activateAndRunWorkflow(workflowID string, workflow models.WorkflowModel, bitriseConfig models.BitriseDataModel, buildRunResults models.BuildRunResultsModel, environments *[]envmanModels.EnvironmentItemModel, lastWorkflowID string) (models.BuildRunResultsModel, error) { - var err error - // Run these workflows before running the target workflow - for _, beforeWorkflowID := range workflow.BeforeRun { - beforeWorkflow, exist := bitriseConfig.Workflows[beforeWorkflowID] - if !exist { - return buildRunResults, fmt.Errorf("Specified Workflow (%s) does not exist", beforeWorkflowID) - } - if beforeWorkflow.Title == "" { - beforeWorkflow.Title = beforeWorkflowID - } - buildRunResults, err = activateAndRunWorkflow(beforeWorkflowID, beforeWorkflow, bitriseConfig, buildRunResults, environments, lastWorkflowID) - if err != nil { - return buildRunResults, err - } - } - - // Run the target workflow - isLastWorkflow := (workflowID == lastWorkflowID) - buildRunResults = runWorkflow(workflow, bitriseConfig.DefaultStepLibSource, buildRunResults, environments, isLastWorkflow) - - // Run these workflows after running the target workflow - for _, afterWorkflowID := range workflow.AfterRun { - afterWorkflow, exist := bitriseConfig.Workflows[afterWorkflowID] - if !exist { - return buildRunResults, fmt.Errorf("Specified Workflow (%s) does not exist", afterWorkflowID) - } - if afterWorkflow.Title == "" { - afterWorkflow.Title = afterWorkflowID - } - buildRunResults, err = activateAndRunWorkflow(afterWorkflowID, afterWorkflow, bitriseConfig, buildRunResults, environments, lastWorkflowID) - if err != nil { - return buildRunResults, err - } - } - - return buildRunResults, nil -} - -func lastWorkflowIDInConfig(workflowToRunID string, bitriseConfig models.BitriseDataModel) (string, error) { - workflowToRun, exist := bitriseConfig.Workflows[workflowToRunID] - if !exist { - return "", errors.New("No worfklow exist with ID: " + workflowToRunID) - } - - if len(workflowToRun.AfterRun) > 0 { - lastAfterID := workflowToRun.AfterRun[len(workflowToRun.AfterRun)-1] - wfID, err := lastWorkflowIDInConfig(lastAfterID, bitriseConfig) - if err != nil { - return "", err - } - workflowToRunID = wfID - } - return workflowToRunID, nil -} - -// RunWorkflowWithConfiguration ... -func runWorkflowWithConfiguration( - startTime time.Time, - workflowToRunID string, - bitriseConfig models.BitriseDataModel, - secretEnvironments []envmanModels.EnvironmentItemModel) (models.BuildRunResultsModel, error) { - - workflowToRun, exist := bitriseConfig.Workflows[workflowToRunID] - if !exist { - return models.BuildRunResultsModel{}, fmt.Errorf("Specified Workflow (%s) does not exist", workflowToRunID) - } - - if workflowToRun.Title == "" { - workflowToRun.Title = workflowToRunID - } - - // Envman setup - if err := os.Setenv(configs.EnvstorePathEnvKey, configs.OutputEnvstorePath); err != nil { - return models.BuildRunResultsModel{}, fmt.Errorf("Failed to add env, err: %s", err) - } - - if err := os.Setenv(configs.FormattedOutputPathEnvKey, configs.FormattedOutputPath); err != nil { - return models.BuildRunResultsModel{}, fmt.Errorf("Failed to add env, err: %s", err) - } - - if err := tools.EnvmanInit(); err != nil { - return models.BuildRunResultsModel{}, errors.New("Failed to run envman init") - } - - // App level environment - environments := append(secretEnvironments, bitriseConfig.App.Environments...) - - if err := os.Setenv("BITRISE_TRIGGERED_WORKFLOW_ID", workflowToRunID); err != nil { - return models.BuildRunResultsModel{}, fmt.Errorf("Failed to set BITRISE_TRIGGERED_WORKFLOW_ID env: %s", err) - } - if err := os.Setenv("BITRISE_TRIGGERED_WORKFLOW_TITLE", workflowToRun.Title); err != nil { - return models.BuildRunResultsModel{}, fmt.Errorf("Failed to set BITRISE_TRIGGERED_WORKFLOW_TITLE env: %s", err) - } - - environments = append(environments, workflowToRun.Environments...) - - lastWorkflowID, err := lastWorkflowIDInConfig(workflowToRunID, bitriseConfig) - if err != nil { - return models.BuildRunResultsModel{}, fmt.Errorf("Failed to get last workflow id: %s", err) - } - - // Bootstrap Toolkits - for _, aToolkit := range toolkits.AllSupportedToolkits() { - toolkitName := aToolkit.ToolkitName() - if !aToolkit.IsToolAvailableInPATH() { - // don't bootstrap if any preinstalled version is available, - // the toolkit's `PrepareForStepRun` can bootstrap for itself later if required - // or if the system installed version is not sufficient - if err := aToolkit.Bootstrap(); err != nil { - return models.BuildRunResultsModel{}, fmt.Errorf("Failed to bootstrap the required toolkit for the step (%s), error: %s", - toolkitName, err) - } - } - } - - // - buildRunResults := models.BuildRunResultsModel{ - StartTime: startTime, - StepmanUpdates: map[string]int{}, - } - - buildRunResults, err = activateAndRunWorkflow(workflowToRunID, workflowToRun, bitriseConfig, buildRunResults, &environments, lastWorkflowID) - if err != nil { - return buildRunResults, errors.New("[BITRISE_CLI] - Failed to activate and run workflow " + workflowToRunID) - } - - // Build finished - bitrise.PrintSummary(buildRunResults) - - // Trigger WorkflowRunDidFinish - if err := plugins.TriggerEvent(plugins.DidFinishRun, buildRunResults); err != nil { - log.Warnf("Failed to trigger WorkflowRunDidFinish, error: %s", err) - } - - return buildRunResults, nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/run_util_test.go b/vendor/github.com/bitrise-io/bitrise/cli/run_util_test.go deleted file mode 100644 index 925a1222..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/run_util_test.go +++ /dev/null @@ -1,463 +0,0 @@ -package cli - -import ( - "encoding/base64" - "os" - "testing" - "time" - - "github.com/bitrise-io/bitrise/bitrise" - "github.com/bitrise-io/bitrise/configs" - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/pointers" - "github.com/stretchr/testify/require" -) - -func TestIsPRMode(t *testing.T) { - prModeEnv := os.Getenv(configs.PRModeEnvKey) - prIDEnv := os.Getenv(configs.PullRequestIDEnvKey) - - // cleanup Envs after these tests - defer func() { - require.NoError(t, os.Setenv(configs.PRModeEnvKey, prModeEnv)) - require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, prIDEnv)) - }() - - t.Log("Should be false for: prGlobalFlag: nil, prModeEnv: '', prIDEnv: ''") - { - require.NoError(t, os.Setenv(configs.PRModeEnvKey, "")) - require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) - - pr, err := isPRMode(nil, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, false, pr) - } - - t.Log("Should be false for: prGlobalFlag: nil, prModeEnv: '', prIDEnv: '', secrets: false") - { - inventoryStr := ` -envs: -- PR: "false" -- PULL_REQUEST_ID: "" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.PRModeEnvKey, "")) - require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) - - pr, err := isPRMode(nil, inventory.Envs) - require.NoError(t, err) - require.Equal(t, false, pr) - } - - t.Log("Should be false for: prGlobalFlag: nil, prModeEnv: 'false', prIDEnv: '', secrets: ''") - { - inventoryStr := ` -envs: -- PR: "" -- PULL_REQUEST_ID: "" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.PRModeEnvKey, "false")) - require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) - - pr, err := isPRMode(nil, inventory.Envs) - require.NoError(t, err) - require.Equal(t, false, pr) - } - - t.Log("Should be false for: prGlobalFlag: false, prModeEnv: 'true', prIDEnv: 'ID', secrets: 'true'") - { - inventoryStr := ` -envs: -- PR: "true" -- PULL_REQUEST_ID: "ID" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.PRModeEnvKey, "true")) - require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "ID")) - - pr, err := isPRMode(pointers.NewBoolPtr(false), inventory.Envs) - require.NoError(t, err) - require.Equal(t, false, pr) - } - - t.Log("Should be true for: prGlobalFlag: true, prModeEnv: '', prIDEnv: ''") - { - require.NoError(t, os.Setenv(configs.PRModeEnvKey, "")) - require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) - - pr, err := isPRMode(pointers.NewBoolPtr(true), []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, true, pr) - } - - t.Log("Should be true for: prGlobalFlag: true, prModeEnv: '', prIDEnv: '', secrets: false") - { - inventoryStr := ` -envs: -- PR: "false" -- PULL_REQUEST_ID: "" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.PRModeEnvKey, "")) - require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) - - pr, err := isPRMode(pointers.NewBoolPtr(true), inventory.Envs) - require.NoError(t, err) - require.Equal(t, true, pr) - } - - t.Log("Should be true for: prGlobalFlag: nil, prModeEnv: 'true', prIDEnv: '', secrets: false") - { - inventoryStr := ` -envs: -- PR: "false" -- PULL_REQUEST_ID: "" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.PRModeEnvKey, "true")) - require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) - - pr, err := isPRMode(nil, inventory.Envs) - require.NoError(t, err) - require.Equal(t, true, pr) - } - - t.Log("Should be true for: prGlobalFlag: nil, prModeEnv: 'false', prIDEnv: 'some', secrets: false") - { - inventoryStr := ` -envs: -- PR: "false" -- PULL_REQUEST_ID: "" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.PRModeEnvKey, "false")) - require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "some")) - - pr, err := isPRMode(nil, inventory.Envs) - require.NoError(t, err) - require.Equal(t, true, pr) - } - - t.Log("Should be true for: prGlobalFlag: nil, prModeEnv: '', prIDEnv: '', secrets: true") - { - inventoryStr := ` -envs: -- PR: "true" -- PULL_REQUEST_ID: "" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.PRModeEnvKey, "")) - require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) - - pr, err := isPRMode(nil, inventory.Envs) - require.NoError(t, err) - require.Equal(t, true, pr) - } - - t.Log("Should be true for: prGlobalFlag: nil, prModeEnv: 'false', prIDEnv: '', secrets: true") - { - inventoryStr := ` -envs: -- PR: "" -- PULL_REQUEST_ID: "some" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.PRModeEnvKey, "false")) - require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) - - pr, err := isPRMode(nil, inventory.Envs) - require.NoError(t, err) - require.Equal(t, true, pr) - } - - t.Log("Should be true for: prGlobalFlag: true, prModeEnv: 'false', prIDEnv: '', secrets: false") - { - inventoryStr := ` -envs: -- PR: "false" -- PULL_REQUEST_ID: "" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.PRModeEnvKey, "false")) - require.NoError(t, os.Setenv(configs.PullRequestIDEnvKey, "")) - - pr, err := isPRMode(pointers.NewBoolPtr(true), inventory.Envs) - require.NoError(t, err) - require.Equal(t, true, pr) - } -} - -func TestIsCIMode(t *testing.T) { - ciModeEnv := os.Getenv(configs.CIModeEnvKey) - - defer func() { - require.NoError(t, os.Setenv(configs.CIModeEnvKey, ciModeEnv)) - }() - - t.Log("Should be false for: ciGlobalFlag: nil, ciModeEnv: 'false'") - { - require.NoError(t, os.Setenv(configs.CIModeEnvKey, "false")) - - ci, err := isCIMode(nil, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, false, ci) - } - - t.Log("Should be false for: ciGlobalFlag: false, ciModeEnv: 'false' secrets: false") - { - inventoryStr := ` -envs: -- CI: "false" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.CIModeEnvKey, "false")) - - ci, err := isCIMode(pointers.NewBoolPtr(false), inventory.Envs) - require.NoError(t, err) - require.Equal(t, false, ci) - } - - t.Log("Should be true for: ciGlobalFlag: true, ciModeEnv: 'false'") - { - require.NoError(t, os.Setenv(configs.CIModeEnvKey, "")) - - ci, err := isCIMode(pointers.NewBoolPtr(true), []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, true, ci) - } - - t.Log("Should be true for: ciGlobalFlag: true, ciModeEnv: '' secrets: false") - { - inventoryStr := ` -envs: -- CI: "false" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.CIModeEnvKey, "")) - - ci, err := isCIMode(pointers.NewBoolPtr(true), inventory.Envs) - require.NoError(t, err) - require.Equal(t, true, ci) - } - - t.Log("Should be true for: ciGlobalFlag: nil, ciModeEnv: 'true' secrets: false") - { - inventoryStr := ` -envs: -- CI: "" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.CIModeEnvKey, "true")) - - ci, err := isCIMode(nil, inventory.Envs) - require.NoError(t, err) - require.Equal(t, true, ci) - } - - t.Log("Should be true for: ciGlobalFlag: nil, ciModeEnv: '' secrets: true") - { - inventoryStr := ` -envs: -- CI: "true" -` - inventory, err := bitrise.InventoryModelFromYAMLBytes([]byte(inventoryStr)) - require.NoError(t, err) - - require.NoError(t, os.Setenv(configs.CIModeEnvKey, "")) - - ci, err := isCIMode(nil, inventory.Envs) - require.NoError(t, err) - require.Equal(t, true, ci) - } -} - -func TestExpandEnvs(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - test: - envs: - - ENV0: "Hello" - - ENV1: "$ENV0 world" - steps: - - script: - inputs: - - content: | - #!/bin/bash - envman add --key ENV2 --value "$ENV1 !" - - script: - inputs: - - content: | - #!/bin/bash - echo "ENV2: $ENV2" - if [ "$ENV2" != "Hello world !" ] ; then - echo "Actual ($ENV2), excpected (Hello world !)" - exit 1 - fi -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "test", config, []envmanModels.EnvironmentItemModel{}) - require.NoError(t, err) - require.Equal(t, 2, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) -} - -func TestEvaluateInputs(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - test: - envs: - - TEST_KEY: "test value" - steps: - - script: - title: "Template test" - inputs: - - content: | - #!/bin/bash - set -v - {{if .IsCI}} - exit 1 - {{else}} - exit 0 - {{end}} - opts: - is_template: true - - script: - title: "Template test" - inputs: - - content: | - #!/bin/bash - set -v - {{if enveq "TEST_KEY" "test value"}} - exit 0 - {{else}} - exit 1 - {{end}} - opts: - is_template: true -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - buildRunResults, err := runWorkflowWithConfiguration(time.Now(), "test", config, []envmanModels.EnvironmentItemModel{}) - require.Equal(t, nil, err) - require.Equal(t, 0, len(buildRunResults.SkippedSteps)) - require.Equal(t, 2, len(buildRunResults.SuccessSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSteps)) - require.Equal(t, 0, len(buildRunResults.FailedSkippableSteps)) -} - -func TestGetBitriseConfigFromBase64Data(t *testing.T) { - configStr := ` -format_version: 0.9.10 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - target: - title: target -` - configBytes := []byte(configStr) - configBase64Str := base64.StdEncoding.EncodeToString(configBytes) - - config, warnings, err := GetBitriseConfigFromBase64Data(configBase64Str) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - require.Equal(t, "0.9.10", config.FormatVersion) - require.Equal(t, "https://github.com/bitrise-io/bitrise-steplib.git", config.DefaultStepLibSource) - - workflow, found := config.Workflows["target"] - require.Equal(t, true, found) - require.Equal(t, "target", workflow.Title) -} - -func TestGetInventoryFromBase64Data(t *testing.T) { - inventoryStr := ` -envs: - - MY_HOME: $HOME - opts: - is_expand: true -` - inventoryBytes := []byte(inventoryStr) - inventoryBase64Str := base64.StdEncoding.EncodeToString(inventoryBytes) - - inventory, err := GetInventoryFromBase64Data(inventoryBase64Str) - require.NoError(t, err) - - env := inventory[0] - - key, value, err := env.GetKeyValuePair() - require.NoError(t, err) - require.Equal(t, "MY_HOME", key) - require.Equal(t, "$HOME", value) - - opts, err := env.GetOptions() - require.NoError(t, err) - require.Equal(t, true, *opts.IsExpand) -} - -func TestInvalidStepID(t *testing.T) { - configStr := ` -format_version: 1.3.0 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - target: - title: Invalid step id - steps: - - invalid-step: - - invalid-step: - - invalid-step: -` - - require.NoError(t, configs.InitPaths()) - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - results, err := runWorkflowWithConfiguration(time.Now(), "target", config, []envmanModels.EnvironmentItemModel{}) - require.Equal(t, 1, len(results.StepmanUpdates)) -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/setup.go b/vendor/github.com/bitrise-io/bitrise/cli/setup.go deleted file mode 100644 index 92c0e0ac..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/setup.go +++ /dev/null @@ -1,68 +0,0 @@ -package cli - -import ( - "fmt" - "os" - - "github.com/bitrise-io/bitrise/bitrise" - "github.com/bitrise-io/go-utils/log" - "github.com/urfave/cli" -) - -var setupCommand = cli.Command{ - Name: "setup", - Usage: "Setup the current host. Install every required tool to run Workflows.", - Action: func(c *cli.Context) error { - if err := setup(c); err != nil { - log.Errorf("Setup failed, error: %s", err) - os.Exit(1) - } - return nil - }, - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "full", - Usage: "Also calls 'brew doctor'.", - }, - cli.BoolFlag{ - Name: "clean", - Usage: "Removes bitrise's workdir before setup.", - }, - }, -} - -// PrintBitriseHeaderASCIIArt ... -func PrintBitriseHeaderASCIIArt(appVersion string) { - // generated here: http://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=Bitrise - fmt.Println(` - ██████╗ ██╗████████╗██████╗ ██╗███████╗███████╗ - ██╔══██╗██║╚══██╔══╝██╔══██╗██║██╔════╝██╔════╝ - ██████╔╝██║ ██║ ██████╔╝██║███████╗█████╗ - ██╔══██╗██║ ██║ ██╔══██╗██║╚════██║██╔══╝ - ██████╔╝██║ ██║ ██║ ██║██║███████║███████╗ - ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝╚══════╝`) - fmt.Println() - log.Donef(" version: %s", appVersion) - fmt.Println() -} - -func setup(c *cli.Context) error { - PrintBitriseHeaderASCIIArt(c.App.Version) - - fullMode := c.Bool("full") - cleanMode := c.Bool("clean") - - if err := bitrise.RunSetup(c.App.Version, fullMode, cleanMode); err != nil { - return err - } - - fmt.Println() - log.Infof("To start using bitrise:") - log.Printf("* cd into your project's directory (if you're not there already)") - log.Printf("* call: bitrise init") - log.Printf("* follow the guide") - fmt.Println() - log.Donef("That's all :)") - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/share.go b/vendor/github.com/bitrise-io/bitrise/cli/share.go deleted file mode 100644 index c6db1428..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/share.go +++ /dev/null @@ -1,16 +0,0 @@ -package cli - -import ( - "log" - - "github.com/bitrise-io/bitrise/tools" - "github.com/urfave/cli" -) - -func share(c *cli.Context) error { - if err := tools.StepmanShare(); err != nil { - log.Fatalf("Bitrise share failed, error: %s", err) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/share_audit.go b/vendor/github.com/bitrise-io/bitrise/cli/share_audit.go deleted file mode 100644 index bb9f9065..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/share_audit.go +++ /dev/null @@ -1,16 +0,0 @@ -package cli - -import ( - "log" - - "github.com/bitrise-io/bitrise/tools" - "github.com/urfave/cli" -) - -func shareAudit(c *cli.Context) error { - if err := tools.StepmanShareAudit(); err != nil { - log.Fatalf("Bitrise share audit failed, error: %s", err) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/share_create.go b/vendor/github.com/bitrise-io/bitrise/cli/share_create.go deleted file mode 100644 index e101a5e8..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/share_create.go +++ /dev/null @@ -1,28 +0,0 @@ -package cli - -import ( - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/bitrise/tools" - "github.com/urfave/cli" -) - -func create(c *cli.Context) error { - // Input validation - tag := c.String(TagKey) - if tag == "" { - log.Fatal("No step tag specified") - } - - gitURI := c.String(GitKey) - if gitURI == "" { - log.Fatal("No step url specified") - } - - stepID := c.String(StepIDKey) - - if err := tools.StepmanShareCreate(tag, gitURI, stepID); err != nil { - log.Fatalf("Bitrise share create failed, error: %s", err) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/share_finish.go b/vendor/github.com/bitrise-io/bitrise/cli/share_finish.go deleted file mode 100644 index c61a19c6..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/share_finish.go +++ /dev/null @@ -1,16 +0,0 @@ -package cli - -import ( - "log" - - "github.com/bitrise-io/bitrise/tools" - "github.com/urfave/cli" -) - -func finish(c *cli.Context) error { - if err := tools.StepmanShareFinish(); err != nil { - log.Fatalf("Bitrise share finish failed, error: %s", err) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/share_start.go b/vendor/github.com/bitrise-io/bitrise/cli/share_start.go deleted file mode 100644 index 77cefa8a..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/share_start.go +++ /dev/null @@ -1,21 +0,0 @@ -package cli - -import ( - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/bitrise/tools" - "github.com/urfave/cli" -) - -func start(c *cli.Context) error { - // Input validation - collectionURI := c.String(CollectionKey) - if collectionURI == "" { - log.Fatal("No step collection specified") - } - - if err := tools.StepmanShareStart(collectionURI); err != nil { - log.Fatalf("Bitrise share start failed, error: %s", err) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/step_info.go b/vendor/github.com/bitrise-io/bitrise/cli/step_info.go deleted file mode 100644 index c951d0af..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/step_info.go +++ /dev/null @@ -1,119 +0,0 @@ -package cli - -import ( - "fmt" - - "github.com/bitrise-io/bitrise/output" - "github.com/bitrise-io/bitrise/tools" - "github.com/urfave/cli" -) - -func printStepLibStep(collectionURI, id, version, format string) error { - switch format { - case output.FormatRaw: - out, err := tools.StepmanRawStepLibStepInfo(collectionURI, id, version) - if out != "" { - fmt.Println("Step info:") - fmt.Printf("%s", out) - } - return err - case output.FormatJSON: - outStr, err := tools.StepmanJSONStepLibStepInfo(collectionURI, id, version) - if err != nil { - return fmt.Errorf("StepmanJSONStepLibStepInfo failed, err: %s", err) - } - fmt.Println(outStr) - break - default: - return fmt.Errorf("Invalid format: %s", format) - } - return nil -} - -func printLocalStepInfo(pth, format string) error { - switch format { - case output.FormatRaw: - out, err := tools.StepmanRawLocalStepInfo(pth) - if out != "" { - fmt.Println("Step info:") - fmt.Printf("%s", out) - } - return err - case output.FormatJSON: - outStr, err := tools.StepmanJSONLocalStepInfo(pth) - if err != nil { - return fmt.Errorf("StepmanJSONLocalStepInfo failed, err: %s", err) - } - fmt.Println(outStr) - break - default: - return fmt.Errorf("Invalid format: %s", format) - } - return nil -} - -func stepInfo(c *cli.Context) error { - warnings := []string{} - - // Expand cli.Context - bitriseConfigBase64Data := c.String(ConfigBase64Key) - - bitriseConfigPath := c.String(ConfigKey) - deprecatedBitriseConfigPath := c.String(PathKey) - if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { - warnings = append(warnings, "'path' key is deprecated, use 'config' instead!") - bitriseConfigPath = deprecatedBitriseConfigPath - } - - format := c.String(OuputFormatKey) - - YMLPath := c.String(StepYMLKey) - collectionURI := c.String(CollectionKey) - - id := "" - if len(c.Args()) < 1 { - registerFatal("No step specified!", warnings, format) - } else { - id = c.Args()[0] - } - - version := c.String(VersionKey) - // - - if format == "" { - format = output.FormatRaw - } else if !(format == output.FormatRaw || format == output.FormatJSON) { - registerFatal(fmt.Sprintf("Invalid format: %s", format), warnings, output.FormatJSON) - } - - if YMLPath != "" { - // - // Local step info - if err := printLocalStepInfo(YMLPath, format); err != nil { - registerFatal(fmt.Sprintf("Failed to print step info (yml path: %s), err: %s", YMLPath, err), warnings, format) - } - } else { - - // - // Steplib step info - if collectionURI == "" { - bitriseConfig, warns, err := CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath) - warnings = append(warnings, warns...) - if err != nil { - registerFatal(fmt.Sprintf("No collection defined and failed to read bitrise config, err: %s", err), warnings, format) - } - - if bitriseConfig.DefaultStepLibSource == "" { - registerFatal("No collection defined and no default collection found in bitrise config", warnings, format) - } - - collectionURI = bitriseConfig.DefaultStepLibSource - } - - if err := printStepLibStep(collectionURI, id, version, format); err != nil { - registerFatal(fmt.Sprintf("Failed to print step info, err: %s", err), warnings, format) - } - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/step_list.go b/vendor/github.com/bitrise-io/bitrise/cli/step_list.go deleted file mode 100644 index 5d5a50a9..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/step_list.go +++ /dev/null @@ -1,73 +0,0 @@ -package cli - -import ( - "fmt" - - "github.com/bitrise-io/bitrise/output" - "github.com/bitrise-io/bitrise/tools" - "github.com/urfave/cli" -) - -func stepList(c *cli.Context) error { - warnings := []string{} - - // Expand cli.Context - bitriseConfigBase64Data := c.String(ConfigBase64Key) - - bitriseConfigPath := c.String(ConfigKey) - deprecatedBitriseConfigPath := c.String(PathKey) - if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { - warnings = append(warnings, "'path' key is deprecated, use 'config' instead!") - bitriseConfigPath = deprecatedBitriseConfigPath - } - - format := c.String(OuputFormatKey) - - collectionURI := c.String(CollectionKey) - // - - // Input validation - if format == "" { - format = output.FormatRaw - } else if !(format == output.FormatRaw || format == output.FormatJSON) { - registerFatal(fmt.Sprintf("Invalid format: %s", format), warnings, output.FormatJSON) - } - - if collectionURI == "" { - bitriseConfig, warns, err := CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath) - warnings = append(warnings, warns...) - if err != nil { - registerFatal(fmt.Sprintf("No collection defined and failed to read bitrise config, err: %s", err), warnings, format) - } - - if bitriseConfig.DefaultStepLibSource == "" { - registerFatal("No collection defined and no default collection found in bitrise config", warnings, format) - } - - collectionURI = bitriseConfig.DefaultStepLibSource - } - - switch format { - case output.FormatRaw: - out, err := tools.StepmanRawStepList(collectionURI) - if out != "" { - fmt.Println("Step list:") - fmt.Printf("%s", out) - } - if err != nil { - registerFatal(fmt.Sprintf("Failed to print step info, err: %s", err), warnings, format) - } - break - case output.FormatJSON: - outStr, err := tools.StepmanJSONStepList(collectionURI) - if err != nil { - registerFatal(fmt.Sprintf("Failed to print step info, err: %s", err), warnings, format) - } - fmt.Println(outStr) - break - default: - registerFatal(fmt.Sprintf("Invalid format: %s", format), warnings, output.FormatJSON) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/tools.go b/vendor/github.com/bitrise-io/bitrise/cli/tools.go deleted file mode 100644 index 9bd5051f..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/tools.go +++ /dev/null @@ -1,37 +0,0 @@ -package cli - -import ( - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/go-utils/command" - "github.com/urfave/cli" -) - -var stepmanCommand = cli.Command{ - Name: "stepman", - Usage: "Runs a stepman command.", - SkipFlagParsing: true, - Action: func(c *cli.Context) error { - if err := runCommandWith("stepman", c); err != nil { - log.Fatalf("Command failed, error: %s", err) - } - return nil - }, -} - -var envmanCommand = cli.Command{ - Name: "envman", - Usage: "Runs an envman command.", - SkipFlagParsing: true, - Action: func(c *cli.Context) error { - if err := runCommandWith("envman", c); err != nil { - log.Fatalf("Command failed, error: %s", err) - } - return nil - }, -} - -func runCommandWith(toolName string, c *cli.Context) error { - args := c.Args() - cmd := command.NewWithStandardOuts(toolName, args...) - return cmd.Run() -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/trigger.go b/vendor/github.com/bitrise-io/bitrise/cli/trigger.go deleted file mode 100644 index ebaa4205..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/trigger.go +++ /dev/null @@ -1,163 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "strings" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/bitrise/models" - "github.com/bitrise-io/bitrise/version" - "github.com/bitrise-io/go-utils/pointers" - "github.com/urfave/cli" -) - -// -------------------- -// Utility -// -------------------- - -func printAvailableTriggerFilters(triggerMap []models.TriggerMapItemModel) { - log.Infoln("The following trigger filters are available:") - - for _, triggerItem := range triggerMap { - if triggerItem.Pattern != "" { - log.Infof(" * pattern: %s", triggerItem.Pattern) - log.Infof(" is_pull_request_allowed: %v", triggerItem.IsPullRequestAllowed) - log.Infof(" workflow: %s", triggerItem.WorkflowID) - } else { - if triggerItem.PushBranch != "" { - log.Infof(" * push_branch: %s", triggerItem.PushBranch) - log.Infof(" workflow: %s", triggerItem.WorkflowID) - } else if triggerItem.PullRequestSourceBranch != "" || triggerItem.PullRequestTargetBranch != "" { - log.Infof(" * pull_request_source_branch: %s", triggerItem.PullRequestSourceBranch) - log.Infof(" pull_request_target_branch: %s", triggerItem.PullRequestTargetBranch) - log.Infof(" workflow: %s", triggerItem.WorkflowID) - } else if triggerItem.Tag != "" { - log.Infof(" * tag: %s", triggerItem.Tag) - log.Infof(" workflow: %s", triggerItem.WorkflowID) - } - } - } -} - -// -------------------- -// CLI command -// -------------------- - -func trigger(c *cli.Context) error { - PrintBitriseHeaderASCIIArt(version.VERSION) - - // Expand cli.Context - var prGlobalFlagPtr *bool - if c.GlobalIsSet(PRKey) { - prGlobalFlagPtr = pointers.NewBoolPtr(c.GlobalBool(PRKey)) - } - - var ciGlobalFlagPtr *bool - if c.GlobalIsSet(CIKey) { - ciGlobalFlagPtr = pointers.NewBoolPtr(c.GlobalBool(CIKey)) - } - - triggerPattern := c.String(PatternKey) - if triggerPattern == "" && len(c.Args()) > 0 { - triggerPattern = c.Args()[0] - } - - pushBranch := c.String(PushBranchKey) - prSourceBranch := c.String(PRSourceBranchKey) - prTargetBranch := c.String(PRTargetBranchKey) - tag := c.String(TagKey) - - bitriseConfigBase64Data := c.String(ConfigBase64Key) - bitriseConfigPath := c.String(ConfigKey) - deprecatedBitriseConfigPath := c.String(PathKey) - if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { - log.Warn("'path' key is deprecated, use 'config' instead!") - bitriseConfigPath = deprecatedBitriseConfigPath - } - - inventoryBase64Data := c.String(InventoryBase64Key) - inventoryPath := c.String(InventoryKey) - - jsonParams := c.String(JSONParamsKey) - jsonParamsBase64 := c.String(JSONParamsBase64Key) - - triggerParams, err := parseTriggerParams( - triggerPattern, - pushBranch, prSourceBranch, prTargetBranch, tag, - bitriseConfigPath, bitriseConfigBase64Data, - inventoryPath, inventoryBase64Data, - jsonParams, jsonParamsBase64) - if err != nil { - return fmt.Errorf("Failed to parse trigger command params, error: %s", err) - } - - // Inventory validation - inventoryEnvironments, err := CreateInventoryFromCLIParams(triggerParams.InventoryBase64Data, triggerParams.InventoryPath) - if err != nil { - log.Fatalf("Failed to create inventory, error: %s", err) - } - - // Config validation - bitriseConfig, warnings, err := CreateBitriseConfigFromCLIParams(triggerParams.BitriseConfigBase64Data, triggerParams.BitriseConfigPath) - for _, warning := range warnings { - log.Warnf("warning: %s", warning) - } - if err != nil { - log.Fatalf("Failed to create bitrise config, error: %s", err) - } - - // Trigger filter validation - if triggerParams.TriggerPattern == "" && - triggerParams.PushBranch == "" && triggerParams.PRSourceBranch == "" && triggerParams.PRTargetBranch == "" && triggerParams.Tag == "" { - log.Error("No trigger pattern nor trigger params specified") - printAvailableTriggerFilters(bitriseConfig.TriggerMap) - os.Exit(1) - } - // - - // Main - isPRMode, err := isPRMode(prGlobalFlagPtr, inventoryEnvironments) - if err != nil { - log.Fatalf("Failed to check PR mode, error: %s", err) - } - - if err := registerPrMode(isPRMode); err != nil { - log.Fatalf("Failed to register PR mode, error: %s", err) - } - - isCIMode, err := isCIMode(ciGlobalFlagPtr, inventoryEnvironments) - if err != nil { - log.Fatalf("Failed to check CI mode, error: %s", err) - } - - if err := registerCIMode(isCIMode); err != nil { - log.Fatalf("Failed to register CI mode, error: %s", err) - } - - workflowToRunID, err := getWorkflowIDByParamsInCompatibleMode(bitriseConfig.TriggerMap, triggerParams, isPRMode) - if err != nil { - log.Errorf("Failed to get workflow id by pattern, error: %s", err) - if strings.Contains(err.Error(), "no matching workflow found with trigger params:") { - printAvailableTriggerFilters(bitriseConfig.TriggerMap) - } - os.Exit(1) - } - - if triggerParams.TriggerPattern != "" { - log.Infof("pattern (%s) triggered workflow (%s)", triggerParams.TriggerPattern, workflowToRunID) - } else { - if triggerParams.PushBranch != "" { - log.Infof("push-branch (%s) triggered workflow (%s)", triggerParams.PushBranch, workflowToRunID) - } else if triggerParams.PRSourceBranch != "" || triggerParams.PRTargetBranch != "" { - log.Infof("pr-source-branch (%s) and pr-target-branch (%s) triggered workflow (%s)", triggerParams.PRSourceBranch, triggerParams.PRTargetBranch, workflowToRunID) - } else if triggerParams.Tag != "" { - log.Infof("tag (%s) triggered workflow (%s)", triggerParams.Tag, workflowToRunID) - } - } - - runAndExit(bitriseConfig, inventoryEnvironments, workflowToRunID) - // - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/trigger_check.go b/vendor/github.com/bitrise-io/bitrise/cli/trigger_check.go deleted file mode 100644 index 692aa34e..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/trigger_check.go +++ /dev/null @@ -1,221 +0,0 @@ -package cli - -import ( - "encoding/json" - "fmt" - "os" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/bitrise/models" - "github.com/bitrise-io/bitrise/output" - "github.com/bitrise-io/go-utils/colorstring" - "github.com/bitrise-io/go-utils/pointers" - "github.com/urfave/cli" -) - -// -------------------- -// Utility -// -------------------- - -func registerFatal(errorMsg string, warnings []string, format string) { - message := ValidationItemModel{ - IsValid: (len(errorMsg) > 0), - Error: errorMsg, - Warnings: warnings, - } - - if format == output.FormatRaw { - for _, warning := range message.Warnings { - log.Warnf("warning: %s", warning) - } - log.Fatal(message.Error) - } else { - bytes, err := json.Marshal(message) - if err != nil { - log.Fatalf("Failed to parse error model, error: %s", err) - } - - fmt.Println(string(bytes)) - os.Exit(1) - } -} - -func migratePatternToParams(params RunAndTriggerParamsModel, isPullRequestMode bool) RunAndTriggerParamsModel { - if isPullRequestMode { - params.PushBranch = "" - params.PRSourceBranch = params.TriggerPattern - params.PRTargetBranch = "" - params.Tag = "" - } else { - params.PushBranch = params.TriggerPattern - params.PRSourceBranch = "" - params.PRTargetBranch = "" - params.Tag = "" - } - - params.TriggerPattern = "" - - return params -} - -func getWorkflowIDByParams(triggerMap models.TriggerMapModel, params RunAndTriggerParamsModel) (string, error) { - for _, item := range triggerMap { - match, err := item.MatchWithParams(params.PushBranch, params.PRSourceBranch, params.PRTargetBranch, params.Tag) - if err != nil { - return "", err - } - if match { - return item.WorkflowID, nil - } - } - - return "", fmt.Errorf("no matching workflow found with trigger params: push-branch: %s, pr-source-branch: %s, pr-target-branch: %s, tag: %s", params.PushBranch, params.PRSourceBranch, params.PRTargetBranch, params.Tag) -} - -// migrates deprecated params.TriggerPattern to params.PushBranch or params.PRSourceBranch based on isPullRequestMode -// and returns the triggered workflow id -func getWorkflowIDByParamsInCompatibleMode(triggerMap models.TriggerMapModel, params RunAndTriggerParamsModel, isPullRequestMode bool) (string, error) { - if params.TriggerPattern != "" { - params = migratePatternToParams(params, isPullRequestMode) - } - - return getWorkflowIDByParams(triggerMap, params) -} - -// -------------------- -// CLI command -// -------------------- - -func triggerCheck(c *cli.Context) error { - warnings := []string{} - - // - // Expand cli.Context - var prGlobalFlagPtr *bool - if c.GlobalIsSet(PRKey) { - prGlobalFlagPtr = pointers.NewBoolPtr(c.GlobalBool(PRKey)) - } - - triggerPattern := c.String(PatternKey) - if triggerPattern == "" && len(c.Args()) > 0 { - triggerPattern = c.Args()[0] - } - - pushBranch := c.String(PushBranchKey) - prSourceBranch := c.String(PRSourceBranchKey) - prTargetBranch := c.String(PRTargetBranchKey) - tag := c.String(TagKey) - - bitriseConfigBase64Data := c.String(ConfigBase64Key) - bitriseConfigPath := c.String(ConfigKey) - deprecatedBitriseConfigPath := c.String(PathKey) - if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { - warnings = append(warnings, "'path' key is deprecated, use 'config' instead!") - bitriseConfigPath = deprecatedBitriseConfigPath - } - - inventoryBase64Data := c.String(InventoryBase64Key) - inventoryPath := c.String(InventoryKey) - - jsonParams := c.String(JSONParamsKey) - jsonParamsBase64 := c.String(JSONParamsBase64Key) - - format := c.String(OuputFormatKey) - - triggerParams, err := parseTriggerCheckParams( - triggerPattern, - pushBranch, prSourceBranch, prTargetBranch, tag, - format, - bitriseConfigPath, bitriseConfigBase64Data, - inventoryPath, inventoryBase64Data, - jsonParams, jsonParamsBase64) - if err != nil { - registerFatal(fmt.Sprintf("Failed to parse trigger check params, err: %s", err), warnings, triggerParams.Format) - } - // - - // Inventory validation - inventoryEnvironments, err := CreateInventoryFromCLIParams(triggerParams.InventoryBase64Data, triggerParams.InventoryPath) - if err != nil { - registerFatal(fmt.Sprintf("Failed to create inventory, err: %s", err), warnings, triggerParams.Format) - } - - // Config validation - bitriseConfig, warns, err := CreateBitriseConfigFromCLIParams(triggerParams.BitriseConfigBase64Data, triggerParams.BitriseConfigPath) - warnings = append(warnings, warns...) - if err != nil { - registerFatal(fmt.Sprintf("Failed to create config, err: %s", err), warnings, triggerParams.Format) - } - - // Format validation - if triggerParams.Format == "" { - triggerParams.Format = output.FormatRaw - } else if !(triggerParams.Format == output.FormatRaw || triggerParams.Format == output.FormatJSON) { - registerFatal(fmt.Sprintf("Invalid format: %s", triggerParams.Format), warnings, output.FormatJSON) - } - - // Trigger filter validation - if triggerParams.TriggerPattern == "" && - triggerParams.PushBranch == "" && triggerParams.PRSourceBranch == "" && triggerParams.PRTargetBranch == "" && triggerParams.Tag == "" { - registerFatal("No trigger pattern nor trigger params specified", warnings, triggerParams.Format) - } - // - - // - // Main - isPRMode, err := isPRMode(prGlobalFlagPtr, inventoryEnvironments) - if err != nil { - registerFatal(fmt.Sprintf("Failed to check PR mode, err: %s", err), warnings, triggerParams.Format) - } - - workflowToRunID, err := getWorkflowIDByParamsInCompatibleMode(bitriseConfig.TriggerMap, triggerParams, isPRMode) - if err != nil { - registerFatal(err.Error(), warnings, triggerParams.Format) - } - - triggerModel := map[string]string{"workflow": workflowToRunID} - - if triggerParams.TriggerPattern != "" { - triggerModel["pattern"] = triggerParams.TriggerPattern - } else { - if triggerParams.PushBranch != "" { - triggerModel["push-branch"] = triggerParams.PushBranch - } else if triggerParams.PRSourceBranch != "" || triggerParams.PRTargetBranch != "" { - if triggerParams.PRSourceBranch != "" { - triggerModel["pr-source-branch"] = triggerParams.PRSourceBranch - } - if triggerParams.PRTargetBranch != "" { - triggerModel["pr-target-branch"] = triggerParams.PRTargetBranch - } - } else if triggerParams.Tag != "" { - triggerModel["tag"] = triggerParams.Tag - } - } - - switch triggerParams.Format { - case output.FormatRaw: - msg := "" - for key, value := range triggerModel { - if key == "workflow" { - msg = msg + fmt.Sprintf("-> %s", colorstring.Blue(value)) - } else { - msg = fmt.Sprintf("%s: %s ", key, value) + msg - } - } - fmt.Println(msg) - break - case output.FormatJSON: - bytes, err := json.Marshal(triggerModel) - if err != nil { - registerFatal(fmt.Sprintf("Failed to parse trigger model, err: %s", err), warnings, triggerParams.Format) - } - - fmt.Println(string(bytes)) - break - default: - registerFatal(fmt.Sprintf("Invalid format: %s", triggerParams.Format), warnings, output.FormatJSON) - } - // - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/trigger_check_test.go b/vendor/github.com/bitrise-io/bitrise/cli/trigger_check_test.go deleted file mode 100644 index d3fc0cdf..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/trigger_check_test.go +++ /dev/null @@ -1,639 +0,0 @@ -package cli - -import ( - "testing" - - "github.com/bitrise-io/bitrise/bitrise" - "github.com/stretchr/testify/require" -) - -func TestMigratePatternToParams(t *testing.T) { - t.Log("converts pattern in NON PR MODE to push-branch param") - { - isPullRequestMode := false - params := RunAndTriggerParamsModel{ - TriggerPattern: "master", - } - - convertedParams := migratePatternToParams(params, isPullRequestMode) - - require.Equal(t, "master", convertedParams.PushBranch) - require.Equal(t, "", convertedParams.PRSourceBranch) - require.Equal(t, "", convertedParams.PRTargetBranch) - require.Equal(t, "", convertedParams.TriggerPattern) - require.Equal(t, "", convertedParams.Tag) - - require.Equal(t, "", convertedParams.WorkflowToRunID) - require.Equal(t, "", convertedParams.Format) - require.Equal(t, "", convertedParams.BitriseConfigPath) - require.Equal(t, "", convertedParams.BitriseConfigBase64Data) - require.Equal(t, "", convertedParams.InventoryPath) - require.Equal(t, "", convertedParams.InventoryBase64Data) - } - - t.Log("converts pattern in PR MODE to pr-source-branch param") - { - isPullRequestMode := true - params := RunAndTriggerParamsModel{ - TriggerPattern: "master", - } - - convertedParams := migratePatternToParams(params, isPullRequestMode) - - require.Equal(t, "", convertedParams.PushBranch) - require.Equal(t, "master", convertedParams.PRSourceBranch) - require.Equal(t, "", convertedParams.PRTargetBranch) - require.Equal(t, "", convertedParams.TriggerPattern) - require.Equal(t, "", convertedParams.Tag) - - require.Equal(t, "", convertedParams.WorkflowToRunID) - require.Equal(t, "", convertedParams.Format) - require.Equal(t, "", convertedParams.BitriseConfigPath) - require.Equal(t, "", convertedParams.BitriseConfigBase64Data) - require.Equal(t, "", convertedParams.InventoryPath) - require.Equal(t, "", convertedParams.InventoryBase64Data) - } - - t.Log("only modifies PushBranch, PRSourceBranch, PRTargetBranch, TriggerPattern") - { - isPullRequestMode := true - params := RunAndTriggerParamsModel{ - PushBranch: "feature/login", - PRSourceBranch: "feature/landing", - PRTargetBranch: "develop", - Tag: "0.9.0", - TriggerPattern: "master", - - WorkflowToRunID: "primary", - Format: "json", - BitriseConfigPath: "bitrise.yml", - BitriseConfigBase64Data: "base64-bitrise.yml", - InventoryPath: "inventory.yml", - InventoryBase64Data: "base64-inventory.yml", - } - - convertedParams := migratePatternToParams(params, isPullRequestMode) - - require.Equal(t, "", convertedParams.PushBranch) - require.Equal(t, "master", convertedParams.PRSourceBranch) - require.Equal(t, "", convertedParams.PRTargetBranch) - require.Equal(t, "", convertedParams.TriggerPattern) - require.Equal(t, "", convertedParams.Tag) - - require.Equal(t, "primary", convertedParams.WorkflowToRunID) - require.Equal(t, "json", convertedParams.Format) - require.Equal(t, "bitrise.yml", convertedParams.BitriseConfigPath) - require.Equal(t, "base64-bitrise.yml", convertedParams.BitriseConfigBase64Data) - require.Equal(t, "inventory.yml", convertedParams.InventoryPath) - require.Equal(t, "base64-inventory.yml", convertedParams.InventoryBase64Data) - } -} - -func TestGetWorkflowIDByParamsInCompatibleMode_new_param_test(t *testing.T) { - t.Log("params - push_branch") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- push_branch: master - workflow: master - -workflows: - master: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{PushBranch: "master"}, false) - require.NoError(t, err) - require.Equal(t, "master", workflowID) - } - - t.Log("params - pull_request_source_branch") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- pull_request_source_branch: feature/* - workflow: test - -workflows: - test: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - params := RunAndTriggerParamsModel{ - PRSourceBranch: "feature/login", - PRTargetBranch: "develop", - } - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) - require.NoError(t, err) - require.Equal(t, "test", workflowID) - } - - t.Log("params - pull_request_target_branch") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- pull_request_target_branch: deploy_* - workflow: release - -workflows: - release: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - params := RunAndTriggerParamsModel{ - PRSourceBranch: "master", - PRTargetBranch: "deploy_1_0_0", - } - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) - require.NoError(t, err) - require.Equal(t, "release", workflowID) - } - - t.Log("params - pull_request_source_branch, pull_request_target_branch") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- pull_request_source_branch: feature/* - pull_request_target_branch: develop - workflow: test - -workflows: - test: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - params := RunAndTriggerParamsModel{ - PRSourceBranch: "feature/login", - PRTargetBranch: "develop", - } - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) - require.NoError(t, err) - require.Equal(t, "test", workflowID) - } - - t.Log("params - tag") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- tag: 1.* - workflow: deploy - -workflows: - deploy: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - params := RunAndTriggerParamsModel{ - Tag: "1.0.0", - } - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) - require.NoError(t, err) - require.Equal(t, "deploy", workflowID) - } - - t.Log("params - tag") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- tag: "*.*.*" - workflow: deploy - -workflows: - deploy: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - params := RunAndTriggerParamsModel{ - Tag: "1.0.0", - } - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) - require.NoError(t, err) - require.Equal(t, "deploy", workflowID) - } - - t.Log("params - tag") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- tag: "v*.*" - workflow: deploy - -workflows: - deploy: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - params := RunAndTriggerParamsModel{ - Tag: "v1.0", - } - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) - require.NoError(t, err) - require.Equal(t, "deploy", workflowID) - } - - t.Log("params - tag - no match") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- tag: "v*.*" - workflow: deploy - -workflows: - deploy: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - params := RunAndTriggerParamsModel{ - Tag: "1.0", - } - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) - require.EqualError(t, err, "no matching workflow found with trigger params: push-branch: , pr-source-branch: , pr-target-branch: , tag: 1.0") - require.Equal(t, "", workflowID) - } - - t.Log("params - tag") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- tag: "v*.*.*" - workflow: deploy - -workflows: - deploy: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - params := RunAndTriggerParamsModel{ - Tag: "v1.0.0", - } - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) - require.NoError(t, err) - require.Equal(t, "deploy", workflowID) - } - - t.Log("params - tag - no match") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- tag: "*.*.*" - workflow: deploy - -workflows: - deploy: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - params := RunAndTriggerParamsModel{ - Tag: "1.0", - } - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) - require.EqualError(t, err, "no matching workflow found with trigger params: push-branch: , pr-source-branch: , pr-target-branch: , tag: 1.0") - require.Equal(t, "", workflowID) - } - - t.Log("params - tag - no match") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- tag: "v*.*.*" - workflow: deploy - -workflows: - deploy: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - params := RunAndTriggerParamsModel{ - Tag: "v1.0", - } - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) - require.EqualError(t, err, "no matching workflow found with trigger params: push-branch: , pr-source-branch: , pr-target-branch: , tag: v1.0") - require.Equal(t, "", workflowID) - } - - t.Log("complex trigger map") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- pattern: feature/* - workflow: test -- push_branch: feature/* - workflow: test -- pull_request_source_branch: feature/* - pull_request_target_branch: develop - workflow: test -- tag: 1.* - workflow: deploy - -workflows: - test: - deploy: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - params := RunAndTriggerParamsModel{ - PRSourceBranch: "feature/login", - PRTargetBranch: "develop", - } - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) - require.NoError(t, err) - require.Equal(t, "test", workflowID) - } - - t.Log("complex trigger map") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- pattern: feature/* - workflow: test -- push_branch: feature/* - workflow: test -- pull_request_source_branch: feature/* - pull_request_target_branch: develop - workflow: test -- tag: 1.* - workflow: deploy - -workflows: - test: - deploy: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - params := RunAndTriggerParamsModel{ - Tag: "1.0.0", - } - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, params, false) - require.NoError(t, err) - require.Equal(t, "deploy", workflowID) - } -} - -func TestGetWorkflowIDByParamsInCompatibleMode_migration_test(t *testing.T) { - t.Log("deprecated code push trigger item") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- pattern: master - is_pull_request_allowed: false - workflow: master - -workflows: - master: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - t.Log("it works with deprecated pattern") - { - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "master"}, false) - require.NoError(t, err) - require.Equal(t, "master", workflowID) - } - - t.Log("it works with new params") - { - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{PushBranch: "master"}, false) - require.NoError(t, err) - require.Equal(t, "master", workflowID) - } - - t.Log("it works with new params") - { - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{PushBranch: "master"}, true) - require.NoError(t, err) - require.Equal(t, "master", workflowID) - } - } - - t.Log("deprecated pr trigger item") - { - configStr := `format_version: 1.4.0 - -trigger_map: -- pattern: master - is_pull_request_allowed: true - workflow: master - -workflows: - master: -` - - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - t.Log("it works with deprecated pattern") - { - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "master"}, false) - require.NoError(t, err) - require.Equal(t, "master", workflowID) - } - - t.Log("it works with new params") - { - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{PushBranch: "master"}, false) - require.NoError(t, err) - require.Equal(t, "master", workflowID) - } - - t.Log("it works with new params") - { - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{PushBranch: "master"}, true) - require.NoError(t, err) - require.Equal(t, "master", workflowID) - } - } -} - -func TestGetWorkflowIDByParamsInCompatibleMode_old_tests(t *testing.T) { - configStr := `format_version: 1.4.0 - -trigger_map: -- pattern: master - is_pull_request_allowed: false - workflow: master -- pattern: feature/* - is_pull_request_allowed: true - workflow: feature -- pattern: "*" - is_pull_request_allowed: true - workflow: primary - -workflows: - test: - master: - feature: - primary: -` - config, warnings, err := bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - t.Log("Default pattern defined & Non pull request mode") - { - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "master"}, false) - require.NoError(t, err) - require.Equal(t, "master", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/a"}, false) - require.NoError(t, err) - require.Equal(t, "feature", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/"}, false) - require.NoError(t, err) - require.Equal(t, "feature", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature"}, false) - require.NoError(t, err) - require.Equal(t, "primary", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "test"}, false) - require.NoError(t, err) - require.Equal(t, "primary", workflowID) - } - - t.Log("Default pattern defined & Pull request mode") - { - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "master"}, true) - require.NoError(t, err) - require.Equal(t, "primary", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/a"}, true) - require.NoError(t, err) - require.Equal(t, "feature", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/"}, true) - require.NoError(t, err) - require.Equal(t, "feature", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature"}, true) - require.NoError(t, err) - require.Equal(t, "primary", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "test"}, true) - require.NoError(t, err) - require.Equal(t, "primary", workflowID) - } - - configStr = `format_version: 1.4.0 - -trigger_map: -- pattern: master - is_pull_request_allowed: false - workflow: master -- pattern: feature/* - is_pull_request_allowed: true - workflow: feature - -workflows: - test: - master: - feature: - primary: - ` - config, warnings, err = bitrise.ConfigModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - - t.Log("No default pattern defined & Non pull request mode") - { - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "master"}, false) - require.NoError(t, err) - require.Equal(t, "master", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/a"}, false) - require.NoError(t, err) - require.Equal(t, "feature", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/"}, false) - require.NoError(t, err) - require.Equal(t, "feature", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature"}, false) - require.NotEqual(t, nil, err) - require.Equal(t, "", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "test"}, false) - require.NotEqual(t, nil, err) - require.Equal(t, "", workflowID) - } - - t.Log("No default pattern defined & Pull request mode") - { - workflowID, err := getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "master"}, true) - require.NotEqual(t, nil, err) - require.Equal(t, "", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/a"}, true) - require.NoError(t, err) - require.Equal(t, "feature", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature/"}, true) - require.NoError(t, err) - require.Equal(t, "feature", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "feature"}, true) - require.NotEqual(t, nil, err) - require.Equal(t, "", workflowID) - - workflowID, err = getWorkflowIDByParamsInCompatibleMode(config.TriggerMap, RunAndTriggerParamsModel{TriggerPattern: "test"}, true) - require.NotEqual(t, nil, err) - require.Equal(t, "", workflowID) - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/validate.go b/vendor/github.com/bitrise-io/bitrise/cli/validate.go deleted file mode 100644 index 597f6566..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/validate.go +++ /dev/null @@ -1,239 +0,0 @@ -package cli - -import ( - "encoding/json" - "fmt" - - "os" - - "strings" - - "github.com/bitrise-io/bitrise/output" - "github.com/bitrise-io/go-utils/colorstring" - flog "github.com/bitrise-io/go-utils/log" - "github.com/urfave/cli" -) - -// ValidationItemModel ... -type ValidationItemModel struct { - IsValid bool `json:"is_valid" yaml:"is_valid"` - Error string `json:"error,omitempty" yaml:"error,omitempty"` - Warnings []string `json:"warnings,omitempty" yaml:"warnings,omitempty"` -} - -// ValidationModel ... -type ValidationModel struct { - Config *ValidationItemModel `json:"config,omitempty" yaml:"config,omitempty"` - Secrets *ValidationItemModel `json:"secrets,omitempty" yaml:"secrets,omitempty"` -} - -// ValidateResponseModel ... -type ValidateResponseModel struct { - Data *ValidationModel `json:"data,omitempty" yaml:"data,omitempty"` - Error string `json:"error,omitempty" yaml:"error,omitempty"` - Warnings []string `json:"warnings,omitempty" yaml:"warnings,omitempty"` -} - -// NewValidationResponse ... -func NewValidationResponse(validation ValidationModel, warnings ...string) ValidateResponseModel { - return ValidateResponseModel{ - Data: &validation, - Warnings: warnings, - } -} - -// NewValidationError ... -func NewValidationError(err string, warnings ...string) ValidateResponseModel { - return ValidateResponseModel{ - Error: err, - Warnings: warnings, - } -} - -// IsValid ... -func (v ValidationModel) IsValid() bool { - if v.Config == nil && v.Secrets == nil { - return false - } - - if v.Config != nil && !v.Config.IsValid { - return false - } - - if v.Secrets != nil && !v.Secrets.IsValid { - return false - } - - return true -} - -// JSON ... -func (v ValidateResponseModel) JSON() string { - bytes, err := json.Marshal(v) - if err != nil { - return fmt.Sprintf(`"Failed to marshal validation result (%#v), err: %s"`, v, err) - } - return string(bytes) -} - -func (v ValidateResponseModel) String() string { - if v.Error != "" { - msg := fmt.Sprintf("%s: %s", colorstring.Red("Error"), v.Error) - if len(v.Warnings) > 0 { - msg += "\nWarning(s):\n" - for i, warning := range v.Warnings { - msg += fmt.Sprintf("- %s", warning) - if i != len(v.Warnings)-1 { - msg += "\n" - } - } - } - return msg - } - - if v.Data != nil { - msg := v.Data.String() - if len(v.Warnings) > 0 { - msg += "\nWarning(s):\n" - for i, warning := range v.Warnings { - msg += fmt.Sprintf("- %s", warning) - if i != len(v.Warnings)-1 { - msg += "\n" - } - } - } - return msg - } - - return "" -} - -// String ... -func (v ValidationModel) String() string { - msg := "" - - if v.Config != nil { - config := *v.Config - if config.IsValid { - msg += fmt.Sprintf("Config is valid: %s", colorstring.Greenf("%v", true)) - } else { - msg += fmt.Sprintf("Config is valid: %s", colorstring.Redf("%v", false)) - msg += fmt.Sprintf("\nError: %s", colorstring.Red(config.Error)) - } - - if len(config.Warnings) > 0 { - msg += "\nWarning(s):\n" - for i, warning := range config.Warnings { - msg += fmt.Sprintf("- %s", warning) - if i != len(config.Warnings)-1 { - msg += "\n" - } - } - } - } - - if v.Secrets != nil { - if v.Config != nil { - msg += "\n" - } - - secret := *v.Secrets - if secret.IsValid { - msg += fmt.Sprintf("Secret is valid: %s", colorstring.Greenf("%v", true)) - } else { - msg += fmt.Sprintf("Secret is valid: %s", colorstring.Redf("%v", false)) - msg += fmt.Sprintf("\nError: %s", colorstring.Red(secret.Error)) - } - } - - return msg -} - -func validate(c *cli.Context) error { - warnings := []string{} - - // Expand cli.Context - bitriseConfigBase64Data := c.String(ConfigBase64Key) - bitriseConfigPath := c.String(ConfigKey) - deprecatedBitriseConfigPath := c.String(PathKey) - if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { - warnings = append(warnings, "'path' key is deprecated, use 'config' instead!") - bitriseConfigPath = deprecatedBitriseConfigPath - } - - inventoryBase64Data := c.String(InventoryBase64Key) - inventoryPath := c.String(InventoryKey) - - format := c.String(OuputFormatKey) - if format == "" { - format = output.FormatRaw - } - // - - var log flog.Logger - log = flog.NewDefaultRawLogger() - if format == output.FormatRaw { - log = flog.NewDefaultRawLogger() - } else if format == output.FormatJSON { - log = flog.NewDefaultJSONLoger() - } else { - log.Print(NewValidationError(fmt.Sprintf("Invalid format: %s", format), warnings...)) - os.Exit(1) - } - - validation := ValidationModel{} - - pth, err := GetBitriseConfigFilePath(bitriseConfigPath) - if err != nil && !strings.Contains(err.Error(), "bitrise.yml path not defined and not found on it's default path:") { - log.Print(NewValidationError(fmt.Sprintf("Failed to get config path, err: %s", err), warnings...)) - os.Exit(1) - } - - if pth != "" || (pth == "" && bitriseConfigBase64Data != "") { - // Config validation - _, warns, err := CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath) - configValidation := ValidationItemModel{ - IsValid: true, - Warnings: warns, - } - if err != nil { - configValidation.IsValid = false - configValidation.Error = err.Error() - } - - validation.Config = &configValidation - } - - pth, err = GetInventoryFilePath(inventoryPath) - if err != nil { - log.Print(NewValidationError(fmt.Sprintf("Failed to get secrets path, err: %s", err), warnings...)) - os.Exit(1) - } - - if pth != "" || inventoryBase64Data != "" { - // Inventory validation - _, err := CreateInventoryFromCLIParams(inventoryBase64Data, inventoryPath) - secretValidation := ValidationItemModel{ - IsValid: true, - } - if err != nil { - secretValidation.IsValid = false - secretValidation.Error = err.Error() - } - - validation.Secrets = &secretValidation - } - - if validation.Config == nil && validation.Secrets == nil { - log.Print(NewValidationError("No config or secrets found for validation", warnings...)) - os.Exit(1) - } - - log.Print(NewValidationResponse(validation, warnings...)) - - if !validation.IsValid() { - os.Exit(1) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/version.go b/vendor/github.com/bitrise-io/bitrise/cli/version.go deleted file mode 100644 index ccdc0e56..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/version.go +++ /dev/null @@ -1,63 +0,0 @@ -package cli - -import ( - "fmt" - "log" - - "runtime" - - "github.com/bitrise-io/bitrise/models" - "github.com/bitrise-io/bitrise/output" - "github.com/bitrise-io/bitrise/version" - "github.com/urfave/cli" -) - -// VersionOutputModel ... -type VersionOutputModel struct { - Version string `json:"version"` - FormatVersion string `json:"format_version"` - OS string `json:"os"` - GO string `json:"go"` - BuildNumber string `json:"build_number"` - Commit string `json:"commit"` -} - -func printVersionCmd(c *cli.Context) error { - fullVersion := c.Bool("full") - - if err := output.ConfigureOutputFormat(c); err != nil { - log.Fatalf("Failed to configure output format, error: %s", err) - } - - versionOutput := VersionOutputModel{ - Version: version.VERSION, - } - - if fullVersion { - versionOutput.FormatVersion = models.Version - versionOutput.BuildNumber = version.BuildNumber - versionOutput.Commit = version.Commit - versionOutput.OS = fmt.Sprintf("%s (%s)", runtime.GOOS, runtime.GOARCH) - versionOutput.GO = runtime.Version() - } - - if output.Format == output.FormatRaw { - if fullVersion { - versionStr := fmt.Sprintf(`version: %s -format version: %s -os: %s -go: %s -build number: %s -commit: %s -`, versionOutput.Version, versionOutput.FormatVersion, versionOutput.OS, versionOutput.GO, versionOutput.BuildNumber, versionOutput.Commit) - fmt.Println(versionStr) - } else { - versionStr := fmt.Sprintf("%s", versionOutput.Version) - fmt.Println(versionStr) - } - } else { - output.Print(versionOutput, output.Format) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/cli/workflow_list.go b/vendor/github.com/bitrise-io/bitrise/cli/workflow_list.go deleted file mode 100644 index 559248b6..00000000 --- a/vendor/github.com/bitrise-io/bitrise/cli/workflow_list.go +++ /dev/null @@ -1,129 +0,0 @@ -package cli - -import ( - "encoding/json" - "fmt" - "sort" - "strings" - - "github.com/bitrise-io/bitrise/output" - "github.com/bitrise-io/go-utils/colorstring" - "github.com/urfave/cli" -) - -func printWorkflList(workflowList map[string]map[string]string, format string, minimal bool) error { - printRawWorkflowMap := func(name string, workflow map[string]string) { - fmt.Printf("⚡️ %s\n", colorstring.Green(name)) - fmt.Printf(" %s: %s\n", colorstring.Yellow("Title"), workflow["title"]) - fmt.Printf(" %s: %s\n", colorstring.Yellow("Summary"), workflow["summary"]) - if !minimal { - fmt.Printf(" %s: %s\n", colorstring.Yellow("Description"), workflow["description"]) - } - fmt.Printf(" %s: bitrise run %s\n", colorstring.Yellow("Run with"), name) - fmt.Println() - } - - switch format { - case output.FormatRaw: - workflowNames := []string{} - utilityWorkflowNames := []string{} - - for wfName := range workflowList { - if strings.HasPrefix(wfName, "_") { - utilityWorkflowNames = append(utilityWorkflowNames, wfName) - } else { - workflowNames = append(workflowNames, wfName) - } - } - sort.Strings(workflowNames) - sort.Strings(utilityWorkflowNames) - - fmt.Println() - - if len(workflowNames) > 0 { - fmt.Printf("%s\n", "Workflows") - fmt.Printf("%s\n", "---------") - for _, name := range workflowNames { - workflow := workflowList[name] - printRawWorkflowMap(name, workflow) - } - fmt.Println() - } - - if len(utilityWorkflowNames) > 0 { - fmt.Printf("%s\n", "Util Workflows") - fmt.Printf("%s\n", "--------------") - for _, name := range utilityWorkflowNames { - workflow := workflowList[name] - printRawWorkflowMap(name, workflow) - } - fmt.Println() - } - - if len(workflowNames) == 0 && len(utilityWorkflowNames) == 0 { - fmt.Printf("Config doesn't contain any workflow") - } - - case output.FormatJSON: - bytes, err := json.Marshal(workflowList) - if err != nil { - return err - } - fmt.Println(string(bytes)) - default: - return fmt.Errorf("Invalid output format: %s", format) - } - return nil -} - -func workflowList(c *cli.Context) error { - warnings := []string{} - - // Expand cli.Context - bitriseConfigBase64Data := c.String(ConfigBase64Key) - - bitriseConfigPath := c.String(ConfigKey) - deprecatedBitriseConfigPath := c.String(PathKey) - if bitriseConfigPath == "" && deprecatedBitriseConfigPath != "" { - warnings = append(warnings, "'path' key is deprecated, use 'config' instead!") - bitriseConfigPath = deprecatedBitriseConfigPath - } - - format := c.String(OuputFormatKey) - - minimal := c.Bool(MinimalModeKey) - // - - // Input validation - if format == "" { - format = output.FormatRaw - } else if !(format == output.FormatRaw || format == output.FormatJSON) { - registerFatal(fmt.Sprintf("Invalid format: %s", format), warnings, output.FormatJSON) - } - - // Config validation - bitriseConfig, warns, err := CreateBitriseConfigFromCLIParams(bitriseConfigBase64Data, bitriseConfigPath) - warnings = append(warnings, warns...) - if err != nil { - registerFatal(fmt.Sprintf("Failed to create bitrise config, err: %s", err), warnings, output.FormatJSON) - } - - workflowList := map[string]map[string]string{} - if len(bitriseConfig.Workflows) > 0 { - for workflowID, workflow := range bitriseConfig.Workflows { - workflowMap := map[string]string{} - workflowMap["title"] = workflow.Title - workflowMap["summary"] = workflow.Summary - if !minimal { - workflowMap["description"] = workflow.Description - } - workflowList[workflowID] = workflowMap - } - } - - if err := printWorkflList(workflowList, format, minimal); err != nil { - registerFatal(fmt.Sprintf("Failed to print workflows, err: %s", err), warnings, output.FormatJSON) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/configs/configs.go b/vendor/github.com/bitrise-io/bitrise/configs/configs.go deleted file mode 100644 index c55b23ff..00000000 --- a/vendor/github.com/bitrise-io/bitrise/configs/configs.go +++ /dev/null @@ -1,159 +0,0 @@ -package configs - -import ( - "encoding/json" - "errors" - "fmt" - "os" - "time" - - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" -) - -// ConfigModel ... -type ConfigModel struct { - SetupVersion string `json:"setup_version"` - LastPluginUpdateCheck time.Time `json:"last_plugin_update_check"` -} - -// --------------------------- -// --- Project level vars / configs - -var ( - // IsCIMode ... - IsCIMode = false - // IsDebugMode ... - IsDebugMode = false - // IsPullRequestMode ... - IsPullRequestMode = false -) - -// --------------------------- -// --- Consts - -const ( - // CIModeEnvKey ... - CIModeEnvKey = "CI" - // PRModeEnvKey ... - PRModeEnvKey = "PR" - // PullRequestIDEnvKey ... - PullRequestIDEnvKey = "PULL_REQUEST_ID" - // DebugModeEnvKey ... - DebugModeEnvKey = "DEBUG" - // LogLevelEnvKey ... - LogLevelEnvKey = "LOGLEVEL" - - // --- Debug Options - - // DebugUseSystemTools ... - DebugUseSystemTools = "BITRISE_DEBUG_USE_SYSTEM_TOOLS" -) - -const ( - bitriseConfigFileName = "config.json" -) - -// IsDebugUseSystemTools ... -func IsDebugUseSystemTools() bool { - return os.Getenv(DebugUseSystemTools) == "true" -} - -func loadBitriseConfig() (ConfigModel, error) { - if err := EnsureBitriseConfigDirExists(); err != nil { - return ConfigModel{}, err - } - - configPth := getBitriseConfigFilePath() - if exist, err := pathutil.IsPathExists(configPth); err != nil { - return ConfigModel{}, err - } else if !exist { - return ConfigModel{}, nil - } - - bytes, err := fileutil.ReadBytesFromFile(configPth) - if err != nil { - return ConfigModel{}, err - } - - if len(bytes) == 0 { - return ConfigModel{}, errors.New("empty config file") - } - - config := ConfigModel{} - if err := json.Unmarshal(bytes, &config); err != nil { - return ConfigModel{}, fmt.Errorf("failed to marshal config (%s), error: %s", string(bytes), err) - } - - return config, nil -} - -func saveBitriseConfig(config ConfigModel) error { - bytes, err := json.Marshal(config) - if err != nil { - return err - } - - configPth := getBitriseConfigFilePath() - return fileutil.WriteBytesToFile(configPth, bytes) -} - -// DeleteBitriseConfigDir ... -func DeleteBitriseConfigDir() error { - confDirPth := GetBitriseHomeDirPath() - return os.RemoveAll(confDirPth) -} - -// EnsureBitriseConfigDirExists ... -func EnsureBitriseConfigDirExists() error { - confDirPth := GetBitriseHomeDirPath() - return pathutil.EnsureDirExist(confDirPth) -} - -// CheckIsPluginUpdateCheckRequired ... -func CheckIsPluginUpdateCheckRequired() bool { - config, err := loadBitriseConfig() - if err != nil { - return false - } - - duration := time.Now().Sub(config.LastPluginUpdateCheck) - if duration.Hours() >= 24 { - return true - } - - return false -} - -// SavePluginUpdateCheck ... -func SavePluginUpdateCheck() error { - config, err := loadBitriseConfig() - if err != nil { - return err - } - - config.LastPluginUpdateCheck = time.Now() - - return saveBitriseConfig(config) -} - -// CheckIsSetupWasDoneForVersion ... -func CheckIsSetupWasDoneForVersion(ver string) bool { - config, err := loadBitriseConfig() - if err != nil { - return false - } - return (config.SetupVersion == ver) -} - -// SaveSetupSuccessForVersion ... -func SaveSetupSuccessForVersion(ver string) error { - config, err := loadBitriseConfig() - if err != nil { - return err - } - - config.SetupVersion = ver - - return saveBitriseConfig(config) -} diff --git a/vendor/github.com/bitrise-io/bitrise/configs/configs_test.go b/vendor/github.com/bitrise-io/bitrise/configs/configs_test.go deleted file mode 100644 index 4aedae5b..00000000 --- a/vendor/github.com/bitrise-io/bitrise/configs/configs_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package configs - -import ( - "os" - "testing" - - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestSetupForVersionChecks(t *testing.T) { - fakeHomePth, err := pathutil.NormalizedOSTempDirPath("_FAKE_HOME") - require.Equal(t, nil, err) - originalHome := os.Getenv("HOME") - - defer func() { - require.Equal(t, nil, os.Setenv("HOME", originalHome)) - require.Equal(t, nil, os.RemoveAll(fakeHomePth)) - }() - - require.Equal(t, nil, os.Setenv("HOME", fakeHomePth)) - - require.Equal(t, false, CheckIsSetupWasDoneForVersion("0.9.7")) - - require.Equal(t, nil, SaveSetupSuccessForVersion("0.9.7")) - - require.Equal(t, true, CheckIsSetupWasDoneForVersion("0.9.7")) - - require.Equal(t, false, CheckIsSetupWasDoneForVersion("0.9.8")) -} diff --git a/vendor/github.com/bitrise-io/bitrise/configs/paths.go b/vendor/github.com/bitrise-io/bitrise/configs/paths.go deleted file mode 100644 index 302818a2..00000000 --- a/vendor/github.com/bitrise-io/bitrise/configs/paths.go +++ /dev/null @@ -1,203 +0,0 @@ -package configs - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/go-utils/pathutil" -) - -var ( - // InputEnvstorePath ... - InputEnvstorePath string - // OutputEnvstorePath ... - OutputEnvstorePath string - // FormattedOutputPath ... - FormattedOutputPath string - // BitriseWorkDirPath ... - BitriseWorkDirPath string - // BitriseWorkStepsDirPath ... - BitriseWorkStepsDirPath string - // CurrentDir ... - CurrentDir string -) - -const ( - // EnvstorePathEnvKey ... - EnvstorePathEnvKey = "ENVMAN_ENVSTORE_PATH" - // FormattedOutputPathEnvKey ... - FormattedOutputPathEnvKey = "BITRISE_STEP_FORMATTED_OUTPUT_FILE_PATH" - // BitriseSourceDirEnvKey ... - BitriseSourceDirEnvKey = "BITRISE_SOURCE_DIR" - // BitriseDeployDirEnvKey ... - BitriseDeployDirEnvKey = "BITRISE_DEPLOY_DIR" - // BitriseCacheDirEnvKey ... - BitriseCacheDirEnvKey = "BITRISE_CACHE_DIR" - // BitriseTmpDirEnvKey ... - BitriseTmpDirEnvKey = "BITRISE_TMP_DIR" -) - -// GetBitriseHomeDirPath ... -func GetBitriseHomeDirPath() string { - return filepath.Join(pathutil.UserHomeDir(), ".bitrise") -} - -func getBitriseConfigFilePath() string { - return filepath.Join(GetBitriseHomeDirPath(), bitriseConfigFileName) -} - -// GetBitriseToolsDirPath ... -func GetBitriseToolsDirPath() string { - return filepath.Join(GetBitriseHomeDirPath(), "tools") -} - -// GetBitriseToolkitsDirPath ... -func GetBitriseToolkitsDirPath() string { - return filepath.Join(GetBitriseHomeDirPath(), "toolkits") -} - -func initBitriseWorkPaths() error { - bitriseWorkDirPath, err := pathutil.NormalizedOSTempDirPath("bitrise") - if err != nil { - return err - } - if exist, err := pathutil.IsPathExists(bitriseWorkDirPath); err != nil { - return err - } else if !exist { - if err := os.MkdirAll(bitriseWorkDirPath, 0777); err != nil { - return err - } - } - BitriseWorkDirPath = bitriseWorkDirPath - - bitriseWorkStepsDirPath, err := filepath.Abs(filepath.Join(BitriseWorkDirPath, "step_src")) - if err != nil { - return err - } - if exist, err := pathutil.IsPathExists(bitriseWorkStepsDirPath); err != nil { - return err - } else if !exist { - if err := os.MkdirAll(bitriseWorkStepsDirPath, 0777); err != nil { - return err - } - } - BitriseWorkStepsDirPath = bitriseWorkStepsDirPath - - return nil -} - -// GeneratePATHEnvString ... -func GeneratePATHEnvString(currentPATHEnv, pathToInclude string) string { - if currentPATHEnv == "" { - return pathToInclude - } - if pathToInclude == "" { - return currentPATHEnv - } - if pathToInclude == currentPATHEnv { - return currentPATHEnv - } - - pthWithPathIncluded := currentPATHEnv - if !strings.HasSuffix(pthWithPathIncluded, pathToInclude) && - !strings.Contains(pthWithPathIncluded, pathToInclude+":") { - pthWithPathIncluded = pathToInclude + ":" + pthWithPathIncluded - } - return pthWithPathIncluded -} - -// InitPaths ... -func InitPaths() error { - if err := initBitriseWorkPaths(); err != nil { - return fmt.Errorf("Failed to init bitrise paths, error: %s", err) - } - - // --- Bitrise TOOLS - { - bitriseToolsDirPth := GetBitriseToolsDirPath() - if err := pathutil.EnsureDirExist(bitriseToolsDirPth); err != nil { - return err - } - pthWithBitriseTools := GeneratePATHEnvString(os.Getenv("PATH"), bitriseToolsDirPth) - - if IsDebugUseSystemTools() { - log.Warn("[BitriseDebug] Using system tools, instead of the ones in BITRISE_HOME") - } else { - if err := os.Setenv("PATH", pthWithBitriseTools); err != nil { - return fmt.Errorf("Failed to set PATH to include BITRISE_HOME/tools! Error: %s", err) - } - } - } - - inputEnvstorePath, err := filepath.Abs(filepath.Join(BitriseWorkDirPath, "input_envstore.yml")) - if err != nil { - return fmt.Errorf("Failed to set input envstore path, error: %s", err) - } - InputEnvstorePath = inputEnvstorePath - - outputEnvstorePath, err := filepath.Abs(filepath.Join(BitriseWorkDirPath, "output_envstore.yml")) - if err != nil { - return fmt.Errorf("Failed to set output envstore path, error: %s", err) - } - OutputEnvstorePath = outputEnvstorePath - - formoutPath, err := filepath.Abs(filepath.Join(BitriseWorkDirPath, "formatted_output.md")) - if err != nil { - return fmt.Errorf("Failed to set formatted output path, error: %s", err) - } - FormattedOutputPath = formoutPath - - currentDir, err := filepath.Abs("./") - if err != nil { - return fmt.Errorf("Failed to set current dir, error: %s", err) - } - CurrentDir = currentDir - - // BITRISE_SOURCE_DIR - if os.Getenv(BitriseSourceDirEnvKey) == "" { - if err := os.Setenv(BitriseSourceDirEnvKey, currentDir); err != nil { - return fmt.Errorf("Failed to set BITRISE_SOURCE_DIR, error: %s", err) - } - } - - // BITRISE_DEPLOY_DIR - if os.Getenv(BitriseDeployDirEnvKey) == "" { - deployDir, err := pathutil.NormalizedOSTempDirPath("deploy") - if err != nil { - return fmt.Errorf("Failed to set deploy dir, error: %s", err) - } - - if err := os.Setenv(BitriseDeployDirEnvKey, deployDir); err != nil { - return fmt.Errorf("Failed to set BITRISE_DEPLOY_DIR, error: %s", err) - } - } - - // BITRISE_CACHE_DIR - if os.Getenv(BitriseCacheDirEnvKey) == "" { - cacheDir, err := pathutil.NormalizedOSTempDirPath("cache") - if err != nil { - return fmt.Errorf("Failed to set cache dir, error: %s", err) - } - - if err := os.Setenv(BitriseCacheDirEnvKey, cacheDir); err != nil { - return fmt.Errorf("Failed to set BITRISE_CACHE_DIR, error: %s", err) - } - } - - //BITRISE_TMP_DIR - if os.Getenv(BitriseTmpDirEnvKey) == "" { - tmpDir, err := pathutil.NormalizedOSTempDirPath("tmp") - if err != nil { - return fmt.Errorf("Failed to set tmp dir, error: %s", err) - } - - if err := os.Setenv(BitriseTmpDirEnvKey, tmpDir); err != nil { - return fmt.Errorf("Failed to set BITRISE_TMP_DIR, error: %s", err) - } - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/configs/paths_test.go b/vendor/github.com/bitrise-io/bitrise/configs/paths_test.go deleted file mode 100644 index aacc1fbc..00000000 --- a/vendor/github.com/bitrise-io/bitrise/configs/paths_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package configs - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestGeneratePATHEnvString(t *testing.T) { - t.Log("Empty starting PATH") - require.Equal(t, "/MY/PATH", - GeneratePATHEnvString("", "/MY/PATH")) - - t.Log("Empty PathToInclude") - require.Equal(t, "/usr/bin:/usr/local/bin:/bin", - GeneratePATHEnvString("/usr/bin:/usr/local/bin:/bin", "")) - - t.Log("Both Empty") - require.Equal(t, "", - GeneratePATHEnvString("", "")) - - t.Log("PATH = the path to include") - require.Equal(t, "/MY/PATH", - GeneratePATHEnvString("/MY/PATH", "/MY/PATH")) - - t.Log("PathToInclude is not in the PATH yet") - require.Equal(t, "/MY/PATH:/usr/bin:/usr/local/bin:/bin", - GeneratePATHEnvString("/usr/bin:/usr/local/bin:/bin", "/MY/PATH")) - - t.Log("PathToInclude is at the START of the PATH") - require.Equal(t, "/MY/PATH:/usr/bin:/usr/local/bin:/bin", - GeneratePATHEnvString("/MY/PATH:/usr/bin:/usr/local/bin:/bin", "/MY/PATH")) - - t.Log("PathToInclude is at the END of the PATH") - require.Equal(t, "/usr/bin:/usr/local/bin:/bin:/MY/PATH", - GeneratePATHEnvString("/usr/bin:/usr/local/bin:/bin:/MY/PATH", "/MY/PATH")) - - t.Log("PathToInclude is in the MIDDLE of the PATH") - require.Equal(t, "/usr/bin:/MY/PATH:/usr/local/bin:/bin", - GeneratePATHEnvString("/usr/bin:/MY/PATH:/usr/local/bin:/bin", "/MY/PATH")) -} - -func TestInitPaths(t *testing.T) { - // - // BITRISE_SOURCE_DIR - - // Unset BITRISE_SOURCE_DIR -> after InitPaths BITRISE_SOURCE_DIR should be CurrentDir - if os.Getenv(BitriseSourceDirEnvKey) != "" { - require.Equal(t, nil, os.Unsetenv(BitriseSourceDirEnvKey)) - } - require.Equal(t, nil, InitPaths()) - require.Equal(t, CurrentDir, os.Getenv(BitriseSourceDirEnvKey)) - - // Set BITRISE_SOURCE_DIR -> after InitPaths BITRISE_SOURCE_DIR should keep content - require.Equal(t, nil, os.Setenv(BitriseSourceDirEnvKey, "$HOME/test")) - require.Equal(t, nil, InitPaths()) - require.Equal(t, "$HOME/test", os.Getenv(BitriseSourceDirEnvKey)) - - // - // BITRISE_DEPLOY_DIR - - // Unset BITRISE_DEPLOY_DIR -> after InitPaths BITRISE_DEPLOY_DIR should be temp dir - if os.Getenv(BitriseDeployDirEnvKey) != "" { - require.Equal(t, nil, os.Unsetenv(BitriseDeployDirEnvKey)) - } - require.Equal(t, nil, InitPaths()) - require.NotEqual(t, "", os.Getenv(BitriseDeployDirEnvKey)) - - // Set BITRISE_DEPLOY_DIR -> after InitPaths BITRISE_DEPLOY_DIR should keep content - require.Equal(t, nil, os.Setenv(BitriseDeployDirEnvKey, "$HOME/test")) - require.Equal(t, nil, InitPaths()) - require.Equal(t, "$HOME/test", os.Getenv(BitriseDeployDirEnvKey)) - - // - // BITRISE_CACHE_DIR - - // Unset BITRISE_CACHE_DIR -> after InitPaths BITRISE_CACHE_DIR should be temp dir - if os.Getenv(BitriseCacheDirEnvKey) != "" { - require.Equal(t, nil, os.Unsetenv(BitriseCacheDirEnvKey)) - } - require.Equal(t, nil, InitPaths()) - require.NotEqual(t, "", os.Getenv(BitriseCacheDirEnvKey)) - - // Set BITRISE_CACHE_DIR -> after InitPaths BITRISE_CACHE_DIR should keep content - require.Equal(t, nil, os.Setenv(BitriseCacheDirEnvKey, "$HOME/test")) - require.Equal(t, nil, InitPaths()) - require.Equal(t, "$HOME/test", os.Getenv(BitriseCacheDirEnvKey)) - - // - // BITRISE_TMP_DIR - - // Unset BITRISE_TMP_DIR -> after InitPaths BITRISE_TMP_DIR should be temp dir - if os.Getenv(BitriseTmpDirEnvKey) != "" { - require.Equal(t, nil, os.Unsetenv(BitriseTmpDirEnvKey)) - } - require.Equal(t, nil, InitPaths()) - require.NotEqual(t, "", os.Getenv(BitriseTmpDirEnvKey)) - - // Set BITRISE_TMP_DIR -> after InitPaths BITRISE_TMP_DIR should keep content - require.Equal(t, nil, os.Setenv(BitriseTmpDirEnvKey, "$HOME/test")) - require.Equal(t, nil, InitPaths()) - require.Equal(t, "$HOME/test", os.Getenv(BitriseTmpDirEnvKey)) -} diff --git a/vendor/github.com/bitrise-io/bitrise/deps.go b/vendor/github.com/bitrise-io/bitrise/deps.go deleted file mode 100644 index 756780b9..00000000 --- a/vendor/github.com/bitrise-io/bitrise/deps.go +++ /dev/null @@ -1,9 +0,0 @@ -// This file contains dependency imports -// which are only required for `go test`. -// Godeps recently introduced a breaking change, which now -// completely ignors every file ending with `_test.go`, -// and so the dependencies which are required only for `go test`. -// So, we'll declare those here. -package main - -import _ "github.com/stretchr/testify/require" diff --git a/vendor/github.com/bitrise-io/bitrise/docker-compose.yml b/vendor/github.com/bitrise-io/bitrise/docker-compose.yml deleted file mode 100644 index 1e0e93f8..00000000 --- a/vendor/github.com/bitrise-io/bitrise/docker-compose.yml +++ /dev/null @@ -1,4 +0,0 @@ -app: - build: . - volumes: - - .:/go/src/github.com/bitrise-io/bitrise diff --git a/vendor/github.com/bitrise-io/bitrise/gows.yml b/vendor/github.com/bitrise-io/bitrise/gows.yml deleted file mode 100644 index 0e298208..00000000 --- a/vendor/github.com/bitrise-io/bitrise/gows.yml +++ /dev/null @@ -1 +0,0 @@ -package_name: github.com/bitrise-io/bitrise diff --git a/vendor/github.com/bitrise-io/bitrise/main.go b/vendor/github.com/bitrise-io/bitrise/main.go deleted file mode 100644 index e1f9a0f9..00000000 --- a/vendor/github.com/bitrise-io/bitrise/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/bitrise-io/bitrise/cli" - -func main() { - cli.Run() -} diff --git a/vendor/github.com/bitrise-io/bitrise/models/models_methods_test.go b/vendor/github.com/bitrise-io/bitrise/models/models_methods_test.go deleted file mode 100644 index 627dc24b..00000000 --- a/vendor/github.com/bitrise-io/bitrise/models/models_methods_test.go +++ /dev/null @@ -1,1567 +0,0 @@ -package models - -import ( - "strings" - "testing" - "time" - - "gopkg.in/yaml.v2" - - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/pointers" - stepmanModels "github.com/bitrise-io/stepman/models" - "github.com/stretchr/testify/require" -) - -func TestCheckDuplicatedTriggerMapItems(t *testing.T) { - t.Log("duplicated push - error") - { - err := checkDuplicatedTriggerMapItems(TriggerMapModel{ - TriggerMapItemModel{ - PushBranch: "master", - WorkflowID: "ci", - }, - TriggerMapItemModel{ - PushBranch: "master", - WorkflowID: "release", - }, - }) - - require.EqualError(t, err, "duplicated trigger item found (push_branch: master)") - } - - t.Log("duplicated pull request - error") - { - err := checkDuplicatedTriggerMapItems(TriggerMapModel{ - TriggerMapItemModel{ - PullRequestSourceBranch: "develop", - WorkflowID: "ci", - }, - TriggerMapItemModel{ - PullRequestSourceBranch: "develop", - WorkflowID: "release", - }, - }) - - require.EqualError(t, err, "duplicated trigger item found (pull_request_source_branch: develop)") - - err = checkDuplicatedTriggerMapItems(TriggerMapModel{ - TriggerMapItemModel{ - PullRequestTargetBranch: "master", - WorkflowID: "ci", - }, - TriggerMapItemModel{ - PullRequestTargetBranch: "master", - WorkflowID: "release", - }, - }) - - require.EqualError(t, err, "duplicated trigger item found (pull_request_target_branch: master)") - - err = checkDuplicatedTriggerMapItems(TriggerMapModel{ - TriggerMapItemModel{ - PullRequestSourceBranch: "develop", - PullRequestTargetBranch: "master", - WorkflowID: "ci", - }, - TriggerMapItemModel{ - PullRequestSourceBranch: "develop", - PullRequestTargetBranch: "master", - WorkflowID: "release", - }, - }) - - require.EqualError(t, err, "duplicated trigger item found (pull_request_source_branch: develop && pull_request_target_branch: master)") - } - - t.Log("duplicated tag - error") - { - err := checkDuplicatedTriggerMapItems(TriggerMapModel{ - TriggerMapItemModel{ - Tag: "0.9.0", - WorkflowID: "ci", - }, - TriggerMapItemModel{ - Tag: "0.9.0", - WorkflowID: "release", - }, - }) - - require.EqualError(t, err, "duplicated trigger item found (tag: 0.9.0)") - } - - t.Log("complex trigger map - no error") - { - err := checkDuplicatedTriggerMapItems(TriggerMapModel{ - TriggerMapItemModel{ - PushBranch: "master", - WorkflowID: "ci", - }, - TriggerMapItemModel{ - PullRequestSourceBranch: "develop", - PullRequestTargetBranch: "master", - WorkflowID: "ci", - }, - TriggerMapItemModel{ - Tag: "0.9.0", - WorkflowID: "release", - }, - }) - - require.NoError(t, err) - } -} - -func TestTriggerMapItemModelString(t *testing.T) { - t.Log("push event") - { - item := TriggerMapItemModel{ - PushBranch: "master", - WorkflowID: "ci", - } - require.Equal(t, "push_branch: master -> workflow: ci", item.String(true)) - require.Equal(t, "push_branch: master", item.String(false)) - } - - t.Log("pull request event") - { - prSourceItem := TriggerMapItemModel{ - PullRequestSourceBranch: "develop", - WorkflowID: "ci", - } - require.Equal(t, "pull_request_source_branch: develop -> workflow: ci", prSourceItem.String(true)) - require.Equal(t, "pull_request_source_branch: develop", prSourceItem.String(false)) - - prTargetItem := TriggerMapItemModel{ - PullRequestTargetBranch: "master", - WorkflowID: "ci", - } - require.Equal(t, "pull_request_target_branch: master -> workflow: ci", prTargetItem.String(true)) - require.Equal(t, "pull_request_target_branch: master", prTargetItem.String(false)) - - prItem := TriggerMapItemModel{ - PullRequestSourceBranch: "develop", - PullRequestTargetBranch: "master", - WorkflowID: "ci", - } - require.Equal(t, "pull_request_source_branch: develop && pull_request_target_branch: master -> workflow: ci", prItem.String(true)) - require.Equal(t, "pull_request_source_branch: develop && pull_request_target_branch: master", prItem.String(false)) - } - - t.Log("tag event") - { - item := TriggerMapItemModel{ - Tag: "0.9.0", - WorkflowID: "release", - } - require.Equal(t, "tag: 0.9.0 -> workflow: release", item.String(true)) - require.Equal(t, "tag: 0.9.0", item.String(false)) - } - - t.Log("deprecated type") - { - prNotAllowedItem := TriggerMapItemModel{ - Pattern: "master", - IsPullRequestAllowed: false, - WorkflowID: "ci", - } - require.Equal(t, "pattern: master && is_pull_request_allowed: false -> workflow: ci", prNotAllowedItem.String(true)) - require.Equal(t, "pattern: master && is_pull_request_allowed: false", prNotAllowedItem.String(false)) - - prAllowedItem := TriggerMapItemModel{ - Pattern: "master", - IsPullRequestAllowed: true, - WorkflowID: "ci", - } - require.Equal(t, "pattern: master && is_pull_request_allowed: true -> workflow: ci", prAllowedItem.String(true)) - require.Equal(t, "pattern: master && is_pull_request_allowed: true", prAllowedItem.String(false)) - } - - t.Log("mixed") - { - item := TriggerMapItemModel{ - PushBranch: "master", - PullRequestSourceBranch: "develop", - PullRequestTargetBranch: "master", - Tag: "0.9.0", - Pattern: "*", - IsPullRequestAllowed: true, - WorkflowID: "ci", - } - require.Equal(t, "push_branch: master pull_request_source_branch: develop && pull_request_target_branch: master tag: 0.9.0 pattern: * && is_pull_request_allowed: true -> workflow: ci", item.String(true)) - require.Equal(t, "push_branch: master pull_request_source_branch: develop && pull_request_target_branch: master tag: 0.9.0 pattern: * && is_pull_request_allowed: true", item.String(false)) - } -} - -func TestTriggerEventType(t *testing.T) { - t.Log("it determins trigger event type") - { - pushBranch := "master" - prSourceBranch := "" - prTargetBranch := "" - tag := "" - - event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, TriggerEventTypeCodePush, event) - } - - t.Log("it determins trigger event type") - { - pushBranch := "" - prSourceBranch := "develop" - prTargetBranch := "" - tag := "" - - event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, TriggerEventTypePullRequest, event) - } - - t.Log("it determins trigger event type") - { - pushBranch := "" - prSourceBranch := "" - prTargetBranch := "master" - tag := "" - - event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, TriggerEventTypePullRequest, event) - } - - t.Log("it determins trigger event type") - { - pushBranch := "" - prSourceBranch := "" - prTargetBranch := "" - tag := "0.9.0" - - event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, TriggerEventTypeTag, event) - } - - t.Log("it fails without inputs") - { - pushBranch := "" - prSourceBranch := "" - prTargetBranch := "" - tag := "" - - event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) - require.Error(t, err) - require.Equal(t, TriggerEventTypeUnknown, event) - } - - t.Log("it fails if event type not clear") - { - pushBranch := "master" - prSourceBranch := "develop" - prTargetBranch := "" - tag := "" - - event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) - require.Error(t, err) - require.Equal(t, TriggerEventTypeUnknown, event) - } - - t.Log("it fails if event type not clear") - { - pushBranch := "master" - prSourceBranch := "" - prTargetBranch := "master" - tag := "" - - event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) - require.Error(t, err) - require.Equal(t, TriggerEventTypeUnknown, event) - } - - t.Log("it fails if event type not clear") - { - pushBranch := "master" - prSourceBranch := "" - prTargetBranch := "" - tag := "0.9.0" - - event, err := triggerEventType(pushBranch, prSourceBranch, prTargetBranch, tag) - require.Error(t, err) - require.Equal(t, TriggerEventTypeUnknown, event) - } -} - -func TestTriggerMapItemValidate(t *testing.T) { - t.Log("utility workflow triggered - Warning") - { - configStr := ` -format_version: 1.3.1 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -trigger_map: -- push_branch: "/release" - workflow: _deps-update - -workflows: - _deps-update: -` - - config, err := configModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - - warnings, err := config.Validate() - require.NoError(t, err) - require.Equal(t, []string{"workflow (_deps-update) defined in trigger item (push_branch: /release -> workflow: _deps-update), but utility workflows can't be triggered directly"}, warnings) - } - - t.Log("workflow not exists") - { - configStr := ` -format_version: 1.3.1 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -trigger_map: -- push_branch: "/release" - workflow: release - -workflows: - ci: -` - - config, err := configModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - - _, err = config.Validate() - require.EqualError(t, err, "workflow (release) defined in trigger item (push_branch: /release -> workflow: release), but does not exist") - } - - t.Log("it validates deprecated trigger item") - { - item := TriggerMapItemModel{ - Pattern: "*", - WorkflowID: "primary", - } - require.NoError(t, item.Validate()) - } - - t.Log("it fails for invalid deprecated trigger item - missing workflow") - { - item := TriggerMapItemModel{ - Pattern: "*", - WorkflowID: "", - } - require.Error(t, item.Validate()) - } - - t.Log("it fails for invalid deprecated trigger item - missing pattern") - { - item := TriggerMapItemModel{ - Pattern: "", - WorkflowID: "primary", - } - require.Error(t, item.Validate()) - } - - t.Log("it validates code-push trigger item") - { - item := TriggerMapItemModel{ - PushBranch: "*", - WorkflowID: "primary", - } - require.NoError(t, item.Validate()) - } - - t.Log("it fails for invalid code-push trigger item - missing push-branch") - { - item := TriggerMapItemModel{ - PushBranch: "", - WorkflowID: "primary", - } - require.Error(t, item.Validate()) - } - - t.Log("it fails for invalid code-push trigger item - missing workflow") - { - item := TriggerMapItemModel{ - PushBranch: "*", - WorkflowID: "", - } - require.Error(t, item.Validate()) - } - - t.Log("it validates pull-request trigger item") - { - item := TriggerMapItemModel{ - PullRequestSourceBranch: "feature/", - WorkflowID: "primary", - } - require.NoError(t, item.Validate()) - } - - t.Log("it validates pull-request trigger item") - { - item := TriggerMapItemModel{ - PullRequestTargetBranch: "master", - WorkflowID: "primary", - } - require.NoError(t, item.Validate()) - } - - t.Log("it fails for invalid pull-request trigger item - missing workflow") - { - item := TriggerMapItemModel{ - PullRequestTargetBranch: "*", - WorkflowID: "", - } - require.Error(t, item.Validate()) - } - - t.Log("it fails for invalid pull-request trigger item - missing workflow") - { - item := TriggerMapItemModel{ - PullRequestSourceBranch: "", - PullRequestTargetBranch: "", - WorkflowID: "primary", - } - require.Error(t, item.Validate()) - } - - t.Log("it fails for mixed trigger item") - { - item := TriggerMapItemModel{ - PushBranch: "master", - PullRequestSourceBranch: "feature/*", - PullRequestTargetBranch: "", - WorkflowID: "primary", - } - require.Error(t, item.Validate()) - } - - t.Log("it fails for mixed trigger item") - { - item := TriggerMapItemModel{ - PushBranch: "master", - Pattern: "*", - WorkflowID: "primary", - } - require.Error(t, item.Validate()) - } -} - -func TestMatchWithParamsCodePushItem(t *testing.T) { - t.Log("code-push against code-push type item - MATCH") - { - pushBranch := "master" - prSourceBranch := "" - prTargetBranch := "" - tag := "" - - item := TriggerMapItemModel{ - PushBranch: "master", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, true, match) - } - - t.Log("code-push against code-push type item - MATCH") - { - pushBranch := "master" - prSourceBranch := "" - prTargetBranch := "" - tag := "" - - item := TriggerMapItemModel{ - PushBranch: "*", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, true, match) - } - - t.Log("code-push against code-push type item - MATCH") - { - pushBranch := "feature/login" - prSourceBranch := "" - prTargetBranch := "" - tag := "" - - item := TriggerMapItemModel{ - PushBranch: "feature/*", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, true, match) - } - - t.Log("code-push against code-push type item - NOT MATCH") - { - pushBranch := "master" - prSourceBranch := "" - prTargetBranch := "" - tag := "" - - item := TriggerMapItemModel{ - PushBranch: "deploy", - WorkflowID: "deploy", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, false, match) - } - - t.Log("code-push against pr type item - NOT MATCH") - { - pushBranch := "master" - prSourceBranch := "" - prTargetBranch := "" - tag := "" - - item := TriggerMapItemModel{ - PullRequestSourceBranch: "develop", - WorkflowID: "test", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, false, match) - } - - t.Log("code-push against pr type item - NOT MATCH") - { - pushBranch := "master" - prSourceBranch := "" - prTargetBranch := "" - tag := "" - - item := TriggerMapItemModel{ - PullRequestTargetBranch: "master", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, false, match) - } - - t.Log("code-push against pr type item - NOT MATCH") - { - pushBranch := "master" - prSourceBranch := "" - prTargetBranch := "" - tag := "" - - item := TriggerMapItemModel{ - PullRequestSourceBranch: "develop", - PullRequestTargetBranch: "master", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, false, match) - } -} - -func TestMatchWithParamsPrTypeItem(t *testing.T) { - t.Log("pr against pr type item - MATCH") - { - pushBranch := "" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "" - - item := TriggerMapItemModel{ - PullRequestSourceBranch: "develop", - PullRequestTargetBranch: "master", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, true, match) - } - - t.Log("pr against pr type item - MATCH") - { - pushBranch := "" - prSourceBranch := "feature/login" - prTargetBranch := "develop" - tag := "" - - item := TriggerMapItemModel{ - PullRequestSourceBranch: "feature/*", - PullRequestTargetBranch: "develop", - WorkflowID: "test", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, true, match) - } - - t.Log("pr against pr type item - MATCH") - { - pushBranch := "" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "" - - item := TriggerMapItemModel{ - PullRequestSourceBranch: "*", - PullRequestTargetBranch: "master", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, true, match) - } - - t.Log("pr against pr type item - MATCH") - { - pushBranch := "" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "" - - item := TriggerMapItemModel{ - PullRequestTargetBranch: "master", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, true, match) - } - - t.Log("pr against pr type item - MATCH") - { - pushBranch := "" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "" - - item := TriggerMapItemModel{ - PullRequestSourceBranch: "develop", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, true, match) - } - - t.Log("pr against pr type item - MATCH") - { - pushBranch := "" - prSourceBranch := "" - prTargetBranch := "deploy_1_0_0" - tag := "" - - item := TriggerMapItemModel{ - PullRequestTargetBranch: "deploy_*", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, true, match) - } - - t.Log("pr against pr type item - NOT MATCH") - { - pushBranch := "" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "" - - item := TriggerMapItemModel{ - PullRequestSourceBranch: "develop", - PullRequestTargetBranch: "deploy", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, false, match) - } - - t.Log("pr against pr type item - NOT MATCH") - { - pushBranch := "" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "" - - item := TriggerMapItemModel{ - PullRequestSourceBranch: "feature/*", - PullRequestTargetBranch: "master", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, false, match) - } - - t.Log("pr against push type item - NOT MATCH") - { - pushBranch := "" - prSourceBranch := "develop" - prTargetBranch := "master" - tag := "" - - item := TriggerMapItemModel{ - PushBranch: "master", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, false, match) - } -} - -func TestMatchWithParamsTagTypeItem(t *testing.T) { - t.Log("tag against tag type item - MATCH") - { - pushBranch := "" - prSourceBranch := "" - prTargetBranch := "" - tag := "0.9.0" - - item := TriggerMapItemModel{ - Tag: "0.9.*", - WorkflowID: "deploy", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, true, match) - } - - t.Log("tag against tag type item - MATCH") - { - pushBranch := "" - prSourceBranch := "" - prTargetBranch := "" - tag := "0.9.0" - - item := TriggerMapItemModel{ - Tag: "0.9.0", - WorkflowID: "deploy", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, true, match) - } - - t.Log("tag against tag type item - MATCH") - { - pushBranch := "" - prSourceBranch := "" - prTargetBranch := "" - tag := "0.9.0-pre" - - item := TriggerMapItemModel{ - Tag: "0.9.*", - WorkflowID: "deploy", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, true, match) - } - - t.Log("tag against tag type item - NOT MATCH") - { - pushBranch := "" - prSourceBranch := "" - prTargetBranch := "" - tag := "0.9.0-pre" - - item := TriggerMapItemModel{ - Tag: "1.*", - WorkflowID: "deploy", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, false, match) - } - - t.Log("tag against push type item - NOT MATCH") - { - pushBranch := "" - prSourceBranch := "" - prTargetBranch := "" - tag := "0.9.0-pre" - - item := TriggerMapItemModel{ - PushBranch: "master", - WorkflowID: "primary", - } - match, err := item.MatchWithParams(pushBranch, prSourceBranch, prTargetBranch, tag) - require.NoError(t, err) - require.Equal(t, false, match) - } -} - -// ---------------------------- -// --- Validate - -// Config -func TestValidateConfig(t *testing.T) { - t.Log("Valid bitriseData ID") - { - bitriseData := BitriseDataModel{ - FormatVersion: "1.4.0", - Workflows: map[string]WorkflowModel{ - "A-Za-z0-9-_.": WorkflowModel{}, - }, - } - - warnings, err := bitriseData.Validate() - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - } - - t.Log("Invalid bitriseData ID - empty") - { - bitriseData := BitriseDataModel{ - FormatVersion: "1.4.0", - Workflows: map[string]WorkflowModel{ - "": WorkflowModel{}, - }, - } - warnings, err := bitriseData.Validate() - require.EqualError(t, err, "invalid workflow ID (): empty") - require.Equal(t, 0, len(warnings)) - } - - t.Log("Invalid bitriseData ID - contains: `/`") - { - bitriseData := BitriseDataModel{ - FormatVersion: "1.4.0", - Workflows: map[string]WorkflowModel{ - "wf/id": WorkflowModel{}, - }, - } - - warnings, err := bitriseData.Validate() - require.NoError(t, err) - require.Equal(t, 1, len(warnings)) - require.Equal(t, "invalid workflow ID (wf/id): doesn't conform to: [A-Za-z0-9-_.]", warnings[0]) - } - - t.Log("Invalid bitriseData ID - contains: `:`") - { - bitriseData := BitriseDataModel{ - FormatVersion: "1.4.0", - Workflows: map[string]WorkflowModel{ - "wf:id": WorkflowModel{}, - }, - } - - warnings, err := bitriseData.Validate() - require.NoError(t, err) - require.Equal(t, 1, len(warnings)) - require.Equal(t, "invalid workflow ID (wf:id): doesn't conform to: [A-Za-z0-9-_.]", warnings[0]) - } - - t.Log("Invalid bitriseData ID - contains: ` `") - { - bitriseData := BitriseDataModel{ - FormatVersion: "1.4.0", - Workflows: map[string]WorkflowModel{ - "wf id": WorkflowModel{}, - }, - } - - warnings, err := bitriseData.Validate() - require.NoError(t, err) - require.Equal(t, 1, len(warnings)) - require.Equal(t, "invalid workflow ID (wf id): doesn't conform to: [A-Za-z0-9-_.]", warnings[0]) - } - - t.Log("Invalid bitriseData ID - contains: ` `") - { - bitriseData := BitriseDataModel{ - FormatVersion: "1.4.0", - Workflows: map[string]WorkflowModel{ - " wfid": WorkflowModel{}, - }, - } - - warnings, err := bitriseData.Validate() - require.NoError(t, err) - require.Equal(t, 1, len(warnings)) - require.Equal(t, "invalid workflow ID ( wfid): doesn't conform to: [A-Za-z0-9-_.]", warnings[0]) - } - - t.Log("Invalid bitriseData ID - contains: ` `") - { - bitriseData := BitriseDataModel{ - FormatVersion: "1.4.0", - Workflows: map[string]WorkflowModel{ - "wfid ": WorkflowModel{}, - }, - } - - warnings, err := bitriseData.Validate() - require.NoError(t, err) - require.Equal(t, 1, len(warnings)) - } -} - -// Workflow -func TestValidateWorkflow(t *testing.T) { - t.Log("before-afetr test") - { - workflow := WorkflowModel{ - BeforeRun: []string{"befor1", "befor2", "befor3"}, - AfterRun: []string{"after1", "after2", "after3"}, - } - - warnings, err := workflow.Validate() - require.NoError(t, err) - require.Equal(t, 0, len(warnings)) - } - - t.Log("invalid workflow - Invalid env: more than 2 fields") - { - configStr := ` -format_version: 1.4.0 - -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - target: - envs: - - ENV_KEY: env_value - opts: - title: test_env - title: Output Test - steps: - - script: - title: Should fail - inputs: - - content: echo "Hello" - BAD_KEY: value -` - - config := BitriseDataModel{} - require.NoError(t, yaml.Unmarshal([]byte(configStr), &config)) - require.NoError(t, config.Normalize()) - - warnings, err := config.Validate() - require.Error(t, err) - require.Equal(t, true, strings.Contains(err.Error(), "more than 2 keys specified: [BAD_KEY content opts]")) - require.Equal(t, 0, len(warnings)) - } - - t.Log("vali workflow - Warning: duplicated inputs") - { - configStr := `format_version: 1.4.0 - -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" - -workflows: - target: - steps: - - script: - title: Should fail - inputs: - - content: echo "Hello" - - content: echo "Hello" -` - - config := BitriseDataModel{} - require.NoError(t, yaml.Unmarshal([]byte(configStr), &config)) - require.NoError(t, config.Normalize()) - - warnings, err := config.Validate() - require.NoError(t, err) - require.Equal(t, 1, len(warnings)) - } -} - -// ---------------------------- -// --- Merge - -func TestMergeEnvironmentWith(t *testing.T) { - diffEnv := envmanModels.EnvironmentItemModel{ - "test_key": "test_value", - envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - Description: pointers.NewStringPtr("test_description"), - Summary: pointers.NewStringPtr("test_summary"), - ValueOptions: []string{"test_valu_options1", "test_valu_options2"}, - IsRequired: pointers.NewBoolPtr(true), - IsExpand: pointers.NewBoolPtr(false), - IsDontChangeValue: pointers.NewBoolPtr(true), - IsTemplate: pointers.NewBoolPtr(true), - }, - } - - t.Log("Different keys") - { - env := envmanModels.EnvironmentItemModel{ - "test_key1": "test_value", - } - require.Error(t, MergeEnvironmentWith(&env, diffEnv)) - } - - t.Log("Normal merge") - { - env := envmanModels.EnvironmentItemModel{ - "test_key": "test_value", - envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ - SkipIfEmpty: pointers.NewBoolPtr(true), - Category: pointers.NewStringPtr("test"), - }, - } - require.NoError(t, MergeEnvironmentWith(&env, diffEnv)) - - options, err := env.GetOptions() - require.NoError(t, err) - - diffOptions, err := diffEnv.GetOptions() - require.NoError(t, err) - - require.Equal(t, *diffOptions.Title, *options.Title) - require.Equal(t, *diffOptions.Description, *options.Description) - require.Equal(t, *diffOptions.Summary, *options.Summary) - require.Equal(t, len(diffOptions.ValueOptions), len(options.ValueOptions)) - require.Equal(t, *diffOptions.IsRequired, *options.IsRequired) - require.Equal(t, *diffOptions.IsExpand, *options.IsExpand) - require.Equal(t, *diffOptions.IsDontChangeValue, *options.IsDontChangeValue) - require.Equal(t, *diffOptions.IsTemplate, *options.IsTemplate) - - require.Equal(t, true, *options.SkipIfEmpty) - require.Equal(t, "test", *options.Category) - } -} - -func TestMergeStepWith(t *testing.T) { - desc := "desc 1" - summ := "sum 1" - website := "web/1" - fork := "fork/1" - published := time.Date(2012, time.January, 1, 0, 0, 0, 0, time.UTC) - - stepData := stepmanModels.StepModel{ - Description: pointers.NewStringPtr(desc), - Summary: pointers.NewStringPtr(summ), - Website: pointers.NewStringPtr(website), - SourceCodeURL: pointers.NewStringPtr(fork), - PublishedAt: pointers.NewTimePtr(published), - HostOsTags: []string{"osx"}, - ProjectTypeTags: []string{"ios"}, - TypeTags: []string{"test"}, - IsRequiresAdminUser: pointers.NewBoolPtr(true), - Inputs: []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "KEY_1": "Value 1", - }, - envmanModels.EnvironmentItemModel{ - "KEY_2": "Value 2", - }, - }, - Outputs: []envmanModels.EnvironmentItemModel{}, - } - - diffTitle := "name 2" - newSuppURL := "supp" - runIfStr := "" - stepDiffToMerge := stepmanModels.StepModel{ - Title: pointers.NewStringPtr(diffTitle), - HostOsTags: []string{"linux"}, - Source: &stepmanModels.StepSourceModel{ - Git: "https://git.url", - }, - Dependencies: []stepmanModels.DependencyModel{ - stepmanModels.DependencyModel{ - Manager: "brew", - Name: "test", - }, - }, - SupportURL: pointers.NewStringPtr(newSuppURL), - RunIf: pointers.NewStringPtr(runIfStr), - Inputs: []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "KEY_2": "Value 2 CHANGED", - }, - }, - Timeout: pointers.NewIntPtr(1), - Toolkit: &stepmanModels.StepToolkitModel{ - Go: &stepmanModels.GoStepToolkitModel{ - PackageName: "test", - }, - }, - } - - mergedStepData, err := MergeStepWith(stepData, stepDiffToMerge) - require.NoError(t, err) - - require.Equal(t, "name 2", *mergedStepData.Title) - require.Equal(t, "desc 1", *mergedStepData.Description) - require.Equal(t, "sum 1", *mergedStepData.Summary) - require.Equal(t, "web/1", *mergedStepData.Website) - require.Equal(t, "fork/1", *mergedStepData.SourceCodeURL) - require.Equal(t, true, (*mergedStepData.PublishedAt).Equal(time.Date(2012, time.January, 1, 0, 0, 0, 0, time.UTC))) - require.Equal(t, "linux", mergedStepData.HostOsTags[0]) - require.Equal(t, "", *mergedStepData.RunIf) - require.Equal(t, 1, len(mergedStepData.Dependencies)) - require.Equal(t, "test", mergedStepData.Toolkit.Go.PackageName) - require.Equal(t, 1, *mergedStepData.Timeout) - - dep := mergedStepData.Dependencies[0] - require.Equal(t, "brew", dep.Manager) - require.Equal(t, "test", dep.Name) - - // inputs - input0 := mergedStepData.Inputs[0] - key0, value0, err := input0.GetKeyValuePair() - - require.NoError(t, err) - require.Equal(t, "KEY_1", key0) - require.Equal(t, "Value 1", value0) - - input1 := mergedStepData.Inputs[1] - key1, value1, err := input1.GetKeyValuePair() - - require.NoError(t, err) - require.Equal(t, "KEY_2", key1) - require.Equal(t, "Value 2 CHANGED", value1) -} - -func TestGetInputByKey(t *testing.T) { - stepData := stepmanModels.StepModel{ - Inputs: []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "KEY_1": "Value 1", - }, - envmanModels.EnvironmentItemModel{ - "KEY_2": "Value 2", - }, - }, - } - - _, found := getInputByKey(stepData, "KEY_1") - require.Equal(t, true, found) - - _, found = getInputByKey(stepData, "KEY_3") - require.Equal(t, false, found) -} - -// ---------------------------- -// --- StepIDData - -func Test_StepIDData_IsUniqueResourceID(t *testing.T) { - stepIDDataWithIDAndVersionSpecified := StepIDData{IDorURI: "stepid", Version: "version"} - stepIDDataWithOnlyVersionSpecified := StepIDData{Version: "version"} - stepIDDataWithOnlyIDSpecified := StepIDData{IDorURI: "stepid"} - stepIDDataEmpty := StepIDData{} - - // Not Unique - for _, aSourceID := range []string{"path", "git", "_", ""} { - stepIDDataWithIDAndVersionSpecified.SteplibSource = aSourceID - require.Equal(t, false, stepIDDataWithIDAndVersionSpecified.IsUniqueResourceID()) - - stepIDDataWithOnlyVersionSpecified.SteplibSource = aSourceID - require.Equal(t, false, stepIDDataWithOnlyVersionSpecified.IsUniqueResourceID()) - - stepIDDataWithOnlyIDSpecified.SteplibSource = aSourceID - require.Equal(t, false, stepIDDataWithOnlyIDSpecified.IsUniqueResourceID()) - - stepIDDataEmpty.SteplibSource = aSourceID - require.Equal(t, false, stepIDDataEmpty.IsUniqueResourceID()) - } - - for _, aSourceID := range []string{"a", "any-other-step-source", "https://github.com/bitrise-io/bitrise-steplib.git"} { - // Only if StepLib, AND both ID and Version are defined, only then - // this is a Unique Resource ID! - stepIDDataWithIDAndVersionSpecified.SteplibSource = aSourceID - require.Equal(t, true, stepIDDataWithIDAndVersionSpecified.IsUniqueResourceID()) - - // In any other case, it's not, - // even if it's from a StepLib - // but missing ID or version! - stepIDDataWithOnlyVersionSpecified.SteplibSource = aSourceID - require.Equal(t, false, stepIDDataWithOnlyVersionSpecified.IsUniqueResourceID()) - - stepIDDataWithOnlyIDSpecified.SteplibSource = aSourceID - require.Equal(t, false, stepIDDataWithOnlyIDSpecified.IsUniqueResourceID()) - - stepIDDataEmpty.SteplibSource = aSourceID - require.Equal(t, false, stepIDDataEmpty.IsUniqueResourceID()) - } -} - -func TestGetStepIDStepDataPair(t *testing.T) { - stepData := stepmanModels.StepModel{} - - t.Log("valid steplist item") - { - stepListItem := StepListItemModel{ - "step1": stepData, - } - - id, _, err := GetStepIDStepDataPair(stepListItem) - require.NoError(t, err) - require.Equal(t, "step1", id) - } - - t.Log("invalid steplist item - more than 1 step") - { - stepListItem := StepListItemModel{ - "step1": stepData, - "step2": stepData, - } - - id, _, err := GetStepIDStepDataPair(stepListItem) - require.Error(t, err) - require.Equal(t, "", id) - } -} - -func TestCreateStepIDDataFromString(t *testing.T) { - t.Log("default / long / verbose ID mode") - { - stepCompositeIDString := "steplib-src::step-id@0.0.1" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") - - require.NoError(t, err) - require.Equal(t, "steplib-src", stepIDData.SteplibSource) - require.Equal(t, "step-id", stepIDData.IDorURI) - require.Equal(t, "0.0.1", stepIDData.Version) - } - - t.Log("no steplib-source") - { - stepCompositeIDString := "step-id@0.0.1" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "default-steplib-src") - - require.NoError(t, err) - require.Equal(t, "default-steplib-src", stepIDData.SteplibSource) - require.Equal(t, "step-id", stepIDData.IDorURI) - require.Equal(t, "0.0.1", stepIDData.Version) - } - - t.Log("invalid/empty step lib source, but default provided") - { - stepCompositeIDString := "::step-id@0.0.1" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "default-steplib-src") - - require.NoError(t, err) - require.Equal(t, "default-steplib-src", stepIDData.SteplibSource) - require.Equal(t, "step-id", stepIDData.IDorURI) - require.Equal(t, "0.0.1", stepIDData.Version) - } - - t.Log("invalid/empty step lib source + no default") - { - stepCompositeIDString := "::step-id@0.0.1" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") - - require.Error(t, err) - require.Equal(t, "", stepIDData.SteplibSource) - require.Equal(t, "", stepIDData.IDorURI) - require.Equal(t, "", stepIDData.Version) - } - - t.Log("no steplib-source & no default -> fail") - { - stepCompositeIDString := "step-id@0.0.1" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") - - require.Error(t, err) - require.Equal(t, "", stepIDData.SteplibSource) - require.Equal(t, "", stepIDData.IDorURI) - require.Equal(t, "", stepIDData.Version) - } - - t.Log("no steplib & no version, only step-id") - { - stepCompositeIDString := "step-id" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "def-lib-src") - - require.NoError(t, err) - require.Equal(t, "def-lib-src", stepIDData.SteplibSource) - require.Equal(t, "step-id", stepIDData.IDorURI) - require.Equal(t, "", stepIDData.Version) - } - - t.Log("empty test") - { - stepCompositeIDString := "" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "def-step-src") - - require.Error(t, err) - require.Equal(t, "", stepIDData.SteplibSource) - require.Equal(t, "", stepIDData.IDorURI) - require.Equal(t, "", stepIDData.Version) - } - - t.Log("special empty test") - { - stepCompositeIDString := "@1.0.0" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "def-step-src") - - require.Error(t, err) - require.Equal(t, "", stepIDData.SteplibSource) - require.Equal(t, "", stepIDData.IDorURI) - require.Equal(t, "", stepIDData.Version) - } - - // - // ----- Local Path - t.Log("local Path") - { - stepCompositeIDString := "path::/some/path" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") - - require.NoError(t, err) - require.Equal(t, "path", stepIDData.SteplibSource) - require.Equal(t, "/some/path", stepIDData.IDorURI) - require.Equal(t, "", stepIDData.Version) - } - - t.Log("local Path") - { - stepCompositeIDString := "path::~/some/path/in/home" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") - - require.NoError(t, err) - require.Equal(t, "path", stepIDData.SteplibSource) - require.Equal(t, "~/some/path/in/home", stepIDData.IDorURI) - require.Equal(t, "", stepIDData.Version) - } - - t.Log("local Path") - { - stepCompositeIDString := "path::$HOME/some/path/in/home" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") - - require.NoError(t, err) - require.Equal(t, "path", stepIDData.SteplibSource) - require.Equal(t, "$HOME/some/path/in/home", stepIDData.IDorURI) - require.Equal(t, "", stepIDData.Version) - } - - // - // ----- Direct git uri - t.Log("direct git uri") - { - stepCompositeIDString := "git::https://github.com/bitrise-io/steps-timestamp.git@develop" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "some-def-coll") - - require.NoError(t, err) - require.Equal(t, "git", stepIDData.SteplibSource) - require.Equal(t, "https://github.com/bitrise-io/steps-timestamp.git", stepIDData.IDorURI) - require.Equal(t, "develop", stepIDData.Version) - } - - t.Log("direct git uri") - { - stepCompositeIDString := "git::git@github.com:bitrise-io/steps-timestamp.git@develop" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") - - require.NoError(t, err) - require.Equal(t, "git", stepIDData.SteplibSource) - require.Equal(t, "git@github.com:bitrise-io/steps-timestamp.git", stepIDData.IDorURI) - require.Equal(t, "develop", stepIDData.Version) - } - - t.Log("direct git uri") - { - stepCompositeIDString := "git::https://github.com/bitrise-io/steps-timestamp.git" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "some-def-coll") - - require.NoError(t, err) - require.Equal(t, "git", stepIDData.SteplibSource) - require.Equal(t, "https://github.com/bitrise-io/steps-timestamp.git", stepIDData.IDorURI) - require.Equal(t, "master", stepIDData.Version) - } - - // - // ----- Old step - t.Log("old step") - { - stepCompositeIDString := "_::https://github.com/bitrise-io/steps-timestamp.git@1.0.0" - stepIDData, err := CreateStepIDDataFromString(stepCompositeIDString, "") - - require.NoError(t, err) - require.Equal(t, "_", stepIDData.SteplibSource) - require.Equal(t, "https://github.com/bitrise-io/steps-timestamp.git", stepIDData.IDorURI) - require.Equal(t, "1.0.0", stepIDData.Version) - } -} - -// ---------------------------- -// --- RemoveRedundantFields - -func TestRemoveEnvironmentRedundantFields(t *testing.T) { - t.Log("Trivial remove - all fields should be default value") - { - env := envmanModels.EnvironmentItemModel{ - "TEST_KEY": "test_value", - envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr(""), - Description: pointers.NewStringPtr(""), - Summary: pointers.NewStringPtr(""), - ValueOptions: []string{}, - IsRequired: pointers.NewBoolPtr(envmanModels.DefaultIsRequired), - IsExpand: pointers.NewBoolPtr(envmanModels.DefaultIsExpand), - IsDontChangeValue: pointers.NewBoolPtr(envmanModels.DefaultIsDontChangeValue), - IsTemplate: pointers.NewBoolPtr(envmanModels.DefaultIsTemplate), - }, - } - require.NoError(t, removeEnvironmentRedundantFields(&env)) - - options, err := env.GetOptions() - require.NoError(t, err) - - require.Equal(t, (*string)(nil), options.Title) - require.Equal(t, (*string)(nil), options.Description) - require.Equal(t, (*string)(nil), options.Summary) - require.Equal(t, 0, len(options.ValueOptions)) - require.Equal(t, (*bool)(nil), options.IsRequired) - require.Equal(t, (*bool)(nil), options.IsExpand) - require.Equal(t, (*bool)(nil), options.IsDontChangeValue) - require.Equal(t, (*bool)(nil), options.IsTemplate) - } - - t.Log("Trivial don't remove - no fields should be default value") - { - env := envmanModels.EnvironmentItemModel{ - "TEST_KEY": "test_value", - envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("t"), - Description: pointers.NewStringPtr("d"), - Summary: pointers.NewStringPtr("s"), - ValueOptions: []string{"i"}, - IsRequired: pointers.NewBoolPtr(true), - IsExpand: pointers.NewBoolPtr(false), - IsDontChangeValue: pointers.NewBoolPtr(true), - IsTemplate: pointers.NewBoolPtr(true), - }, - } - require.NoError(t, removeEnvironmentRedundantFields(&env)) - - options, err := env.GetOptions() - require.NoError(t, err) - - require.Equal(t, "t", *options.Title) - require.Equal(t, "d", *options.Description) - require.Equal(t, "s", *options.Summary) - require.Equal(t, "i", options.ValueOptions[0]) - require.Equal(t, true, *options.IsRequired) - require.Equal(t, false, *options.IsExpand) - require.Equal(t, true, *options.IsDontChangeValue) - require.Equal(t, true, *options.IsTemplate) - } - - t.Log("No options - opts field shouldn't exist") - { - env := envmanModels.EnvironmentItemModel{ - "TEST_KEY": "test_value", - } - require.NoError(t, removeEnvironmentRedundantFields(&env)) - - _, ok := env[envmanModels.OptionsKey] - require.Equal(t, false, ok) - } -} - -func configModelFromYAMLBytes(configBytes []byte) (bitriseData BitriseDataModel, err error) { - if err = yaml.Unmarshal(configBytes, &bitriseData); err != nil { - return - } - return -} - -func TestRemoveWorkflowRedundantFields(t *testing.T) { - configStr := `format_version: 2 -default_step_lib_source: "https://github.com/bitrise-io/bitrise-steplib.git" -project_type: ios - -app: - summary: "sum" - envs: - - ENV_KEY: env_value - opts: - is_required: true - -workflows: - target: - envs: - - ENV_KEY: env_value - opts: - title: test_env - title: Output Test - steps: - - script: - description: test -` - - config, err := configModelFromYAMLBytes([]byte(configStr)) - require.NoError(t, err) - - err = config.RemoveRedundantFields() - require.NoError(t, err) - - require.Equal(t, "2", config.FormatVersion) - require.Equal(t, "https://github.com/bitrise-io/bitrise-steplib.git", config.DefaultStepLibSource) - require.Equal(t, "ios", config.ProjectType) - - require.Equal(t, "", config.App.Title) - require.Equal(t, "", config.App.Description) - require.Equal(t, "sum", config.App.Summary) - - for _, env := range config.App.Environments { - options, err := env.GetOptions() - require.NoError(t, err) - - require.Nil(t, options.Title) - require.Nil(t, options.Description) - require.Nil(t, options.Summary) - require.Equal(t, 0, len(options.ValueOptions)) - require.Equal(t, true, *options.IsRequired) - require.Nil(t, options.IsExpand) - require.Nil(t, options.IsDontChangeValue) - } - - for _, workflow := range config.Workflows { - require.Equal(t, "Output Test", workflow.Title) - require.Equal(t, "", workflow.Description) - require.Equal(t, "", workflow.Summary) - - for _, env := range workflow.Environments { - options, err := env.GetOptions() - require.NoError(t, err) - - require.Equal(t, "test_env", *options.Title) - require.Nil(t, options.Description) - require.Nil(t, options.Summary) - require.Equal(t, 0, len(options.ValueOptions)) - require.Nil(t, options.IsRequired) - require.Nil(t, options.IsExpand) - require.Nil(t, options.IsDontChangeValue) - } - - for _, stepListItem := range workflow.Steps { - _, step, err := GetStepIDStepDataPair(stepListItem) - require.NoError(t, err) - - require.Nil(t, step.Title) - require.Equal(t, "test", *step.Description) - require.Nil(t, step.Summary) - require.Nil(t, step.Website) - require.Nil(t, step.SourceCodeURL) - require.Nil(t, step.SupportURL) - require.Nil(t, step.PublishedAt) - require.Nil(t, step.Source) - require.Nil(t, step.Deps) - require.Equal(t, 0, len(step.HostOsTags)) - require.Equal(t, 0, len(step.ProjectTypeTags)) - require.Equal(t, 0, len(step.TypeTags)) - require.Nil(t, step.IsRequiresAdminUser) - require.Nil(t, step.IsAlwaysRun) - require.Nil(t, step.IsSkippable) - require.Nil(t, step.RunIf) - require.Equal(t, 0, len(step.Inputs)) - require.Equal(t, 0, len(step.Outputs)) - } - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/output/output.go b/vendor/github.com/bitrise-io/bitrise/output/output.go deleted file mode 100644 index a3e7d747..00000000 --- a/vendor/github.com/bitrise-io/bitrise/output/output.go +++ /dev/null @@ -1,64 +0,0 @@ -package output - -import ( - "encoding/json" - "fmt" - - "gopkg.in/yaml.v2" - - log "github.com/Sirupsen/logrus" - "github.com/urfave/cli" -) - -const ( - // FormatKey ... - FormatKey = "format" - // FormatRaw ... - FormatRaw = "raw" - // FormatJSON ... - FormatJSON = "json" - // FormatYML ... - FormatYML = "yml" -) - -// Format ... -var Format = FormatRaw - -// ConfigureOutputFormat ... -func ConfigureOutputFormat(c *cli.Context) error { - outFmt := c.String(FormatKey) - switch outFmt { - case FormatRaw, FormatJSON, FormatYML: - // valid - Format = outFmt - case "": - // default - Format = FormatRaw - default: - // invalid - return fmt.Errorf("Invalid Output Format: %s", outFmt) - } - return nil -} - -// Print ... -func Print(outModel interface{}, format string) { - switch format { - case FormatJSON: - serBytes, err := json.Marshal(outModel) - if err != nil { - log.Errorf("[.print] ERROR: %s", err) - return - } - fmt.Printf("%s\n", serBytes) - case FormatYML: - serBytes, err := yaml.Marshal(outModel) - if err != nil { - log.Errorf("[output.print] ERROR: %s", err) - return - } - fmt.Printf("%s\n", serBytes) - default: - log.Errorf("[output.print] Invalid output format: %s", format) - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/events.go b/vendor/github.com/bitrise-io/bitrise/plugins/events.go deleted file mode 100644 index ab7075cc..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/events.go +++ /dev/null @@ -1,72 +0,0 @@ -package plugins - -import ( - "encoding/json" - "fmt" -) - -// TriggerEventName ... -type TriggerEventName string - -const ( - // DidFinishRun ... - DidFinishRun TriggerEventName = "DidFinishRun" -) - -// TriggerEvent ... -func TriggerEvent(name TriggerEventName, payload interface{}) error { - // Create plugin input - payloadBytes, err := json.Marshal(payload) - if err != nil { - return err - } - - pluginInput := PluginInput{ - PluginInputPayloadKey: string(payloadBytes), - PluginInputTriggerEventKey: string(name), - } - - // Load plugins - plugins, err := LoadPlugins(string(name)) - if err != nil { - return err - } - - // Run plugins - for _, plugin := range plugins { - if err := RunPluginByEvent(plugin, pluginInput); err != nil { - return err - } - } - - return nil -} - -// LoadPlugins ... -func LoadPlugins(eventName string) ([]Plugin, error) { - routing, err := readPluginRouting() - if err != nil { - return []Plugin{}, err - } - - pluginNames := []string{} - for name, route := range routing.RouteMap { - if route.TriggerEvent == eventName { - pluginNames = append(pluginNames, name) - } - } - - plugins := []Plugin{} - for _, name := range pluginNames { - plugin, found, err := LoadPlugin(name) - if err != nil { - return []Plugin{}, err - } - if !found { - return []Plugin{}, fmt.Errorf("Plugin (%s) exist in routing, but not found", name) - } - plugins = append(plugins, plugin) - } - - return plugins, nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/git.go b/vendor/github.com/bitrise-io/bitrise/plugins/git.go deleted file mode 100644 index 9eb608e7..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/git.go +++ /dev/null @@ -1,221 +0,0 @@ -package plugins - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "regexp" - "sort" - "strings" - - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/errorutil" - "github.com/bitrise-io/go-utils/pathutil" - ver "github.com/hashicorp/go-version" -) - -//======================================= -// Util -//======================================= - -func filterVersionTags(tagList []string) []*ver.Version { - versionTags := []*ver.Version{} - for _, tag := range tagList { - versionTag, err := ver.NewVersion(tag) - if err == nil && versionTag != nil { - versionTags = append(versionTags, versionTag) - } - } - return versionTags -} - -//======================================= -// Git -//======================================= - -func createError(prinatableCmd, cmdOut string, cmdErr error) error { - message := fmt.Sprintf("command (%s) failed", prinatableCmd) - if len(cmdOut) > 0 { - message += "\nout: " + cmdOut - } - if !errorutil.IsExitStatusError(cmdErr) { - message += "\nerr: " + cmdErr.Error() - } - return errors.New(message) -} - -func runAndHandle(cmd *command.Model) error { - if out, err := cmd.RunAndReturnTrimmedCombinedOutput(); err != nil { - return createError(cmd.PrintableCommandArgs(), out, err) - } - return nil -} - -func runForOutputAndHandle(cmd *command.Model) (string, error) { - out, err := cmd.RunAndReturnTrimmedCombinedOutput() - if err != nil { - return "", createError(cmd.PrintableCommandArgs(), out, err) - } - return out, nil -} - -func commitHashOfTag(cloneIntoDir, tag string) (string, error) { - cmd := command.New("git", "show-ref", "--hash", tag) - cmd.SetDir(cloneIntoDir) - return runForOutputAndHandle(cmd) -} - -func gitRemoteTagList(cloneIntoDir string) ([]string, error) { - cmd := command.New("git", "ls-remote", "--tags") - cmd.SetDir(cloneIntoDir) - out, err := runForOutputAndHandle(cmd) - if err != nil { - return []string{}, err - } - if out == "" { - return []string{}, nil - } - - var exp = regexp.MustCompile(`(^[a-z0-9]+)+.*refs/tags/([0-9.]+)`) - versionMap := map[string]bool{} - outSplit := strings.Split(out, "\n") - - for _, line := range outSplit { - result := exp.FindAllStringSubmatch(line, -1) - if len(result) > 0 { - matches := result[0] - - if len(matches) == 3 { - version := matches[2] - versionMap[version] = true - } - } - } - - versions := []string{} - for key := range versionMap { - versions = append(versions, key) - } - - return versions, nil -} - -func gitInit(cloneIntoDir string) error { - cmd := command.New("git", "init") - cmd.SetDir(cloneIntoDir) - return runAndHandle(cmd) -} - -func gitAddRemote(cloneIntoDir, repositoryURL string) error { - cmd := command.New("git", "remote", "add", "origin", repositoryURL) - cmd.SetDir(cloneIntoDir) - return runAndHandle(cmd) -} - -func gitFetch(cloneIntoDir string) error { - cmd := command.New("git", "fetch") - cmd.SetDir(cloneIntoDir) - return runAndHandle(cmd) -} - -func gitCheckout(cloneIntoDir, gitCheckoutParam string) error { - cmd := command.New("git", "checkout", gitCheckoutParam) - cmd.SetDir(cloneIntoDir) - return runAndHandle(cmd) -} - -func gitLog(cloneIntoDir, formatParam string) (string, error) { - cmd := command.New("git", "log", "-1", "--format="+formatParam) - cmd.SetDir(cloneIntoDir) - return runForOutputAndHandle(cmd) -} - -func gitInitWithRemote(cloneIntoDir, repositoryURL string) error { - gitCheckPath := filepath.Join(cloneIntoDir, ".git") - if exist, err := pathutil.IsPathExists(gitCheckPath); err != nil { - return fmt.Errorf("Failed to file path (%s), err: %s", gitCheckPath, err) - } else if exist { - return fmt.Errorf(".git folder already exists in the destination dir (%s)", gitCheckPath) - } - - if err := os.MkdirAll(cloneIntoDir, 0777); err != nil { - return fmt.Errorf("Failed to create the clone_destination_dir at: %s", cloneIntoDir) - } - - if err := gitInit(cloneIntoDir); err != nil { - return fmt.Errorf("Could not init git repository, err: %s", cloneIntoDir) - } - - if err := gitAddRemote(cloneIntoDir, repositoryURL); err != nil { - return fmt.Errorf("Could not add remote, err: %s", err) - } - - if err := gitFetch(cloneIntoDir); err != nil { - return fmt.Errorf("Could not fetch from repository, err: %s", err) - } - - return nil -} - -//======================================= -// Main -//======================================= - -// ByVersion .. -type ByVersion []*ver.Version - -func (s ByVersion) Len() int { - return len(s) -} -func (s ByVersion) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} -func (s ByVersion) Less(i, j int) bool { - return s[i].LessThan(s[j]) -} - -// GitVersionTags ... -func GitVersionTags(gitRepoDir string) ([]*ver.Version, error) { - tagList, err := gitRemoteTagList(gitRepoDir) - if err != nil { - return []*ver.Version{}, fmt.Errorf("Could not get version tag list, error: %s", err) - } - - tags := filterVersionTags(tagList) - - sort.Sort(ByVersion(tags)) - - return tags, nil -} - -// GitCloneAndCheckoutVersionOrLatestVersion ... -func GitCloneAndCheckoutVersionOrLatestVersion(cloneIntoDir, repositoryURL, checkoutVersion string) (string, error) { - if err := gitInitWithRemote(cloneIntoDir, repositoryURL); err != nil { - return "", fmt.Errorf("git init failed, error: %s", err) - } - - if checkoutVersion == "" { - versionTagList, err := GitVersionTags(cloneIntoDir) - if err != nil { - return "", fmt.Errorf("could not get version tag list, error: %s", err) - } - - if len(versionTagList) == 0 { - return "", fmt.Errorf("no version tag found") - } - - versionPtr := versionTagList[len(versionTagList)-1] - if versionPtr == nil { - return "", fmt.Errorf("uninitialized version found") - } - - checkoutVersion = versionPtr.String() - } - - if err := gitCheckout(cloneIntoDir, checkoutVersion); err != nil { - return "", fmt.Errorf("could not checkout (%s), err :%s", checkoutVersion, err) - } - - return checkoutVersion, nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/git_test.go b/vendor/github.com/bitrise-io/bitrise/plugins/git_test.go deleted file mode 100644 index 14d2956c..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/git_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package plugins - -import ( - "os" - "testing" - - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestFilterVersionTags(t *testing.T) { - t.Log("single version tag") - { - versionTags := filterVersionTags([]string{"1.0.0"}) - require.Equal(t, 1, len(versionTags)) - require.Equal(t, "1.0.0", versionTags[0].String()) - } - - t.Log("version tag list") - { - versionTags := filterVersionTags([]string{"1.0.0", "1.1.0", "1.1.1"}) - require.Equal(t, 3, len(versionTags)) - require.Equal(t, "1.0.0", versionTags[0].String()) - require.Equal(t, "1.1.0", versionTags[1].String()) - require.Equal(t, "1.1.1", versionTags[2].String()) - } - - t.Log("non version tag") - { - versionTags := filterVersionTags([]string{"release"}) - require.Equal(t, 0, len(versionTags)) - } - - t.Log("version tag + non version tag") - { - versionTags := filterVersionTags([]string{"1.0.0", "release"}) - require.Equal(t, 1, len(versionTags)) - require.Equal(t, "1.0.0", versionTags[0].String()) - } -} - -func TestClonePluginSrc(t *testing.T) { - t.Log("example plugin - latest version") - { - pluginSource := examplePluginGitURL - versionTag := "" - destinationDir, err := pathutil.NormalizedOSTempDirPath("TestClonePluginSrc") - require.NoError(t, err) - - exist, err := pathutil.IsPathExists(destinationDir) - require.NoError(t, err) - if exist { - err := os.RemoveAll(destinationDir) - require.NoError(t, err) - } - - version, err := GitCloneAndCheckoutVersionOrLatestVersion(destinationDir, pluginSource, versionTag) - require.NoError(t, err) - require.NotNil(t, version) - - exist, err = pathutil.IsPathExists(destinationDir) - require.NoError(t, err) - require.Equal(t, true, exist) - } - - t.Log("example plugin - 0.9.0 version") - { - pluginSource := examplePluginGitURL - versionTag := "0.9.0" - destinationDir, err := pathutil.NormalizedOSTempDirPath("TestClonePluginSrc") - require.NoError(t, err) - - exist, err := pathutil.IsPathExists(destinationDir) - require.NoError(t, err) - if exist { - err := os.RemoveAll(destinationDir) - require.NoError(t, err) - } - - version, err := GitCloneAndCheckoutVersionOrLatestVersion(destinationDir, pluginSource, versionTag) - require.NoError(t, err) - require.NotNil(t, version) - require.Equal(t, "0.9.0", version) - - exist, err = pathutil.IsPathExists(destinationDir) - require.NoError(t, err) - require.Equal(t, true, exist) - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/install.go b/vendor/github.com/bitrise-io/bitrise/plugins/install.go deleted file mode 100644 index a893b66b..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/install.go +++ /dev/null @@ -1,289 +0,0 @@ -package plugins - -import ( - "fmt" - "io" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/go-utils/progress" - ver "github.com/hashicorp/go-version" -) - -//======================================= -// Util -//======================================= - -func validatePath(pth string) error { - if exist, err := pathutil.IsPathExists(pth); err != nil { - return fmt.Errorf("failed to check path (%s), error: %s", pth, err) - } else if !exist { - return fmt.Errorf("no file found at (%s)", pth) - } - return nil -} - -func validateVersion(current, requiredMin ver.Version, requiredMax *ver.Version) error { - if current.LessThan(&requiredMin) { - return fmt.Errorf("current version (%s) is less then min version (%s) ", current.String(), requiredMin.String()) - } else if requiredMax != nil && current.GreaterThan(requiredMax) { - return fmt.Errorf("current version (%s) is greater than max version (%s) ", current.String(), (*requiredMax).String()) - } - return nil -} - -func downloadPluginBin(sourceURL, destinationPth string) error { - url, err := url.Parse(sourceURL) - if err != nil { - return fmt.Errorf("failed to parse url (%s), error: %s", sourceURL, err) - } - - // Download local binary - if url.Scheme == "file" { - src := strings.Replace(sourceURL, url.Scheme+"://", "", -1) - - if err := command.CopyFile(src, destinationPth); err != nil { - return fmt.Errorf("failed to copy (%s) to (%s)", src, destinationPth) - } - return nil - } - - // Download remote binary - out, err := os.Create(destinationPth) - defer func() { - if err := out.Close(); err != nil { - log.Warnf("failed to close (%s)", destinationPth) - } - }() - if err != nil { - return fmt.Errorf("failed to create (%s), error: %s", destinationPth, err) - } - - resp, err := http.Get(sourceURL) - if err != nil { - return fmt.Errorf("failed to download from (%s), error: %s", sourceURL, err) - } - defer func() { - if err := resp.Body.Close(); err != nil { - log.Warnf("failed to close (%s) body", sourceURL) - } - }() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("non success status code (%d)", resp.StatusCode) - } - - _, err = io.Copy(out, resp.Body) - if err != nil { - return fmt.Errorf("failed to download from (%s), error: %s", sourceURL, err) - } - - return nil -} - -func cleanupPlugin(name string) error { - pluginDir := GetPluginDir(name) - - if err := os.RemoveAll(pluginDir); err != nil { - return err - } - - if err := DeletePluginRoute(name); err != nil { - return err - } - - return nil -} - -func installLocalPlugin(pluginSourceURI, pluginLocalPth string) (Plugin, error) { - // Parse & validate plugin - tmpPluginYMLPath := filepath.Join(pluginLocalPth, pluginDefinitionFileName) - - if err := validatePath(tmpPluginYMLPath); err != nil { - return Plugin{}, fmt.Errorf("bitrise-plugin.yml validation failed, error: %s", err) - } - - newPlugin, err := ParsePluginFromYML(tmpPluginYMLPath) - if err != nil { - return Plugin{}, fmt.Errorf("failed to parse bitrise-plugin.yml (%s), error: %s", tmpPluginYMLPath, err) - } - - if err := validatePlugin(newPlugin, pluginSourceURI); err != nil { - return Plugin{}, fmt.Errorf("plugin validation failed, error: %s", err) - } - // --- - - // Check if plugin already installed - if route, found, err := ReadPluginRoute(newPlugin.Name); err != nil { - return Plugin{}, fmt.Errorf("failed to check if plugin already installed, error: %s", err) - } else if found { - if route.Source != pluginSourceURI { - return Plugin{}, fmt.Errorf("plugin already installed with name (%s) from different source (%s)", route.Name, route.Source) - } - - installedPluginVersionPtr, err := GetPluginVersion(route.Name) - if err != nil { - return Plugin{}, fmt.Errorf("failed to check installed plugin (%s) version, error: %s", route.Name, err) - } - - if installedPluginVersionPtr != nil { - log.Warnf("installed plugin found with version (%s), overriding it...", (*installedPluginVersionPtr).String()) - } else { - log.Warnf("installed local plugin found, overriding it...") - } - } - // --- - - tmpPluginDir, err := pathutil.NormalizedOSTempDirPath("__plugin__") - if err != nil { - return Plugin{}, fmt.Errorf("failed to create tmp plugin dir, error: %s", err) - } - - // Install plugin executable - executableURL := newPlugin.ExecutableURL() - if executableURL != "" { - tmpPluginBinDir := filepath.Join(tmpPluginDir, "bin") - if err := os.MkdirAll(tmpPluginBinDir, 0777); err != nil { - return Plugin{}, fmt.Errorf("failed to create tmp plugin bin dir, error: %s", err) - } - - tmpPluginBinPth := filepath.Join(tmpPluginBinDir, newPlugin.Name) - - var err error - progress.NewDefaultWrapper("Downloading plugin binary").WrapAction(func() { - err = downloadPluginBin(executableURL, tmpPluginBinPth) - }) - if err != nil { - return Plugin{}, fmt.Errorf("failed to download plugin executable from (%s), error: %s", executableURL, err) - } - } - // --- - - // Install plugin source - tmpPluginSrcDir := filepath.Join(tmpPluginDir, "src") - if err := os.MkdirAll(tmpPluginSrcDir, 0777); err != nil { - return Plugin{}, fmt.Errorf("failed to create tmp plugin src dir, error: %s", err) - } - - if err := command.CopyDir(pluginLocalPth, tmpPluginSrcDir, true); err != nil { - return Plugin{}, fmt.Errorf("failed to copy plugin from (%s) to (%s), error: %s", pluginLocalPth, tmpPluginSrcDir, err) - } - // --- - - // Create plugin work dir - tmpPluginDataDir := filepath.Join(tmpPluginDir, "data") - if err := os.MkdirAll(tmpPluginDataDir, 0777); err != nil { - return Plugin{}, fmt.Errorf("failed to create tmp plugin data dir (%s), error: %s", tmpPluginDataDir, err) - } - // --- - - pluginDir := GetPluginDir(newPlugin.Name) - if err := command.CopyDir(tmpPluginDir, pluginDir, true); err != nil { - if err := cleanupPlugin(newPlugin.Name); err != nil { - log.Warnf("Failed to cleanup plugin (%s), error: %s", newPlugin.Name, err) - } - return Plugin{}, fmt.Errorf("failed to copy plugin, error: %s", err) - } - - if executableURL != "" { - pluginBinDir := GetPluginBinDir(newPlugin.Name) - pluginBinPth := filepath.Join(pluginBinDir, newPlugin.Name) - if err := os.Chmod(pluginBinPth, 0777); err != nil { - if err := cleanupPlugin(newPlugin.Name); err != nil { - log.Warnf("Failed to cleanup plugin (%s), error: %s", newPlugin.Name, err) - } - return Plugin{}, fmt.Errorf("failed to make plugin bin executable, error: %s", err) - } - } - - return newPlugin, nil -} - -func isLocalURL(urlStr string) bool { - parsed, err := url.Parse(urlStr) - if err != nil { - return false - } - if parsed == nil { - return false - } - return (parsed.Scheme == "file" || parsed.Scheme == "") -} - -//======================================= -// Main -//======================================= - -// InstallPlugin ... -func InstallPlugin(pluginSourceURI, versionTag string) (Plugin, string, error) { - newVersion := "" - pluginDir := "" - - if !isLocalURL(pluginSourceURI) { - pluginSrcTmpDir, err := pathutil.NormalizedOSTempDirPath("plugin-src-tmp") - if err != nil { - return Plugin{}, "", fmt.Errorf("failed to create plugin src temp directory, error: %s", err) - } - defer func() { - if err := os.RemoveAll(pluginSrcTmpDir); err != nil { - log.Warnf("Failed to remove path (%s)", pluginSrcTmpDir) - } - }() - - version := "" - err = nil - - progress.NewDefaultWrapper("git clone plugin source").WrapAction(func() { - version, err = GitCloneAndCheckoutVersionOrLatestVersion(pluginSrcTmpDir, pluginSourceURI, versionTag) - }) - - if err != nil { - return Plugin{}, "", fmt.Errorf("failed to download plugin, error: %s", err) - } - - pluginDir = pluginSrcTmpDir - newVersion = version - } else { - pluginSourceURI = strings.TrimPrefix(pluginSourceURI, "file://") - pluginDir = pluginSourceURI - } - - newPlugin, err := installLocalPlugin(pluginSourceURI, pluginDir) - if err != nil { - return Plugin{}, "", err - } - - // Register plugin - if err := CreateAndAddPluginRoute(newPlugin, pluginSourceURI, newVersion); err != nil { - if err := cleanupPlugin(newPlugin.Name); err != nil { - log.Warnf("Failed to cleanup plugin (%s), error: %s", newPlugin.Name, err) - } - return Plugin{}, "", fmt.Errorf("failed to add plugin route, error: %s", err) - } - // --- - - return newPlugin, newVersion, nil -} - -// DeletePlugin ... -func DeletePlugin(name string) error { - pluginDir := GetPluginDir(name) - - if exists, err := pathutil.IsDirExists(pluginDir); err != nil { - return err - } else if !exists { - return fmt.Errorf("Plugin (%s) not installed", name) - } - - if err := os.RemoveAll(pluginDir); err != nil { - return fmt.Errorf("failed to delete dir (%s)", pluginDir) - } - - return DeletePluginRoute(name) -} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/install_test.go b/vendor/github.com/bitrise-io/bitrise/plugins/install_test.go deleted file mode 100644 index 53833e9a..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/install_test.go +++ /dev/null @@ -1,223 +0,0 @@ -package plugins - -import ( - "os" - "path/filepath" - "testing" - - "github.com/bitrise-io/go-utils/pathutil" - ver "github.com/hashicorp/go-version" - "github.com/stretchr/testify/require" -) - -const examplePluginGitURL = "https://github.com/bitrise-core/bitrise-plugins-example.git" -const analyticsPluginBinURL = "https://github.com/bitrise-core/bitrise-plugins-analytics/releases/download/0.9.1/analytics-Darwin-x86_64" - -func TestIsLocalURL(t *testing.T) { - t.Log("local url - absolute") - { - require.Equal(t, true, isLocalURL("/usr/bin")) - } - - t.Log("local url - relative") - { - require.Equal(t, true, isLocalURL("../usr/bin")) - } - - t.Log("local url - with prefix: file://") - { - require.Equal(t, true, isLocalURL("file:///usr/bin")) - } - - t.Log("local url - relative with prefix: file://") - { - require.Equal(t, true, isLocalURL("file://./../usr/bin")) - } - - t.Log("remote url") - { - require.Equal(t, false, isLocalURL("https://bitrise.io")) - } - - t.Log("remote url- git ssh url") - { - require.Equal(t, false, isLocalURL("git@github.com:bitrise-io/bitrise.git")) - } -} - -func TestValidateVersion(t *testing.T) { - t.Log("required min - pass") - { - requiredMin, err := ver.NewVersion("1.0.0") - require.NoError(t, err) - - current, err := ver.NewVersion("1.0.1") - require.NoError(t, err) - - err = validateVersion(*current, *requiredMin, nil) - require.NoError(t, err) - } - - t.Log("required min - fail") - { - requiredMin, err := ver.NewVersion("1.0.2") - require.NoError(t, err) - - current, err := ver.NewVersion("1.0.1") - require.NoError(t, err) - - err = validateVersion(*current, *requiredMin, nil) - require.Error(t, err) - } - - t.Log("required min + required max - pass") - { - requiredMin, err := ver.NewVersion("1.0.0") - require.NoError(t, err) - - requiredMax, err := ver.NewVersion("1.0.2") - require.NoError(t, err) - - current, err := ver.NewVersion("1.0.1") - require.NoError(t, err) - - err = validateVersion(*current, *requiredMin, requiredMax) - require.NoError(t, err) - } - - t.Log("required min + required max - pass") - { - requiredMin, err := ver.NewVersion("1.0.0") - require.NoError(t, err) - - requiredMax, err := ver.NewVersion("1.0.0") - require.NoError(t, err) - - current, err := ver.NewVersion("1.0.0") - require.NoError(t, err) - - err = validateVersion(*current, *requiredMin, requiredMax) - require.NoError(t, err) - } - - t.Log("required min + required max - min fail") - { - requiredMin, err := ver.NewVersion("1.0.1") - require.NoError(t, err) - - requiredMax, err := ver.NewVersion("1.0.2") - require.NoError(t, err) - - current, err := ver.NewVersion("1.0.0") - require.NoError(t, err) - - err = validateVersion(*current, *requiredMin, requiredMax) - require.Error(t, err) - } - - t.Log("required min + required max - max fail") - { - requiredMin, err := ver.NewVersion("1.0.0") - require.NoError(t, err) - - requiredMax, err := ver.NewVersion("1.0.1") - require.NoError(t, err) - - current, err := ver.NewVersion("1.0.2") - require.NoError(t, err) - - err = validateVersion(*current, *requiredMin, requiredMax) - require.Error(t, err) - } -} - -func TestValidateRequirements(t *testing.T) { - bitriseVersion, err := ver.NewVersion("1.0.0") - require.NoError(t, err) - - envmanVersion, err := ver.NewVersion("1.0.0") - require.NoError(t, err) - - stepmanVersion, err := ver.NewVersion("1.0.0") - require.NoError(t, err) - - currentVersionMap := map[string]ver.Version{ - "bitrise": *bitriseVersion, - "envman": *envmanVersion, - "stepman": *stepmanVersion, - } - - t.Log("valid requirements") - { - requirements := []Requirement{ - Requirement{ - Tool: "bitrise", - MinVersion: "1.0.0", - MaxVersion: "1.0.0", - }, - Requirement{ - Tool: "envman", - MinVersion: "0.9.0", - MaxVersion: "1.1.0", - }, - Requirement{ - Tool: "stepman", - MinVersion: "1.0.0", - MaxVersion: "1.0.0", - }, - } - - err := validateRequirements(requirements, currentVersionMap) - require.NoError(t, err) - } - - t.Log("invalid requirements") - { - requirements := []Requirement{ - Requirement{ - Tool: "bitrise", - MinVersion: "1.0.0", - MaxVersion: "1.0.0", - }, - Requirement{ - Tool: "envman", - MinVersion: "1.1.0", - MaxVersion: "1.1.0", - }, - Requirement{ - Tool: "stepman", - MinVersion: "1.0.0", - MaxVersion: "1.0.0", - }, - } - - err := validateRequirements(requirements, currentVersionMap) - require.Error(t, err) - } -} - -func TestDownloadPluginBin(t *testing.T) { - t.Log("example plugin bin - ") - { - pluginBinURL := analyticsPluginBinURL - destinationDir, err := pathutil.NormalizedOSTempDirPath("TestDownloadPluginBin") - require.NoError(t, err) - - exist, err := pathutil.IsPathExists(destinationDir) - require.NoError(t, err) - if exist { - err := os.RemoveAll(destinationDir) - require.NoError(t, err) - } - - require.NoError(t, os.MkdirAll(destinationDir, 0777)) - - destinationPth := filepath.Join(destinationDir, "example") - - require.NoError(t, downloadPluginBin(pluginBinURL, destinationPth)) - - exist, err = pathutil.IsPathExists(destinationPth) - require.NoError(t, err) - require.Equal(t, true, exist) - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/model_methods_test.go b/vendor/github.com/bitrise-io/bitrise/plugins/model_methods_test.go deleted file mode 100644 index 86a2d0c8..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/model_methods_test.go +++ /dev/null @@ -1,368 +0,0 @@ -package plugins - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func write(t *testing.T, content, toPth string) { - toDir := filepath.Dir(toPth) - exist, err := pathutil.IsDirExists(toDir) - require.NoError(t, err) - if !exist { - require.NoError(t, os.MkdirAll(toDir, 0700)) - } - require.NoError(t, fileutil.WriteStringToFile(toPth, content)) -} - -func TestParseAndValidatePluginFromYML(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__plugin_test__") - require.NoError(t, err) - - t.Log("simple plugin - with executables") - { - pluginStr := `name: step -description: |- - Manage Bitrise CLI steps -trigger: -executable: - osx: bin_url - linux: bin_url -requirements: -- tool: bitrise - min_version: 1.3.0 - max_version: "" -` - - pth := filepath.Join(tmpDir, "bitrise-plugin.yml") - write(t, pluginStr, pth) - - plugin, err := ParsePluginFromYML(pth) - require.NoError(t, err) - - require.NoError(t, validatePlugin(plugin, pth)) - - require.Equal(t, "step", plugin.Name) - require.Equal(t, "Manage Bitrise CLI steps", plugin.Description) - require.Equal(t, 1, len(plugin.Requirements)) - - requirement := plugin.Requirements[0] - require.Equal(t, "bitrise", requirement.Tool) - require.Equal(t, "1.3.0", requirement.MinVersion) - require.Equal(t, "", requirement.MaxVersion) - } - - t.Log("invalid plugin - no name") - { - pluginStr := `name: -description: |- - Manage Bitrise CLI steps -trigger: -executable: - osx: bin_url - linux: bin_url -requirements: -- tool: bitrise - min_version: 1.3.0 - max_version: "" -` - - pth := filepath.Join(tmpDir, "bitrise-plugin.yml") - write(t, pluginStr, pth) - - plugin, err := ParsePluginFromYML(pth) - require.NoError(t, err) - require.EqualError(t, validatePlugin(plugin, pth), "missing name") - } - - t.Log("invalid plugin - no linux executable") - { - pluginStr := `name: step -description: |- - Manage Bitrise CLI steps -trigger: -executable: - osx: bin_url - linux: -requirements: -- tool: bitrise - min_version: 1.3.0 - max_version: "" -` - - pth := filepath.Join(tmpDir, "bitrise-plugin.yml") - write(t, pluginStr, pth) - - plugin, err := ParsePluginFromYML(pth) - require.NoError(t, err) - require.EqualError(t, validatePlugin(plugin, pth), "both osx and linux executable should be defined, or non of them") - } - - t.Log("invalid plugin - no osx executable") - { - pluginStr := `name: step -description: |- - Manage Bitrise CLI steps -trigger: -executable: - osx: - linux: bin_url -requirements: -- tool: bitrise - min_version: 1.3.0 - max_version: "" -` - - pth := filepath.Join(tmpDir, "bitrise-plugin.yml") - write(t, pluginStr, pth) - - plugin, err := ParsePluginFromYML(pth) - require.NoError(t, err) - require.EqualError(t, validatePlugin(plugin, pth), "both osx and linux executable should be defined, or non of them") - } - - t.Log("invalid plugin - no executables, no bitrise-plugin.sh") - { - pluginStr := `name: step -description: |- - Manage Bitrise CLI steps -trigger: -requirements: -- tool: bitrise - min_version: 1.3.0 - max_version: "" -` - - pth := filepath.Join(tmpDir, "bitrise-plugin.yml") - write(t, pluginStr, pth) - - plugin, err := ParsePluginFromYML(pth) - require.NoError(t, err) - - err = validatePlugin(plugin, pth) - require.Error(t, err) - require.Equal(t, true, strings.Contains(err.Error(), "no executable defined, nor bitrise-plugin.sh exist at:")) - } - - t.Log("simple plugin - with bitrise-plugin.sh") - { - pluginStr := `name: step -description: |- - Manage Bitrise CLI steps -trigger: -requirements: -- tool: bitrise - min_version: 1.3.0 - max_version: "" -` - - pth := filepath.Join(tmpDir, "bitrise-plugin.yml") - write(t, pluginStr, pth) - - write(t, "test", filepath.Join(tmpDir, "bitrise-plugin.sh")) - - plugin, err := ParsePluginFromYML(pth) - require.NoError(t, err) - - require.NoError(t, validatePlugin(plugin, pth)) - - require.Equal(t, "step", plugin.Name) - require.Equal(t, "Manage Bitrise CLI steps", plugin.Description) - require.Equal(t, 1, len(plugin.Requirements)) - - requirement := plugin.Requirements[0] - require.Equal(t, "bitrise", requirement.Tool) - require.Equal(t, "1.3.0", requirement.MinVersion) - require.Equal(t, "", requirement.MaxVersion) - } -} - -func TestSortByName(t *testing.T) { - t.Log("single plugin") - { - pluginA := Plugin{Name: "A"} - - plugins := []Plugin{pluginA} - - SortByName(plugins) - require.Equal(t, "A", plugins[0].Name) - } - - t.Log("simple sort") - { - pluginA := Plugin{Name: "A"} - pluginB := Plugin{Name: "B"} - pluginC := Plugin{Name: "C"} - - plugins := []Plugin{pluginC, pluginA, pluginB} - - SortByName(plugins) - require.Equal(t, "A", plugins[0].Name) - require.Equal(t, "B", plugins[1].Name) - require.Equal(t, "C", plugins[2].Name) - } -} - -func TestNewPluginRoutingFromBytes(t *testing.T) { - t.Log("simple routing") - { - routingStr := `route_map: - name: - name: name - source: source - version: "1.0.0" - commit_hash: hash - executable: "./test" -` - - routing, err := NewPluginRoutingFromBytes([]byte(routingStr)) - require.NoError(t, err) - - route, found := routing.RouteMap["name"] - require.Equal(t, true, found) - require.Equal(t, "name", route.Name) - require.Equal(t, "source", route.Source) - require.Equal(t, "1.0.0", route.Version) - require.Equal(t, "hash", route.CommitHash) - require.Equal(t, "./test", route.Executable) - } -} - -func TestValidateRouting(t *testing.T) { - t.Log("simple routing") - { - routing := PluginRouting{ - RouteMap: map[string]PluginRoute{ - "test": PluginRoute{ - Name: "test", - Source: "source", - Version: "1.0.0", - CommitHash: "hash", - Executable: "./executable", - }, - }, - } - - require.NoError(t, routing.Validate()) - } - - t.Log("invalid routing - missing required route's key") - { - routing := PluginRouting{ - RouteMap: map[string]PluginRoute{ - "": PluginRoute{ - Name: "test", - Source: "source", - Version: "1.0.0", - CommitHash: "hash", - Executable: "./executable", - }, - }, - } - - require.Error(t, routing.Validate()) - } - - t.Log("invalid routing - route's key, route's name missmatch") - { - routing := PluginRouting{ - RouteMap: map[string]PluginRoute{ - "test1": PluginRoute{ - Name: "test2", - Source: "source", - Version: "1.0.0", - CommitHash: "hash", - Executable: "./executable", - }, - }, - } - - require.Error(t, routing.Validate()) - } -} - -func TestAddRoute(t *testing.T) { - t.Log("simple add") - { - routing := PluginRouting{ - RouteMap: map[string]PluginRoute{ - "test1": PluginRoute{ - Name: "test1", - Source: "source1", - Version: "1.0.1", - CommitHash: "hash1", - Executable: "./executable1", - }, - }, - } - - route := PluginRoute{ - Name: "test2", - Source: "source2", - Version: "1.0.2", - CommitHash: "hash2", - Executable: "./executable2", - } - - routing.AddRoute(route) - - route, found := routing.RouteMap["test1"] - require.Equal(t, true, found) - require.Equal(t, "test1", route.Name) - require.Equal(t, "source1", route.Source) - require.Equal(t, "1.0.1", route.Version) - require.Equal(t, "hash1", route.CommitHash) - require.Equal(t, "./executable1", route.Executable) - - route, found = routing.RouteMap["test2"] - require.Equal(t, true, found) - require.Equal(t, "test2", route.Name) - require.Equal(t, "source2", route.Source) - require.Equal(t, "1.0.2", route.Version) - require.Equal(t, "hash2", route.CommitHash) - require.Equal(t, "./executable2", route.Executable) - } -} - -func DeleteRoute(t *testing.T) { - t.Log("simple delete") - { - routing := PluginRouting{ - RouteMap: map[string]PluginRoute{ - "test1": PluginRoute{ - Name: "test1", - Source: "source1", - Version: "1.0.1", - CommitHash: "hash1", - Executable: "./executable1", - }, - "test2": PluginRoute{ - Name: "test2", - Source: "source2", - Version: "1.0.2", - CommitHash: "hash2", - Executable: "./executable2", - }, - }, - } - - routing.DeleteRoute("test2") - - route, found := routing.RouteMap["test1"] - require.Equal(t, true, found) - require.Equal(t, "test1", route.Name) - require.Equal(t, "source1", route.Source) - require.Equal(t, "1.0.1", route.Version) - require.Equal(t, "hash1", route.CommitHash) - require.Equal(t, "./executable1", route.Executable) - - route, found = routing.RouteMap["test2"] - require.Equal(t, false, found) - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/models.go b/vendor/github.com/bitrise-io/bitrise/plugins/models.go deleted file mode 100644 index b498c0a0..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/models.go +++ /dev/null @@ -1,60 +0,0 @@ -package plugins - -const ( - // TypeGeneric ... - TypeGeneric = "_" - // TypeInit ... - TypeInit = "init" - // TypeRun .... - TypeRun = "run" -) - -// PluginRoute ... -type PluginRoute struct { - Name string `yaml:"name"` - Source string `yaml:"source"` - Version string `yaml:"version"` - CommitHash string `yaml:"commit_hash"` - Executable string `yaml:"executable"` - TriggerEvent string `yaml:"trigger"` - LatestAvailableVersion string `yaml:"latest_available_version"` -} - -// PluginRouting ... -type PluginRouting struct { - RouteMap map[string]PluginRoute `yaml:"route_map"` -} - -// ExecutableModel ... -type ExecutableModel struct { - OSX string `yaml:"osx,omitempty"` - Linux string `yaml:"linux,omitempty"` -} - -// Requirement ... -type Requirement struct { - Tool string `yaml:"tool"` - MinVersion string `yaml:"min_version"` - MaxVersion string `yaml:"max_version"` -} - -// Plugin ... -type Plugin struct { - Name string `yaml:"name,omitempty"` - Description string `yaml:"description,omitempty"` - Executable ExecutableModel `yaml:"executable,omitempty"` - TriggerEvent string `yaml:"trigger,omitempty"` - Requirements []Requirement `yaml:"requirements,omitempty"` -} - -// PluginInfoModel ... -type PluginInfoModel struct { - Name string `json:"name,omitempty"` - Version string `json:"version,omitempty"` - Source string `json:"source,omitempty"` - Plugin Plugin `json:"plugin,omitempty"` - DefinitionPth string `json:"definition_pth,omitempty"` -} - -// PluginInfos ... -type PluginInfos []PluginInfoModel diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/models_methods.go b/vendor/github.com/bitrise-io/bitrise/plugins/models_methods.go deleted file mode 100644 index f6034c00..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/models_methods.go +++ /dev/null @@ -1,344 +0,0 @@ -package plugins - -import ( - "encoding/json" - "errors" - "fmt" - "path/filepath" - "sort" - - "gopkg.in/yaml.v2" - - "github.com/bitrise-io/bitrise/version" - "github.com/bitrise-io/go-utils/colorstring" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - ver "github.com/hashicorp/go-version" -) - -//======================================= -// Plugin -//======================================= - -// String ... -func (info PluginInfoModel) String() string { - str := fmt.Sprintf("%s %s\n", colorstring.Blue("Name:"), info.Name) - str += fmt.Sprintf("%s %s\n", colorstring.Blue("Version:"), info.Version) - str += fmt.Sprintf("%s %s\n", colorstring.Blue("Source:"), info.Source) - str += fmt.Sprintf("%s\n", colorstring.Blue("Definition:")) - - definition, err := fileutil.ReadStringFromFile(info.DefinitionPth) - if err != nil { - str += colorstring.Redf("Failed to read plugin definition, error: %s", err) - return str - } - - str += definition - return str -} - -// JSON ... -func (info PluginInfoModel) JSON() string { - bytes, err := json.Marshal(info) - if err != nil { - return fmt.Sprintf(`"Failed to marshal plugin info (%#v), err: %s"`, info, err) - } - return string(bytes) + "\n" -} - -// String ... -func (infos PluginInfos) String() string { - str := "" - for _, info := range infos { - str += info.String() - str += "\n---\n\n" - } - return str -} - -// JSON ... -func (infos PluginInfos) JSON() string { - bytes, err := json.Marshal(infos) - if err != nil { - return fmt.Sprintf(`"Failed to marshal plugin infos (%#v), err: %s"`, infos, err) - } - return string(bytes) + "\n" -} - -func validateRequirements(requirements []Requirement, currentVersionMap map[string]ver.Version) error { - var err error - - for _, requirement := range requirements { - currentVersion := currentVersionMap[requirement.Tool] - - var minVersionPtr *ver.Version - if requirement.MinVersion == "" { - return fmt.Errorf("plugin requirement min version is required") - } - - minVersionPtr, err = ver.NewVersion(requirement.MinVersion) - if err != nil { - return fmt.Errorf("failed to parse plugin required min version (%s) for tool (%s), error: %s", requirement.MinVersion, requirement.Tool, err) - } - - var maxVersionPtr *ver.Version - if requirement.MaxVersion != "" { - maxVersionPtr, err = ver.NewVersion(requirement.MaxVersion) - if err != nil { - return fmt.Errorf("failed to parse plugin requirement version (%s) for tool (%s), error: %s", requirement.MaxVersion, requirement.Tool, err) - } - } - - if err := validateVersion(currentVersion, *minVersionPtr, maxVersionPtr); err != nil { - return fmt.Errorf("checking plugin tool (%s) requirements failed, error: %s", requirement.Tool, err) - } - } - - return nil -} - -func parsePluginFromBytes(bytes []byte) (plugin Plugin, err error) { - if err = yaml.Unmarshal(bytes, &plugin); err != nil { - return Plugin{}, err - } - return plugin, nil -} - -func validatePlugin(plugin Plugin, pluginDefinitionPth string) error { - // Validate plugin - if plugin.Name == "" { - return errors.New("missing name") - } - - osxRemoteExecutable := false - if plugin.Executable.OSX != "" { - osxRemoteExecutable = true - } - - linuxRemoteExecutable := false - if plugin.Executable.Linux != "" { - linuxRemoteExecutable = true - } - - if linuxRemoteExecutable != osxRemoteExecutable { - return errors.New("both osx and linux executable should be defined, or non of them") - } - - if !linuxRemoteExecutable && !osxRemoteExecutable { - pluginDir := filepath.Dir(pluginDefinitionPth) - pluginScriptPth := filepath.Join(pluginDir, pluginScriptFileName) - if exist, err := pathutil.IsPathExists(pluginScriptPth); err != nil { - return err - } else if !exist { - return fmt.Errorf("no executable defined, nor bitrise-plugin.sh exist at: %s", pluginScriptPth) - } - } - // --- - - // Ensure dependencies - currentVersionMap, err := version.ToolVersionMap() - if err != nil { - return fmt.Errorf("failed to get current version map, error: %s", err) - } - - if err := validateRequirements(plugin.Requirements, currentVersionMap); err != nil { - return fmt.Errorf("requirements validation failed, error: %s", err) - } - // --- - - return nil -} - -// ParsePluginFromYML ... -func ParsePluginFromYML(ymlPth string) (Plugin, error) { - // Parse plugin - if isExists, err := pathutil.IsPathExists(ymlPth); err != nil { - return Plugin{}, err - } else if !isExists { - return Plugin{}, fmt.Errorf("plugin definition does not exist at: %s", ymlPth) - } - - bytes, err := fileutil.ReadBytesFromFile(ymlPth) - if err != nil { - return Plugin{}, err - } - - plugin, err := parsePluginFromBytes(bytes) - if err != nil { - return Plugin{}, err - } - - return plugin, nil -} - -func (plugin Plugin) String() string { - pluginStr := colorstring.Green(plugin.Name) - pluginStr += fmt.Sprintf("\n Description: %s", plugin.Description) - return pluginStr -} - -func systemOsName() (string, error) { - osOut, err := command.RunCommandAndReturnCombinedStdoutAndStderr("uname", "-s") - if err != nil { - return "", err - } - return strip(osOut), nil -} - -// ExecutableURL ... -func (plugin Plugin) ExecutableURL() string { - systemOS, err := systemOsName() - if err != nil { - return "" - } - - switch systemOS { - case "Darwin": - return plugin.Executable.OSX - case "Linux": - return plugin.Executable.Linux - default: - return "" - } -} - -//======================================= -// Sorting - -// SortByName ... -func SortByName(plugins []Plugin) { - byName := func(p1, p2 *Plugin) bool { - return p1.Name < p2.Name - } - - sortBy(byName).sort(plugins) -} - -type sortBy func(p1, p2 *Plugin) bool - -func (by sortBy) sort(plugins []Plugin) { - ps := &pluginSorter{ - plugins: plugins, - sortBy: by, - } - sort.Sort(ps) -} - -type pluginSorter struct { - plugins []Plugin - sortBy sortBy -} - -//======================================= -// sort.Interface - -func (s *pluginSorter) Len() int { - return len(s.plugins) -} - -func (s *pluginSorter) Swap(i, j int) { - s.plugins[i], s.plugins[j] = s.plugins[j], s.plugins[i] -} - -func (s *pluginSorter) Less(i, j int) bool { - return s.sortBy(&s.plugins[i], &s.plugins[j]) -} - -//======================================= -// PluginRoute -//======================================= - -// NewPluginRoute ... -func NewPluginRoute(name, source, executable, version, triggerEvent string) (PluginRoute, error) { - route := PluginRoute{ - Name: name, - Source: source, - Executable: executable, - Version: version, - TriggerEvent: triggerEvent, - } - if err := route.Validate(); err != nil { - return PluginRoute{}, err - } - return route, nil -} - -// Validate ... -func (route PluginRoute) Validate() error { - if route.Name == "" { - return fmt.Errorf("invalid route: missing required name") - } - if route.Source == "" { - return fmt.Errorf("invalid route: missing required source") - } - if route.Version != "" { - if _, err := ver.NewVersion(route.Version); err != nil { - return fmt.Errorf("invalid route: invalid version (%s)", route.Version) - } - } - return nil -} - -//======================================= -// PluginRouting -//======================================= - -// NewPluginRouting ... -func NewPluginRouting() PluginRouting { - return PluginRouting{RouteMap: map[string]PluginRoute{}} -} - -// NewPluginRoutingFromBytes ... -func NewPluginRoutingFromBytes(bytes []byte) (PluginRouting, error) { - var routing PluginRouting - if err := yaml.Unmarshal(bytes, &routing); err != nil { - return PluginRouting{}, err - } - if err := routing.Validate(); err != nil { - return PluginRouting{}, err - } - return routing, nil -} - -// NewPluginRoutingFromYMLOrEmpty ... -func NewPluginRoutingFromYMLOrEmpty(ymlPth string) (PluginRouting, error) { - if exist, err := pathutil.IsPathExists(ymlPth); err != nil { - return PluginRouting{}, err - } else if exist { - bytes, err := fileutil.ReadBytesFromFile(ymlPth) - if err != nil { - return PluginRouting{}, err - } - - return NewPluginRoutingFromBytes(bytes) - } - - return NewPluginRouting(), nil -} - -// Validate ... -func (routing PluginRouting) Validate() error { - for name, route := range routing.RouteMap { - if name == "" { - return fmt.Errorf("invalid routing: missing required route's key") - } - if name != route.Name { - return fmt.Errorf("invalid routing: route's key (%s) should equal to route's name (%s)", name, route.Name) - } - if err := route.Validate(); err != nil { - return fmt.Errorf("invalid routing: invalid plugin: %s", err) - } - } - return nil -} - -// AddRoute ... -func (routing *PluginRouting) AddRoute(route PluginRoute) { - routing.RouteMap[route.Name] = route -} - -// DeleteRoute ... -func (routing *PluginRouting) DeleteRoute(routeName string) { - delete(routing.RouteMap, routeName) -} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/paths.go b/vendor/github.com/bitrise-io/bitrise/plugins/paths.go deleted file mode 100644 index 73345be8..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/paths.go +++ /dev/null @@ -1,186 +0,0 @@ -package plugins - -import ( - "fmt" - "path/filepath" - - "gopkg.in/yaml.v2" - - "github.com/bitrise-io/bitrise/configs" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" - ver "github.com/hashicorp/go-version" -) - -const ( - pluginsDirName = "plugins" - pluginSpecFileName = "spec.yml" - - pluginScriptFileName = "bitrise-plugin.sh" - pluginDefinitionFileName = "bitrise-plugin.yml" -) - -var ( - pluginsDir = "" - pluginsRoutingPth = "" -) - -// ----------------------- -// --- Routing -// ----------------------- - -// CreateAndAddPluginRoute ... -func CreateAndAddPluginRoute(plugin Plugin, source, version string) error { - newRoute, err := NewPluginRoute(plugin.Name, source, plugin.ExecutableURL(), version, plugin.TriggerEvent) - if err != nil { - return err - } - - return AddPluginRoute(newRoute) -} - -// AddPluginRoute ... -func AddPluginRoute(route PluginRoute) error { - routing, err := readPluginRouting() - if err != nil { - return err - } - - routing.AddRoute(route) - - return writeRoutingToFile(routing) -} - -// DeletePluginRoute ... -func DeletePluginRoute(name string) error { - routing, err := readPluginRouting() - if err != nil { - return err - } - - routing.DeleteRoute(name) - - return writeRoutingToFile(routing) -} - -// GetPluginVersion ... -func GetPluginVersion(name string) (*ver.Version, error) { - route, found, err := ReadPluginRoute(name) - if err != nil { - return nil, err - } - - if !found { - return nil, fmt.Errorf("plugin not installed with name (%s)", name) - } - - if route.Version == "" { - return nil, nil - } - - pluginVersion, err := ver.NewVersion(route.Version) - if err != nil { - return nil, err - } - if pluginVersion == nil { - return nil, fmt.Errorf("failed to parse version (%s)", route.Version) - } - - return pluginVersion, nil -} - -// ReadPluginRoute ... -func ReadPluginRoute(name string) (PluginRoute, bool, error) { - routing, err := readPluginRouting() - if err != nil { - return PluginRoute{}, false, err - } - - route, found := routing.RouteMap[name] - return route, found, nil -} - -func writeRoutingToFile(routing PluginRouting) error { - bytes, err := yaml.Marshal(routing) - if err != nil { - return err - } - - return fileutil.WriteBytesToFile(pluginsRoutingPth, bytes) -} - -func readPluginRouting() (PluginRouting, error) { - return NewPluginRoutingFromYMLOrEmpty(pluginsRoutingPth) -} - -// ----------------------- -// --- Paths -// ----------------------- - -// GetPluginDir ... -func GetPluginDir(name string) string { - return filepath.Join(pluginsDir, name) -} - -// GetPluginSrcDir ... -func GetPluginSrcDir(name string) string { - return filepath.Join(GetPluginDir(name), "src") -} - -// GetPluginBinDir ... -func GetPluginBinDir(name string) string { - return filepath.Join(GetPluginDir(name), "bin") -} - -// GetPluginDataDir ... -func GetPluginDataDir(name string) string { - return filepath.Join(GetPluginDir(name), "data") -} - -// GetPluginDefinitionPath ... -func GetPluginDefinitionPath(name string) string { - return filepath.Join(GetPluginSrcDir(name), pluginDefinitionFileName) -} - -// GetPluginExecutablePath ... -func GetPluginExecutablePath(name string) (string, bool, error) { - route, found, err := ReadPluginRoute(name) - if err != nil { - return "", false, err - } - if !found { - return "", false, fmt.Errorf("plugin not installed with name (%s)", name) - } - - if route.Executable != "" { - return filepath.Join(GetPluginBinDir(name), name), true, nil - } - return filepath.Join(GetPluginSrcDir(name), pluginScriptFileName), false, nil -} - -// ----------------------- -// --- Init -// ----------------------- - -// InitPaths ... -func InitPaths() error { - // Plugins dir - if err := configs.EnsureBitriseConfigDirExists(); err != nil { - log.Errorf("Failed to ensure bitrise configs dir, err: %s", err) - } - - bitriseDir := configs.GetBitriseHomeDirPath() - tmpPluginsDir := filepath.Join(bitriseDir, pluginsDirName) - - if err := pathutil.EnsureDirExist(tmpPluginsDir); err != nil { - return err - } - - pluginsDir = tmpPluginsDir - - // Plugins routing - pluginsRoutingPth = filepath.Join(pluginsDir, pluginSpecFileName) - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/plugins.go b/vendor/github.com/bitrise-io/bitrise/plugins/plugins.go deleted file mode 100644 index 67b73efe..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/plugins.go +++ /dev/null @@ -1,162 +0,0 @@ -package plugins - -import ( - "fmt" - "path/filepath" - "strings" - - "github.com/bitrise-io/go-utils/pathutil" -) - -const ( - // PluginInputPayloadKey ... - PluginInputPayloadKey = "BITRISE_PLUGIN_INPUT_PAYLOAD" - // PluginInputBitriseVersionKey ... - PluginInputBitriseVersionKey = "BITRISE_PLUGIN_INPUT_BITRISE_VERSION" - // PluginInputTriggerEventKey ... - PluginInputTriggerEventKey = "BITRISE_PLUGIN_INPUT_TRIGGER" - // PluginInputPluginModeKey ... - PluginInputPluginModeKey = "BITRISE_PLUGIN_INPUT_PLUGIN_MODE" - // PluginInputDataDirKey ... - PluginInputDataDirKey = "BITRISE_PLUGIN_INPUT_DATA_DIR" - // PluginInputFormatVersionKey ... - PluginInputFormatVersionKey = "BITRISE_PLUGIN_INPUT_FORMAT_VERSION" - - // PluginOutputEnvKey ... - PluginOutputEnvKey = "BITRISE_PLUGIN_OUTPUT" -) - -const bitrisePluginPrefix = ":" - -const ( - // TriggerMode ... - TriggerMode PluginMode = "trigger" - // CommandMode ... - CommandMode PluginMode = "command" -) - -// PluginMode ... -type PluginMode string - -// PluginInput ... -type PluginInput map[string]string - -// ParseArgs ... -func ParseArgs(args []string) (string, []string, bool) { - - if len(args) == 0 { - return "", []string{}, false - } - - pluginName := "" - pluginArgs := []string{} - for idx, arg := range args { - - if strings.Contains(arg, bitrisePluginPrefix) { - pluginSplits := strings.Split(arg, ":") - - if len(pluginSplits) != 2 { - return "", []string{}, false - } - - pluginName = pluginSplits[1] - if len(args) > idx { - pluginArgs = args[idx+1 : len(args)] - } - return pluginName, pluginArgs, true - } - } - - return "", []string{}, false -} - -// CheckForNewVersion ... -func CheckForNewVersion(plugin Plugin) (string, error) { - route, found, err := ReadPluginRoute(plugin.Name) - if err != nil { - return "", err - } - if !found { - return "", fmt.Errorf("no route found for already loaded plugin (%s)", plugin.Name) - } - if route.Version == "" { - // local plugin, can not update - return "", nil - } - - pluginSrcDir := GetPluginSrcDir(plugin.Name) - - gitDirPath := filepath.Join(pluginSrcDir, ".git") - if exist, err := pathutil.IsPathExists(gitDirPath); err != nil { - return "", fmt.Errorf("failed to check if .git folder exist at (%s), error: %s", gitDirPath, err) - } else if !exist { - return "", fmt.Errorf(".git folder not exist at (%s), error: %s", gitDirPath, err) - } - - versions, err := GitVersionTags(pluginSrcDir) - if err != nil { - return "", err - } - - if len(versions) == 0 { - return "", nil - } - - latestVersion := versions[len(versions)-1] - - currentVersion, err := GetPluginVersion(plugin.Name) - if err != nil { - return "", fmt.Errorf("failed to check installed plugin (%s) version, error: %s", plugin.Name, err) - } - - if currentVersion == nil { - return "", nil - } - - if latestVersion.GreaterThan(currentVersion) { - return latestVersion.String(), nil - } - - return "", nil -} - -// LoadPlugin ... -func LoadPlugin(name string) (Plugin, bool, error) { - pluginDir := GetPluginDir(name) - - if exists, err := pathutil.IsDirExists(pluginDir); err != nil { - return Plugin{}, false, fmt.Errorf("Failed to check dir (%s), err: %s", pluginDir, err) - } else if !exists { - return Plugin{}, false, nil - } - - pluginYMLPath := GetPluginDefinitionPath(name) - plugin, err := ParsePluginFromYML(pluginYMLPath) - if err != nil { - return Plugin{}, true, err - } - - return plugin, true, nil -} - -// InstalledPluginList ... -func InstalledPluginList() ([]Plugin, error) { - routing, err := readPluginRouting() - if err != nil { - return []Plugin{}, err - } - - pluginList := []Plugin{} - - for name := range routing.RouteMap { - if plugin, found, err := LoadPlugin(name); err != nil { - return []Plugin{}, err - } else if !found { - return []Plugin{}, fmt.Errorf("Plugin (%s) found in route, but could not load it", name) - } else { - pluginList = append(pluginList, plugin) - } - } - - return pluginList, nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/plugins_test.go b/vendor/github.com/bitrise-io/bitrise/plugins/plugins_test.go deleted file mode 100644 index 7bf913aa..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/plugins_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package plugins - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestParseArgs(t *testing.T) { - t.Log("simple plugin command") - { - args := []string{"bitrise", ":example"} - pluginName, pluginArgs, isPlugin := ParseArgs(args) - require.Equal(t, true, isPlugin) - require.Equal(t, "example", pluginName) - require.Equal(t, 0, len(pluginArgs)) - } - - t.Log("simple plugin command - with bitrise flags") - { - args := []string{"bitrise", "-l", "debug", ":example"} - pluginName, pluginArgs, isPlugin := ParseArgs(args) - require.Equal(t, true, isPlugin) - require.Equal(t, "example", pluginName) - require.Equal(t, 0, len(pluginArgs)) - } - - t.Log("plugin command - with args") - { - args := []string{"bitrise", ":example", "hello", "bitrise"} - pluginName, pluginArgs, isPlugin := ParseArgs(args) - require.Equal(t, true, isPlugin) - require.Equal(t, "example", pluginName) - require.EqualValues(t, []string{"hello", "bitrise"}, pluginArgs) - } - - t.Log("plugin command - with falg") - { - args := []string{"bitrise", ":example", "hello", "--name", "bitrise"} - pluginName, pluginArgs, isPlugin := ParseArgs(args) - require.Equal(t, true, isPlugin) - require.Equal(t, "example", pluginName) - require.EqualValues(t, []string{"hello", "--name", "bitrise"}, pluginArgs) - } - - t.Log("not plugin command") - { - args := []string{"bitrise", "hello", "bitrise"} - pluginName, pluginArgs, isPlugin := ParseArgs(args) - require.Equal(t, false, isPlugin) - require.Equal(t, "", pluginName) - require.Equal(t, 0, len(pluginArgs)) - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/run.go b/vendor/github.com/bitrise-io/bitrise/plugins/run.go deleted file mode 100644 index 530d3f00..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/run.go +++ /dev/null @@ -1,188 +0,0 @@ -package plugins - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/bitrise-io/bitrise/configs" - "github.com/bitrise-io/bitrise/models" - "github.com/bitrise-io/bitrise/tools" - "github.com/bitrise-io/bitrise/version" - "github.com/bitrise-io/go-utils/log" - flog "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" -) - -//======================================= -// Util -//======================================= - -func strip(str string) string { - dirty := true - strippedStr := str - for dirty { - hasWhiteSpacePrefix := false - if strings.HasPrefix(strippedStr, " ") { - hasWhiteSpacePrefix = true - strippedStr = strings.TrimPrefix(strippedStr, " ") - } - - hasWhiteSpaceSuffix := false - if strings.HasSuffix(strippedStr, " ") { - hasWhiteSpaceSuffix = true - strippedStr = strings.TrimSuffix(strippedStr, " ") - } - - hasNewlinePrefix := false - if strings.HasPrefix(strippedStr, "\n") { - hasNewlinePrefix = true - strippedStr = strings.TrimPrefix(strippedStr, "\n") - } - - hasNewlineSuffix := false - if strings.HasSuffix(strippedStr, "\n") { - hasNewlinePrefix = true - strippedStr = strings.TrimSuffix(strippedStr, "\n") - } - - if !hasWhiteSpacePrefix && !hasWhiteSpaceSuffix && !hasNewlinePrefix && !hasNewlineSuffix { - dirty = false - } - } - return strippedStr -} - -//======================================= -// Main -//======================================= - -// RunPluginByEvent ... -func RunPluginByEvent(plugin Plugin, pluginInput PluginInput) error { - pluginInput[PluginInputPluginModeKey] = string(TriggerMode) - - return runPlugin(plugin, []string{}, pluginInput) -} - -// RunPluginByCommand ... -func RunPluginByCommand(plugin Plugin, args []string) error { - pluginInput := PluginInput{ - PluginInputPluginModeKey: string(CommandMode), - } - - return runPlugin(plugin, args, pluginInput) -} - -func printPluginUpdateInfos(newVersion string, plugin Plugin) { - flog.Warnf("") - flog.Warnf("New version (%s) of plugin (%s) available", newVersion, plugin.Name) - flog.Printf("Run command to update plugin:") - fmt.Println() - flog.Donef("$ bitrise plugin update %s", plugin.Name) -} - -func runPlugin(plugin Plugin, args []string, pluginInput PluginInput) error { - if !configs.IsCIMode && configs.CheckIsPluginUpdateCheckRequired() { - // Check for new version - log.Infof("Checking for plugin (%s) new version...", plugin.Name) - - if newVersion, err := CheckForNewVersion(plugin); err != nil { - log.Warnf("") - log.Warnf("Failed to check for plugin (%s) new version, error: %s", plugin.Name, err) - } else if newVersion != "" { - printPluginUpdateInfos(newVersion, plugin) - - route, found, err := ReadPluginRoute(plugin.Name) - if err != nil { - return err - } - if !found { - return fmt.Errorf("no route found for already loaded plugin (%s)", plugin.Name) - } - - route.LatestAvailableVersion = newVersion - - if err := AddPluginRoute(route); err != nil { - return fmt.Errorf("failed to register available plugin (%s) update (%s), error: %s", plugin.Name, newVersion, err) - } - } else { - } - - if err := configs.SavePluginUpdateCheck(); err != nil { - return err - } - - fmt.Println() - } else { - route, found, err := ReadPluginRoute(plugin.Name) - if err != nil { - return err - } - if !found { - return fmt.Errorf("no route found for already loaded plugin (%s)", plugin.Name) - } - - if route.LatestAvailableVersion != "" { - printPluginUpdateInfos(route.LatestAvailableVersion, plugin) - fmt.Println() - } - } - - // Append common data to plugin iputs - bitriseVersion, err := version.BitriseCliVersion() - if err != nil { - return err - } - pluginInput[PluginInputBitriseVersionKey] = bitriseVersion.String() - pluginInput[PluginInputDataDirKey] = GetPluginDataDir(plugin.Name) - pluginInput[PluginInputFormatVersionKey] = models.Version - - // Prepare plugin envstore - pluginWorkDir, err := pathutil.NormalizedOSTempDirPath("plugin-work-dir") - if err != nil { - return err - } - defer func() { - if err := os.RemoveAll(pluginWorkDir); err != nil { - log.Warnf("Failed to remove path (%s)", pluginWorkDir) - } - }() - - pluginEnvstorePath := filepath.Join(pluginWorkDir, "envstore.yml") - - if err := tools.EnvmanInitAtPath(pluginEnvstorePath); err != nil { - return err - } - - if err := tools.EnvmanAdd(pluginEnvstorePath, configs.EnvstorePathEnvKey, pluginEnvstorePath, false, false); err != nil { - return err - } - - // Add plugin inputs - for key, value := range pluginInput { - if err := tools.EnvmanAdd(pluginEnvstorePath, key, value, false, false); err != nil { - return err - } - } - - // Run plugin executable - pluginExecutable, isBin, err := GetPluginExecutablePath(plugin.Name) - if err != nil { - return err - } - - cmd := []string{} - - if isBin { - cmd = append([]string{pluginExecutable}, args...) - } else { - cmd = append([]string{"bash", pluginExecutable}, args...) - } - - if _, err := tools.EnvmanRun(pluginEnvstorePath, "", cmd); err != nil { - return err - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/plugins/run_test.go b/vendor/github.com/bitrise-io/bitrise/plugins/run_test.go deleted file mode 100644 index 77387d63..00000000 --- a/vendor/github.com/bitrise-io/bitrise/plugins/run_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package plugins - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestStrip(t *testing.T) { - str := "test case" - require.Equal(t, "test case", strip(str)) - - str = " test case" - require.Equal(t, "test case", strip(str)) - - str = "test case " - require.Equal(t, "test case", strip(str)) - - str = " test case " - require.Equal(t, "test case", strip(str)) - - str = "" - require.Equal(t, "", strip(str)) -} diff --git a/vendor/github.com/bitrise-io/bitrise/release_config.yml b/vendor/github.com/bitrise-io/bitrise/release_config.yml deleted file mode 100644 index 14737840..00000000 --- a/vendor/github.com/bitrise-io/bitrise/release_config.yml +++ /dev/null @@ -1,41 +0,0 @@ -release: - development_branch: master - release_branch: master -changelog: - path: CHANGELOG.md - content_template: |- - {{range .ContentItems}}## {{.EndTaggedCommit.Tag}} ({{.EndTaggedCommit.Date.Format "2006 Jan 02"}}) - - ### Release Notes - - * __BREAKING__ : change 1 - * change 2 - - ### Install or upgrade - - To install this version, run the following commands (in a bash shell): - - ``` - curl -fL https://github.com/bitrise-io/bitrise/releases/download/{{.EndTaggedCommit.Tag}}/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise - ``` - - Then: - - ``` - chmod +x /usr/local/bin/bitrise - ``` - - That's all, you're ready to go! - - Optionally, you can call `bitrise setup` to verify that everything what's required for bitrise to run - is installed and available, but if you forget to do this it'll be performed the first - time you call bitrise run. - - ### Release Commits - {{.StartTaggedCommit.Tag}} -> {{.EndTaggedCommit.Tag}} - - {{range .Commits}}* [{{firstChars .Hash 7}}] {{.Author}} - {{.Message}} ({{.Date.Format "2006 Jan 02"}}) - {{end}} - - {{end}} - header_template: '## Changelog (Current version: {{.Version}})' - footer_template: 'Updated: {{.CurrentDate.Format "2006 Jan 02"}}' diff --git a/vendor/github.com/bitrise-io/bitrise/toolkits/bash.go b/vendor/github.com/bitrise-io/bitrise/toolkits/bash.go deleted file mode 100644 index 38d9ad46..00000000 --- a/vendor/github.com/bitrise-io/bitrise/toolkits/bash.go +++ /dev/null @@ -1,77 +0,0 @@ -package toolkits - -import ( - "fmt" - "path/filepath" - - "github.com/bitrise-io/bitrise/models" - "github.com/bitrise-io/bitrise/utils" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/stringutil" - stepmanModels "github.com/bitrise-io/stepman/models" -) - -// BashToolkit ... -type BashToolkit struct { -} - -// Check ... -func (toolkit BashToolkit) Check() (bool, ToolkitCheckResult, error) { - binPath, err := utils.CheckProgramInstalledPath("bash") - if err != nil { - return false, ToolkitCheckResult{}, fmt.Errorf("Failed to get bash binary path, error: %s", err) - } - - verOut, err := command.RunCommandAndReturnStdout("bash", "--version") - if err != nil { - return false, ToolkitCheckResult{}, fmt.Errorf("Failed to check bash version, error: %s", err) - } - - verStr := stringutil.ReadFirstLine(verOut, true) - - return false, ToolkitCheckResult{ - Path: binPath, - Version: verStr, - }, nil -} - -// IsToolAvailableInPATH ... -func (toolkit BashToolkit) IsToolAvailableInPATH() bool { - binPath, err := utils.CheckProgramInstalledPath("bash") - if err != nil { - return false - } - return len(binPath) > 0 -} - -// Bootstrap ... -func (toolkit BashToolkit) Bootstrap() error { - return nil -} - -// Install ... -func (toolkit BashToolkit) Install() error { - return nil -} - -// ToolkitName ... -func (toolkit BashToolkit) ToolkitName() string { - return "bash" -} - -// PrepareForStepRun ... -func (toolkit BashToolkit) PrepareForStepRun(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) error { - return nil -} - -// StepRunCommandArguments ... -func (toolkit BashToolkit) StepRunCommandArguments(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) ([]string, error) { - entryFile := "step.sh" - if step.Toolkit != nil && step.Toolkit.Bash != nil && step.Toolkit.Bash.EntryFile != "" { - entryFile = step.Toolkit.Bash.EntryFile - } - - stepFilePath := filepath.Join(stepAbsDirPath, entryFile) - cmd := []string{"bash", stepFilePath} - return cmd, nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/toolkits/golang.go b/vendor/github.com/bitrise-io/bitrise/toolkits/golang.go deleted file mode 100644 index d1512717..00000000 --- a/vendor/github.com/bitrise-io/bitrise/toolkits/golang.go +++ /dev/null @@ -1,387 +0,0 @@ -package toolkits - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "regexp" - "runtime" - "strings" - "time" - - "github.com/bitrise-io/bitrise/configs" - "github.com/bitrise-io/bitrise/models" - "github.com/bitrise-io/bitrise/tools" - "github.com/bitrise-io/bitrise/utils" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/go-utils/progress" - "github.com/bitrise-io/go-utils/retry" - "github.com/bitrise-io/go-utils/versions" - stepmanModels "github.com/bitrise-io/stepman/models" - "github.com/bitrise-tools/gows/gows" -) - -const ( - minGoVersionForToolkit = "1.8.3" -) - -// === Base Toolkit struct === - -// GoToolkit ... -type GoToolkit struct { -} - -// ToolkitName ... -func (toolkit GoToolkit) ToolkitName() string { - return "go" -} - -// === Toolkit: Check === - -// GoConfigurationModel ... -type GoConfigurationModel struct { - // full path of the go binary to use - GoBinaryPath string - // GOROOT env var value to set (unless empty) - GOROOT string -} - -func checkGoConfiguration(goConfig GoConfigurationModel) (bool, ToolkitCheckResult, error) { - cmdEnvs := os.Environ() - if len(goConfig.GOROOT) > 0 { - cmdEnvs = append(cmdEnvs, "GOROOT="+goConfig.GOROOT) - } - verOut, err := command.New(goConfig.GoBinaryPath, "version").SetEnvs(cmdEnvs...).RunAndReturnTrimmedOutput() - if err != nil { - return false, ToolkitCheckResult{}, fmt.Errorf("Failed to check go version, error: %s", err) - } - - verStr, err := parseGoVersionFromGoVersionOutput(verOut) - if err != nil { - return false, ToolkitCheckResult{}, fmt.Errorf("Failed to parse go version, error: %s", err) - } - - checkRes := ToolkitCheckResult{ - Path: goConfig.GoBinaryPath, - Version: verStr, - } - - // version check - isVersionOk, err := versions.IsVersionGreaterOrEqual(verStr, minGoVersionForToolkit) - if err != nil { - return false, checkRes, fmt.Errorf("Failed to validate installed go version, error: %s", err) - } - if !isVersionOk { - return true, checkRes, nil - } - - return false, checkRes, nil -} - -func selectGoConfiguration() (bool, ToolkitCheckResult, GoConfigurationModel, error) { - potentialGoConfigurations := []GoConfigurationModel{} - // from PATH - { - binPath, err := utils.CheckProgramInstalledPath("go") - if err == nil { - potentialGoConfigurations = append(potentialGoConfigurations, GoConfigurationModel{GoBinaryPath: binPath}) - } - } - // from Bitrise Toolkits - { - binPath := goBinaryInToolkitFullPath() - if isExist, err := pathutil.IsPathExists(binPath); err != nil { - log.Warnf("Failed to check the status of the 'go' binary inside the Bitrise Toolkit dir, error: %s", err) - } else if isExist { - potentialGoConfigurations = append(potentialGoConfigurations, GoConfigurationModel{ - GoBinaryPath: binPath, - GOROOT: goToolkitInstallRootPath(), - }) - } - } - - isRequireInstall := true - checkResult := ToolkitCheckResult{} - goConfig := GoConfigurationModel{} - var checkError error - for _, aPotentialGoInfoToUse := range potentialGoConfigurations { - isInstReq, chkRes, err := checkGoConfiguration(aPotentialGoInfoToUse) - checkResult = chkRes - checkError = err - if !isInstReq { - // select this one - goConfig = aPotentialGoInfoToUse - isRequireInstall = false - break - } - } - - if len(potentialGoConfigurations) > 0 && isRequireInstall { - log.Warnf("Installed go found (path: %s), but not a supported version: %s", checkResult.Path, checkResult.Version) - } - - return isRequireInstall, checkResult, goConfig, checkError -} - -// Check ... -func (toolkit GoToolkit) Check() (bool, ToolkitCheckResult, error) { - isInstallRequired, checkResult, _, err := selectGoConfiguration() - return isInstallRequired, checkResult, err -} - -func parseGoVersionFromGoVersionOutput(goVersionCallOutput string) (string, error) { - origGoVersionCallOutput := goVersionCallOutput - goVersionCallOutput = strings.TrimSpace(goVersionCallOutput) - if goVersionCallOutput == "" { - return "", errors.New("Failed to parse Go version, error: version call output was empty") - } - - // example goVersionCallOutput: go version go1.7 darwin/amd64 - goVerExp := regexp.MustCompile(`go version go(?P[0-9.]+) (?P[a-zA-Z0-9]+/[a-zA-Z0-9]+)`) - expRes := goVerExp.FindStringSubmatch(goVersionCallOutput) - if expRes == nil { - return "", fmt.Errorf("Failed to parse Go version, error: failed to find version in input: %s", origGoVersionCallOutput) - } - verStr := expRes[1] - - return verStr, nil -} - -// IsToolAvailableInPATH ... -func (toolkit GoToolkit) IsToolAvailableInPATH() bool { - if configs.IsDebugUseSystemTools() { - log.Warnf("[BitriseDebug] Using system tools (system installed Go), instead of the ones in BITRISE_HOME") - return true - } - - if _, err := utils.CheckProgramInstalledPath("go"); err != nil { - return false - } - - if _, err := command.RunCommandAndReturnStdout("go", "version"); err != nil { - return false - } - - return true -} - -// === Toolkit: Bootstrap === - -// Bootstrap ... -func (toolkit GoToolkit) Bootstrap() error { - if toolkit.IsToolAvailableInPATH() { - return nil - } - - pthWithGoBins := configs.GeneratePATHEnvString(os.Getenv("PATH"), goToolkitBinsPath()) - if err := os.Setenv("PATH", pthWithGoBins); err != nil { - return fmt.Errorf("Failed to set PATH to include the Go toolkit bins, error: %s", err) - } - - if err := os.Setenv("GOROOT", goToolkitInstallRootPath()); err != nil { - return fmt.Errorf("Failed to set GOROOT to Go toolkit root, error: %s", err) - } - - return nil -} - -// === Toolkit: Install === - -func installGoTar(goTarGzPath string) error { - installToPath := goToolkitInstallToPath() - - if err := os.RemoveAll(installToPath); err != nil { - return fmt.Errorf("Failed to remove previous Go toolkit install (path: %s), error: %s", installToPath, err) - } - if err := pathutil.EnsureDirExist(installToPath); err != nil { - return fmt.Errorf("Failed create Go toolkit directory (path: %s), error: %s", installToPath, err) - } - - cmd := command.New("tar", "-C", installToPath, "-xzf", goTarGzPath) - if combinedOut, err := cmd.RunAndReturnTrimmedCombinedOutput(); err != nil { - log.Errorf(" [!] Failed to uncompress Go toolkit, output:") - log.Errorf(combinedOut) - return fmt.Errorf("Failed to uncompress Go toolkit, error: %s", err) - } - return nil -} - -// Install ... -func (toolkit GoToolkit) Install() error { - versionStr := minGoVersionForToolkit - osStr := runtime.GOOS - archStr := runtime.GOARCH - extentionStr := "tar.gz" - if osStr == "windows" { - extentionStr = "zip" - } - downloadURL := fmt.Sprintf("https://storage.googleapis.com/golang/go%s.%s-%s.%s", versionStr, osStr, archStr, extentionStr) - - goTmpDirPath := goToolkitTmpDirPath() - if err := pathutil.EnsureDirExist(goTmpDirPath); err != nil { - return fmt.Errorf("Failed to create Toolkits TMP directory, error: %s", err) - } - - localFileName := "go." + extentionStr - goArchiveDownloadPath := filepath.Join(goTmpDirPath, localFileName) - - var downloadErr error - progress.NewDefaultWrapper("Downloading").WrapAction(func() { - downloadErr = retry.Times(2).Wait(5 * time.Second).Try(func(attempt uint) error { - if attempt > 0 { - log.Warnf("==> Download failed, retrying ...") - } - return tools.DownloadFile(downloadURL, goArchiveDownloadPath) - }) - }) - if downloadErr != nil { - return fmt.Errorf("Failed to download toolkit (%s), error: %s", downloadURL, downloadErr) - } - - fmt.Println("=> Installing ...") - if err := installGoTar(goArchiveDownloadPath); err != nil { - return fmt.Errorf("Failed to install Go toolkit, error: %s", err) - } - if err := os.Remove(goArchiveDownloadPath); err != nil { - return fmt.Errorf("Failed to remove the downloaded Go archive (path: %s), error: %s", goArchiveDownloadPath, err) - } - fmt.Println("=> Installing [DONE]") - - return nil -} - -// === Toolkit: Prepare for Step Run === - -func goBuildInIsolation(packageName, srcPath, outputBinPath string) error { - workspaceRootPath, err := pathutil.NormalizedOSTempDirPath("bitrise-go-toolkit") - if err != nil { - return fmt.Errorf("Failed to create root directory of isolated workspace, error: %s", err) - } - - // origGOPATH := os.Getenv("GOPATH") - // if origGOPATH == "" { - // return fmt.Errorf("You don't have a GOPATH environment - please set it; GOPATH/bin will be symlinked") - // } - - // if err := gows.CreateGopathBinSymlink(origGOPATH, workspaceRootPath); err != nil { - // return fmt.Errorf("Failed to create GOPATH/bin symlink, error: %s", err) - // } - - fullPackageWorkspacePath := filepath.Join(workspaceRootPath, "src", packageName) - if err := gows.CreateOrUpdateSymlink(srcPath, fullPackageWorkspacePath); err != nil { - return fmt.Errorf("Failed to create Project->Workspace symlink, error: %s", err) - } - - { - isInstallRequired, _, goConfig, err := selectGoConfiguration() - if err != nil { - return fmt.Errorf("Failed to select an appropriate Go installation for compiling the step, error: %s", err) - } - if isInstallRequired { - return fmt.Errorf("Failed to select an appropriate Go installation for compiling the step, error: %s", - "Found Go version is older than required. Please run 'bitrise setup' to check and install the required version") - } - - cmd := gows.CreateCommand(workspaceRootPath, workspaceRootPath, - goConfig.GoBinaryPath, "build", "-o", outputBinPath, packageName) - cmd.Env = append(cmd.Env, "GOROOT="+goConfig.GOROOT) - if err := cmd.Run(); err != nil { - return fmt.Errorf("Failed to install package, error: %s", err) - } - } - - { - if err := os.RemoveAll(workspaceRootPath); err != nil { - return fmt.Errorf("Failed to delete temporary isolated workspace, error: %s", err) - } - } - - return nil -} - -// stepIDorURI : doesn't work for "path::./" yet!! -func stepBinaryFilename(sIDData models.StepIDData) string { - // - replaceRexp, err := regexp.Compile("[^A-Za-z0-9.-]") - if err != nil { - log.Warnf("Invalid regex, error: %s", err) - return "" - } - - compositeStepID := fmt.Sprintf("%s-%s-%s", - sIDData.SteplibSource, sIDData.IDorURI, sIDData.Version) - - safeStepID := replaceRexp.ReplaceAllString(compositeStepID, "_") - // - return safeStepID -} - -func stepBinaryCacheFullPath(sIDData models.StepIDData) string { - return filepath.Join(goToolkitCacheRootPath(), stepBinaryFilename(sIDData)) -} - -// PrepareForStepRun ... -func (toolkit GoToolkit) PrepareForStepRun(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) error { - fullStepBinPath := stepBinaryCacheFullPath(sIDData) - - // try to use cached binary, if possible - if sIDData.IsUniqueResourceID() { - if exists, err := pathutil.IsPathExists(fullStepBinPath); err != nil { - log.Warnf("Failed to check cached binary for step, error: %s", err) - } else if exists { - return nil - } - } - - // it's not cached, so compile it - - if step.Toolkit == nil { - return errors.New("No Toolkit information specified in step") - } - if step.Toolkit.Go == nil { - return errors.New("No Toolkit.Go information specified in step") - } - packageName := step.Toolkit.Go.PackageName - - return goBuildInIsolation(packageName, stepAbsDirPath, fullStepBinPath) -} - -// === Toolkit: Step Run === - -// StepRunCommandArguments ... -func (toolkit GoToolkit) StepRunCommandArguments(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) ([]string, error) { - fullStepBinPath := stepBinaryCacheFullPath(sIDData) - return []string{fullStepBinPath}, nil -} - -// === Toolkit path utility function === - -func goToolkitRootPath() string { - return filepath.Join(configs.GetBitriseToolkitsDirPath(), "go") -} - -func goToolkitTmpDirPath() string { - return filepath.Join(goToolkitRootPath(), "tmp") -} - -func goToolkitInstallToPath() string { - return filepath.Join(goToolkitRootPath(), "inst") -} -func goToolkitCacheRootPath() string { - return filepath.Join(goToolkitRootPath(), "cache") -} - -func goToolkitInstallRootPath() string { - return filepath.Join(goToolkitInstallToPath(), "go") -} - -func goToolkitBinsPath() string { - return filepath.Join(goToolkitInstallRootPath(), "bin") -} - -func goBinaryInToolkitFullPath() string { - return filepath.Join(goToolkitBinsPath(), "go") -} diff --git a/vendor/github.com/bitrise-io/bitrise/toolkits/golang_test.go b/vendor/github.com/bitrise-io/bitrise/toolkits/golang_test.go deleted file mode 100644 index 0b5a3c28..00000000 --- a/vendor/github.com/bitrise-io/bitrise/toolkits/golang_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package toolkits - -import ( - "testing" - - "github.com/bitrise-io/bitrise/models" - "github.com/stretchr/testify/require" -) - -func Test_stepBinaryFilename(t *testing.T) { - { - sIDData := models.StepIDData{SteplibSource: "path", IDorURI: "./", Version: ""} - require.Equal(t, "path-._-", stepBinaryFilename(sIDData)) - } - - { - sIDData := models.StepIDData{SteplibSource: "git", IDorURI: "https://github.com/bitrise-steplib/steps-go-toolkit-hello-world.git", Version: "master"} - require.Equal(t, "git-https___github.com_bitrise-steplib_steps-go-toolkit-hello-world.git-master", stepBinaryFilename(sIDData)) - } - - { - sIDData := models.StepIDData{SteplibSource: "_", IDorURI: "https://github.com/bitrise-steplib/steps-go-toolkit-hello-world.git", Version: "master"} - require.Equal(t, "_-https___github.com_bitrise-steplib_steps-go-toolkit-hello-world.git-master", stepBinaryFilename(sIDData)) - } - - { - sIDData := models.StepIDData{SteplibSource: "https://github.com/bitrise-io/bitrise-steplib.git", IDorURI: "script", Version: "1.2.3"} - require.Equal(t, "https___github.com_bitrise-io_bitrise-steplib.git-script-1.2.3", stepBinaryFilename(sIDData)) - } -} - -func Test_parseGoVersionFromGoVersionOutput(t *testing.T) { - t.Log("Example OK") - { - verStr, err := parseGoVersionFromGoVersionOutput("go version go1.7 darwin/amd64") - require.NoError(t, err) - require.Equal(t, "1.7", verStr) - } - - t.Log("Example OK 2") - { - verStr, err := parseGoVersionFromGoVersionOutput(`go version go1.7 darwin/amd64 - -`) - require.NoError(t, err) - require.Equal(t, "1.7", verStr) - } - - t.Log("Example OK 3") - { - verStr, err := parseGoVersionFromGoVersionOutput("go version go1.7.1 darwin/amd64") - require.NoError(t, err) - require.Equal(t, "1.7.1", verStr) - } - - t.Log("Empty") - { - verStr, err := parseGoVersionFromGoVersionOutput("") - require.EqualError(t, err, "Failed to parse Go version, error: version call output was empty") - require.Equal(t, "", verStr) - } - - t.Log("Empty 2") - { - verStr, err := parseGoVersionFromGoVersionOutput(` - -`) - require.EqualError(t, err, "Failed to parse Go version, error: version call output was empty") - require.Equal(t, "", verStr) - } - - t.Log("Invalid") - { - verStr, err := parseGoVersionFromGoVersionOutput("go version REMOVED darwin/amd64") - require.EqualError(t, err, "Failed to parse Go version, error: failed to find version in input: go version REMOVED darwin/amd64") - require.Equal(t, "", verStr) - } -} diff --git a/vendor/github.com/bitrise-io/bitrise/toolkits/toolkit.go b/vendor/github.com/bitrise-io/bitrise/toolkits/toolkit.go deleted file mode 100644 index 26e165d3..00000000 --- a/vendor/github.com/bitrise-io/bitrise/toolkits/toolkit.go +++ /dev/null @@ -1,82 +0,0 @@ -package toolkits - -import ( - "github.com/bitrise-io/bitrise/models" - stepmanModels "github.com/bitrise-io/stepman/models" -) - -// ToolkitCheckResult ... -type ToolkitCheckResult struct { - Path string - Version string -} - -// Toolkit ... -type Toolkit interface { - // ToolkitName : a one liner name/id of the toolkit, for logging purposes - ToolkitName() string - - // Check the toolkit - first returned value (bool) indicates - // whether the toolkit have to be installed (true=install required | false=no install required). - // "Have to be installed" can be true if the toolkit is not installed, - // or if an older version is installed, and an update/newer version is required. - Check() (bool, ToolkitCheckResult, error) - - // Install the toolkit - Install() error - - // Check whether the toolkit's tool (e.g. Go, Ruby, Bash, ...) is available - // and "usable"" without any bootstrapping. - // Return true even if the version is older than the required version for this toolkit, - // you can pick / init the right version later. This function only checks - // whether the system has a pre-installed version of the toolkit's tool or not, - // no compability check is required! - IsToolAvailableInPATH() bool - - // Bootstrap : initialize the toolkit for use, ONLY IF THERE'S NO SYSTEM INSTALLED VERSION! - // If there's any version of the tool (e.g. Go) installed, Bootstrap should not overwrite it, - // so that the non toolkit steps will still use the system installed version! - // - // Will run only once, before the build would actually start, - // so that it can set e.g. a default Go or other language version, if there's no System Installed version! - // - // Bootstrap should only set a sensible default if there's no System Installed version of the tool, - // but should not enforce the toolkit's version of the tool! - // The `PrepareForStepRun` function will be called for every step, - // the toolkit should be "enforced" there, BUT ONLY FOR THAT FUNCTION (e.g. don't call os.Setenv there!) - Bootstrap() error - - // PrepareForStepRun can be used to pre-compile or otherwise prepare for the step's execution. - // - // Important: do NOT enforce the toolkit for subsequent / unrelated steps or functions, - // the toolkit should/can be "enforced" here (e.g. during the compilation), - // BUT ONLY for this function! E.g. don't call `os.Setenv` or something similar - // which would affect other functions, just pass the required envs to the compilation command! - PrepareForStepRun(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) error - - // StepRunCommandArguments ... - StepRunCommandArguments(step stepmanModels.StepModel, sIDData models.StepIDData, stepAbsDirPath string) ([]string, error) -} - -// -// === Utils === - -// ToolkitForStep ... -func ToolkitForStep(step stepmanModels.StepModel) Toolkit { - if step.Toolkit != nil { - stepToolkit := step.Toolkit - if stepToolkit.Go != nil { - return GoToolkit{} - } else if stepToolkit.Bash != nil { - return BashToolkit{} - } - } - - // default - return BashToolkit{} -} - -// AllSupportedToolkits ... -func AllSupportedToolkits() []Toolkit { - return []Toolkit{GoToolkit{}, BashToolkit{}} -} diff --git a/vendor/github.com/bitrise-io/bitrise/toolkits/toolkit_test.go b/vendor/github.com/bitrise-io/bitrise/toolkits/toolkit_test.go deleted file mode 100644 index cdcc8596..00000000 --- a/vendor/github.com/bitrise-io/bitrise/toolkits/toolkit_test.go +++ /dev/null @@ -1 +0,0 @@ -package toolkits diff --git a/vendor/github.com/bitrise-io/bitrise/tools/tools.go b/vendor/github.com/bitrise-io/bitrise/tools/tools.go deleted file mode 100644 index 4d6bbf52..00000000 --- a/vendor/github.com/bitrise-io/bitrise/tools/tools.go +++ /dev/null @@ -1,361 +0,0 @@ -package tools - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - - "golang.org/x/sys/unix" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/bitrise/configs" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/errorutil" - "github.com/bitrise-io/go-utils/pathutil" -) - -// UnameGOOS ... -func UnameGOOS() (string, error) { - switch runtime.GOOS { - case "darwin": - return "Darwin", nil - case "linux": - return "Linux", nil - } - return "", fmt.Errorf("Unsupported platform (%s)", runtime.GOOS) -} - -// UnameGOARCH ... -func UnameGOARCH() (string, error) { - switch runtime.GOARCH { - case "amd64": - return "x86_64", nil - } - return "", fmt.Errorf("Unsupported architecture (%s)", runtime.GOARCH) -} - -// InstallToolFromGitHub ... -func InstallToolFromGitHub(toolname, githubUser, toolVersion string) error { - unameGOOS, err := UnameGOOS() - if err != nil { - return fmt.Errorf("Failed to determine OS: %s", err) - } - unameGOARCH, err := UnameGOARCH() - if err != nil { - return fmt.Errorf("Failed to determine ARCH: %s", err) - } - downloadURL := "https://github.com/" + githubUser + "/" + toolname + "/releases/download/" + toolVersion + "/" + toolname + "-" + unameGOOS + "-" + unameGOARCH - - return InstallFromURL(toolname, downloadURL) -} - -// DownloadFile ... -func DownloadFile(downloadURL, targetDirPath string) error { - outFile, err := os.Create(targetDirPath) - defer func() { - if err := outFile.Close(); err != nil { - log.Warnf("Failed to close (%s)", targetDirPath) - } - }() - if err != nil { - return fmt.Errorf("failed to create (%s), error: %s", targetDirPath, err) - } - - resp, err := http.Get(downloadURL) - if err != nil { - return fmt.Errorf("failed to download from (%s), error: %s", downloadURL, err) - } - defer func() { - if err := resp.Body.Close(); err != nil { - log.Warnf("failed to close (%s) body", downloadURL) - } - }() - - _, err = io.Copy(outFile, resp.Body) - if err != nil { - return fmt.Errorf("failed to download from (%s), error: %s", downloadURL, err) - } - - return nil -} - -// InstallFromURL ... -func InstallFromURL(toolBinName, downloadURL string) error { - if len(toolBinName) < 1 { - return fmt.Errorf("no Tool (bin) Name provided! URL was: %s", downloadURL) - } - - tmpDir, err := pathutil.NormalizedOSTempDirPath("__tmp_download_dest__") - if err != nil { - return fmt.Errorf("failed to create tmp dir for download destination") - } - tmpDestinationPth := filepath.Join(tmpDir, toolBinName) - - if err := DownloadFile(downloadURL, tmpDestinationPth); err != nil { - return fmt.Errorf("failed to download, error: %s", err) - } - - bitriseToolsDirPath := configs.GetBitriseToolsDirPath() - destinationPth := filepath.Join(bitriseToolsDirPath, toolBinName) - - if exist, err := pathutil.IsPathExists(destinationPth); err != nil { - return fmt.Errorf("failed to check if file exist (%s), error: %s", destinationPth, err) - } else if exist { - if err := os.Remove(destinationPth); err != nil { - return fmt.Errorf("failed to remove file (%s), error: %s", destinationPth, err) - } - } - - if err := MoveFile(tmpDestinationPth, destinationPth); err != nil { - return fmt.Errorf("failed to copy (%s) to (%s), error: %s", tmpDestinationPth, destinationPth, err) - } - - if err := os.Chmod(destinationPth, 0755); err != nil { - return fmt.Errorf("failed to make file (%s) executable, error: %s", destinationPth, err) - } - - return nil -} - -// ------------------ -// --- Stepman - -// StepmanSetup ... -func StepmanSetup(collection string) error { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "setup", "--collection", collection} - return command.RunCommand("stepman", args...) -} - -// StepmanActivate ... -func StepmanActivate(collection, stepID, stepVersion, dir, ymlPth string) error { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "activate", "--collection", collection, - "--id", stepID, "--version", stepVersion, "--path", dir, "--copyyml", ymlPth} - return command.RunCommand("stepman", args...) -} - -// StepmanUpdate ... -func StepmanUpdate(collection string) error { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "update", "--collection", collection} - return command.RunCommand("stepman", args...) -} - -// StepmanRawStepLibStepInfo ... -func StepmanRawStepLibStepInfo(collection, stepID, stepVersion string) (string, error) { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "step-info", "--collection", collection, - "--id", stepID, "--version", stepVersion, "--format", "raw"} - return command.RunCommandAndReturnCombinedStdoutAndStderr("stepman", args...) -} - -// StepmanRawLocalStepInfo ... -func StepmanRawLocalStepInfo(pth string) (string, error) { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "step-info", "--step-yml", pth, "--format", "raw"} - return command.RunCommandAndReturnCombinedStdoutAndStderr("stepman", args...) -} - -// StepmanJSONStepLibStepInfo ... -func StepmanJSONStepLibStepInfo(collection, stepID, stepVersion string) (string, error) { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "step-info", "--collection", collection, - "--id", stepID, "--version", stepVersion, "--format", "json"} - - var outBuffer bytes.Buffer - var errBuffer bytes.Buffer - - if err := command.RunCommandWithWriters(io.Writer(&outBuffer), io.Writer(&errBuffer), "stepman", args...); err != nil { - return outBuffer.String(), fmt.Errorf("Error: %s, details: %s", err, errBuffer.String()) - } - - return outBuffer.String(), nil -} - -// StepmanJSONLocalStepInfo ... -func StepmanJSONLocalStepInfo(pth string) (string, error) { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "step-info", "--step-yml", pth, "--format", "json"} - - var outBuffer bytes.Buffer - var errBuffer bytes.Buffer - - if err := command.RunCommandWithWriters(io.Writer(&outBuffer), io.Writer(&errBuffer), "stepman", args...); err != nil { - return outBuffer.String(), fmt.Errorf("Error: %s, details: %s", err, errBuffer.String()) - } - - return outBuffer.String(), nil -} - -// StepmanRawStepList ... -func StepmanRawStepList(collection string) (string, error) { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "step-list", "--collection", collection, "--format", "raw"} - return command.RunCommandAndReturnCombinedStdoutAndStderr("stepman", args...) -} - -// StepmanJSONStepList ... -func StepmanJSONStepList(collection string) (string, error) { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "step-list", "--collection", collection, "--format", "json"} - - var outBuffer bytes.Buffer - var errBuffer bytes.Buffer - - if err := command.RunCommandWithWriters(io.Writer(&outBuffer), io.Writer(&errBuffer), "stepman", args...); err != nil { - return outBuffer.String(), fmt.Errorf("Error: %s, details: %s", err, errBuffer.String()) - } - - return outBuffer.String(), nil -} - -// -// Share - -// StepmanShare ... -func StepmanShare() error { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "share", "--toolmode"} - return command.RunCommand("stepman", args...) -} - -// StepmanShareAudit ... -func StepmanShareAudit() error { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "share", "audit", "--toolmode"} - return command.RunCommand("stepman", args...) -} - -// StepmanShareCreate ... -func StepmanShareCreate(tag, git, stepID string) error { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "share", "create", "--tag", tag, "--git", git, "--stepid", stepID, "--toolmode"} - return command.RunCommand("stepman", args...) -} - -// StepmanShareFinish ... -func StepmanShareFinish() error { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "share", "finish", "--toolmode"} - return command.RunCommand("stepman", args...) -} - -// StepmanShareStart ... -func StepmanShareStart(collection string) error { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "share", "start", "--collection", collection, "--toolmode"} - return command.RunCommand("stepman", args...) -} - -// ------------------ -// --- Envman - -// EnvmanInit ... -func EnvmanInit() error { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "init"} - return command.RunCommand("envman", args...) -} - -// EnvmanInitAtPath ... -func EnvmanInitAtPath(envstorePth string) error { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "--path", envstorePth, "init", "--clear"} - return command.RunCommand("envman", args...) -} - -// EnvmanAdd ... -func EnvmanAdd(envstorePth, key, value string, expand, skipIfEmpty bool) error { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "--path", envstorePth, "add", "--key", key, "--append"} - if !expand { - args = append(args, "--no-expand") - } - if skipIfEmpty { - args = append(args, "--skip-if-empty") - } - - envman := exec.Command("envman", args...) - envman.Stdin = strings.NewReader(value) - envman.Stdout = os.Stdout - envman.Stderr = os.Stderr - return envman.Run() -} - -// EnvmanClear ... -func EnvmanClear(envstorePth string) error { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "--path", envstorePth, "clear"} - out, err := command.New("envman", args...).RunAndReturnTrimmedCombinedOutput() - if err != nil { - errorMsg := err.Error() - if errorutil.IsExitStatusError(err) && out != "" { - errorMsg = out - } - return fmt.Errorf("failed to clear envstore (%s), error: %s", envstorePth, errorMsg) - } - return nil -} - -// EnvmanRun ... -func EnvmanRun(envstorePth, workDirPth string, cmd []string) (int, error) { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "--path", envstorePth, "run"} - args = append(args, cmd...) - - return command.RunCommandInDirAndReturnExitCode(workDirPth, "envman", args...) -} - -// EnvmanJSONPrint ... -func EnvmanJSONPrint(envstorePth string) (string, error) { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "--path", envstorePth, "print", "--format", "json", "--expand"} - - var outBuffer bytes.Buffer - var errBuffer bytes.Buffer - - if err := command.RunCommandWithWriters(io.Writer(&outBuffer), io.Writer(&errBuffer), "envman", args...); err != nil { - return outBuffer.String(), fmt.Errorf("Error: %s, details: %s", err, errBuffer.String()) - } - - return outBuffer.String(), nil -} - -// MoveFile ... -func MoveFile(oldpath, newpath string) error { - err := os.Rename(oldpath, newpath) - if err == nil { - return nil - } - - if linkErr, ok := err.(*os.LinkError); ok { - if linkErr.Err == unix.EXDEV { - info, err := os.Stat(oldpath) - if err != nil { - return err - } - - data, err := ioutil.ReadFile(oldpath) - if err != nil { - return err - } - - err = ioutil.WriteFile(newpath, data, info.Mode()) - if err != nil { - return err - } - - return os.Remove(oldpath) - } - } - - return err -} diff --git a/vendor/github.com/bitrise-io/bitrise/tools/tools_test.go b/vendor/github.com/bitrise-io/bitrise/tools/tools_test.go deleted file mode 100644 index 7bbf1bcf..00000000 --- a/vendor/github.com/bitrise-io/bitrise/tools/tools_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package tools - -import ( - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "runtime" - "testing" - - "github.com/bitrise-io/bitrise/configs" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestMoveFile(t *testing.T) { - srcPath := filepath.Join(os.TempDir(), "src.tmp") - _, err := os.Create(srcPath) - require.NoError(t, err) - - dstPath := filepath.Join(os.TempDir(), "dst.tmp") - require.NoError(t, MoveFile(srcPath, dstPath)) - - info, err := os.Stat(dstPath) - require.NoError(t, err) - require.False(t, info.IsDir()) - - require.NoError(t, os.Remove(dstPath)) -} - -func TestMoveFileDifferentDevices(t *testing.T) { - require.True(t, runtime.GOOS == "linux" || runtime.GOOS == "darwin") - - ramdiskPath := "" - ramdiskName := "RAMDISK" - volumeName := "" - if runtime.GOOS == "linux" { - tmpDir, err := ioutil.TempDir("", ramdiskName) - require.NoError(t, err) - - ramdiskPath = tmpDir - require.NoError(t, exec.Command("mount", "-t", "tmpfs", "-o", "size=12m", "tmpfs", ramdiskPath).Run()) - } else if runtime.GOOS == "darwin" { - out, err := command.New("hdiutil", "attach", "-nomount", "ram://64").RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err) - - volumeName = out - require.NoError(t, exec.Command("diskutil", "erasevolume", "MS-DOS", ramdiskName, volumeName).Run()) - - ramdiskPath = "/Volumes/" + ramdiskName - } - - filename := "test.tmp" - srcPath := filepath.Join(os.TempDir(), filename) - _, err := os.Create(srcPath) - require.NoError(t, err) - - dstPath := filepath.Join(ramdiskPath, filename) - require.NoError(t, MoveFile(srcPath, dstPath)) - - info, err := os.Stat(dstPath) - require.NoError(t, err) - require.False(t, info.IsDir()) - - if runtime.GOOS == "linux" { - require.NoError(t, exec.Command("umount", ramdiskPath).Run()) - require.NoError(t, os.RemoveAll(ramdiskPath)) - } else if runtime.GOOS == "darwin" { - require.NoError(t, exec.Command("hdiutil", "detach", volumeName).Run()) - } -} - -func TestStepmanJSONStepLibStepInfo(t *testing.T) { - // setup - require.NoError(t, configs.InitPaths()) - - // Valid params -- Err should empty, output filled - require.Equal(t, nil, StepmanSetup("https://github.com/bitrise-io/bitrise-steplib")) - - outStr, err := StepmanJSONStepLibStepInfo("https://github.com/bitrise-io/bitrise-steplib", "script", "0.9.0") - require.NoError(t, err) - require.NotEqual(t, "", outStr) - - // Invalid params -- Err should empty, output filled - outStr, err = StepmanJSONStepLibStepInfo("https://github.com/bitrise-io/bitrise-steplib", "script", "2") - require.NotEqual(t, nil, err) - require.Equal(t, "", outStr) -} - -func TestEnvmanJSONPrint(t *testing.T) { - // Initialized envstore -- Err should empty, output filled - testDirPth, err := pathutil.NormalizedOSTempDirPath("test_env_store") - require.NoError(t, err) - - envstorePth := filepath.Join(testDirPth, "envstore.yml") - - require.Equal(t, nil, EnvmanInitAtPath(envstorePth)) - - outStr, err := EnvmanJSONPrint(envstorePth) - require.NoError(t, err) - require.NotEqual(t, "", outStr) - - // Not initialized envstore -- Err should filled, output empty - testDirPth, err = pathutil.NormalizedOSTempDirPath("test_env_store") - require.NoError(t, err) - - envstorePth = filepath.Join("test_env_store", "envstore.yml") - - outStr, err = EnvmanJSONPrint(envstorePth) - require.NotEqual(t, nil, err) - require.Equal(t, "", outStr) -} diff --git a/vendor/github.com/bitrise-io/bitrise/utils/utils.go b/vendor/github.com/bitrise-io/bitrise/utils/utils.go deleted file mode 100644 index 31746729..00000000 --- a/vendor/github.com/bitrise-io/bitrise/utils/utils.go +++ /dev/null @@ -1,16 +0,0 @@ -package utils - -import ( - "os" - "os/exec" - "strings" -) - -// CheckProgramInstalledPath ... -func CheckProgramInstalledPath(clcommand string) (string, error) { - cmd := exec.Command("which", clcommand) - cmd.Stderr = os.Stderr - outBytes, err := cmd.Output() - outStr := string(outBytes) - return strings.TrimSpace(outStr), err -} diff --git a/vendor/github.com/bitrise-io/bitrise/version/build.go b/vendor/github.com/bitrise-io/bitrise/version/build.go deleted file mode 100644 index 06c70c10..00000000 --- a/vendor/github.com/bitrise-io/bitrise/version/build.go +++ /dev/null @@ -1,7 +0,0 @@ -package version - -// BuildNumber ... -var BuildNumber = "" - -// Commit ... -var Commit = "" diff --git a/vendor/github.com/bitrise-io/bitrise/version/tool_version.go b/vendor/github.com/bitrise-io/bitrise/version/tool_version.go deleted file mode 100644 index ca2266da..00000000 --- a/vendor/github.com/bitrise-io/bitrise/version/tool_version.go +++ /dev/null @@ -1,87 +0,0 @@ -package version - -import ( - "fmt" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/go-utils/command" - "github.com/hashicorp/go-version" -) - -// StepmanVersion ... -func StepmanVersion() (version.Version, error) { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "--version"} - - versionOut, err := command.RunCommandAndReturnCombinedStdoutAndStderr("stepman", args...) - if err != nil { - return version.Version{}, err - } - - versionPtr, err := version.NewVersion(versionOut) - if err != nil { - return version.Version{}, err - } - if versionPtr == nil { - return version.Version{}, fmt.Errorf("Failed to parse version (%s)", versionOut) - } - - return *versionPtr, nil -} - -// EnvmanVersion ... -func EnvmanVersion() (version.Version, error) { - logLevel := log.GetLevel().String() - args := []string{"--loglevel", logLevel, "--version"} - versionOut, err := command.RunCommandAndReturnCombinedStdoutAndStderr("envman", args...) - if err != nil { - return version.Version{}, err - } - - versionPtr, err := version.NewVersion(versionOut) - if err != nil { - return version.Version{}, err - } - if versionPtr == nil { - return version.Version{}, fmt.Errorf("Failed to parse version (%s)", versionOut) - } - - return *versionPtr, nil -} - -// BitriseCliVersion ... -func BitriseCliVersion() (version.Version, error) { - versionPtr, err := version.NewVersion(VERSION) - if err != nil { - return version.Version{}, err - } - if versionPtr == nil { - return version.Version{}, fmt.Errorf("Failed to parse version (%s)", VERSION) - } - - return *versionPtr, nil -} - -// ToolVersionMap ... -func ToolVersionMap() (map[string]version.Version, error) { - envmanVersion, err := EnvmanVersion() - if err != nil { - return map[string]version.Version{}, err - } - - stepmanVersion, err := StepmanVersion() - if err != nil { - return map[string]version.Version{}, err - } - - bitriseVersion, err := BitriseCliVersion() - if err != nil { - return map[string]version.Version{}, err - } - - return map[string]version.Version{ - "bitrise": bitriseVersion, - "envman": envmanVersion, - "stepman": stepmanVersion, - }, nil -} diff --git a/vendor/github.com/bitrise-io/bitrise/version/version.go b/vendor/github.com/bitrise-io/bitrise/version/version.go deleted file mode 100644 index 6f4c87e0..00000000 --- a/vendor/github.com/bitrise-io/bitrise/version/version.go +++ /dev/null @@ -1,4 +0,0 @@ -package version - -// VERSION ... -const VERSION = "1.8.0" diff --git a/vendor/github.com/bitrise-io/envman/.gitignore b/vendor/github.com/bitrise-io/envman/.gitignore deleted file mode 100644 index c8619c05..00000000 --- a/vendor/github.com/bitrise-io/envman/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -_tmp/ -.envstore.yml -.bitrise -.bitrise.secrets.yml -_bin/ -.gows.user.yml diff --git a/vendor/github.com/bitrise-io/envman/Dockerfile b/vendor/github.com/bitrise-io/envman/Dockerfile deleted file mode 100644 index b0ad5ec4..00000000 --- a/vendor/github.com/bitrise-io/envman/Dockerfile +++ /dev/null @@ -1,50 +0,0 @@ -FROM ubuntu:16.04 - -ENV PROJ_NAME envman -ENV BITRISE_CLI_VERSION 1.21.0 -ENV GO_VERSION go1.10.3.linux-amd64.tar.gz - -RUN apt-get update - -RUN DEBIAN_FRONTEND=noninteractive apt-get -y install \ - # Requiered for Bitrise CLI - git \ - mercurial \ - curl \ - wget \ - rsync \ - sudo \ - expect \ - build-essential - -# -# Install Bitrise CLI -RUN curl -L https://github.com/bitrise-io/bitrise/releases/download/$BITRISE_CLI_VERSION/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -RUN chmod +x /usr/local/bin/bitrise -RUN bitrise setup - -# -# Install Go -# from official binary package -RUN wget -q https://storage.googleapis.com/golang/$GO_VERSION -O go-bins.tar.gz \ - && tar -C /usr/local -xvzf go-bins.tar.gz \ - && rm go-bins.tar.gz - -# ENV setup -ENV PATH $PATH:/usr/local/go/bin -# Go Workspace dirs & envs -# From the official Golang Dockerfile -# https://github.com/docker-library/golang -ENV GOPATH /go -ENV PATH $GOPATH/bin:$PATH -# 755 because Ruby complains if 777 (warning: Insecure world writable dir ... in PATH) -RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 755 "$GOPATH" - -RUN mkdir -p /go/src/github.com/bitrise-io/$PROJ_NAME -COPY . /go/src/github.com/bitrise-io/$PROJ_NAME - -WORKDIR /go/src/github.com/bitrise-io/$PROJ_NAME - -# install -RUN go install -CMD $PROJ_NAME --version diff --git a/vendor/github.com/bitrise-io/envman/Gopkg.lock b/vendor/github.com/bitrise-io/envman/Gopkg.lock deleted file mode 100644 index ca0ab9b2..00000000 --- a/vendor/github.com/bitrise-io/envman/Gopkg.lock +++ /dev/null @@ -1,121 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - branch = "master" - digest = "1:d972dce278dd6023fccf060a44cc51c33f0ce25f0522970c000c502766826cb8" - name = "github.com/Sirupsen/logrus" - packages = ["."] - pruneopts = "" - revision = "eef6b768ab01a0598a0a6db97bad2a37d31df1d1" - -[[projects]] - branch = "master" - digest = "1:6083fc4e90fe661aad326c42fa16fb85774cb55711560d1208c82ff290534836" - name = "github.com/bitrise-io/go-utils" - packages = [ - "command", - "errorutil", - "fileutil", - "parseutil", - "pathutil", - "pointers", - ] - pruneopts = "" - revision = "2a09aab8380d7842750328aebd5671bcccea89c8" - -[[projects]] - branch = "master" - digest = "1:6825c56bedbe125a302e7516f731fa841ce9db05f873dbc5745ee807a8c3d3a2" - name = "github.com/bitrise-io/goinp" - packages = ["goinp"] - pruneopts = "" - revision = "f451b19ba4d087d8272d411ad91af5f4218fd517" - -[[projects]] - digest = "1:0deddd908b6b4b768cfc272c16ee61e7088a60f7fe2f06c547bd3d8e1f8b8e77" - name = "github.com/davecgh/go-spew" - packages = ["spew"] - pruneopts = "" - revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" - version = "v1.1.1" - -[[projects]] - digest = "1:6a874e3ddfb9db2b42bd8c85b6875407c702fa868eed20634ff489bc896ccfd3" - name = "github.com/konsorten/go-windows-terminal-sequences" - packages = ["."] - pruneopts = "" - revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" - version = "v1.0.1" - -[[projects]] - digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" - name = "github.com/pmezard/go-difflib" - packages = ["difflib"] - pruneopts = "" - revision = "792786c7400a136282c1664665ae0a8db921c6c2" - version = "v1.0.0" - -[[projects]] - branch = "master" - digest = "1:381bcbeb112a51493d9d998bbba207a529c73dbb49b3fd789e48c63fac1f192c" - name = "github.com/stretchr/testify" - packages = [ - "assert", - "require", - ] - pruneopts = "" - revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" - -[[projects]] - branch = "master" - digest = "1:438d5957cf1c26ca62975dc4b1b59242acc9e527f39ed3f6cda57ee6d3653daf" - name = "github.com/urfave/cli" - packages = ["."] - pruneopts = "" - revision = "b67dcf995b6a7b7f14fad5fcb7cc5441b05e814b" - -[[projects]] - branch = "master" - digest = "1:59b49c47c11a48f1054529207f65907c014ecf5f9a7c0d9c0f1616dec7b062ed" - name = "golang.org/x/crypto" - packages = ["ssh/terminal"] - pruneopts = "" - revision = "ff983b9c42bc9fbf91556e191cc8efb585c16908" - -[[projects]] - branch = "master" - digest = "1:30d295ca4b98f09df913be86c1ac48d1d321eb2a84543206a87704e5d25db19b" - name = "golang.org/x/sys" - packages = [ - "unix", - "windows", - ] - pruneopts = "" - revision = "7fbe1cd0fcc20051e1fcb87fbabec4a1bacaaeba" - -[[projects]] - branch = "v2" - digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d" - name = "gopkg.in/yaml.v2" - packages = ["."] - pruneopts = "" - revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "github.com/Sirupsen/logrus", - "github.com/bitrise-io/go-utils/command", - "github.com/bitrise-io/go-utils/fileutil", - "github.com/bitrise-io/go-utils/parseutil", - "github.com/bitrise-io/go-utils/pathutil", - "github.com/bitrise-io/go-utils/pointers", - "github.com/bitrise-io/goinp/goinp", - "github.com/stretchr/testify/require", - "github.com/urfave/cli", - "gopkg.in/yaml.v2", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/vendor/github.com/bitrise-io/envman/Gopkg.toml b/vendor/github.com/bitrise-io/envman/Gopkg.toml deleted file mode 100644 index cb817b33..00000000 --- a/vendor/github.com/bitrise-io/envman/Gopkg.toml +++ /dev/null @@ -1,23 +0,0 @@ -[[constraint]] - name = "github.com/Sirupsen/logrus" - branch = "master" - -[[constraint]] - name = "github.com/bitrise-io/go-utils" - branch = "master" - -[[constraint]] - name = "github.com/bitrise-io/goinp" - branch = "master" - -[[constraint]] - name = "github.com/stretchr/testify" - branch = "master" - -[[constraint]] - name = "github.com/urfave/cli" - branch = "master" - -[[constraint]] - name = "gopkg.in/yaml.v2" - branch = "v2" diff --git a/vendor/github.com/bitrise-io/envman/README.md b/vendor/github.com/bitrise-io/envman/README.md deleted file mode 100644 index 64782e77..00000000 --- a/vendor/github.com/bitrise-io/envman/README.md +++ /dev/null @@ -1,196 +0,0 @@ -# envman - -Manage your Environment Variable collections. Switch between Environment Variable sets -quickly and easily, or run a single command with a pre-defined set of Environment Variables. - -`envman` can also be used as a bridge between separate tools, one tool can register its -outputs through `envman` and the next tool can access these as simple environment variables. - -**Public Beta:** this repository is still under active development, -frequent changes are expected, but we we don't plan to introduce breaking changes, -unless really necessary. **Feedback is greatly appreciated!** - -*Part of the Bitrise Continuous Integration, Delivery and Automations Stack, -with [bitrise](https://github.com/bitrise-io/bitrise) and [stepman](https://github.com/bitrise-io/stepman).* - -## Who and for what? - -- connect tools with each other : one tool saves an ENV, the other uses it (input & output) -- manage your environment-sets : use different ENVs for different projects -- complex environment values : if you want to store a complex input as ENV (for example a change log text/summary), `envman` makes this easy, you don't have to encode the value so that when you call bash's `export KEY=value` it will store your value as you intended. You can just give `envman` the value as it is through a `--valuefile` option or as an input stream (or just edit the related `.envstore` file manually), no encoding required. -- switch between environment sets : if you work on multiple projects where each one requires different environments you can manage this with `envman` - -## Install - -Check the latest release for instructions at: [https://github.com/bitrise-io/envman/releases](https://github.com/bitrise-io/envman/releases) - -## How? - Use cases - -- multi PATH handling: you have `packer` in your `$HOME` dir, in a bin subdir and terraform in another -- create an envman `.envset` to include these in your `$PATH` - -## Develop & Test in Docker - -Build: - -``` -docker build -t envman . -``` - -Run: - -``` -docker run --rm -it -v `pwd`:/go/src/github.com/bitrise-io/envman --name envman-dev envman /bin/bash -``` - -## How does it work? - -`envman` will run the command you specify -with the environments found in its environment store. - -When you add a new key-value pair with `envman add` it stores -the key and value in an `.envstore` file in the current -directory (this can be changed), and when you call `envman run` -the next time the environments in the `.envstore` file will -be loaded for the command, and the command will be executed -with an environment which includes the specified environment -variables. - -This is the same as you would manually set all the -environments, one by one with `export KEY=value` (in bash) -before calling the command. - -`envman` makes it easy to switch between environment sets -and to isolate these environment sets from each other - -you don't have to `unset` environments, the specified -environment set will only be available for that single -run of `envman` / the command and won't affect subsequent -calls of the command or `envman`. - -## Usage example: Simple Bash example - -Add/store an environment variable: - -``` -envman add --key MY_TEST_ENV_KEY --value 'test value for test key' -``` - -Echo it: - -``` -envman run bash -c 'echo "Environment test: $MY_TEST_ENV_KEY"' -``` - -*Why `bash -c` is required? Because `echo` in itself -does not do any environment expansion, it simply prints -its input. So if you want to see the value of an environment -you have to run it through a tool/shell which actually -performs the environment expansion (replaces the environment -key with the value associated with it).* - -## Usage example: Ruby - -### Add environment variable with `--value` flag - -``` -system( "envman add --key SOME_KEY --value 'some value' --expand false" ) -``` - -### Add environment variable from an input stream - -``` -IO.popen('envman add --key SOME_KEY', 'r+') {|f| - f.write('some value') - f.close_write - f.read -} -``` - -### Add environment variable with a value file - -``` -require 'tempfile' - -file = Tempfile.new('SOME_FILE_NAME') -file.write('some value') -file.close - -system( "envman add --key SOME_KEY --valuefile #{file.path}" ) - -file.unlink -``` - -## Usage example: Go - -### Add environment variable with `--value` flag - -``` -import "os/exec" - -c := exec.Command("envman", "add", "--key", "SOME_KEY", "--value", "some value") -err := c.Run() -if err != nil { - // Handle error -} -``` - -### Add environment variable from an input stream - -``` -import "os/exec" - -func RunPipedEnvmanAdd(keyStr, valueStr string) error { - envman := exec.Command("envman", "add", "--key", keyStr) - envman.Stdin = strings.NewReader(valueStr) - envman.Stdout = os.Stdout - envman.Stderr = os.Stderr - return envman.Run() -} - -err := RunPipedEnvmanAdd("SOME_KEY", "some value") -if err != nil { - // Handle error -} -``` - -### Add environment variable with a value file - -``` -import ( - "os/exec" - "fmt" -) - -c := exec.Command("envman", "add", "--key", "SOME_KEY", "--valuefile", "/path/to/file/which/contains/the/value") -err := c.Run() -if err != nil { - // Handle error -} -``` - -## Usage example: Bash - -### Add environment variable with `--value` flag - -``` -envman add --key SOME_KEY --value 'some value' -``` - -### Add environment variable from an input stream - -``` -echo "some value" | envman add --key SOME_KEY -``` - -### Add environment variable with a value file - -``` -envman add --key SOME_KEY --valuefile /path/to/file/which/contains/the/value --expand false -``` - -### Release a new version - -- merge every code changes to the master branch -- do not forget to merge every version related file changes: - - update the version number (in version.go file) -- push the new version tag to the master branch \ No newline at end of file diff --git a/vendor/github.com/bitrise-io/envman/_tests/integration/add_test.go b/vendor/github.com/bitrise-io/envman/_tests/integration/add_test.go deleted file mode 100644 index 59d416e6..00000000 --- a/vendor/github.com/bitrise-io/envman/_tests/integration/add_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package integration - -import ( - "encoding/json" - "io" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/bitrise-io/envman/envman" - - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func addCommand(key, value, envstore string) *command.Model { - return command.New(binPath(), "-l", "debug", "-p", envstore, "add", "--key", key, "--value", value) -} - -func addFileCommand(key, pth, envstore string) *command.Model { - return command.New(binPath(), "-l", "debug", "-p", envstore, "add", "--key", key, "--valuefile", pth) -} - -func addPipeCommand(key string, reader io.Reader, envstore string) *command.Model { - return command.New(binPath(), "-l", "debug", "-p", envstore, "add", "--key", key).SetStdin(reader) -} - -func TestAdd(t *testing.T) { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__envman__") - require.NoError(t, err) - - envstore := filepath.Join(tmpDir, ".envstore") - f, err := os.Create(envstore) - require.NoError(t, err) - require.NoError(t, f.Close()) - - t.Log("add flag value") - { - out, err := addCommand("KEY", "value", envstore).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - cont, err := fileutil.ReadStringFromFile(envstore) - require.NoError(t, err, out) - require.Equal(t, "envs:\n- KEY: value\n", cont) - } - - t.Log("add file flag value") - { - pth := filepath.Join(tmpDir, "file") - require.NoError(t, fileutil.WriteStringToFile(pth, "some content")) - - out, err := addFileCommand("KEY", pth, envstore).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - cont, err := fileutil.ReadStringFromFile(envstore) - require.NoError(t, err, out) - require.Equal(t, "envs:\n- KEY: some content\n", cont) - } - - t.Log("add piped value") - { - out, err := addPipeCommand("KEY", strings.NewReader("some piped value"), envstore).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - cont, err := fileutil.ReadStringFromFile(envstore) - require.NoError(t, err, out) - require.Equal(t, "envs:\n- KEY: some piped value\n", cont) - } - - t.Log("add piped value - zero EnvBytesLimitInKB") - { - configPath := filepath.Join(pathutil.UserHomeDir(), ".envman", "configs.json") - require.NoError(t, pathutil.EnsureDirExist(filepath.Dir(configPath))) - - exists, err := pathutil.IsPathExists(configPath) - require.NoError(t, err) - - var origData []byte - if exists { - origData, err = fileutil.ReadBytesFromFile(configPath) - require.NoError(t, err) - } - - cfgData, err := json.Marshal(envman.ConfigsModel{EnvBytesLimitInKB: 0, EnvListBytesLimitInKB: 3}) - require.NoError(t, err) - - require.NoError(t, fileutil.WriteBytesToFile(configPath, cfgData)) - - out, err := addPipeCommand("KEY", strings.NewReader(strings.Repeat("0", 2*1024)), envstore).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - if exists { - require.NoError(t, fileutil.WriteBytesToFile(configPath, origData)) - } else { - require.NoError(t, os.RemoveAll(configPath)) - } - } - - t.Log("add piped value - over limit") - { - configPath := filepath.Join(pathutil.UserHomeDir(), ".envman", "configs.json") - require.NoError(t, pathutil.EnsureDirExist(filepath.Dir(configPath))) - - exists, err := pathutil.IsPathExists(configPath) - require.NoError(t, err) - - var origData []byte - if exists { - origData, err = fileutil.ReadBytesFromFile(configPath) - require.NoError(t, err) - } - - cfgData, err := json.Marshal(envman.ConfigsModel{EnvBytesLimitInKB: 1, EnvListBytesLimitInKB: 2}) - require.NoError(t, err) - - require.NoError(t, fileutil.WriteBytesToFile(configPath, cfgData)) - - out, err := addPipeCommand("KEY", strings.NewReader(strings.Repeat("0", 2*1024)), envstore).RunAndReturnTrimmedCombinedOutput() - require.Error(t, err) - require.True(t, strings.Contains(out, "environment value size (2 KB) - max allowed size: 1 KB")) - - if exists { - require.NoError(t, fileutil.WriteBytesToFile(configPath, origData)) - } else { - require.NoError(t, os.RemoveAll(configPath)) - } - } - - t.Log("add piped value - empty value") - { - out, err := addPipeCommand("KEY", strings.NewReader(""), envstore).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - cont, err := fileutil.ReadStringFromFile(envstore) - require.NoError(t, err, out) - require.Equal(t, "envs:\n- KEY: \"\"\n", cont) - } -} diff --git a/vendor/github.com/bitrise-io/envman/_tests/integration/helper.go b/vendor/github.com/bitrise-io/envman/_tests/integration/helper.go deleted file mode 100644 index fbd55914..00000000 --- a/vendor/github.com/bitrise-io/envman/_tests/integration/helper.go +++ /dev/null @@ -1,7 +0,0 @@ -package integration - -import "os" - -func binPath() string { - return os.Getenv("INTEGRATION_TEST_BINARY_PATH") -} diff --git a/vendor/github.com/bitrise-io/envman/bitrise.yml b/vendor/github.com/bitrise-io/envman/bitrise.yml deleted file mode 100644 index e8ac35bb..00000000 --- a/vendor/github.com/bitrise-io/envman/bitrise.yml +++ /dev/null @@ -1,97 +0,0 @@ -format_version: "5" -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -project_type: other - -app: - envs: - - BIN_NAME: envman - -workflows: - test: - title: Runs tests - steps: - - go-list: - - golint: - - errcheck: - - go-test: - - codecov: - run_if: .IsCI - inputs: - - other_options: -f ${GO_CODE_COVERAGE_REPORT_PATH} - - CODECOV_TOKEN: "$CODECOV_UPLOAD_TOKEN" - - script: - title: Run integration tests - inputs: - - content: |- - #!/usr/bin/env bash - set -ex - - current_envman="$(pwd)/_tmp/test_envman" - go build -o "$current_envman" - - export PR="" PULL_REQUEST_ID="" - export INTEGRATION_TEST_BINARY_PATH="$current_envman" - go test -v ./_tests/integration/... - - create-binaries: - title: Create binaries - steps: - - script: - title: Create binaries - inputs: - - content: | - #!/bin/bash - set -ex - - echo - echo "Create final binaries" - echo " Build number: $BITRISE_BUILD_NUMBER" - - export ARCH=x86_64 - export GOARCH=amd64 - - # Create Darwin bin - export OS=Darwin - export GOOS=darwin - - DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" - echo " Create final Darwin binary at: $DEPLOY_PATH" - - version_package="github.com/bitrise-io/envman/version" - - go build \ - -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ - -o "$DEPLOY_PATH" - - envman add --key OSX_DEPLOY_PATH --value $DEPLOY_PATH - cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH - echo " Copy final Darwin binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" - - - # Create Linux binary - export OS=Linux - export GOOS=linux - - DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" - echo " Create final Linux binary at: $DEPLOY_PATH" - - go build \ - -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ - -o "$DEPLOY_PATH" - - envman add --key LINUX_DEPLOY_PATH --value $DEPLOY_PATH - cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH - echo " Copy final Linux binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" - - dep-update: - title: Dep update - steps: - - script: - title: Dependency update - inputs: - - content: |- - #!/bin/bash - set -ex - go get -u -v github.com/golang/dep/cmd/dep - dep ensure -v - dep ensure -v -update diff --git a/vendor/github.com/bitrise-io/envman/cli/add.go b/vendor/github.com/bitrise-io/envman/cli/add.go deleted file mode 100644 index cfefb7bb..00000000 --- a/vendor/github.com/bitrise-io/envman/cli/add.go +++ /dev/null @@ -1,225 +0,0 @@ -package cli - -import ( - "errors" - "io/ioutil" - "os" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/envman/envman" - "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/pointers" - "github.com/urfave/cli" -) - -var errTimeout = errors.New("timeout") - -func envListSizeInBytes(envs []models.EnvironmentItemModel) (int, error) { - valueSizeInBytes := 0 - for _, env := range envs { - _, value, err := env.GetKeyValuePair() - if err != nil { - return 0, err - } - valueSizeInBytes += len([]byte(value)) - } - return valueSizeInBytes, nil -} - -func validateEnv(key, value string, envList []models.EnvironmentItemModel) (string, error) { - if key == "" { - return "", errors.New("Key is not specified, required") - } - - configs, err := envman.GetConfigs() - if err != nil { - return "", err - } - - valueSizeInBytes := len([]byte(value)) - if configs.EnvBytesLimitInKB > 0 { - if valueSizeInBytes > configs.EnvBytesLimitInKB*1024 { - valueSizeInKB := ((float64)(valueSizeInBytes)) / 1024.0 - log.Warnf("environment value (%s...) too large", value[0:100]) - log.Warnf("environment value size (%#v KB) - max allowed size: %#v KB", valueSizeInKB, (float64)(configs.EnvBytesLimitInKB)) - return "environment value too large - rejected", nil - } - } - - if configs.EnvListBytesLimitInKB > 0 { - envListSizeInBytes, err := envListSizeInBytes(envList) - if err != nil { - return "", err - } - if envListSizeInBytes+valueSizeInBytes > configs.EnvListBytesLimitInKB*1024 { - listSizeInKB := (float64)(envListSizeInBytes)/1024 + (float64)(valueSizeInBytes)/1024 - log.Warn("environment list too large") - log.Warnf("environment list size (%#v KB) - max allowed size: %#v KB", listSizeInKB, (float64)(configs.EnvListBytesLimitInKB)) - return "", errors.New("environment list too large") - } - } - return value, nil -} - -func addEnv(key string, value string, expand, replace, skipIfEmpty bool) error { - // Load envs, or create if not exist - environments, err := envman.ReadEnvsOrCreateEmptyList() - if err != nil { - return err - } - - // Validate input - validatedValue, err := validateEnv(key, value, environments) - if err != nil { - return err - } - value = validatedValue - - // Add or update envlist - newEnv := models.EnvironmentItemModel{ - key: value, - models.OptionsKey: models.EnvironmentItemOptionsModel{ - IsExpand: pointers.NewBoolPtr(expand), - SkipIfEmpty: pointers.NewBoolPtr(skipIfEmpty), - }, - } - if err := newEnv.NormalizeValidateFillDefaults(); err != nil { - return err - } - - newEnvSlice, err := envman.UpdateOrAddToEnvlist(environments, newEnv, replace) - if err != nil { - return err - } - - return envman.WriteEnvMapToFile(envman.CurrentEnvStoreFilePath, newEnvSlice) -} - -func loadValueFromFile(pth string) (string, error) { - buf, err := ioutil.ReadFile(pth) - if err != nil { - return "", err - } - - str := string(buf) - return str, nil -} - -func logEnvs() error { - environments, err := envman.ReadEnvs(envman.CurrentEnvStoreFilePath) - if err != nil { - return err - } - - if len(environments) == 0 { - log.Info("[ENVMAN] Empty envstore") - } else { - for _, env := range environments { - key, value, err := env.GetKeyValuePair() - if err != nil { - return err - } - - opts, err := env.GetOptions() - if err != nil { - return err - } - - envString := "- " + key + ": " + value - log.Debugln(envString) - if !*opts.IsExpand { - expandString := " " + "isExpand" + ": " + "false" - log.Debugln(expandString) - } - } - } - - return nil -} - -func add(c *cli.Context) error { - log.Debugln("[ENVMAN] Work path:", envman.CurrentEnvStoreFilePath) - - key := c.String(KeyKey) - expand := !c.Bool(NoExpandKey) - replace := !c.Bool(AppendKey) - skipIfEmpty := c.Bool(SkipIfEmptyKey) - - var value string - - // read flag value - if c.IsSet(ValueKey) { - value = c.String(ValueKey) - log.Debugf("adding flag value: (%s)", value) - } - - // read flag file - if value == "" && c.String(ValueFileKey) != "" { - var err error - if value, err = loadValueFromFile(c.String(ValueFileKey)); err != nil { - log.Fatal("[ENVMAN] Failed to read file value: ", err) - } - log.Debugf("adding file flag value: (%s)", value) - } - - // read piped stdin value - if value == "" { - info, err := os.Stdin.Stat() - if err != nil { - log.Fatalf("[ENVMAN] Failed to get standard input FileInfo: %s", err) - } - if info.Mode()&os.ModeNamedPipe == os.ModeNamedPipe { - log.Debugf("adding from piped stdin") - - configs, err := envman.GetConfigs() - if err != nil { - log.Fatalf("[ENVMAN] Failed to load envman config: %s", err) - } - - data, err := ioutil.ReadAll(os.Stdin) - if err != nil { - log.Fatalf("[ENVMAN] Failed to read standard input: %s", err) - } - - if configs.EnvBytesLimitInKB > 0 && len(data) > configs.EnvBytesLimitInKB*1024 { - valueSizeInKB := ((float64)(len(data))) / 1024.0 - log.Warnf("environment value too large") - log.Warnf("environment value size (%#v KB) - max allowed size: %#v KB", valueSizeInKB, (float64)(configs.EnvBytesLimitInKB)) - log.Fatalf("[ENVMAN] environment value too large - rejected") - } - - if configs.EnvListBytesLimitInKB > 0 { - envList, err := envman.ReadEnvsOrCreateEmptyList() - if err != nil { - log.Fatalf("[ENVMAN] failed to get env list, error: %s", err) - } - envListSizeInBytes, err := envListSizeInBytes(envList) - if err != nil { - log.Fatalf("[ENVMAN] failed to get env list size, error: %s", err) - } - if envListSizeInBytes+len(data) > configs.EnvListBytesLimitInKB*1024 { - listSizeInKB := (float64)(envListSizeInBytes)/1024 + (float64)(len(data))/1024 - log.Warn("environment list too large") - log.Warnf("environment list size (%#v KB) - max allowed size: %#v KB", listSizeInKB, (float64)(configs.EnvListBytesLimitInKB)) - log.Fatalf("[ENVMAN] environment list too large") - } - } - - value = string(data) - - log.Debugf("stdin value: (%s)", value) - } - } - - if err := addEnv(key, value, expand, replace, skipIfEmpty); err != nil { - log.Fatal("[ENVMAN] Failed to add env:", err) - } - - log.Debugln("[ENVMAN] Env added") - - if err := logEnvs(); err != nil { - log.Fatal("[ENVMAN] Failed to print:", err) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/envman/cli/add_test.go b/vendor/github.com/bitrise-io/envman/cli/add_test.go deleted file mode 100644 index 5a58b531..00000000 --- a/vendor/github.com/bitrise-io/envman/cli/add_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package cli - -import ( - "errors" - "strings" - "testing" - - "github.com/bitrise-io/envman/models" - "github.com/stretchr/testify/require" -) - -func TestEnvListSizeInBytes(t *testing.T) { - str100Bytes := strings.Repeat("a", 100) - require.Equal(t, 100, len([]byte(str100Bytes))) - - env := models.EnvironmentItemModel{ - "key": str100Bytes, - } - - envList := []models.EnvironmentItemModel{env} - size, err := envListSizeInBytes(envList) - require.Equal(t, nil, err) - require.Equal(t, 100, size) - - envList = []models.EnvironmentItemModel{env, env} - size, err = envListSizeInBytes(envList) - require.Equal(t, nil, err) - require.Equal(t, 200, size) -} - -func TestValidateEnv(t *testing.T) { - // Valid - max allowed - str20KBytes := strings.Repeat("a", (20 * 1024)) - env1 := models.EnvironmentItemModel{ - "key": str20KBytes, - } - envs := []models.EnvironmentItemModel{env1} - - valValue, err := validateEnv("key", str20KBytes, envs) - require.NoError(t, err) - require.Equal(t, str20KBytes, valValue) - - // List oversize - // first create a large, but valid env set - for i := 0; i < 3; i++ { - envs = append(envs, env1) - } - - valValue, err = validateEnv("key", str20KBytes, envs) - require.NoError(t, err) - require.Equal(t, str20KBytes, valValue) - - // append one more -> too large - envs = append(envs, env1) - _, err = validateEnv("key", str20KBytes, envs) - require.Equal(t, errors.New("environment list too large"), err) - - // List oversize + too big value - str10Kbytes := strings.Repeat("a", (10 * 1024)) - env1 = models.EnvironmentItemModel{ - "key": str10Kbytes, - } - envs = []models.EnvironmentItemModel{} - for i := 0; i < 8; i++ { - env := models.EnvironmentItemModel{ - "key": str10Kbytes, - } - envs = append(envs, env) - } - - str21Kbytes := strings.Repeat("a", (21 * 1024)) - - valValue, err = validateEnv("key", str21Kbytes, envs) - require.NoError(t, err) - require.Equal(t, "environment value too large - rejected", valValue) -} diff --git a/vendor/github.com/bitrise-io/envman/cli/clear.go b/vendor/github.com/bitrise-io/envman/cli/clear.go deleted file mode 100644 index d9ae53a5..00000000 --- a/vendor/github.com/bitrise-io/envman/cli/clear.go +++ /dev/null @@ -1,34 +0,0 @@ -package cli - -import ( - "errors" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/envman/envman" - "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/urfave/cli" -) - -func clearEnvs() error { - if isExists, err := pathutil.IsPathExists(envman.CurrentEnvStoreFilePath); err != nil { - return err - } else if !isExists { - errMsg := "EnvStore not found in path:" + envman.CurrentEnvStoreFilePath - return errors.New(errMsg) - } - - return envman.WriteEnvMapToFile(envman.CurrentEnvStoreFilePath, []models.EnvironmentItemModel{}) -} - -func clear(c *cli.Context) error { - log.Debugln("[ENVMAN] - Work path:", envman.CurrentEnvStoreFilePath) - - if err := clearEnvs(); err != nil { - log.Fatal("[ENVMAN] - Failed to clear EnvStore:", err) - } - - log.Info("[ENVMAN] - EnvStore cleared") - - return nil -} diff --git a/vendor/github.com/bitrise-io/envman/cli/cli.go b/vendor/github.com/bitrise-io/envman/cli/cli.go deleted file mode 100644 index 6db10352..00000000 --- a/vendor/github.com/bitrise-io/envman/cli/cli.go +++ /dev/null @@ -1,96 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "path" - "path/filepath" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/envman/envman" - "github.com/bitrise-io/envman/version" - "github.com/urfave/cli" -) - -const ( - defaultEnvStoreName = ".envstore.yml" - helpTemplate = ` -NAME: {{.Name}} - {{.Usage}} - -USAGE: {{.Name}} {{if .Flags}}[OPTIONS] {{end}}COMMAND [arg...] - -VERSION: {{.Version}}{{if or .Author .Email}} - -AUTHOR:{{if .Author}} - {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} - {{.Email}}{{end}}{{end}} -{{if .Flags}} -GLOBAL OPTIONS: - {{range .Flags}}{{.}} - {{end}}{{end}} -COMMANDS: - {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}} -COMMAND HELP: {{.Name}} COMMAND --help/-h - -` -) - -func before(c *cli.Context) error { - log.SetFormatter(&log.TextFormatter{ForceColors: true, FullTimestamp: true, TimestampFormat: "15:04:05"}) - - // Log level - if logLevel, err := log.ParseLevel(c.String(LogLevelKey)); err != nil { - log.Fatal("[BITRISE_CLI] - Failed to parse log level:", err) - } else { - log.SetLevel(logLevel) - } - - // Befor parsing cli, and running command - // we need to decide wich path will be used by envman - envman.CurrentEnvStoreFilePath = c.String(PathKey) - if envman.CurrentEnvStoreFilePath == "" { - if path, err := filepath.Abs(path.Join("./", defaultEnvStoreName)); err != nil { - log.Fatal("[ENVMAN] - Failed to set envman work path in current dir:", err) - } else { - envman.CurrentEnvStoreFilePath = path - } - } - - envman.ToolMode = c.Bool(ToolKey) - if envman.ToolMode { - log.Info("[ENVMAN] - Tool mode on") - } - - if _, err := envman.GetConfigs(); err != nil { - log.Fatal("[ENVMAN] - Failed to init configs:", err) - } - - return nil -} - -// Run the Envman CLI. -func Run() { - cli.HelpFlag = cli.BoolFlag{Name: HelpKey + ", " + helpKeyShort, Usage: "Show help."} - cli.AppHelpTemplate = helpTemplate - - cli.VersionFlag = cli.BoolFlag{Name: VersionKey + ", " + versionKeyShort, Usage: "Print the version."} - cli.VersionPrinter = func(c *cli.Context) { fmt.Println(c.App.Version) } - - app := cli.NewApp() - app.Name = path.Base(os.Args[0]) - app.Usage = "Environment variable manager" - app.Version = version.VERSION - - app.Author = "" - app.Email = "" - - app.Before = before - - app.Flags = flags - app.Commands = commands - - if err := app.Run(os.Args); err != nil { - log.Fatal("[ENVMAN] - Finished:", err) - } -} diff --git a/vendor/github.com/bitrise-io/envman/cli/commands.go b/vendor/github.com/bitrise-io/envman/cli/commands.go deleted file mode 100644 index f122daaf..00000000 --- a/vendor/github.com/bitrise-io/envman/cli/commands.go +++ /dev/null @@ -1,72 +0,0 @@ -package cli - -import "github.com/urfave/cli" - -var ( - commands = []cli.Command{ - { - Name: "version", - Usage: "Prints the version", - Action: printVersionCmd, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "format", - Usage: "Output format. Accepted: json, yml", - }, - cli.BoolFlag{ - Name: "full", - Usage: "Prints the build number and commit as well.", - }, - }, - }, - { - Name: "init", - Aliases: []string{"i"}, - Usage: "Create an empty .envstore.yml into the current working directory, or to the path specified by the --path flag.", - Action: initEnvStore, - Flags: []cli.Flag{ - flClear, - }, - }, - { - Name: "add", - Aliases: []string{"a"}, - Usage: "Add new, or update an exist environment variable.", - Action: add, - Flags: []cli.Flag{ - flKey, - flValue, - flValueFile, - flNoExpand, - flAppend, - cli.BoolFlag{ - Name: SkipIfEmptyKey, - Usage: "If enabled the added environment variable will be skipped during envman run, if the value is empty. If not set then the empty value will be used.", - }, - }, - }, - { - Name: "clear", - Aliases: []string{"c"}, - Usage: "Clear the envstore.", - Action: clear, - }, - { - Name: "print", - Aliases: []string{"p"}, - Usage: "Print out the environment variables in envstore.", - Action: print, - Flags: []cli.Flag{ - flFormat, - flExpand, - }, - }, - { - Name: "run", - Aliases: []string{"r"}, - Usage: "Run the specified command with the environment variables stored in the envstore.", - SkipFlagParsing: true, - Action: run, - }, - } -) diff --git a/vendor/github.com/bitrise-io/envman/cli/flags.go b/vendor/github.com/bitrise-io/envman/cli/flags.go deleted file mode 100644 index 60025f91..00000000 --- a/vendor/github.com/bitrise-io/envman/cli/flags.go +++ /dev/null @@ -1,127 +0,0 @@ -package cli - -import "github.com/urfave/cli" - -const ( - // PathEnvKey ... - PathEnvKey = "ENVMAN_ENVSTORE_PATH" - // PathKey ... - PathKey = "path" - pathKeyShort = "p" - - // LogLevelEnvKey ... - LogLevelEnvKey = "LOGLEVEL" - // LogLevelKey ... - LogLevelKey = "loglevel" - logLevelKeyShort = "l" - - // KeyKey ... - KeyKey = "key" - keyKeyShortT = "k" - - // ValueKey ... - ValueKey = "value" - valueKeyShort = "v" - - // ValueFileKey ... - ValueFileKey = "valuefile" - valueFileKeyShort = "f" - - // NoExpandKey ... - NoExpandKey = "no-expand" - noExpandKeyShort = "n" - - // AppendKey ... - AppendKey = "append" - appendKeyShort = "a" - - // SkipIfEmptyKey ... - SkipIfEmptyKey = "skip-if-empty" - - // ToolEnvKey ... - ToolEnvKey = "ENVMAN_TOOLMODE" - // ToolKey ... - ToolKey = "tool" - toolKeyShort = "t" - - // ClearKey .... - ClearKey = "clear" - clearKeyShort = "c" - - // HelpKey ... - HelpKey = "help" - helpKeyShort = "h" - - // VersionKey ... - VersionKey = "version" - versionKeyShort = "v" - - // ExpandKey ... - ExpandKey = "expand" - // FormatKey ... - FormatKey = "format" - // OutputFormatRaw ... - OutputFormatRaw = "raw" - // OutputFormatJSON ... - OutputFormatJSON = "json" -) - -var ( - // App flags - flLogLevel = cli.StringFlag{ - Name: LogLevelKey + ", " + logLevelKeyShort, - Value: "info", - Usage: "Log level (options: debug, info, warn, error, fatal, panic).", - EnvVar: LogLevelEnvKey, - } - flPath = cli.StringFlag{ - Name: PathKey + ", " + pathKeyShort, - EnvVar: PathEnvKey, - Value: "", - Usage: "Path of the envstore.", - } - flTool = cli.BoolFlag{ - Name: ToolKey + ", " + toolKeyShort, - EnvVar: ToolEnvKey, - Usage: "If enabled, envman will NOT ask for user inputs.", - } - flags = []cli.Flag{ - flLogLevel, - flPath, - flTool, - } - - // Command flags - flKey = cli.StringFlag{ - Name: KeyKey + ", " + keyKeyShortT, - Usage: "Key of the environment variable. Empty string (\"\") is NOT accepted.", - } - flValue = cli.StringFlag{ - Name: ValueKey + ", " + valueKeyShort, - Usage: "Value of the environment variable. Empty string is accepted.", - } - flValueFile = cli.StringFlag{ - Name: ValueFileKey + ", " + valueFileKeyShort, - Usage: "Path of a file which contains the environment variable's value to be stored.", - } - flNoExpand = cli.BoolFlag{ - Name: NoExpandKey + ", " + noExpandKeyShort, - Usage: "If enabled, envman will NOT replaces ${var} or $var in the string according to the values of the current environment variables.", - } - flAppend = cli.BoolFlag{ - Name: AppendKey + ", " + appendKeyShort, - Usage: "If enabled, new env will append to envstore, otherwise if env exist with specified key, will replaced.", - } - flClear = cli.BoolFlag{ - Name: ClearKey + ", " + clearKeyShort, - Usage: "If enabled, 'envman init' removes envstore if exist.", - } - flFormat = cli.StringFlag{ - Name: FormatKey, - Usage: "Output format (options: raw, json).", - } - flExpand = cli.BoolFlag{ - Name: ExpandKey, - Usage: "If enabled, expanded envs will use.", - } -) diff --git a/vendor/github.com/bitrise-io/envman/cli/init.go b/vendor/github.com/bitrise-io/envman/cli/init.go deleted file mode 100644 index 049ba50b..00000000 --- a/vendor/github.com/bitrise-io/envman/cli/init.go +++ /dev/null @@ -1,27 +0,0 @@ -package cli - -import ( - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/envman/envman" - "github.com/bitrise-io/go-utils/command" - "github.com/urfave/cli" -) - -func initEnvStore(c *cli.Context) error { - log.Debugln("[ENVMAN] - Work path:", envman.CurrentEnvStoreFilePath) - - clear := c.Bool(ClearKey) - if clear { - if err := command.RemoveFile(envman.CurrentEnvStoreFilePath); err != nil { - log.Fatal("[ENVMAN] - Failed to clear path:", err) - } - } - - if err := envman.InitAtPath(envman.CurrentEnvStoreFilePath); err != nil { - log.Fatal("[ENVMAN] - Failed to init at path:", err) - } - - log.Debugln("[ENVMAN] - Initialized") - - return nil -} diff --git a/vendor/github.com/bitrise-io/envman/cli/print.go b/vendor/github.com/bitrise-io/envman/cli/print.go deleted file mode 100644 index 7aa9d984..00000000 --- a/vendor/github.com/bitrise-io/envman/cli/print.go +++ /dev/null @@ -1,95 +0,0 @@ -package cli - -import ( - "encoding/json" - "fmt" - "os" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/envman/envman" - "github.com/bitrise-io/envman/models" - "github.com/urfave/cli" -) - -func printJSONEnvs(envList models.EnvsJSONListModel) error { - bytes, err := json.Marshal(envList) - if err != nil { - return err - } - - fmt.Println(string(bytes)) - return nil -} - -func printRawEnvs(envList models.EnvsJSONListModel) { - fmt.Println() - for key, value := range envList { - fmt.Printf("%s: %s\n", key, value) - } - fmt.Println() -} - -func convertToEnsJSONModel(envs []models.EnvironmentItemModel, expand bool) (models.EnvsJSONListModel, error) { - JSONModels := models.EnvsJSONListModel{} - for _, env := range envs { - key, value, err := env.GetKeyValuePair() - if err != nil { - return models.EnvsJSONListModel{}, err - } - - opts, err := env.GetOptions() - if err != nil { - return models.EnvsJSONListModel{}, err - } - - if expand && (opts.IsExpand != nil && *opts.IsExpand) { - value = expandEnvsInString(value) - } - - JSONModels[key] = value - - if err := os.Setenv(key, value); err != nil { - return models.EnvsJSONListModel{}, err - } - } - return JSONModels, nil -} - -func print(c *cli.Context) error { - // Input validation - format := c.String(FormatKey) - if format == "" { - format = OutputFormatRaw - } else if !(format == OutputFormatRaw || format == OutputFormatJSON) { - log.Fatalf("Invalid format: %s", format) - } - - expand := c.Bool(ExpandKey) - - // Read envs - environments, err := envman.ReadEnvs(envman.CurrentEnvStoreFilePath) - if err != nil { - log.Fatalf("Failed to read envs, error: %s", err) - } - - envsJSONList, err := convertToEnsJSONModel(environments, expand) - if err != nil { - log.Fatalf("Failed to convert envs, error: %s", err) - } - - // Print envs - switch format { - case OutputFormatRaw: - printRawEnvs(envsJSONList) - break - case OutputFormatJSON: - if err := printJSONEnvs(envsJSONList); err != nil { - log.Fatalf("Failed to print env list, err: %s", err) - } - break - default: - log.Fatalf("[STEPMAN] - Invalid format: %s", format) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/envman/cli/print_test.go b/vendor/github.com/bitrise-io/envman/cli/print_test.go deleted file mode 100644 index dfd3f801..00000000 --- a/vendor/github.com/bitrise-io/envman/cli/print_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package cli - -import ( - "os" - "path" - "testing" - - "github.com/bitrise-io/envman/envman" - "github.com/stretchr/testify/require" -) - -func TestPrint(t *testing.T) { - envsStr := ` -envs: -- TEST_HOME1: $HOME -- TEST_HOME2: $TEST_HOME1/test -` - environments, err := envman.ParseEnvsYML([]byte(envsStr)) - require.Equal(t, nil, err) - - envsJSONList, err := convertToEnsJSONModel(environments, false) - require.Equal(t, nil, err) - require.Equal(t, "$HOME", envsJSONList["TEST_HOME1"]) - require.Equal(t, "$TEST_HOME1/test", envsJSONList["TEST_HOME2"]) - - testHome1 := os.Getenv("HOME") - testHome2 := path.Join(testHome1, "test") - envsJSONList, err = convertToEnsJSONModel(environments, true) - require.Equal(t, nil, err) - require.Equal(t, testHome1, envsJSONList["TEST_HOME1"]) - require.Equal(t, testHome2, envsJSONList["TEST_HOME2"]) -} diff --git a/vendor/github.com/bitrise-io/envman/cli/run.go b/vendor/github.com/bitrise-io/envman/cli/run.go deleted file mode 100644 index fb137240..00000000 --- a/vendor/github.com/bitrise-io/envman/cli/run.go +++ /dev/null @@ -1,102 +0,0 @@ -package cli - -import ( - "os" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/envman/envman" - "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/command" - "github.com/urfave/cli" -) - -// CommandModel ... -type CommandModel struct { - Command string - Argumentums []string - Environments []models.EnvironmentItemModel -} - -func expandEnvsInString(inp string) string { - return os.ExpandEnv(inp) -} - -func commandEnvs(envs []models.EnvironmentItemModel) ([]string, error) { - for _, env := range envs { - key, value, err := env.GetKeyValuePair() - if err != nil { - return []string{}, err - } - - opts, err := env.GetOptions() - if err != nil { - return []string{}, err - } - - if *opts.SkipIfEmpty && value == "" { - continue - } - - var valueStr string - if *opts.IsExpand { - valueStr = expandEnvsInString(value) - } else { - valueStr = value - } - - if err := os.Setenv(key, valueStr); err != nil { - return []string{}, err - } - } - return os.Environ(), nil -} - -func runCommandModel(cmdModel CommandModel) (int, error) { - cmdEnvs, err := commandEnvs(cmdModel.Environments) - if err != nil { - return 1, err - } - - return command.RunCommandWithEnvsAndReturnExitCode(cmdEnvs, cmdModel.Command, cmdModel.Argumentums...) -} - -func run(c *cli.Context) error { - log.Debug("[ENVMAN] - Work path:", envman.CurrentEnvStoreFilePath) - - if len(c.Args()) > 0 { - doCmdEnvs, err := envman.ReadEnvs(envman.CurrentEnvStoreFilePath) - if err != nil { - log.Fatal("[ENVMAN] - Failed to load EnvStore:", err) - } - - doCommand := c.Args()[0] - - doArgs := []string{} - if len(c.Args()) > 1 { - doArgs = c.Args()[1:] - } - - cmdToExecute := CommandModel{ - Command: doCommand, - Environments: doCmdEnvs, - Argumentums: doArgs, - } - - log.Debug("[ENVMAN] - Executing command:", cmdToExecute) - - if exit, err := runCommandModel(cmdToExecute); err != nil { - log.Debug("[ENVMAN] - Failed to execute command:", err) - if exit == 0 { - log.Error("[ENVMAN] - Failed to execute command:", err) - exit = 1 - } - os.Exit(exit) - } - - log.Debug("[ENVMAN] - Command executed") - } else { - log.Fatal("[ENVMAN] - No command specified") - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/envman/cli/run_test.go b/vendor/github.com/bitrise-io/envman/cli/run_test.go deleted file mode 100644 index 48646dc4..00000000 --- a/vendor/github.com/bitrise-io/envman/cli/run_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package cli - -import ( - "os" - "strings" - "testing" - - "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/pointers" - "github.com/stretchr/testify/require" -) - -func TestExpandEnvsInString(t *testing.T) { - t.Log("Expand env") - { - require.Equal(t, nil, os.Setenv("MY_ENV_KEY", "key")) - - inp := "${MY_ENV_KEY} of my home" - expanded := expandEnvsInString(inp) - - key := os.Getenv("MY_ENV_KEY") - require.NotEqual(t, "", expanded) - require.Equal(t, key+" of my home", expanded) - } -} - -func TestCommandEnvs(t *testing.T) { - t.Log("commandEnvs test") - { - env1 := models.EnvironmentItemModel{ - "test_key1": "test_value1", - } - require.Equal(t, nil, env1.FillMissingDefaults()) - - env2 := models.EnvironmentItemModel{ - "test_key2": "test_value2", - } - require.Equal(t, nil, env2.FillMissingDefaults()) - - envs := []models.EnvironmentItemModel{env1, env2} - - sessionEnvs, err := commandEnvs(envs) - require.Equal(t, nil, err) - - env1Found := false - env2Found := false - for _, envString := range sessionEnvs { - comp := strings.Split(envString, "=") - key := comp[0] - value := comp[1] - - envKey1, envValue1, err := env1.GetKeyValuePair() - require.Equal(t, nil, err) - - envKey2, envValue2, err := env2.GetKeyValuePair() - require.Equal(t, nil, err) - - if key == envKey1 && value == envValue1 { - env1Found = true - } - if key == envKey2 && value == envValue2 { - env2Found = true - } - } - require.Equal(t, true, env1Found) - require.Equal(t, true, env2Found) - } - - // Test skip_if_empty - t.Log("skip_if_empty=false && value=empty => should add") - { - env1 := models.EnvironmentItemModel{ - "test_key3": "", - } - require.Equal(t, nil, env1.FillMissingDefaults()) - - env2 := models.EnvironmentItemModel{ - "test_key4": "test_value4", - } - require.Equal(t, nil, env2.FillMissingDefaults()) - - envs := []models.EnvironmentItemModel{env1, env2} - - sessionEnvs, err := commandEnvs(envs) - require.Equal(t, nil, err) - - env1Found := false - env2Found := false - for _, envString := range sessionEnvs { - comp := strings.Split(envString, "=") - key := comp[0] - value := comp[1] - - envKey1, envValue1, err := env1.GetKeyValuePair() - require.Equal(t, nil, err) - - envKey2, envValue2, err := env2.GetKeyValuePair() - require.Equal(t, nil, err) - - if key == envKey1 && value == envValue1 { - env1Found = true - } - if key == envKey2 && value == envValue2 { - env2Found = true - } - } - require.Equal(t, true, env1Found) - require.Equal(t, true, env2Found) - } - - t.Log("skip_if_empty=true && value=empty => should NOT add") - { - env1 := models.EnvironmentItemModel{ - "test_key5": "", - "opts": models.EnvironmentItemOptionsModel{ - SkipIfEmpty: pointers.NewBoolPtr(true), - }, - } - require.Equal(t, nil, env1.FillMissingDefaults()) - - env2 := models.EnvironmentItemModel{ - "test_key6": "test_value6", - } - require.Equal(t, nil, env2.FillMissingDefaults()) - - envs := []models.EnvironmentItemModel{env1, env2} - - sessionEnvs, err := commandEnvs(envs) - require.Equal(t, nil, err) - - env1Found := false - env2Found := false - for _, envString := range sessionEnvs { - comp := strings.Split(envString, "=") - key := comp[0] - value := comp[1] - - envKey1, envValue1, err := env1.GetKeyValuePair() - require.Equal(t, nil, err) - - envKey2, envValue2, err := env2.GetKeyValuePair() - require.Equal(t, nil, err) - - if key == envKey1 && value == envValue1 { - env1Found = true - } - if key == envKey2 && value == envValue2 { - env2Found = true - } - } - require.Equal(t, false, env1Found) - require.Equal(t, true, env2Found) - } - - t.Log("skip_if_empty=true && value=NOT_empty => should add") - { - env1 := models.EnvironmentItemModel{ - "test_key7": "test_value7", - "opts": models.EnvironmentItemOptionsModel{ - SkipIfEmpty: pointers.NewBoolPtr(true), - }, - } - require.Equal(t, nil, env1.FillMissingDefaults()) - - env2 := models.EnvironmentItemModel{ - "test_key8": "test_value8", - } - require.Equal(t, nil, env2.FillMissingDefaults()) - - envs := []models.EnvironmentItemModel{env1, env2} - - sessionEnvs, err := commandEnvs(envs) - require.Equal(t, nil, err) - - env1Found := false - env2Found := false - for _, envString := range sessionEnvs { - comp := strings.Split(envString, "=") - key := comp[0] - value := comp[1] - - envKey1, envValue1, err := env1.GetKeyValuePair() - require.Equal(t, nil, err) - - envKey2, envValue2, err := env2.GetKeyValuePair() - require.Equal(t, nil, err) - - if key == envKey1 && value == envValue1 { - env1Found = true - } - if key == envKey2 && value == envValue2 { - env2Found = true - } - } - require.Equal(t, true, env1Found) - require.Equal(t, true, env2Found) - } - - t.Log("expand envs test") - { - env1 := models.EnvironmentItemModel{ - "env1": "Hello", - } - require.Equal(t, nil, env1.FillMissingDefaults()) - - env2 := models.EnvironmentItemModel{ - "env2": "${env1} world", - } - require.Equal(t, nil, env2.FillMissingDefaults()) - - env3 := models.EnvironmentItemModel{ - "env3": "${env2} !", - } - require.Equal(t, nil, env3.FillMissingDefaults()) - - envs := []models.EnvironmentItemModel{env1, env2, env3} - - sessionEnvs, err := commandEnvs(envs) - require.Equal(t, nil, err) - - env3Found := false - for _, envString := range sessionEnvs { - comp := strings.Split(envString, "=") - key := comp[0] - value := comp[1] - - envKey3, _, err := env3.GetKeyValuePair() - require.Equal(t, nil, err) - - if key == envKey3 { - require.Equal(t, "Hello world !", value) - env3Found = true - } - } - require.Equal(t, true, env3Found) - } -} diff --git a/vendor/github.com/bitrise-io/envman/cli/version.go b/vendor/github.com/bitrise-io/envman/cli/version.go deleted file mode 100644 index e206d880..00000000 --- a/vendor/github.com/bitrise-io/envman/cli/version.go +++ /dev/null @@ -1,46 +0,0 @@ -package cli - -import ( - "fmt" - "log" - - "github.com/bitrise-io/envman/output" - "github.com/bitrise-io/envman/version" - "github.com/urfave/cli" -) - -// VersionOutputModel ... -type VersionOutputModel struct { - Version string `json:"version"` - BuildNumber string `json:"build_number"` - Commit string `json:"commit"` -} - -func printVersionCmd(c *cli.Context) error { - fullVersion := c.Bool("full") - - if err := output.ConfigureOutputFormat(c); err != nil { - log.Fatalf("Error: %s", err) - } - - versionOutput := VersionOutputModel{ - Version: version.VERSION, - } - - if fullVersion { - versionOutput.BuildNumber = version.BuildNumber - versionOutput.Commit = version.Commit - } - - if output.Format == output.FormatRaw { - if fullVersion { - fmt.Printf("version: %v\nbuild_number: %v\ncommit: %v\n", versionOutput.Version, versionOutput.BuildNumber, versionOutput.Commit) - } else { - fmt.Println(versionOutput.Version) - } - } else { - output.Print(versionOutput, output.Format) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/envman/docker-compose.yml b/vendor/github.com/bitrise-io/envman/docker-compose.yml deleted file mode 100644 index be582a93..00000000 --- a/vendor/github.com/bitrise-io/envman/docker-compose.yml +++ /dev/null @@ -1,4 +0,0 @@ -app: - build: . - volumes: - - .:/go/src/github.com/bitrise-io/envman diff --git a/vendor/github.com/bitrise-io/envman/envman/configs.go b/vendor/github.com/bitrise-io/envman/envman/configs.go deleted file mode 100644 index 8c09c803..00000000 --- a/vendor/github.com/bitrise-io/envman/envman/configs.go +++ /dev/null @@ -1,99 +0,0 @@ -package envman - -import ( - "encoding/json" - "os" - "path" - - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" -) - -const ( - envmanConfigFileName = "configs.json" - defaultEnvBytesLimitInKB = 20 - defaultEnvListBytesLimitInKB = 100 -) - -// ConfigsModel ... -type ConfigsModel struct { - EnvBytesLimitInKB int `json:"env_bytes_limit_in_kb,omitempty"` - EnvListBytesLimitInKB int `json:"env_list_bytes_limit_in_kb,omitempty"` -} - -func getEnvmanConfigsDirPath() string { - return path.Join(pathutil.UserHomeDir(), ".envman") -} - -func getEnvmanConfigsFilePath() string { - return path.Join(getEnvmanConfigsDirPath(), envmanConfigFileName) -} - -func ensureEnvmanConfigDirExists() error { - confDirPth := getEnvmanConfigsDirPath() - isExists, err := pathutil.IsDirExists(confDirPth) - if !isExists || err != nil { - if err := os.MkdirAll(confDirPth, 0777); err != nil { - return err - } - } - return nil -} - -func createDefaultConfigsModel() ConfigsModel { - return ConfigsModel{ - EnvBytesLimitInKB: defaultEnvBytesLimitInKB, - EnvListBytesLimitInKB: defaultEnvListBytesLimitInKB, - } -} - -// GetConfigs ... -func GetConfigs() (ConfigsModel, error) { - configPth := getEnvmanConfigsFilePath() - defaultConfigs := createDefaultConfigsModel() - - if isExist, err := pathutil.IsPathExists(configPth); err != nil { - return ConfigsModel{}, err - } else if !isExist { - return defaultConfigs, nil - } - - bytes, err := fileutil.ReadBytesFromFile(configPth) - if err != nil { - return ConfigsModel{}, err - } - - type ConfigsFileMode struct { - EnvBytesLimitInKB *int `json:"env_bytes_limit_in_kb,omitempty"` - EnvListBytesLimitInKB *int `json:"env_list_bytes_limit_in_kb,omitempty"` - } - - var userConfigs ConfigsFileMode - if err := json.Unmarshal(bytes, &userConfigs); err != nil { - return ConfigsModel{}, err - } - - if userConfigs.EnvBytesLimitInKB != nil { - defaultConfigs.EnvBytesLimitInKB = *userConfigs.EnvBytesLimitInKB - } - if userConfigs.EnvListBytesLimitInKB != nil { - defaultConfigs.EnvListBytesLimitInKB = *userConfigs.EnvListBytesLimitInKB - } - - return defaultConfigs, nil -} - -// saveConfigs ... -// only used for unit testing at the moment -func saveConfigs(configModel ConfigsModel) error { - if err := ensureEnvmanConfigDirExists(); err != nil { - return err - } - - bytes, err := json.Marshal(configModel) - if err != nil { - return err - } - configsPth := getEnvmanConfigsFilePath() - return fileutil.WriteBytesToFile(configsPth, bytes) -} diff --git a/vendor/github.com/bitrise-io/envman/envman/configs_test.go b/vendor/github.com/bitrise-io/envman/envman/configs_test.go deleted file mode 100644 index 2a6ee4e8..00000000 --- a/vendor/github.com/bitrise-io/envman/envman/configs_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package envman - -import ( - "os" - "testing" - - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestGetConfigs(t *testing.T) { - // fake home, to save the configs into - fakeHomePth, err := pathutil.NormalizedOSTempDirPath("_FAKE_HOME") - t.Logf("fakeHomePth: %s", fakeHomePth) - require.NoError(t, err) - originalHome := os.Getenv("HOME") - defer func() { - require.NoError(t, os.Setenv("HOME", originalHome)) - require.NoError(t, os.RemoveAll(fakeHomePth)) - }() - require.Equal(t, nil, os.Setenv("HOME", fakeHomePth)) - - configPth := getEnvmanConfigsFilePath() - t.Logf("configPth: %s", configPth) - - // --- TESTING - - baseConf, err := GetConfigs() - t.Logf("baseConf: %#v", baseConf) - require.NoError(t, err) - require.Equal(t, defaultEnvBytesLimitInKB, baseConf.EnvBytesLimitInKB) - require.Equal(t, defaultEnvListBytesLimitInKB, baseConf.EnvListBytesLimitInKB) - - // modify it - baseConf.EnvBytesLimitInKB = 123 - baseConf.EnvListBytesLimitInKB = 321 - - // save to file - require.NoError(t, saveConfigs(baseConf)) - - // read it back - configs, err := GetConfigs() - t.Logf("configs: %#v", configs) - require.NoError(t, err) - require.Equal(t, configs, baseConf) - require.Equal(t, 123, configs.EnvBytesLimitInKB) - require.Equal(t, 321, configs.EnvListBytesLimitInKB) - - // delete the tmp config file - require.NoError(t, os.Remove(configPth)) -} diff --git a/vendor/github.com/bitrise-io/envman/envman/util.go b/vendor/github.com/bitrise-io/envman/envman/util.go deleted file mode 100644 index fd24fa1a..00000000 --- a/vendor/github.com/bitrise-io/envman/envman/util.go +++ /dev/null @@ -1,254 +0,0 @@ -package envman - -import ( - "errors" - - "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/goinp/goinp" - "gopkg.in/yaml.v2" -) - -var ( - // CurrentEnvStoreFilePath ... - CurrentEnvStoreFilePath string - - // ToolMode ... - ToolMode bool -) - -// ------------------- -// --- Environment handling methods - -// UpdateOrAddToEnvlist ... -func UpdateOrAddToEnvlist(oldEnvSlice []models.EnvironmentItemModel, newEnv models.EnvironmentItemModel, replace bool) ([]models.EnvironmentItemModel, error) { - newKey, _, err := newEnv.GetKeyValuePair() - if err != nil { - return []models.EnvironmentItemModel{}, err - } - - var newEnvs []models.EnvironmentItemModel - exist := false - - if replace { - match := 0 - for _, env := range oldEnvSlice { - key, _, err := env.GetKeyValuePair() - if err != nil { - return []models.EnvironmentItemModel{}, err - } - - if key == newKey { - match = match + 1 - } - } - if match > 1 { - if ToolMode { - return []models.EnvironmentItemModel{}, errors.New("More then one env exist with key '" + newKey + "'") - } - msg := " More then one env exist with key '" + newKey + "' replace all/append ['replace/append'] ?" - answer, err := goinp.AskForString(msg) - if err != nil { - return []models.EnvironmentItemModel{}, err - } - - switch answer { - case "replace": - break - case "append": - replace = false - break - default: - return []models.EnvironmentItemModel{}, errors.New("Failed to parse answer: '" + answer + "' use ['replace/append']!") - } - } - } - - for _, env := range oldEnvSlice { - key, _, err := env.GetKeyValuePair() - if err != nil { - return []models.EnvironmentItemModel{}, err - } - - if replace && key == newKey { - exist = true - newEnvs = append(newEnvs, newEnv) - } else { - newEnvs = append(newEnvs, env) - } - } - - if !exist { - newEnvs = append(newEnvs, newEnv) - } - - return newEnvs, nil -} - -func removeDefaults(env *models.EnvironmentItemModel) error { - opts, err := env.GetOptions() - if err != nil { - return err - } - - if opts.Title != nil && *opts.Title == "" { - opts.Title = nil - } - if opts.Description != nil && *opts.Description == "" { - opts.Description = nil - } - if opts.Summary != nil && *opts.Summary == "" { - opts.Summary = nil - } - if opts.IsRequired != nil && *opts.IsRequired == models.DefaultIsRequired { - opts.IsRequired = nil - } - if opts.IsDontChangeValue != nil && *opts.IsDontChangeValue == models.DefaultIsDontChangeValue { - opts.IsDontChangeValue = nil - } - if opts.IsTemplate != nil && *opts.IsTemplate == models.DefaultIsTemplate { - opts.IsTemplate = nil - } - if opts.IsExpand != nil && *opts.IsExpand == models.DefaultIsExpand { - opts.IsExpand = nil - } - if opts.IsSensitive != nil && *opts.IsSensitive == models.DefaultIsSensitive { - opts.IsSensitive = nil - } - if opts.SkipIfEmpty != nil && *opts.SkipIfEmpty == models.DefaultSkipIfEmpty { - opts.SkipIfEmpty = nil - } - - (*env)[models.OptionsKey] = opts - return nil -} - -func generateFormattedYMLForEnvModels(envs []models.EnvironmentItemModel) (models.EnvsSerializeModel, error) { - envMapSlice := []models.EnvironmentItemModel{} - for _, env := range envs { - err := removeDefaults(&env) - if err != nil { - return models.EnvsSerializeModel{}, err - } - - hasOptions := false - opts, err := env.GetOptions() - if err != nil { - return models.EnvsSerializeModel{}, err - } - - if opts.Title != nil { - hasOptions = true - } - if opts.Description != nil { - hasOptions = true - } - if opts.Summary != nil { - hasOptions = true - } - if len(opts.ValueOptions) > 0 { - hasOptions = true - } - if opts.IsRequired != nil { - hasOptions = true - } - if opts.IsDontChangeValue != nil { - hasOptions = true - } - if opts.IsTemplate != nil { - hasOptions = true - } - if opts.IsExpand != nil { - hasOptions = true - } - if opts.IsSensitive != nil { - hasOptions = true - } - if opts.SkipIfEmpty != nil { - hasOptions = true - } - - if !hasOptions { - delete(env, models.OptionsKey) - } - - envMapSlice = append(envMapSlice, env) - } - - return models.EnvsSerializeModel{ - Envs: envMapSlice, - }, nil -} - -// ------------------- -// --- File methods - -// WriteEnvMapToFile ... -func WriteEnvMapToFile(pth string, envs []models.EnvironmentItemModel) error { - if pth == "" { - return errors.New("No path provided") - } - - envYML, err := generateFormattedYMLForEnvModels(envs) - if err != nil { - return err - } - bytes, err := yaml.Marshal(envYML) - if err != nil { - return err - } - return fileutil.WriteBytesToFile(pth, bytes) -} - -// InitAtPath ... -func InitAtPath(pth string) error { - if exist, err := pathutil.IsPathExists(pth); err != nil { - return err - } else if !exist { - if err := WriteEnvMapToFile(pth, []models.EnvironmentItemModel{}); err != nil { - return err - } - } else { - errorMsg := "Path already exist: " + pth - return errors.New(errorMsg) - } - return nil -} - -// ParseEnvsYML ... -func ParseEnvsYML(bytes []byte) ([]models.EnvironmentItemModel, error) { - var envsYML models.EnvsSerializeModel - if err := yaml.Unmarshal(bytes, &envsYML); err != nil { - return []models.EnvironmentItemModel{}, err - } - for _, env := range envsYML.Envs { - if err := env.NormalizeValidateFillDefaults(); err != nil { - return []models.EnvironmentItemModel{}, err - } - } - return envsYML.Envs, nil -} - -// ReadEnvs ... -func ReadEnvs(pth string) ([]models.EnvironmentItemModel, error) { - bytes, err := fileutil.ReadBytesFromFile(pth) - if err != nil { - return []models.EnvironmentItemModel{}, err - } - - return ParseEnvsYML(bytes) -} - -// ReadEnvsOrCreateEmptyList ... -func ReadEnvsOrCreateEmptyList() ([]models.EnvironmentItemModel, error) { - envModels, err := ReadEnvs(CurrentEnvStoreFilePath) - if err != nil { - if err.Error() == "No environment variable list found" { - err = InitAtPath(CurrentEnvStoreFilePath) - return []models.EnvironmentItemModel{}, err - } - return []models.EnvironmentItemModel{}, err - } - return envModels, nil -} diff --git a/vendor/github.com/bitrise-io/envman/envman/util_test.go b/vendor/github.com/bitrise-io/envman/envman/util_test.go deleted file mode 100644 index 17fd9aa0..00000000 --- a/vendor/github.com/bitrise-io/envman/envman/util_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package envman - -import ( - "testing" - - "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/pointers" - "github.com/stretchr/testify/require" -) - -func countOfEnvInEnvSlice(env models.EnvironmentItemModel, envSlice []models.EnvironmentItemModel) (cnt int, err error) { - for _, e := range envSlice { - key, value, err := env.GetKeyValuePair() - if err != nil { - return 0, err - } - - k, v, err := e.GetKeyValuePair() - if err != nil { - return 0, err - } - - if key == k && value == v { - cnt++ - } - } - return -} - -func countOfEnvKeyInEnvSlice(env models.EnvironmentItemModel, envSlice []models.EnvironmentItemModel) (cnt int, err error) { - for _, e := range envSlice { - key, _, err := env.GetKeyValuePair() - if err != nil { - return 0, err - } - - k, _, err := e.GetKeyValuePair() - if err != nil { - return 0, err - } - - if key == k { - cnt++ - } - } - return -} - -func TestUpdateOrAddToEnvlist(t *testing.T) { - env1 := models.EnvironmentItemModel{ - "test_key1": "test_value1", - } - require.Equal(t, nil, env1.FillMissingDefaults()) - - env2 := models.EnvironmentItemModel{ - "test_key2": "test_value2", - } - require.Equal(t, nil, env2.FillMissingDefaults()) - - // Should add to list, but not override - oldEnvSlice := []models.EnvironmentItemModel{env1, env2} - newEnvSlice, err := UpdateOrAddToEnvlist(oldEnvSlice, env1, false) - require.Equal(t, nil, err) - - env1Cnt, err := countOfEnvKeyInEnvSlice(env1, newEnvSlice) - require.Equal(t, nil, err) - require.Equal(t, 2, env1Cnt) - - env2Cnt, err := countOfEnvKeyInEnvSlice(env2, newEnvSlice) - require.Equal(t, nil, err) - require.Equal(t, 1, env2Cnt) - - // Should update list - oldEnvSlice = []models.EnvironmentItemModel{env1, env2} - newEnvSlice, err = UpdateOrAddToEnvlist(oldEnvSlice, env1, true) - require.Equal(t, nil, err) - - env1Cnt, err = countOfEnvKeyInEnvSlice(env1, newEnvSlice) - require.Equal(t, nil, err) - require.Equal(t, 1, env1Cnt) - - env2Cnt, err = countOfEnvKeyInEnvSlice(env2, newEnvSlice) - require.Equal(t, nil, err) - require.Equal(t, 1, env2Cnt) -} - -func TestRemoveDefaults(t *testing.T) { - // Filled env - env := models.EnvironmentItemModel{ - "test_key": "test_value", - models.OptionsKey: models.EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - IsTemplate: pointers.NewBoolPtr(!models.DefaultIsTemplate), - - Description: pointers.NewStringPtr(""), - Summary: pointers.NewStringPtr(""), - ValueOptions: []string{}, - IsRequired: pointers.NewBoolPtr(models.DefaultIsRequired), - IsDontChangeValue: pointers.NewBoolPtr(models.DefaultIsDontChangeValue), - IsExpand: pointers.NewBoolPtr(models.DefaultIsExpand), - IsSensitive: pointers.NewBoolPtr(models.DefaultIsSensitive), - SkipIfEmpty: pointers.NewBoolPtr(models.DefaultSkipIfEmpty), - }, - } - - require.Equal(t, nil, removeDefaults(&env)) - - opts, err := env.GetOptions() - require.Equal(t, nil, err) - - require.NotEqual(t, (*string)(nil), opts.Title) - require.Equal(t, "test_title", *opts.Title) - require.NotEqual(t, (*bool)(nil), opts.IsTemplate) - require.Equal(t, !models.DefaultIsTemplate, *opts.IsTemplate) - - require.Equal(t, (*string)(nil), opts.Description) - require.Equal(t, (*string)(nil), opts.Summary) - require.Equal(t, 0, len(opts.ValueOptions)) - require.Equal(t, (*bool)(nil), opts.IsRequired) - require.Equal(t, (*bool)(nil), opts.IsDontChangeValue) - require.Equal(t, (*bool)(nil), opts.IsExpand) - require.Equal(t, (*bool)(nil), opts.IsSensitive) - require.Equal(t, (*bool)(nil), opts.SkipIfEmpty) - -} diff --git a/vendor/github.com/bitrise-io/envman/gows.yml b/vendor/github.com/bitrise-io/envman/gows.yml deleted file mode 100644 index 7fd86324..00000000 --- a/vendor/github.com/bitrise-io/envman/gows.yml +++ /dev/null @@ -1 +0,0 @@ -package_name: github.com/bitrise-io/envman diff --git a/vendor/github.com/bitrise-io/envman/main.go b/vendor/github.com/bitrise-io/envman/main.go deleted file mode 100644 index 02d0f39e..00000000 --- a/vendor/github.com/bitrise-io/envman/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/bitrise-io/envman/cli" - -func main() { - cli.Run() -} diff --git a/vendor/github.com/bitrise-io/envman/models/models.go b/vendor/github.com/bitrise-io/envman/models/models.go index aaec00a7..e3d2d505 100644 --- a/vendor/github.com/bitrise-io/envman/models/models.go +++ b/vendor/github.com/bitrise-io/envman/models/models.go @@ -15,6 +15,7 @@ type EnvironmentItemOptionsModel struct { IsDontChangeValue *bool `json:"is_dont_change_value,omitempty" yaml:"is_dont_change_value,omitempty"` IsTemplate *bool `json:"is_template,omitempty" yaml:"is_template,omitempty"` IsSensitive *bool `json:"is_sensitive,omitempty" yaml:"is_sensitive,omitempty"` + Unset *bool `json:"unset,omitempty" yaml:"unset,omitempty"` // Meta map[string]interface{} `json:"meta,omitempty" yaml:"meta,omitempty"` } diff --git a/vendor/github.com/bitrise-io/envman/models/models_methods.go b/vendor/github.com/bitrise-io/envman/models/models_methods.go index 9f93de66..000d3210 100644 --- a/vendor/github.com/bitrise-io/envman/models/models_methods.go +++ b/vendor/github.com/bitrise-io/envman/models/models_methods.go @@ -29,6 +29,8 @@ const ( DefaultIsDontChangeValue = false // DefaultIsTemplate ... DefaultIsTemplate = false + // DefaultUnset ... + DefaultUnset = false ) // NewEnvJSONList ... @@ -169,6 +171,12 @@ func (envSerModel *EnvironmentItemOptionsModel) ParseFromInterfaceMap(input map[ return fmt.Errorf("failed to parse bool value (%#v) for key (%s)", value, keyStr) } envSerModel.SkipIfEmpty = castedBoolPtr + case "unset": + castedBoolPtr, ok := parseutil.CastToBoolPtr(value) + if !ok { + return fmt.Errorf("failed to parse bool value (%#v) for key (%s)", value, keyStr) + } + envSerModel.Unset = castedBoolPtr case "meta": castedMapStringInterface, ok := parseutil.CastToMapStringInterface(value) if !ok { @@ -294,6 +302,10 @@ func (env *EnvironmentItemModel) FillMissingDefaults() error { if options.Meta == nil { options.Meta = map[string]interface{}{} } + if options.Unset == nil { + options.Unset = pointers.NewBoolPtr(DefaultUnset) + } + (*env)[OptionsKey] = options return nil } diff --git a/vendor/github.com/bitrise-io/envman/models/models_methods_test.go b/vendor/github.com/bitrise-io/envman/models/models_methods_test.go deleted file mode 100644 index 0c5e1b47..00000000 --- a/vendor/github.com/bitrise-io/envman/models/models_methods_test.go +++ /dev/null @@ -1,492 +0,0 @@ -package models - -import ( - "testing" - - yaml "gopkg.in/yaml.v2" - - "encoding/json" - - "github.com/bitrise-io/go-utils/pointers" - "github.com/stretchr/testify/require" -) - -func TestGetKeyValuePair(t *testing.T) { - // Filled env - env := EnvironmentItemModel{ - "test_key": "test_value", - OptionsKey: EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - Description: pointers.NewStringPtr("test_description"), - Summary: pointers.NewStringPtr("test_summary"), - Category: pointers.NewStringPtr("category"), - ValueOptions: []string{"test_key2", "test_value2"}, - IsRequired: pointers.NewBoolPtr(true), - IsExpand: pointers.NewBoolPtr(false), - IsSensitive: pointers.NewBoolPtr(false), - IsDontChangeValue: pointers.NewBoolPtr(true), - IsTemplate: pointers.NewBoolPtr(false), - SkipIfEmpty: pointers.NewBoolPtr(false), - }, - } - - key, value, err := env.GetKeyValuePair() - require.NoError(t, err) - - require.Equal(t, "test_key", key) - require.Equal(t, "test_value", value) - - // More then 2 fields - env = EnvironmentItemModel{ - "test_key": "test_value", - "test_key1": "test_value1", - OptionsKey: EnvironmentItemOptionsModel{Title: pointers.NewStringPtr("test_title")}, - } - - key, value, err = env.GetKeyValuePair() - require.EqualError(t, err, `more than 2 keys specified: [opts test_key test_key1]`) - - // 2 key-value fields - env = EnvironmentItemModel{ - "test_key": "test_value", - "test_key1": "test_value1", - } - - key, value, err = env.GetKeyValuePair() - require.EqualError(t, err, `more than 1 environment key specified: [test_key test_key1]`) - - // Not string value - env = EnvironmentItemModel{"test_key": true} - - key, value, err = env.GetKeyValuePair() - require.NoError(t, err) - - require.Equal(t, "test_key", key) - require.Equal(t, "true", value) - - // Empty key - env = EnvironmentItemModel{"": "test_value"} - - key, value, err = env.GetKeyValuePair() - require.EqualError(t, err, "no environment key found, keys: []") - - // Missing key-value - env = EnvironmentItemModel{OptionsKey: EnvironmentItemOptionsModel{Title: pointers.NewStringPtr("test_title")}} - - key, value, err = env.GetKeyValuePair() - require.EqualError(t, err, "no environment key found, keys: [opts]") -} - -func TestParseFromInterfaceMap(t *testing.T) { - envOptions := EnvironmentItemOptionsModel{} - model := map[string]interface{}{} - - // Normal - model["title"] = "test_title" - model["value_options"] = []string{"test_key2", "test_value2"} - model["is_expand"] = true - model["is_sensitive"] = false - require.NoError(t, envOptions.ParseFromInterfaceMap(model)) - require.Equal(t, "test_title", *envOptions.Title) - require.Equal(t, "test_key2", envOptions.ValueOptions[0]) - require.Equal(t, "test_value2", envOptions.ValueOptions[1]) - require.Equal(t, true, *envOptions.IsExpand) - require.Equal(t, false, *envOptions.IsSensitive) - - // title is not a string - model = map[string]interface{}{} - model["title"] = true - require.NoError(t, envOptions.ParseFromInterfaceMap(model)) - require.Equal(t, "true", *envOptions.Title) - - // value_options is not a string slice - model = map[string]interface{}{} - model["value_options"] = []interface{}{true, false} - require.NoError(t, envOptions.ParseFromInterfaceMap(model)) - require.Equal(t, 2, len(envOptions.ValueOptions)) - require.Equal(t, "true", envOptions.ValueOptions[0]) - require.Equal(t, "false", envOptions.ValueOptions[1]) - - // is_required is not a bool - model = map[string]interface{}{} - model["is_required"] = pointers.NewBoolPtr(true) - require.Error(t, envOptions.ParseFromInterfaceMap(model)) - require.Nil(t, envOptions.IsRequired) - - model = map[string]interface{}{} - model["is_required"] = "YeS" - require.NoError(t, envOptions.ParseFromInterfaceMap(model)) - require.Equal(t, true, *envOptions.IsRequired) - - model = map[string]interface{}{} - model["is_required"] = "NO" - require.NoError(t, envOptions.ParseFromInterfaceMap(model)) - require.Equal(t, false, *envOptions.IsRequired) - - model = map[string]interface{}{} - model["is_required"] = "y" - require.NoError(t, envOptions.ParseFromInterfaceMap(model)) - require.Equal(t, true, *envOptions.IsRequired) - - model = map[string]interface{}{} - model["skip_if_empty"] = "true" - require.NoError(t, envOptions.ParseFromInterfaceMap(model)) - require.Equal(t, true, *envOptions.SkipIfEmpty) - - t.Log("parse meta field - Fail: string is not castable to map[string]interface{}") - { - model := map[string]interface{}{} - model["meta"] = "value" - require.Error(t, envOptions.ParseFromInterfaceMap(model)) - require.Nil(t, envOptions.Meta) - } - - t.Log("parse meta field") - { - serializedObj := `key: "value"` - var obj interface{} - require.NoError(t, yaml.Unmarshal([]byte(serializedObj), &obj)) - - model := map[string]interface{}{} - model["meta"] = obj - require.NoError(t, envOptions.ParseFromInterfaceMap(model)) - require.Equal(t, map[string]interface{}{"key": "value"}, envOptions.Meta) - } - - // other_key is not supported key - model = map[string]interface{}{} - model["other_key"] = true - require.NoError(t, envOptions.ParseFromInterfaceMap(model)) -} - -func TestGetOptions(t *testing.T) { - // Filled env - env := EnvironmentItemModel{ - "test_key": "test_value", - OptionsKey: EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - IsExpand: pointers.NewBoolPtr(false), - }, - } - opts, err := env.GetOptions() - require.NoError(t, err) - - require.NotNil(t, opts.Title) - require.Equal(t, "test_title", *opts.Title) - - require.NotNil(t, opts.IsExpand) - require.Equal(t, false, *opts.IsExpand) - - // Missing opts - env = EnvironmentItemModel{ - "test_key": "test_value", - } - _, err = env.GetOptions() - require.NoError(t, err) - - // Wrong opts - env = EnvironmentItemModel{ - "test_key": "test_value", - OptionsKey: map[interface{}]interface{}{ - "title": "test_title", - "test": "test_description", - }, - } - _, err = env.GetOptions() - require.NoError(t, err) -} - -func TestNormalize(t *testing.T) { - // Filled with map[string]interface{} options - env := EnvironmentItemModel{ - "test_key": "test_value", - OptionsKey: map[interface{}]interface{}{ - "title": "test_title", - "description": "test_description", - "summary": "test_summary", - "value_options": []string{"test_key2", "test_value2"}, - "is_required": true, - "skip_if_empty": false, - }, - } - - require.NoError(t, env.Normalize()) - - opts, err := env.GetOptions() - require.NoError(t, err) - - require.NotNil(t, opts.Title) - require.Equal(t, "test_title", *opts.Title) - - require.NotNil(t, opts.Description) - require.Equal(t, "test_description", *opts.Description) - - require.NotNil(t, opts.Summary) - require.Equal(t, "test_summary", *opts.Summary) - - require.Equal(t, 2, len(opts.ValueOptions)) - - require.NotNil(t, opts.IsRequired) - require.Equal(t, true, *opts.IsRequired) - - require.NotNil(t, opts.SkipIfEmpty) - require.Equal(t, false, *opts.SkipIfEmpty) - - // Filled with EnvironmentItemOptionsModel options - env = EnvironmentItemModel{ - "test_key": "test_value", - OptionsKey: EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - Description: pointers.NewStringPtr("test_description"), - Summary: pointers.NewStringPtr("test_summary"), - ValueOptions: []string{"test_key2", "test_value2"}, - IsRequired: pointers.NewBoolPtr(true), - }, - } - - require.NoError(t, env.Normalize()) - - opts, err = env.GetOptions() - require.NoError(t, err) - - require.NotNil(t, opts.Title) - require.Equal(t, "test_title", *opts.Title) - - require.NotNil(t, opts.Description) - require.Equal(t, "test_description", *opts.Description) - - require.NotNil(t, opts.Summary) - require.Equal(t, "test_summary", *opts.Summary) - - require.Equal(t, 2, len(opts.ValueOptions)) - - require.NotNil(t, opts.IsRequired) - require.Equal(t, true, *opts.IsRequired) - - // Empty options - env = EnvironmentItemModel{ - "test_key": "test_value", - } - - require.NoError(t, env.Normalize()) - - opts, err = env.GetOptions() - require.NoError(t, err) - - require.Equal(t, (*string)(nil), opts.Title) - require.Equal(t, (*string)(nil), opts.Description) - require.Equal(t, (*string)(nil), opts.Summary) - require.Equal(t, 0, len(opts.ValueOptions)) - require.Equal(t, (*bool)(nil), opts.IsRequired) - require.Equal(t, (*bool)(nil), opts.IsDontChangeValue) - require.Equal(t, (*bool)(nil), opts.IsExpand) - require.Equal(t, (*bool)(nil), opts.IsTemplate) - require.Equal(t, (*bool)(nil), opts.SkipIfEmpty) -} - -func TestFillMissingDefaults(t *testing.T) { - // Empty env - env := EnvironmentItemModel{ - "test_key": "test_value", - } - - require.NoError(t, env.FillMissingDefaults()) - - opts, err := env.GetOptions() - require.NoError(t, err) - - require.NotNil(t, opts.Description) - require.Equal(t, "", *opts.Description) - - require.NotNil(t, opts.Summary) - require.Equal(t, "", *opts.Summary) - - require.NotNil(t, opts.Category) - require.Equal(t, "", *opts.Category) - - require.NotNil(t, opts.IsRequired) - require.Equal(t, DefaultIsRequired, *opts.IsRequired) - - require.NotNil(t, opts.IsExpand) - require.Equal(t, DefaultIsExpand, *opts.IsExpand) - - require.NotNil(t, opts.IsDontChangeValue) - require.Equal(t, DefaultIsDontChangeValue, *opts.IsDontChangeValue) - - require.NotNil(t, opts.IsTemplate) - require.Equal(t, DefaultIsDontChangeValue, *opts.IsTemplate) - - require.NotNil(t, opts.SkipIfEmpty) - require.Equal(t, DefaultSkipIfEmpty, *opts.SkipIfEmpty) - - // Filled env - env = EnvironmentItemModel{ - "test_key": "test_value", - OptionsKey: EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - Description: pointers.NewStringPtr("test_description"), - Summary: pointers.NewStringPtr("test_summary"), - Category: pointers.NewStringPtr("required"), - ValueOptions: []string{"test_key2", "test_value2"}, - IsRequired: pointers.NewBoolPtr(true), - IsExpand: pointers.NewBoolPtr(true), - IsDontChangeValue: pointers.NewBoolPtr(false), - IsTemplate: pointers.NewBoolPtr(false), - SkipIfEmpty: pointers.NewBoolPtr(false), - }, - } - - require.NoError(t, env.FillMissingDefaults()) - - opts, err = env.GetOptions() - require.NoError(t, err) - - require.NotNil(t, opts.Title) - require.Equal(t, "test_title", *opts.Title) - - require.NotNil(t, opts.Description) - require.Equal(t, "test_description", *opts.Description) - - require.NotNil(t, opts.Summary) - require.Equal(t, "test_summary", *opts.Summary) - - require.NotNil(t, opts.Category) - require.Equal(t, "required", *opts.Category) - - require.Equal(t, 2, len(opts.ValueOptions)) - - require.NotNil(t, opts.IsRequired) - require.Equal(t, true, *opts.IsRequired) - - require.NotNil(t, opts.IsExpand) - require.Equal(t, true, *opts.IsExpand) - - require.NotNil(t, opts.IsDontChangeValue) - require.Equal(t, false, *opts.IsDontChangeValue) - - require.NotNil(t, opts.IsTemplate) - require.Equal(t, false, *opts.IsTemplate) - - require.NotNil(t, opts.SkipIfEmpty) - require.Equal(t, false, *opts.SkipIfEmpty) -} - -func TestValidate(t *testing.T) { - // No key-value - env := EnvironmentItemModel{ - OptionsKey: EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - Description: pointers.NewStringPtr("test_description"), - Summary: pointers.NewStringPtr("test_summary"), - Category: pointers.NewStringPtr("required"), - ValueOptions: []string{"test_key2", "test_value2"}, - IsRequired: pointers.NewBoolPtr(true), - IsExpand: pointers.NewBoolPtr(true), - IsDontChangeValue: pointers.NewBoolPtr(false), - }, - } - require.EqualError(t, env.Validate(), "no environment key found, keys: [opts]") - - // Empty key - env = EnvironmentItemModel{ - "": "test_value", - } - require.EqualError(t, env.Validate(), "no environment key found, keys: []") - - // Valid env - env = EnvironmentItemModel{ - "test_key": "test_value", - } - require.NoError(t, env.Validate()) -} - -func Test_EnvsSerializeModel_Normalize(t *testing.T) { - yamlContent := `envs: -- KEY_ONE: first value -- KEY_TWO: second value, with options - opts: - is_expand: true -` - var objFromYAML EnvsSerializeModel - require.NoError(t, yaml.Unmarshal([]byte(yamlContent), &objFromYAML)) - - // the objFromYAML object in this state can't be serialized to JSON directly, - // as the YAML parser parses the `opts` into map[interface]interface, - // which is not supported by JSON - { - _, err := json.Marshal(objFromYAML) - require.EqualError(t, err, `json: unsupported type: map[interface {}]interface {}`) - } - - // now, if we call Normalize on this object, that will convert the map[interface]interface - // into map[string]interface, which is JSON serializable - require.NoError(t, objFromYAML.Normalize()) - - // let's try the serialization again - this time it will work! - { - jsonContBytes, err := json.Marshal(objFromYAML) - require.NoError(t, err) - require.Equal(t, `{"envs":[{"KEY_ONE":"first value","opts":{}},{"KEY_TWO":"second value, with options","opts":{"is_expand":true}}]}`, string(jsonContBytes)) - } - - t.Log("test meta field") - { - yamlContent := `envs: -- KEY_ONE: first value -- KEY_TWO: second value, with options - opts: - meta: - is_expose: true -` - var objFromYAML EnvsSerializeModel - require.NoError(t, yaml.Unmarshal([]byte(yamlContent), &objFromYAML)) - - // the objFromYAML object in this state can't be serialized to JSON directly, - // as the YAML parser parses the `opts` into map[interface]interface, - // which is not supported by JSON - { - _, err := json.Marshal(objFromYAML) - require.EqualError(t, err, `json: unsupported type: map[interface {}]interface {}`) - } - - // now, if we call Normalize on this object, that will convert the map[interface]interface - // into map[string]interface, which is JSON serializable - require.NoError(t, objFromYAML.Normalize()) - - // let's try the serialization again - this time it will work! - { - jsonContBytes, err := json.Marshal(objFromYAML) - require.NoError(t, err) - require.Equal(t, `{"envs":[{"KEY_ONE":"first value","opts":{}},{"KEY_TWO":"second value, with options","opts":{"meta":{"is_expose":true}}}]}`, string(jsonContBytes)) - - var serializeModel EnvsSerializeModel - require.NoError(t, yaml.Unmarshal([]byte(yamlContent), &serializeModel)) - require.Equal(t, 2, len(serializeModel.Envs)) - for _, env := range serializeModel.Envs { - key, value, err := env.GetKeyValuePair() - require.NoError(t, err) - - if key == "KEY_ONE" { - require.Equal(t, "first value", value) - - options, err := env.GetOptions() - require.NoError(t, err) - require.Equal(t, EnvironmentItemOptionsModel{}, options) - } else if key == "KEY_TWO" { - require.Equal(t, "second value, with options", value) - - options, err := env.GetOptions() - require.NoError(t, err) - require.NotNil(t, options.Meta) - - isExposeValue := options.Meta["is_expose"] - isExpose, ok := isExposeValue.(bool) - require.Equal(t, true, ok) - require.Equal(t, true, isExpose) - } else { - t.Fatalf("unexpected key found: %s", key) - } - } - } - } -} diff --git a/vendor/github.com/bitrise-io/envman/output/output.go b/vendor/github.com/bitrise-io/envman/output/output.go deleted file mode 100644 index a3e7d747..00000000 --- a/vendor/github.com/bitrise-io/envman/output/output.go +++ /dev/null @@ -1,64 +0,0 @@ -package output - -import ( - "encoding/json" - "fmt" - - "gopkg.in/yaml.v2" - - log "github.com/Sirupsen/logrus" - "github.com/urfave/cli" -) - -const ( - // FormatKey ... - FormatKey = "format" - // FormatRaw ... - FormatRaw = "raw" - // FormatJSON ... - FormatJSON = "json" - // FormatYML ... - FormatYML = "yml" -) - -// Format ... -var Format = FormatRaw - -// ConfigureOutputFormat ... -func ConfigureOutputFormat(c *cli.Context) error { - outFmt := c.String(FormatKey) - switch outFmt { - case FormatRaw, FormatJSON, FormatYML: - // valid - Format = outFmt - case "": - // default - Format = FormatRaw - default: - // invalid - return fmt.Errorf("Invalid Output Format: %s", outFmt) - } - return nil -} - -// Print ... -func Print(outModel interface{}, format string) { - switch format { - case FormatJSON: - serBytes, err := json.Marshal(outModel) - if err != nil { - log.Errorf("[.print] ERROR: %s", err) - return - } - fmt.Printf("%s\n", serBytes) - case FormatYML: - serBytes, err := yaml.Marshal(outModel) - if err != nil { - log.Errorf("[output.print] ERROR: %s", err) - return - } - fmt.Printf("%s\n", serBytes) - default: - log.Errorf("[output.print] Invalid output format: %s", format) - } -} diff --git a/vendor/github.com/bitrise-io/envman/version/build.go b/vendor/github.com/bitrise-io/envman/version/build.go deleted file mode 100644 index 06c70c10..00000000 --- a/vendor/github.com/bitrise-io/envman/version/build.go +++ /dev/null @@ -1,7 +0,0 @@ -package version - -// BuildNumber ... -var BuildNumber = "" - -// Commit ... -var Commit = "" diff --git a/vendor/github.com/bitrise-io/envman/version/version.go b/vendor/github.com/bitrise-io/envman/version/version.go deleted file mode 100644 index ae3330e3..00000000 --- a/vendor/github.com/bitrise-io/envman/version/version.go +++ /dev/null @@ -1,4 +0,0 @@ -package version - -// VERSION ... -const VERSION = "2.1.3" diff --git a/vendor/github.com/bitrise-io/go-utils/command/rubyscript/rubyscript.go b/vendor/github.com/bitrise-io/go-utils/command/rubyscript/rubyscript.go new file mode 100644 index 00000000..becf1d58 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/command/rubyscript/rubyscript.go @@ -0,0 +1,88 @@ +package rubyscript + +import ( + "path" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" +) + +// Helper ... +type Helper struct { + scriptContent string + tmpDir string + gemfilePth string +} + +// New ... +func New(scriptContent string) Helper { + return Helper{ + scriptContent: scriptContent, + } +} + +func (h *Helper) ensureTmpDir() (string, error) { + if h.tmpDir != "" { + return h.tmpDir, nil + } + + tmpDir, err := pathutil.NormalizedOSTempDirPath("__ruby-script-runner__") + if err != nil { + return "", err + } + + h.tmpDir = tmpDir + + return tmpDir, nil +} + +// BundleInstallCommand ... +func (h *Helper) BundleInstallCommand(gemfileContent, gemfileLockContent string) (*command.Model, error) { + tmpDir, err := h.ensureTmpDir() + if err != nil { + return nil, err + } + + gemfilePth := path.Join(tmpDir, "Gemfile") + if err := fileutil.WriteStringToFile(gemfilePth, gemfileContent); err != nil { + return nil, err + } + + if gemfileLockContent != "" { + gemfileLockPth := path.Join(tmpDir, "Gemfile.lock") + if err := fileutil.WriteStringToFile(gemfileLockPth, gemfileLockContent); err != nil { + return nil, err + } + } + + h.gemfilePth = gemfilePth + + // use '--gemfile=' flag to specify Gemfile path + // ... In general, bundler will assume that the location of the Gemfile(5) is also the project root, + // and will look for the Gemfile.lock and vendor/cache relative to it. ... + return command.New("bundle", "install", "--gemfile="+gemfilePth), nil +} + +// RunScriptCommand ... +func (h Helper) RunScriptCommand() (*command.Model, error) { + tmpDir, err := h.ensureTmpDir() + if err != nil { + return nil, err + } + + rubyScriptPth := path.Join(tmpDir, "script.rb") + if err := fileutil.WriteStringToFile(rubyScriptPth, h.scriptContent); err != nil { + return nil, err + } + + var cmd *command.Model + if h.gemfilePth != "" { + cmd = command.New("bundle", "exec", "ruby", rubyScriptPth) + cmd.AppendEnvs("BUNDLE_GEMFILE=" + h.gemfilePth) + } else { + cmd = command.New("ruby", rubyScriptPth) + } + + return cmd, nil +} diff --git a/vendor/github.com/bitrise-io/go-utils/parseutil/parseutil.go b/vendor/github.com/bitrise-io/go-utils/parseutil/parseutil.go new file mode 100644 index 00000000..08cec366 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/parseutil/parseutil.go @@ -0,0 +1,95 @@ +package parseutil + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/bitrise-io/go-utils/pointers" +) + +// ParseBool ... +func ParseBool(userInputStr string) (bool, error) { + if userInputStr == "" { + return false, errors.New("No string to parse") + } + userInputStr = strings.TrimSpace(userInputStr) + + lowercased := strings.ToLower(userInputStr) + if lowercased == "yes" || lowercased == "y" { + return true, nil + } + if lowercased == "no" || lowercased == "n" { + return false, nil + } + return strconv.ParseBool(lowercased) +} + +// CastToString ... +func CastToString(value interface{}) string { + casted, ok := value.(string) + + if !ok { + castedStr := fmt.Sprintf("%v", value) + casted = castedStr + } + + return casted +} + +// CastToStringPtr ... +func CastToStringPtr(value interface{}) *string { + castedValue := CastToString(value) + return pointers.NewStringPtr(castedValue) +} + +// CastToBool ... +func CastToBool(value interface{}) (bool, bool) { + casted, ok := value.(bool) + + if !ok { + castedStr := CastToString(value) + + castedBool, err := ParseBool(castedStr) + if err != nil { + return false, false + } + + casted = castedBool + } + + return casted, true +} + +// CastToBoolPtr ... +func CastToBoolPtr(value interface{}) (*bool, bool) { + castedValue, ok := CastToBool(value) + if !ok { + return nil, false + } + return pointers.NewBoolPtr(castedValue), true +} + +// CastToMapStringInterface ... +func CastToMapStringInterface(value interface{}) (map[string]interface{}, bool) { + castedValue, ok := value.(map[interface{}]interface{}) + desiredMap := map[string]interface{}{} + for key, value := range castedValue { + keyStr, ok := key.(string) + if !ok { + return map[string]interface{}{}, false + } + desiredMap[keyStr] = value + } + return desiredMap, ok +} + +// CastToMapStringInterfacePtr ... +func CastToMapStringInterfacePtr(value interface{}) (*map[string]interface{}, bool) { + casted, ok := CastToMapStringInterface(value) + if !ok { + return nil, false + } + return pointers.NewMapStringInterfacePtr(casted), true +} diff --git a/vendor/github.com/bitrise-io/go-utils/pointers/pointers.go b/vendor/github.com/bitrise-io/go-utils/pointers/pointers.go new file mode 100644 index 00000000..e26647d0 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/pointers/pointers.go @@ -0,0 +1,98 @@ +package pointers + +import "time" + +// NewBoolPtr ... +func NewBoolPtr(val bool) *bool { + ptrValue := new(bool) + *ptrValue = val + return ptrValue +} + +// NewStringPtr ... +func NewStringPtr(val string) *string { + ptrValue := new(string) + *ptrValue = val + return ptrValue +} + +// NewTimePtr ... +func NewTimePtr(val time.Time) *time.Time { + ptrValue := new(time.Time) + *ptrValue = val + return ptrValue +} + +// NewIntPtr ... +func NewIntPtr(val int) *int { + ptrValue := new(int) + *ptrValue = val + return ptrValue +} + +// NewInt64Ptr ... +func NewInt64Ptr(val int64) *int64 { + ptrValue := new(int64) + *ptrValue = val + return ptrValue +} + +// NewMapStringInterfacePtr ... +func NewMapStringInterfacePtr(val map[string]interface{}) *map[string]interface{} { + ptrValue := new(map[string]interface{}) + *ptrValue = map[string]interface{}{} + for key, value := range val { + (*ptrValue)[key] = value + } + return ptrValue +} + +// ------------------------------------------------------ +// --- Safe Getters + +// Bool ... +func Bool(val *bool) bool { + return BoolWithDefault(val, false) +} + +// BoolWithDefault ... +func BoolWithDefault(val *bool, defaultValue bool) bool { + if val == nil { + return defaultValue + } + return *val +} + +// String ... +func String(val *string) string { + return StringWithDefault(val, "") +} + +// StringWithDefault ... +func StringWithDefault(val *string, defaultValue string) string { + if val == nil { + return defaultValue + } + return *val +} + +// TimeWithDefault ... +func TimeWithDefault(val *time.Time, defaultValue time.Time) time.Time { + if val == nil { + return defaultValue + } + return *val +} + +// Int ... +func Int(val *int) int { + return IntWithDefault(val, 0) +} + +// IntWithDefault ... +func IntWithDefault(val *int, defaultValue int) int { + if val == nil { + return defaultValue + } + return *val +} diff --git a/vendor/github.com/bitrise-io/stepman/.gitignore b/vendor/github.com/bitrise-io/stepman/.gitignore deleted file mode 100644 index 4cc072b1..00000000 --- a/vendor/github.com/bitrise-io/stepman/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -_temp/ -.bitrise -.bitrise.secrets.yml -_bin -_tmp -.gows.user.yml diff --git a/vendor/github.com/bitrise-io/stepman/Dockerfile b/vendor/github.com/bitrise-io/stepman/Dockerfile deleted file mode 100644 index c1669c88..00000000 --- a/vendor/github.com/bitrise-io/stepman/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM golang:1.5.2-wheezy - -ENV PROJ_NAME stepman - -RUN apt-get update - -RUN DEBIAN_FRONTEND=noninteractive apt-get -y install git mercurial curl rsync ruby - -# -# Install Bitrise CLI -RUN curl -L https://github.com/bitrise-io/bitrise/releases/download/1.2.4/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise -RUN chmod +x /usr/local/bin/bitrise -RUN bitrise setup --minimal - -# From the official Golang Dockerfile -# https://github.com/docker-library/golang/blob/master/1.4/Dockerfile -RUN mkdir -p /go/src /go/bin && chmod -R 777 /go -ENV GOPATH /go -ENV PATH /go/bin:$PATH - -RUN mkdir -p /go/src/github.com/bitrise-io/$PROJ_NAME -COPY . /go/src/github.com/bitrise-io/$PROJ_NAME - -WORKDIR /go/src/github.com/bitrise-io/$PROJ_NAME -# godep -RUN go get -u github.com/tools/godep -RUN godep restore -# install -RUN go install - -CMD $PROJ_NAME --version diff --git a/vendor/github.com/bitrise-io/stepman/Gopkg.lock b/vendor/github.com/bitrise-io/stepman/Gopkg.lock deleted file mode 100644 index fb904174..00000000 --- a/vendor/github.com/bitrise-io/stepman/Gopkg.lock +++ /dev/null @@ -1,152 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - branch = "master" - digest = "1:d972dce278dd6023fccf060a44cc51c33f0ce25f0522970c000c502766826cb8" - name = "github.com/Sirupsen/logrus" - packages = ["."] - pruneopts = "" - revision = "eef6b768ab01a0598a0a6db97bad2a37d31df1d1" - -[[projects]] - branch = "master" - digest = "1:aad9e0eeac37ffe5409673ca6d1160fca6786408cf904fba456b888bee6dba42" - name = "github.com/bitrise-io/envman" - packages = ["models"] - pruneopts = "" - revision = "41ea1b6f422eabd86189caf7da0c633208ba4760" - -[[projects]] - branch = "master" - digest = "1:6083fc4e90fe661aad326c42fa16fb85774cb55711560d1208c82ff290534836" - name = "github.com/bitrise-io/go-utils" - packages = [ - "colorstring", - "command", - "command/git", - "errorutil", - "fileutil", - "log", - "parseutil", - "pathutil", - "pointers", - "retry", - "stringutil", - "urlutil", - "versions", - ] - pruneopts = "" - revision = "2a09aab8380d7842750328aebd5671bcccea89c8" - -[[projects]] - branch = "master" - digest = "1:6825c56bedbe125a302e7516f731fa841ce9db05f873dbc5745ee807a8c3d3a2" - name = "github.com/bitrise-io/goinp" - packages = ["goinp"] - pruneopts = "" - revision = "f451b19ba4d087d8272d411ad91af5f4218fd517" - -[[projects]] - branch = "master" - digest = "1:006a3e4b6551ef1656f6917f6907f30b8b5a6ece203852f40bec891a7492e27c" - name = "github.com/bitrise-tools/colorstring" - packages = ["."] - pruneopts = "" - revision = "a8cd701151924e6beffb74a34a556b320e0cfcf7" - -[[projects]] - digest = "1:0deddd908b6b4b768cfc272c16ee61e7088a60f7fe2f06c547bd3d8e1f8b8e77" - name = "github.com/davecgh/go-spew" - packages = ["spew"] - pruneopts = "" - revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" - version = "v1.1.1" - -[[projects]] - digest = "1:6a874e3ddfb9db2b42bd8c85b6875407c702fa868eed20634ff489bc896ccfd3" - name = "github.com/konsorten/go-windows-terminal-sequences" - packages = ["."] - pruneopts = "" - revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" - version = "v1.0.1" - -[[projects]] - digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" - name = "github.com/pmezard/go-difflib" - packages = ["difflib"] - pruneopts = "" - revision = "792786c7400a136282c1664665ae0a8db921c6c2" - version = "v1.0.0" - -[[projects]] - branch = "master" - digest = "1:381bcbeb112a51493d9d998bbba207a529c73dbb49b3fd789e48c63fac1f192c" - name = "github.com/stretchr/testify" - packages = [ - "assert", - "require", - ] - pruneopts = "" - revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" - -[[projects]] - branch = "master" - digest = "1:438d5957cf1c26ca62975dc4b1b59242acc9e527f39ed3f6cda57ee6d3653daf" - name = "github.com/urfave/cli" - packages = ["."] - pruneopts = "" - revision = "b67dcf995b6a7b7f14fad5fcb7cc5441b05e814b" - -[[projects]] - branch = "master" - digest = "1:59b49c47c11a48f1054529207f65907c014ecf5f9a7c0d9c0f1616dec7b062ed" - name = "golang.org/x/crypto" - packages = ["ssh/terminal"] - pruneopts = "" - revision = "ff983b9c42bc9fbf91556e191cc8efb585c16908" - -[[projects]] - branch = "master" - digest = "1:30d295ca4b98f09df913be86c1ac48d1d321eb2a84543206a87704e5d25db19b" - name = "golang.org/x/sys" - packages = [ - "unix", - "windows", - ] - pruneopts = "" - revision = "7fbe1cd0fcc20051e1fcb87fbabec4a1bacaaeba" - -[[projects]] - branch = "v2" - digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d" - name = "gopkg.in/yaml.v2" - packages = ["."] - pruneopts = "" - revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "github.com/Sirupsen/logrus", - "github.com/bitrise-io/envman/models", - "github.com/bitrise-io/go-utils/colorstring", - "github.com/bitrise-io/go-utils/command", - "github.com/bitrise-io/go-utils/command/git", - "github.com/bitrise-io/go-utils/fileutil", - "github.com/bitrise-io/go-utils/log", - "github.com/bitrise-io/go-utils/pathutil", - "github.com/bitrise-io/go-utils/pointers", - "github.com/bitrise-io/go-utils/retry", - "github.com/bitrise-io/go-utils/stringutil", - "github.com/bitrise-io/go-utils/urlutil", - "github.com/bitrise-io/go-utils/versions", - "github.com/bitrise-io/goinp/goinp", - "github.com/bitrise-tools/colorstring", - "github.com/stretchr/testify/require", - "github.com/urfave/cli", - "gopkg.in/yaml.v2", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/vendor/github.com/bitrise-io/stepman/Gopkg.toml b/vendor/github.com/bitrise-io/stepman/Gopkg.toml deleted file mode 100644 index 847f5be4..00000000 --- a/vendor/github.com/bitrise-io/stepman/Gopkg.toml +++ /dev/null @@ -1,27 +0,0 @@ -[[constraint]] - name = "github.com/Sirupsen/logrus" - branch = "master" - -[[constraint]] - name = "github.com/bitrise-io/envman" - branch = "master" - -[[constraint]] - name = "github.com/bitrise-io/go-utils" - branch = "master" - -[[constraint]] - name = "github.com/bitrise-io/goinp" - branch = "master" - -[[constraint]] - name = "github.com/stretchr/testify" - branch = "master" - -[[constraint]] - name = "github.com/urfave/cli" - branch = "master" - -[[constraint]] - name = "gopkg.in/yaml.v2" - branch = "v2" diff --git a/vendor/github.com/bitrise-io/stepman/README.md b/vendor/github.com/bitrise-io/stepman/README.md deleted file mode 100644 index 4e61ae57..00000000 --- a/vendor/github.com/bitrise-io/stepman/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# stepman - -You can manage decentralized StepLib Step (script) Collections with `stepman`. - -You can register multiple collections, audit them, manage the caching of individual Steps locally, -define and handle fallback URLs for downloading the Step codes (archives), -and share new Steps into public StepLib collections with `stepman`. - -**Public Beta:** this repository is still under active development, -frequent changes are expected, but we we don't plan to introduce breaking changes, -unless really necessary. **Feedback is greatly appreciated!** - -*Part of the Bitrise Continuous Integration, Delivery and Automations Stack, -with [bitrise](https://github.com/bitrise-io/bitrise) and [envman](https://github.com/bitrise-io/envman).* - - -## Install - -Check the latest release for instructions at: [https://github.com/bitrise-io/stepman/releases](https://github.com/bitrise-io/stepman/releases) - - -## Share your own Step - -Call `stepman share` and follow the guide it prints. - -### Release a new version - -- merge every code changes to the master branch - -- do not forget to merge every version related file changes: - - - update the version number (in version.go file) - - update version tests (in _tests/integration/version_test.go file) - -- push the new version tag to the master branch \ No newline at end of file diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/helper.go b/vendor/github.com/bitrise-io/stepman/_tests/integration/helper.go deleted file mode 100644 index f11abd67..00000000 --- a/vendor/github.com/bitrise-io/stepman/_tests/integration/helper.go +++ /dev/null @@ -1,23 +0,0 @@ -package integration - -import ( - "os" - - "github.com/bitrise-io/go-utils/command" -) - -const defaultLibraryURI = "https://github.com/bitrise-io/bitrise-steplib.git" - -func binPath() string { - return os.Getenv("INTEGRATION_TEST_BINARY_PATH") -} - -func cleanupLibrary(libraryURI string) error { - cmd := command.New(binPath(), "delete", "--collection", libraryURI) - return cmd.Run() -} - -func setupLibrary(libraryURI string) error { - cmd := command.New(binPath(), "setup", "--collection", libraryURI) - return cmd.Run() -} diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/setup_test.go b/vendor/github.com/bitrise-io/stepman/_tests/integration/setup_test.go deleted file mode 100644 index 7961f70b..00000000 --- a/vendor/github.com/bitrise-io/stepman/_tests/integration/setup_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package integration - -import ( - "testing" - - "os" - - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/command/git" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestSetup(t *testing.T) { - t.Log("remote library") - { - out, err := command.New(binPath(), "delete", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "setup", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "delete", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - } - - t.Log("local library") - { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__library__") - require.NoError(t, err) - defer func() { - require.NoError(t, os.RemoveAll(tmpDir)) - }() - - repo, err := git.New(tmpDir) - require.NoError(t, err) - require.NoError(t, repo.Clone(defaultLibraryURI).Run()) - - out, err := command.New(binPath(), "delete", "-c", tmpDir).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "setup", "--local", "-c", tmpDir).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "delete", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "setup", "--local", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "delete", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "setup", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "delete", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - } -} diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/step_info_test.go b/vendor/github.com/bitrise-io/stepman/_tests/integration/step_info_test.go deleted file mode 100644 index 15fcd595..00000000 --- a/vendor/github.com/bitrise-io/stepman/_tests/integration/step_info_test.go +++ /dev/null @@ -1,286 +0,0 @@ -package integration - -import ( - "testing" - - "strings" - - "github.com/bitrise-io/go-utils/command" - "github.com/stretchr/testify/require" -) - -func TestStepInfo(t *testing.T) { - out, err := command.New(binPath(), "setup", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - t.Log("library step") - { - out, err = command.New(binPath(), "step-info", "--collection", defaultLibraryURI, "--id", "apk-info", "--version", "1.0.4").RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - require.Equal(t, apkInfo104Defintiion, out) - } - - t.Log("library step --format json") - { - out, err = command.New(binPath(), "step-info", "--collection", defaultLibraryURI, "--id", "apk-info", "--version", "1.0.4", "--format", "json").RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - require.Equal(t, true, strings.Contains(out, apkInfo104DefintiionJSON), out) - } - - t.Log("local step") - { - out, err := command.New(binPath(), "step-info", "--collection", "path", "--id", "./test-step").RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - require.Equal(t, localTestStepDefintion, out) - } - - t.Log("local step - deprecated --step-yml flag") - { - out, err := command.New(binPath(), "step-info", "--step-yml", "./test-step").RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - require.Equal(t, localTestStepDefintion, out) - } - - t.Log("local step --format json") - { - out, err := command.New(binPath(), "step-info", "--collection", "path", "--id", "./test-step", "--format", "json").RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - require.Equal(t, localTestStepDefintionJSON, out) - } - - t.Log("git step") - { - out, err := command.New(binPath(), "step-info", "--collection", "git", "--id", "https://github.com/bitrise-steplib/steps-xamarin-user-management.git", "--version", "1.0.3").RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - require.Equal(t, gitTestStepDefinition, out) - } - - t.Log("git step --format json") - { - out, err := command.New(binPath(), "step-info", "--collection", "git", "--id", "https://github.com/bitrise-steplib/steps-xamarin-user-management.git", "--version", "1.0.3", "--format", "json").RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - require.Equal(t, true, strings.Contains(out, gitTestStepDefintionJSON), out) - } -} - -func TestStepInfoExitCode(t *testing.T) { - t.Log("default setup - desired exit code: 0") - { - out, err := command.New(binPath(), "setup", "--collection", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - } - - t.Log("latest version - desired exit code: 0") - { - out, err := command.New(binPath(), "step-info", "--collection", defaultLibraryURI, "--id", "script").RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - } - - t.Log("latest version, json format - desired exit code: 0") - { - out, err := command.New(binPath(), "step-info", "--collection", defaultLibraryURI, "--id", "script", "--format", "json").RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - } - - t.Log("invalid version -1 - desired exit code: NOT 0") - { - out, err := command.New(binPath(), "step-info", "--collection", defaultLibraryURI, "--id", "script", "--version", "-1").RunAndReturnTrimmedCombinedOutput() - require.Error(t, err, out) - } - - t.Log("invalid version -1 - desired exit code: NOT 0") - { - out, err := command.New(binPath(), "step-info", "--collection", defaultLibraryURI, "--id", "script", "--version", "-1", "--format", "json").RunAndReturnTrimmedCombinedOutput() - require.Error(t, err, out) - } -} - -const gitTestStepDefintionJSON = `{"library":"git","id":"https://github.com/bitrise-steplib/steps-xamarin-user-management.git","version":"1.0.3","info":{},"step":{"title":"Xamarin User Management","summary":"This step helps you authenticate your user with Xamarin and to download your Xamarin liceses.","description":"This step helps you authenticate your user with Xamarin and to download your Xamarin licenses.","website":"https://github.com/bitrise-steplib/steps-xamarin-user-management","source_code_url":"https://github.com/bitrise-steplib/steps-xamarin-user-management","support_url":"https://github.com/bitrise-steplib/steps-xamarin-user-management/issues","host_os_tags":["osx-10.10"],"project_type_tags":["xamarin"],"is_requires_admin_user":false,"is_always_run":true,"is_skippable":false,"run_if":".IsCI","timeout":0,"inputs":[{"build_slug":"$BITRISE_BUILD_SLUG","opts":{"is_expand":true,"skip_if_empty":false,"title":"Bitrise build slug","description":"Bitrise build slug\n","summary":"","category":"","is_required":true,"is_dont_change_value":false,"is_template":false,"is_sensitive":false}},{"opts":{"is_expand":true,"skip_if_empty":false,"title":"Xamarin.iOS License","description":"Set to yes if you want to download the Xamarin.iOS license file\n","summary":"","category":"","value_options":["yes","no"],"is_required":true,"is_dont_change_value":false,"is_template":false,"is_sensitive":false},"xamarin_ios_license":"yes"},{"opts":{"is_expand":true,"skip_if_empty":false,"title":"Xamarin.Android License","description":"Set to yes if you want to download the Xamarin.Android license file\n","summary":"","category":"","value_options":["yes","no"],"is_required":true,"is_dont_change_value":false,"is_template":false,"is_sensitive":false},"xamarin_android_license":"yes"},{"opts":{"is_expand":true,"skip_if_empty":false,"title":"Xamarin.Mac License","description":"Set to yes if you want to download the Xamarin.Mac license file\n","summary":"","category":"","value_options":["yes","no"],"is_required":true,"is_dont_change_value":false,"is_template":false,"is_sensitive":false},"xamarin_mac_license":"no"}]}` - -const gitTestStepDefinition = "\x1b[34;1m" + `Library:` + "\x1b[0m" + ` git -` + "\x1b[34;1m" + `ID:` + "\x1b[0m" + ` https://github.com/bitrise-steplib/steps-xamarin-user-management.git -` + "\x1b[34;1m" + `Version:` + "\x1b[0m" + ` 1.0.3 -` + "\x1b[34;1m" + `LatestVersion:` + "\x1b[0m" + ` -` + "\x1b[34;1m" + `Definition:` + "\x1b[0m" + ` - -title: "Xamarin User Management" -summary: This step helps you authenticate your user with Xamarin and to download your Xamarin liceses. -description: |- - This step helps you authenticate your user with Xamarin and to download your Xamarin licenses. -website: https://github.com/bitrise-steplib/steps-xamarin-user-management -source_code_url: https://github.com/bitrise-steplib/steps-xamarin-user-management -support_url: https://github.com/bitrise-steplib/steps-xamarin-user-management/issues -host_os_tags: - - osx-10.10 -project_type_tags: - - xamarin -type_tags: -is_requires_admin_user: false -is_always_run: true -is_skippable: false -run_if: .IsCI -inputs: - - build_slug: $BITRISE_BUILD_SLUG - opts: - title: Bitrise build slug - description: | - Bitrise build slug - is_required: true - is_expand: true - - xamarin_ios_license: "yes" - opts: - title: Xamarin.iOS License - description: | - Set to yes if you want to download the Xamarin.iOS license file - value_options: - - "yes" - - "no" - is_required: true - is_expand: true - - xamarin_android_license: "yes" - opts: - title: Xamarin.Android License - description: | - Set to yes if you want to download the Xamarin.Android license file - value_options: - - "yes" - - "no" - is_required: true - is_expand: true - - xamarin_mac_license: "no" - opts: - title: Xamarin.Mac License - description: | - Set to yes if you want to download the Xamarin.Mac license file - value_options: - - "yes" - - "no" - is_required: true - is_expand: true` - -const localTestStepDefintionJSON = "{\"library\":\"path\",\"id\":\"./test-step\",\"info\":{},\"step\":{\"title\":\"STEP TEMPLATE\",\"summary\":\"A short summary of the step. Don't make it too long ;)\",\"description\":\"This is a Step template.\\nContains everything what's required for a valid Stepman managed step.\\n\\nA Step's description (and generally any description property)\\ncan be a [Markdown](https://en.wikipedia.org/wiki/Markdown) formatted text.\\n\\nTo create your own Step:\\n\\n1. Create a new repository on GitHub\\n2. Copy the files from this folder into your repository\\n3. That's all, you can use it on your own machine\\n4. Once you're happy with it you can share it with others.\",\"website\":\"https://github.com/...\",\"source_code_url\":\"https://github.com/...\",\"support_url\":\"https://github.com/.../issues\",\"host_os_tags\":[\"osx-10.10\"],\"project_type_tags\":[\"ios\",\"android\",\"xamarin\"],\"type_tags\":[\"script\"],\"deps\":{\"brew\":[{\"name\":\"git\"},{\"name\":\"wget\"}],\"apt_get\":[{\"name\":\"git\"},{\"name\":\"wget\"}]},\"is_requires_admin_user\":true,\"is_always_run\":false,\"is_skippable\":false,\"run_if\":\"\",\"timeout\":0,\"inputs\":[{\"example_step_input\":\"Default Value - you can leave this empty if you want to\",\"opts\":{\"is_expand\":true,\"skip_if_empty\":false,\"title\":\"Example Step Input\",\"description\":\"Description of this input.\\n\\nCan be Markdown formatted text.\\n\",\"summary\":\"Summary. No more than 2-3 sentences.\",\"category\":\"\",\"is_required\":true,\"is_dont_change_value\":false,\"is_template\":false,\"is_sensitive\":false}}],\"outputs\":[{\"EXAMPLE_STEP_OUTPUT\":null,\"opts\":{\"is_expand\":true,\"skip_if_empty\":false,\"title\":\"Example Step Output\",\"description\":\"Description of this output.\\n\\nCan be Markdown formatted text.\\n\",\"summary\":\"Summary. No more than 2-3 sentences.\",\"category\":\"\",\"is_required\":false,\"is_dont_change_value\":false,\"is_template\":false,\"is_sensitive\":false}}]},\"definition_pth\":\"test-step/step.yml\"}" - -const localTestStepDefintion = "\x1b[34;1m" + `Library:` + "\x1b[0m" + ` path -` + "\x1b[34;1m" + `ID:` + "\x1b[0m" + ` ./test-step -` + "\x1b[34;1m" + `Version:` + "\x1b[0m" + ` -` + "\x1b[34;1m" + `LatestVersion:` + "\x1b[0m" + ` -` + "\x1b[34;1m" + `Definition:` + "\x1b[0m" + ` - -title: "STEP TEMPLATE" -summary: A short summary of the step. Don't make it too long ;) -description: |- - This is a Step template. - Contains everything what's required for a valid Stepman managed step. - - A Step's description (and generally any description property) - can be a [Markdown](https://en.wikipedia.org/wiki/Markdown) formatted text. - - To create your own Step: - - 1. Create a new repository on GitHub - 2. Copy the files from this folder into your repository - 3. That's all, you can use it on your own machine - 4. Once you're happy with it you can share it with others. -website: https://github.com/... -source_code_url: https://github.com/... -support_url: https://github.com/.../issues -host_os_tags: - - osx-10.10 -project_type_tags: - - ios - - android - - xamarin -type_tags: - - script -is_requires_admin_user: true -is_always_run: false -is_skippable: false -deps: - brew: - - name: git - - name: wget - apt_get: - - name: git - - name: wget -run_if: "" -inputs: - - example_step_input: Default Value - you can leave this empty if you want to - opts: - title: "Example Step Input" - summary: Summary. No more than 2-3 sentences. - description: | - Description of this input. - - Can be Markdown formatted text. - is_expand: true - is_required: true - value_options: [] -outputs: - - EXAMPLE_STEP_OUTPUT: - opts: - title: "Example Step Output" - summary: Summary. No more than 2-3 sentences. - description: | - Description of this output. - - Can be Markdown formatted text.` - -const apkInfo104DefintiionJSON = `{"library":"https://github.com/bitrise-io/bitrise-steplib.git","id":"apk-info","version":"1.0.4","latest_version":"1.4.2","info":{},"step":{"title":"APK info","summary":"APK Android info provider","description":"Provides all possible Android APK information as package name, version name or version code.","website":"https://github.com/thefuntasty/bitrise-step-apk-info","source_code_url":"https://github.com/thefuntasty/bitrise-step-apk-info","support_url":"https://github.com/thefuntasty/bitrise-step-apk-info/issues","published_at":"2016-10-19T15:35:00.882498804+02:00","source":{"git":"https://github.com/thefuntasty/bitrise-step-apk-info.git","commit":"104e26a8800fc9363658b5837cf4747e5f26b032"},"asset_urls":{"icon.svg":"https://bitrise-steplib-collection.s3.amazonaws.com/steps/apk-info/assets/icon.svg"},"project_type_tags":["android"],"type_tags":["android","apk"],"is_requires_admin_user":false,"is_always_run":false,"is_skippable":false,"run_if":"","timeout":0,"inputs":[{"apk_path":"$BITRISE_APK_PATH","opts":{"category":"","description":"File path to APK file to get info from.\n","is_dont_change_value":false,"is_expand":true,"is_required":true,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"APK file path"}}],"outputs":[{"ANDROID_APP_PACKAGE_NAME":null,"opts":{"category":"","description":"Android application package name, ex. com.package.my","is_dont_change_value":false,"is_expand":true,"is_required":false,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"Android application package name"}},{"ANDROID_APK_FILE_SIZE":null,"opts":{"category":"","description":"Android APK file size, in bytes","is_dont_change_value":false,"is_expand":true,"is_required":false,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"Android APK file size"}},{"ANDROID_APP_NAME":null,"opts":{"category":"","description":"Android application name from APK","is_dont_change_value":false,"is_expand":true,"is_required":false,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"Android application name"}},{"ANDROID_APP_VERSION_NAME":null,"opts":{"category":"","description":"Android application version name from APK, ex. 1.0.0","is_dont_change_value":false,"is_expand":true,"is_required":false,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"Android application version name"}},{"ANDROID_APP_VERSION_CODE":null,"opts":{"category":"","description":"Android application version code from APK, ex. 10","is_dont_change_value":false,"is_expand":true,"is_required":false,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"Android application version code"}},{"ANDROID_ICON_PATH":null,"opts":{"category":"","description":"File path to android application icon","is_dont_change_value":false,"is_expand":true,"is_required":false,"is_sensitive":false,"is_template":false,"skip_if_empty":false,"summary":"","title":"File path to icon"}}]}` - -const apkInfo104Defintiion = "\x1b[34;1m" + `Library:` + "\x1b[0m" + ` https://github.com/bitrise-io/bitrise-steplib.git -` + "\x1b[34;1m" + `ID:` + "\x1b[0m" + ` apk-info -` + "\x1b[34;1m" + `Version:` + "\x1b[0m" + ` 1.0.4 -` + "\x1b[34;1m" + `LatestVersion:` + "\x1b[0m" + ` 1.4.2 -` + "\x1b[34;1m" + `Definition:` + "\x1b[0m" + ` - -title: APK info -summary: APK Android info provider -description: Provides all possible Android APK information as package name, version - name or version code. -website: https://github.com/thefuntasty/bitrise-step-apk-info -source_code_url: https://github.com/thefuntasty/bitrise-step-apk-info -support_url: https://github.com/thefuntasty/bitrise-step-apk-info/issues -published_at: 2016-10-19T15:35:00.882498804+02:00 -source: - git: https://github.com/thefuntasty/bitrise-step-apk-info.git - commit: 104e26a8800fc9363658b5837cf4747e5f26b032 -project_type_tags: -- android -type_tags: -- android -- apk -is_requires_admin_user: false -is_always_run: false -is_skippable: false -inputs: -- apk_path: $BITRISE_APK_PATH - opts: - description: | - File path to APK file to get info from. - is_required: true - title: APK file path -outputs: -- ANDROID_APP_PACKAGE_NAME: null - opts: - description: Android application package name, ex. com.package.my - title: Android application package name -- ANDROID_APK_FILE_SIZE: null - opts: - description: Android APK file size, in bytes - title: Android APK file size -- ANDROID_APP_NAME: null - opts: - description: Android application name from APK - title: Android application name -- ANDROID_APP_VERSION_NAME: null - opts: - description: Android application version name from APK, ex. 1.0.0 - title: Android application version name -- ANDROID_APP_VERSION_CODE: null - opts: - description: Android application version code from APK, ex. 10 - title: Android application version code -- ANDROID_ICON_PATH: null - opts: - description: File path to android application icon - title: File path to icon` diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/.gitignore b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/.gitignore deleted file mode 100755 index 6397b469..00000000 --- a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.bitrise* diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/README.md b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/README.md deleted file mode 100755 index 6ba2a372..00000000 --- a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/README.md +++ /dev/null @@ -1,93 +0,0 @@ -# My Awesome Step - -My Awesome Step is a solid starting code base for -a new Step. - - -## How to use this Step - -Can be run directly with the [bitrise CLI](https://github.com/bitrise-io/bitrise), -just `git clone` this repository, `cd` into it's folder in your Terminal/Command Line -and call `bitrise run test`. - -*Check the `bitrise.yml` file for required inputs which have to be -added to your `.bitrise.secrets.yml` file!* - -Step by step: - -1. Open up your Terminal / Command Line -2. `git clone` the repository -3. `cd` into the directory of the step (the one you just `git clone`d) -5. Create a `.bitrise.secrets.yml` file in the same directory of `bitrise.yml` - the `.bitrise.secrets.yml` is a git ignored file, you can store your secrets in -6. Check the `bitrise.yml` file for any secret you should set in `.bitrise.secrets.yml` - * Best practice is to mark these options with something like `# define these in your .bitrise.secrets.yml`, in the `app:envs` section. -7. Once you have all the required secret parameters in your `.bitrise.secrets.yml` you can just run this step with the [bitrise CLI](https://github.com/bitrise-io/bitrise): `bitrise run test` - -An example `.bitrise.secrets.yml` file: - -``` -envs: -- A_SECRET_PARAM_ONE: the value for secret one -- A_SECRET_PARAM_TWO: the value for secret two -``` - -## How to create your own step - -1. Create a new git repository for your step (**don't fork** the *step template*, create a *new* repository) -2. Copy the [step template](https://github.com/bitrise-steplib/step-template) files into your repository -3. Fill the `step.sh` with your functionality -4. Wire out your inputs to `step.yml` (`inputs` section) -5. Fill out the other parts of the `step.yml` too -6. Provide test values for the inputs in the `bitrise.yml` -7. Run your step with `bitrise run test` - if it works, you're ready - -__For Step development guidelines & best practices__ check this documentation: [https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md](https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md). - -**NOTE:** - -If you want to use your step in your project's `bitrise.yml`: - -1. git push the step into it's repository -2. reference it in your `bitrise.yml` with the `git::PUBLIC-GIT-CLONE-URL@BRANCH` step reference style: - -``` -- git::https://github.com/user/my-step.git@branch: - title: My step - inputs: - - my_input_1: "my value 1" - - my_input_2: "my value 2" -``` - -You can find more examples of step reference styles -in the [bitrise CLI repository](https://github.com/bitrise-io/bitrise/blob/master/_examples/tutorials/steps-and-workflows/bitrise.yml#L65). - -## How to contribute to this Step - -1. Fork this repository -2. `git clone` it -3. Create a branch you'll work on -4. To use/test the step just follow the **How to use this Step** section -5. Do the changes you want to -6. Run/test the step before sending your contribution - * You can also test the step in your `bitrise` project, either on your Mac or on [bitrise.io](https://www.bitrise.io) - * You just have to replace the step ID in your project's `bitrise.yml` with either a relative path, or with a git URL format - * (relative) path format: instead of `- original-step-id:` use `- path::./relative/path/of/script/on/your/Mac:` - * direct git URL format: instead of `- original-step-id:` use `- git::https://github.com/user/step.git@branch:` - * You can find more example of alternative step referencing at: https://github.com/bitrise-io/bitrise/blob/master/_examples/tutorials/steps-and-workflows/bitrise.yml -7. Once you're done just commit your changes & create a Pull Request - - -## Share your own Step - -You can share your Step or step version with the [bitrise CLI](https://github.com/bitrise-io/bitrise). If you use the `bitrise.yml` included in this repository, all you have to do is: - -1. In your Terminal / Command Line `cd` into this directory (where the `bitrise.yml` of the step is located) -1. Run: `bitrise run test` to test the step -1. Run: `bitrise run audit-this-step` to audit the `step.yml` -1. Check the `share-this-step` workflow in the `bitrise.yml`, and fill out the - `envs` if you haven't done so already (don't forget to bump the version number if this is an update - of your step!) -1. Then run: `bitrise run share-this-step` to share the step (version) you specified in the `envs` -1. Send the Pull Request, as described in the logs of `bitrise run share-this-step` - -That's all ;) diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/bitrise.yml b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/bitrise.yml deleted file mode 100755 index da3e64f4..00000000 --- a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/bitrise.yml +++ /dev/null @@ -1,84 +0,0 @@ -format_version: 1.1.0 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git - -app: - envs: - # define these in your .bitrise.secrets.yml - - A_SECRET_PARAM: $A_SECRET_PARAM - -workflows: - test: - steps: - - change-workdir: - title: Switch working dir to test / _tmp dir - description: |- - To prevent step testing issues, like referencing relative - files with just './some-file' in the step's code, which would - work for testing the step from this directory directly - but would break if the step is included in another `bitrise.yml`. - run_if: true - inputs: - - path: ./_tmp - - is_create_path: true - - path::./: - title: Step Test - description: |- - The example input has a default value, - you can overwrite it if you want to, just like we did below, - but the step would use the default value specified in the `step.yml` - file if you would not specify another value. - run_if: true - inputs: - - example_step_input: Example Step Input's value - - - # ---------------------------------------------------------------- - # --- workflows to Share this step into a Step Library - audit-this-step: - steps: - - script: - inputs: - - content: |- - #!/bin/bash - set -ex - stepman audit --step-yml ./step.yml - - share-this-step: - envs: - # if you want to share this step into a StepLib - - MY_STEPLIB_REPO_FORK_GIT_URL: - - STEP_ID_IN_STEPLIB: - - STEP_GIT_VERION_TAG_TO_SHARE: - - STEP_GIT_CLONE_URL: - description: |- - If this is the first time you try to share a Step you should - first call: $ bitrise share - - This will print you a guide, and information about how Step sharing - works. Please read it at least once! - - As noted in the Step sharing guide you'll have to fork the - StepLib you want to share this step into. Once you're done with forking - the repository you should set your own fork's git clone URL - in the `.bitrise.secrets.yml` file, or here in the `envs` section, - as the value of the `MY_STEPLIB_REPO_FORK_GIT_URL` environment. - - You're now ready to share this Step, just make sure that - the `STEP_ID_IN_STEPLIB` and `STEP_GIT_VERION_TAG_TO_SHARE` - environments are set to the desired values! - - To share this Step into a StepLib you can just run: $ bitrise run share-this-step - - Once it finishes the only thing left is to actually create a Pull Request, - the way described in the guide printed at the end of the process. - before_run: - - audit-this-step - steps: - - script: - inputs: - - content: |- - #!/bin/bash - set -ex - bitrise share start -c ${MY_STEPLIB_REPO_FORK_GIT_URL} - bitrise share create --stepid ${STEP_ID_IN_STEPLIB} --tag ${STEP_GIT_VERION_TAG_TO_SHARE} --git ${STEP_GIT_CLONE_URL} - bitrise share finish diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.sh b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.sh deleted file mode 100755 index 2cf7beee..00000000 --- a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -echo "This is the value specified for the input 'example_step_input': ${example_step_input}" - -# -# --- Export Environment Variables for other Steps: -# You can export Environment Variables for other Steps with -# envman, which is automatically installed by `bitrise setup`. -# A very simple example: -# envman add --key EXAMPLE_STEP_OUTPUT --value 'the value you want to share' -# Envman can handle piped inputs, which is useful if the text you want to -# share is complex and you don't want to deal with proper bash escaping: -# cat file_with_complex_input | envman add --KEY EXAMPLE_STEP_OUTPUT -# You can find more usage examples on envman's GitHub page -# at: https://github.com/bitrise-io/envman - -# -# --- Exit codes: -# The exit code of your Step is very important. If you return -# with a 0 exit code `bitrise` will register your Step as "successful". -# Any non zero exit code will be registered as "failed" by `bitrise`. diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.yml b/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.yml deleted file mode 100755 index f188ee2d..00000000 --- a/vendor/github.com/bitrise-io/stepman/_tests/integration/test-step/step.yml +++ /dev/null @@ -1,58 +0,0 @@ -title: "STEP TEMPLATE" -summary: A short summary of the step. Don't make it too long ;) -description: |- - This is a Step template. - Contains everything what's required for a valid Stepman managed step. - - A Step's description (and generally any description property) - can be a [Markdown](https://en.wikipedia.org/wiki/Markdown) formatted text. - - To create your own Step: - - 1. Create a new repository on GitHub - 2. Copy the files from this folder into your repository - 3. That's all, you can use it on your own machine - 4. Once you're happy with it you can share it with others. -website: https://github.com/... -source_code_url: https://github.com/... -support_url: https://github.com/.../issues -host_os_tags: - - osx-10.10 -project_type_tags: - - ios - - android - - xamarin -type_tags: - - script -is_requires_admin_user: true -is_always_run: false -is_skippable: false -deps: - brew: - - name: git - - name: wget - apt_get: - - name: git - - name: wget -run_if: "" -inputs: - - example_step_input: Default Value - you can leave this empty if you want to - opts: - title: "Example Step Input" - summary: Summary. No more than 2-3 sentences. - description: | - Description of this input. - - Can be Markdown formatted text. - is_expand: true - is_required: true - value_options: [] -outputs: - - EXAMPLE_STEP_OUTPUT: - opts: - title: "Example Step Output" - summary: Summary. No more than 2-3 sentences. - description: | - Description of this output. - - Can be Markdown formatted text. diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/update_test.go b/vendor/github.com/bitrise-io/stepman/_tests/integration/update_test.go deleted file mode 100644 index 5dd3d187..00000000 --- a/vendor/github.com/bitrise-io/stepman/_tests/integration/update_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package integration - -import ( - "os" - "testing" - - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/command/git" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/stretchr/testify/require" -) - -func TestUpdate(t *testing.T) { - t.Log("remote library") - { - out, err := command.New(binPath(), "delete", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "setup", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "update", "-c", defaultLibraryURI).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - } - - t.Log("local library") - { - tmpDir, err := pathutil.NormalizedOSTempDirPath("__library__") - require.NoError(t, err) - defer func() { - require.NoError(t, os.RemoveAll(tmpDir)) - }() - repo, err := git.New(tmpDir) - require.NoError(t, err) - require.NoError(t, repo.Clone(defaultLibraryURI).Run()) - - out, err := command.New(binPath(), "delete", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "setup", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "update", "-c", tmpDir).RunAndReturnTrimmedCombinedOutput() - require.Error(t, err, out) - - out, err = command.New(binPath(), "update", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - - out, err = command.New(binPath(), "delete", "-c", "file://"+tmpDir).RunAndReturnTrimmedCombinedOutput() - require.NoError(t, err, out) - } -} diff --git a/vendor/github.com/bitrise-io/stepman/_tests/integration/version_test.go b/vendor/github.com/bitrise-io/stepman/_tests/integration/version_test.go deleted file mode 100644 index 2dc8fcf3..00000000 --- a/vendor/github.com/bitrise-io/stepman/_tests/integration/version_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package integration - -import ( - "fmt" - "runtime" - "testing" - - "github.com/bitrise-io/go-utils/command" - "github.com/stretchr/testify/require" -) - -func Test_VersionOutput(t *testing.T) { - t.Log("Version") - { - out, err := command.RunCommandAndReturnCombinedStdoutAndStderr(binPath(), "version") - require.NoError(t, err, out) - require.Equal(t, "0.11.0", out) - } - - t.Log("Version --full") - { - out, err := command.RunCommandAndReturnCombinedStdoutAndStderr(binPath(), "version", "--full") - require.NoError(t, err, out) - - expectedOSVersion := fmt.Sprintf("%s (%s)", runtime.GOOS, runtime.GOARCH) - expectedVersionOut := fmt.Sprintf(`version: 0.11.0 -os: %s -go: %s -build_number: -commit:`, expectedOSVersion, runtime.Version()) - - require.Equal(t, expectedVersionOut, out) - } -} diff --git a/vendor/github.com/bitrise-io/stepman/bitrise.yml b/vendor/github.com/bitrise-io/stepman/bitrise.yml deleted file mode 100644 index e0c6e412..00000000 --- a/vendor/github.com/bitrise-io/stepman/bitrise.yml +++ /dev/null @@ -1,99 +0,0 @@ -format_version: "5" -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -project_type: other - -app: - envs: - - BIN_NAME: stepman - -workflows: - # ---------------------------------------------------------------- - # --- workflows for CI and testing - test: - title: Runs tests - steps: - - go-list: - - golint: - - errcheck: - - go-test: - - codecov: - run_if: .IsCI - inputs: - - other_options: -f ${GO_CODE_COVERAGE_REPORT_PATH} - - CODECOV_TOKEN: "$CODECOV_UPLOAD_TOKEN" - - script: - title: Run integration tests - inputs: - - content: |- - #!/usr/bin/env bash - set -ex - - current_stepman="$(pwd)/_tmp/test_stepman" - go build -o "$current_stepman" - - export PR="" PULL_REQUEST_ID="" - export INTEGRATION_TEST_BINARY_PATH="$current_stepman" - go test -v ./_tests/integration/... - - create-binaries: - title: Create binaries - steps: - - script: - title: Create binaries - inputs: - - content: | - #!/bin/bash - set -ex - - echo - echo "Create final binaries" - echo " Build number: $BITRISE_BUILD_NUMBER" - - export ARCH=x86_64 - export GOARCH=amd64 - - # Create Darwin bin - export OS=Darwin - export GOOS=darwin - - DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" - echo " Create final Darwin binary at: $DEPLOY_PATH" - - version_package="github.com/bitrise-io/stepman/version" - - go build \ - -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ - -o "$DEPLOY_PATH" - - envman add --key OSX_DEPLOY_PATH --value $DEPLOY_PATH - cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH - echo " Copy final Darwin binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" - - - # Create Linux binary - export OS=Linux - export GOOS=linux - - DEPLOY_PATH="_bin/$BIN_NAME-$OS-$ARCH" - echo " Create final Linux binary at: $DEPLOY_PATH" - - go build \ - -ldflags "-X $version_package.BuildNumber=$BITRISE_BUILD_NUMBER -X $version_package.Commit=$GIT_CLONE_COMMIT_HASH" \ - -o "$DEPLOY_PATH" - - envman add --key LINUX_DEPLOY_PATH --value $DEPLOY_PATH - cp $DEPLOY_PATH $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH - echo " Copy final Linux binary to: $BITRISE_DEPLOY_DIR/$BIN_NAME-$OS-$ARCH" - - dep-update: - title: Dep update - steps: - - script: - title: Dependency update - inputs: - - content: |- - #!/bin/bash - set -ex - go get -u -v github.com/golang/dep/cmd/dep - dep ensure -v - dep ensure -v -update diff --git a/vendor/github.com/bitrise-io/stepman/cli/activate.go b/vendor/github.com/bitrise-io/stepman/cli/activate.go deleted file mode 100644 index 53364ea1..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/activate.go +++ /dev/null @@ -1,138 +0,0 @@ -package cli - -import ( - "os" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/stepman/stepman" - "github.com/urfave/cli" -) - -func activate(c *cli.Context) error { - // Input validation - collectionURI := c.String(CollectionKey) - if collectionURI == "" { - log.Fatalf("No step collection specified") - } - - id := c.String(IDKey) - if id == "" { - log.Fatalf("Missing step id") - } - - path := c.String(PathKey) - if path == "" { - log.Fatalf("Missing destination path") - } - - version := c.String(VersionKey) - copyYML := c.String(CopyYMLKey) - update := c.Bool(UpdateKey) - - // Check if step exist in collection - collection, err := stepman.ReadStepSpec(collectionURI) - if err != nil { - log.Fatalf("Failed to read steps spec (spec.json), error: %s", err) - } - - _, stepFound, versionFound := collection.GetStep(id, version) - if !stepFound || !versionFound { - if !update { - if !stepFound { - log.Fatalf("Collection doesn't contain step with id: %s", id) - } else if !versionFound { - log.Fatalf("Collection doesn't contain step (%s) with version: %s", id, version) - } - } - - if !stepFound { - log.Infof("Collection doesn't contain step with id: %s -- Updating StepLib", id) - } else if !versionFound { - log.Infof("Collection doesn't contain step (%s) with version: %s -- Updating StepLib", id, version) - } - - collection, err = stepman.UpdateLibrary(collectionURI) - if err != nil { - log.Fatalf("Failed to update collection (%s), err: %s", collectionURI, err) - } - - _, stepFound, versionFound := collection.GetStep(id, version) - if !stepFound { - if !stepFound { - log.Fatalf("Collection doesn't contain step with id: %s", id) - } else if !versionFound { - log.Fatalf("Collection doesn't contain step (%s) with version: %s", id, version) - } - } - } - - // If version doesn't provided use latest - if version == "" { - latest, err := collection.GetLatestStepVersion(id) - if err != nil { - log.Fatalf("Failed to get step latest version, error: %s", err) - } - version = latest - } - - // Check step exist in local cache - step, stepFound, versionFound := collection.GetStep(id, version) - if !stepFound { - log.Fatalf("Collection doesn't contain step with id: %s", id) - } else if !versionFound { - log.Fatalf("Collection doesn't contain step (%s) with version: %s", id, version) - } - - if step.Source == nil { - log.Fatalf("Invalid step, missing Source property") - } - - route, found := stepman.ReadRoute(collectionURI) - if !found { - log.Fatalf("No route found for lib: %s", collectionURI) - } - - stepCacheDir := stepman.GetStepCacheDirPath(route, id, version) - if exist, err := pathutil.IsPathExists(stepCacheDir); err != nil { - log.Fatalf("Failed to check path, error: %s", err) - } else if !exist { - if err := stepman.DownloadStep(collectionURI, collection, id, version, step.Source.Commit); err != nil { - log.Fatalf("Failed to download step, error: %s", err) - } - } - - // Copy to specified path - srcFolder := stepCacheDir - destFolder := path - - if exist, err := pathutil.IsPathExists(destFolder); err != nil { - log.Fatalf("Failed to check path, error: %s", err) - } else if !exist { - if err := os.MkdirAll(destFolder, 0777); err != nil { - log.Fatalf("Failed to create path, error: %s", err) - } - } - - if err = command.CopyDir(srcFolder+"/", destFolder, true); err != nil { - log.Fatalf("Failed to copy step, error: %s", err) - } - - // Copy step.yml to specified path - if copyYML != "" { - if exist, err := pathutil.IsPathExists(copyYML); err != nil { - log.Fatalf("Failed to check path, error: %s", err) - } else if exist { - log.Fatalf("Failed to copy step.yml, error: destination path exists") - } - - stepCollectionDir := stepman.GetStepCollectionDirPath(route, id, version) - stepYMLSrc := stepCollectionDir + "/step.yml" - if err = command.CopyFile(stepYMLSrc, copyYML); err != nil { - log.Fatalf("Failed to copy step.yml, error: %s", err) - } - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/audit.go b/vendor/github.com/bitrise-io/stepman/cli/audit.go deleted file mode 100644 index 91a96195..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/audit.go +++ /dev/null @@ -1,164 +0,0 @@ -package cli - -import ( - "fmt" - "strings" - "time" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/go-utils/colorstring" - "github.com/bitrise-io/go-utils/command/git" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/go-utils/retry" - "github.com/bitrise-io/stepman/models" - "github.com/bitrise-io/stepman/stepman" - "github.com/urfave/cli" -) - -func auditStepBeforeShare(pth string) error { - stepModel, err := stepman.ParseStepDefinition(pth, false) - if err != nil { - return err - } - return stepModel.AuditBeforeShare() -} - -func detectStepIDAndVersionFromPath(pth string) (stepID, stepVersion string, err error) { - pathComps := strings.Split(pth, "/") - if len(pathComps) < 4 { - err = fmt.Errorf("Path should contain at least 4 components: steps, step-id, step-version, step.yml: %s", pth) - return - } - // we only care about the last 4 component of the path - pathComps = pathComps[len(pathComps)-4:] - if pathComps[0] != "steps" { - err = fmt.Errorf("Invalid step.yml path, 'steps' should be included right before the step-id: %s", pth) - return - } - if pathComps[3] != "step.yml" { - err = fmt.Errorf("Invalid step.yml path, should end with 'step.yml': %s", pth) - return - } - stepID = pathComps[1] - stepVersion = pathComps[2] - return -} - -func auditStepBeforeSharePullRequest(pth string) error { - stepID, version, err := detectStepIDAndVersionFromPath(pth) - if err != nil { - return err - } - - stepModel, err := stepman.ParseStepDefinition(pth, false) - if err != nil { - return err - } - - return auditStepModelBeforeSharePullRequest(stepModel, stepID, version) -} - -func auditStepModelBeforeSharePullRequest(step models.StepModel, stepID, version string) error { - if err := step.Audit(); err != nil { - return fmt.Errorf("Failed to audit step infos, error: %s", err) - } - - pth, err := pathutil.NormalizedOSTempDirPath(stepID + version) - if err != nil { - return fmt.Errorf("Failed to create a temporary directory for the step's audit, error: %s", err) - } - - if step.Source == nil { - return fmt.Errorf("Missing Source porperty") - } - - repo, err := git.New(pth) - if err != nil { - return err - } - - err = retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { - return repo.CloneTagOrBranch(step.Source.Git, version).Run() - }) - if err != nil { - return fmt.Errorf("Failed to git-clone the step (url: %s) version (%s), error: %s", - step.Source.Git, version, err) - } - - latestCommit, err := repo.RevParse("HEAD").RunAndReturnTrimmedCombinedOutput() - if err != nil { - return fmt.Errorf("Failed to get commit, error: %s", err) - } - if latestCommit != step.Source.Commit { - return fmt.Errorf("Step commit hash (%s) should be the latest commit hash (%s) on git tag", step.Source.Commit, latestCommit) - } - - return nil -} - -func auditStepLibBeforeSharePullRequest(gitURI string) error { - if exist, err := stepman.RootExistForLibrary(gitURI); err != nil { - return err - } else if !exist { - return fmt.Errorf("Missing routing for collection, call 'stepman setup -c %s' before audit", gitURI) - } - - collection, err := stepman.ReadStepSpec(gitURI) - if err != nil { - return err - } - - for stepID, stepGroup := range collection.Steps { - log.Debugf("Start audit StepGrup, with ID: (%s)", stepID) - for version, step := range stepGroup.Versions { - log.Debugf("Start audit Step (%s) (%s)", stepID, version) - if err := auditStepModelBeforeSharePullRequest(step, stepID, version); err != nil { - log.Errorf(" * "+colorstring.Redf("[FAILED] ")+"Failed audit (%s) (%s)", stepID, version) - return fmt.Errorf(" Error: %s", err.Error()) - } - log.Infof(" * "+colorstring.Greenf("[OK] ")+"Success audit (%s) (%s)", stepID, version) - } - } - return nil -} - -func audit(c *cli.Context) error { - // Input validation - beforePR := c.Bool("before-pr") - - collectionURI := c.String("collection") - if collectionURI != "" { - if beforePR { - log.Warnln("before-pr flag is used only for Step audit") - } - - if err := auditStepLibBeforeSharePullRequest(collectionURI); err != nil { - log.Fatalf("Audit Step Collection failed, err: %s", err) - } - } else { - stepYMLPath := c.String("step-yml") - if stepYMLPath != "" { - if exist, err := pathutil.IsPathExists(stepYMLPath); err != nil { - log.Fatalf("Failed to check path (%s), err: %s", stepYMLPath, err) - } else if !exist { - log.Fatalf("step.yml doesn't exist at: %s", stepYMLPath) - } - - if beforePR { - if err := auditStepBeforeSharePullRequest(stepYMLPath); err != nil { - log.Fatalf("Step audit failed, err: %s", err) - } - } else { - if err := auditStepBeforeShare(stepYMLPath); err != nil { - log.Fatalf("Step audit failed, err: %s", err) - } - } - - log.Infof(" * "+colorstring.Greenf("[OK] ")+"Success audit (%s)", stepYMLPath) - } else { - log.Fatalln("'stepman audit' command needs --collection or --step-yml flag") - } - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/audit_test.go b/vendor/github.com/bitrise-io/stepman/cli/audit_test.go deleted file mode 100644 index 34d0d1a2..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/audit_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package cli - -import ( - "testing" - "time" - - "github.com/bitrise-io/go-utils/pointers" - "github.com/bitrise-io/stepman/models" -) - -// Test - Stepman audit step -// Checks if step Source.Commit meets the git commit hash of realese version -// 'auditStep(...)' calls 'cmdex.GitCloneTagOrBranchAndValidateCommitHash(...)', which method validates the commit hash -func TestValidateStepCommitHash(t *testing.T) { - // Slack step - valid hash - stepSlack := models.StepModel{ - Title: pointers.NewStringPtr("hash_test"), - Summary: pointers.NewStringPtr("summary"), - Website: pointers.NewStringPtr("website"), - PublishedAt: pointers.NewTimePtr(time.Date(2012, time.January, 1, 0, 0, 0, 0, time.UTC)), - Source: &models.StepSourceModel{ - Git: "https://github.com/bitrise-io/steps-slack-message.git", - Commit: "756f39f76f94d525aaea2fc2d0c5a23799f8ec97", - }, - } - if err := auditStepModelBeforeSharePullRequest(stepSlack, "slack", "2.1.0"); err != nil { - t.Fatal("Step audit failed:", err) - } - - // Slack step - invalid hash - stepSlack.Source.Commit = "should fail commit" - if err := auditStepModelBeforeSharePullRequest(stepSlack, "slack", "2.1.0"); err == nil { - t.Fatal("Step audit should fail") - } - - // Slack step - empty hash - stepSlack.Source.Commit = "" - if err := auditStepModelBeforeSharePullRequest(stepSlack, "slack", "2.1.0"); err == nil { - t.Fatal("Step audit should fail") - } -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/cli.go b/vendor/github.com/bitrise-io/stepman/cli/cli.go deleted file mode 100644 index c599987f..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/cli.go +++ /dev/null @@ -1,67 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "path" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/stepman/stepman" - "github.com/bitrise-io/stepman/version" - "github.com/urfave/cli" -) - -func initLogFormatter() { - log.SetFormatter(&log.TextFormatter{ - ForceColors: true, - FullTimestamp: true, - TimestampFormat: "15:04:05", - }) -} - -func before(c *cli.Context) error { - initLogFormatter() - initHelpAndVersionFlags() - initAppHelpTemplate() - - // Log level - logLevel, err := log.ParseLevel(c.String(LogLevelKey)) - if err != nil { - return fmt.Errorf("Failed to parse log level, error: %s", err) - } - log.SetLevel(logLevel) - - // Setup - err = stepman.CreateStepManDirIfNeeded() - if err != nil { - return err - } - - return nil -} - -func printVersion(c *cli.Context) { - fmt.Println(c.App.Version) -} - -// Run ... -func Run() { - cli.VersionPrinter = printVersion - - app := cli.NewApp() - app.Name = path.Base(os.Args[0]) - app.Usage = "Step manager" - app.Version = version.VERSION - - app.Author = "" - app.Email = "" - - app.Before = before - - app.Flags = flags - app.Commands = commands - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/collections.go b/vendor/github.com/bitrise-io/stepman/cli/collections.go deleted file mode 100644 index d7a7c92d..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/collections.go +++ /dev/null @@ -1,107 +0,0 @@ -package cli - -import ( - "encoding/json" - "fmt" - "os" - - "github.com/bitrise-io/go-utils/colorstring" - flog "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/stepman/models" - "github.com/bitrise-io/stepman/stepman" - "github.com/urfave/cli" -) - -// ------------- -// Output Models - -// OutputModel ... -type OutputModel struct { - Data *([]models.SteplibInfoModel) `json:"data,omitempty" yaml:"data,omitempty"` - Error string `json:"error,omitempty" yaml:"error,omitempty"` -} - -// String ... -func (output OutputModel) String() string { - if output.Error != "" { - return fmt.Sprintf("%s: %s", colorstring.Red("Error"), output.Error) - } - - if output.Data == nil { - return "" - } - - str := "" - steplibInfos := *output.Data - for idx, steplibInfo := range steplibInfos { - str += colorstring.Bluef("%s\n", steplibInfo.URI) - str += fmt.Sprintf(" spec_path: %s\n", steplibInfo.SpecPath) - if idx != len(steplibInfos)-1 { - str += "\n" - } - } - return str -} - -// JSON ... -func (output OutputModel) JSON() string { - bytes, err := json.Marshal(output) - if err != nil { - return fmt.Sprintf(`"Failed to marshal output (%#v), err: %s"`, output, err) - } - return string(bytes) -} - -// NewOutput ... -func NewOutput(steplibInfos []models.SteplibInfoModel) OutputModel { - return OutputModel{ - Data: &steplibInfos, - } -} - -// NewErrorOutput ... -func NewErrorOutput(format string, v ...interface{}) OutputModel { - return OutputModel{ - Error: fmt.Sprintf(format, v...), - } -} - -// ------------- - -func collections(c *cli.Context) error { - format := c.String(FormatKey) - if format == "" { - format = OutputFormatRaw - } - - var log flog.Logger - if format == OutputFormatRaw { - log = flog.NewDefaultRawLogger() - } else if format == OutputFormatJSON { - log = flog.NewDefaultJSONLoger() - } else { - fmt.Printf("%s: invalid format: %s\n", colorstring.Red("Error"), format) - os.Exit(1) - } - - steplibInfos := []models.SteplibInfoModel{} - stepLibURIs := stepman.GetAllStepCollectionPath() - for _, steplibURI := range stepLibURIs { - route, found := stepman.ReadRoute(steplibURI) - if !found { - log.Print(NewErrorOutput("No routing found for steplib: %s", steplibURI)) - os.Exit(1) - } - - specPth := stepman.GetStepSpecPath(route) - - steplibInfos = append(steplibInfos, models.SteplibInfoModel{ - URI: steplibURI, - SpecPath: specPth, - }) - } - - log.Print(NewOutput(steplibInfos)) - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/commands.go b/vendor/github.com/bitrise-io/stepman/cli/commands.go deleted file mode 100644 index 0b713371..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/commands.go +++ /dev/null @@ -1,177 +0,0 @@ -package cli - -import "github.com/urfave/cli" - -var ( - commands = []cli.Command{ - { - Name: "version", - Usage: "Prints the version", - Action: printVersionCmd, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "format", - Usage: "Output format. Accepted: json, yml", - }, - cli.BoolFlag{ - Name: "full", - Usage: "Prints the build number and commit as well.", - }, - }, - }, - { - Name: "setup", - Usage: "Initialize the specified collection, it's required before using a collection.", - Action: setup, - Flags: []cli.Flag{ - flCollection, - flLocalCollection, - flCopySpecJSON, - }, - }, - { - Name: "update", - Usage: "Update the collection, if no --collection flag provided, all collections will updated.", - Action: update, - Flags: []cli.Flag{ - flCollection, - }, - }, - { - Name: "collections", - Usage: "List of localy available collections.", - Action: collections, - Flags: []cli.Flag{ - flFormat, - }, - }, - { - Name: "step-list", - Usage: "List of available steps.", - Action: stepList, - Flags: []cli.Flag{ - flCollection, - flFormat, - }, - }, - stepInfoCommand, - { - Name: "download", - Usage: "Download the step with provided --id and --version, from specified --collection, into local step downloads cache. If no --version defined, the latest version of the step (latest found in the collection) will be downloaded into the cache.", - Action: download, - Flags: []cli.Flag{ - flCollection, - flID, - flVersion, - flUpdate, - }, - }, - { - Name: "activate", - Usage: "Copy the step with specified --id, and --version, into provided path. If --version flag is not set, the latest version of the step will be used. If --copyyml flag is set, step.yml will be copied to the given path.", - Action: activate, - Flags: []cli.Flag{ - flCollection, - flID, - flVersion, - flPath, - flCopyYML, - flUpdate, - }, - }, - { - Name: "audit", - Usage: "Validates Step or Step Collection.", - Action: audit, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: CollectionKey + ", " + collectionKeyShort, - Usage: "For validating Step Collection before share.", - EnvVar: CollectionPathEnvKey, - }, - cli.StringFlag{ - Name: "step-yml", - Usage: "For validating Step before share or before share Pull Request.", - }, - cli.BoolFlag{ - Name: "before-pr", - Usage: "If flag is set, Step Pull Request required fields will be checked to. Note: only for Step audit.", - }, - }, - }, - { - Name: "share", - Usage: "Publish your step.", - Action: share, - Flags: []cli.Flag{ - flToolMode, - }, - Subcommands: []cli.Command{ - { - Name: "start", - Usage: "Preparations for publishing.", - Action: start, - Flags: []cli.Flag{ - flCollection, - flToolMode, - }, - }, - { - Name: "create", - Usage: "Create your change - add it to your own copy of the collection.", - Action: create, - Flags: []cli.Flag{ - flTag, - flGit, - flStepID, - flToolMode, - }, - }, - { - Name: "audit", - Usage: "Validates the step collection.", - Action: shareAudit, - Flags: []cli.Flag{ - flToolMode, - }, - }, - { - Name: "finish", - Usage: "Finish up.", - Action: finish, - Flags: []cli.Flag{ - flToolMode, - }, - }, - }, - }, - { - Name: "delete", - Usage: "Delete the specified collection from local caches.", - Action: deleteStepLib, - Flags: []cli.Flag{ - flCollection, - }, - }, - { - Name: "export-spec", - Usage: "Export the generated StepLib spec.", - Action: export, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "steplib", - Usage: "StepLib URI", - }, - cli.StringFlag{ - Name: "output", - Usage: "Output path", - }, - cli.StringFlag{ - Name: "export-type", - Value: "full", - Usage: "Export type, options: [full, latest, minimal]", - }, - }, - }, - } -) diff --git a/vendor/github.com/bitrise-io/stepman/cli/delete_steplib.go b/vendor/github.com/bitrise-io/stepman/cli/delete_steplib.go deleted file mode 100644 index cc7b6bf2..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/delete_steplib.go +++ /dev/null @@ -1,35 +0,0 @@ -package cli - -import ( - "fmt" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/stepman/stepman" - "github.com/urfave/cli" -) - -func deleteStepLib(c *cli.Context) error { - // Input validation - collectionURI := c.String(CollectionKey) - if collectionURI == "" { - return fmt.Errorf("Missing required input: collection") - } - - log.Infof("Delete StepLib: %s", collectionURI) - - route, found := stepman.ReadRoute(collectionURI) - if !found { - log.Warnf("No route found for collection: %s, cleaning up routing..", collectionURI) - if err := stepman.CleanupDanglingLibrary(collectionURI); err != nil { - log.Errorf("Error cleaning up lib: %s", collectionURI) - } - log.Infof("Call 'stepman setup -c %s' for a clean setup", collectionURI) - return nil - } - - if err := stepman.CleanupRoute(route); err != nil { - return fmt.Errorf("Failed to cleanup route for StepLib: %s", collectionURI) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/download.go b/vendor/github.com/bitrise-io/stepman/cli/download.go deleted file mode 100644 index 25f04e10..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/download.go +++ /dev/null @@ -1,80 +0,0 @@ -package cli - -import ( - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/stepman/stepman" - "github.com/urfave/cli" -) - -func download(c *cli.Context) error { - // Input validation - collectionURI := c.String(CollectionKey) - if collectionURI == "" { - log.Fatalf("No step collection specified") - } - route, found := stepman.ReadRoute(collectionURI) - if !found { - log.Fatalf("No route found for lib: %s", collectionURI) - } - - id := c.String(IDKey) - if id == "" { - log.Fatalf("Missing step id") - } - - collection, err := stepman.ReadStepSpec(collectionURI) - if err != nil { - log.Fatalf("Failed to read step spec, error: %s", err) - } - - version := c.String(VersionKey) - if version == "" { - latest, err := collection.GetLatestStepVersion(id) - if err != nil { - log.Fatalf("Failed to get step latest version, error: %s", err) - } - version = latest - } - - update := c.Bool(UpdateKey) - - // Check step exist in collection - step, stepFound, versionFound := collection.GetStep(id, version) - if !stepFound || !versionFound { - if update { - if !stepFound { - log.Infof("Collection doesn't contain step with id: %s -- Updating StepLib", id) - } else if !versionFound { - log.Infof("Collection doesn't contain step (%s) with version: %s -- Updating StepLib", id, version) - } - - if err := stepman.ReGenerateLibrarySpec(route); err != nil { - log.Fatalf("Failed to update collection:%s error:%v", collectionURI, err) - } - - if _, stepFound, versionFound := collection.GetStep(id, version); !stepFound || !versionFound { - if !stepFound { - log.Fatalf("Even the updated collection doesn't contain step with id: %s", id) - } else if !versionFound { - log.Fatalf("Even the updated collection doesn't contain step (%s) with version: %s", id, version) - } - } - } else { - if !stepFound { - log.Fatalf("Collection doesn't contain step with id: %s -- Updating StepLib", id) - } else if !versionFound { - log.Fatalf("Collection doesn't contain step (%s) with version: %s -- Updating StepLib", id, version) - } - } - } - - if step.Source == nil { - log.Fatalf("Missing step's (%s) Source property", id) - } - - if err := stepman.DownloadStep(collectionURI, collection, id, version, step.Source.Commit); err != nil { - log.Fatalf("Failed to download step, error: %s", err) - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/export.go b/vendor/github.com/bitrise-io/stepman/cli/export.go deleted file mode 100644 index 204d2117..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/export.go +++ /dev/null @@ -1,147 +0,0 @@ -package cli - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/stepman/models" - "github.com/bitrise-io/stepman/stepman" - "github.com/urfave/cli" -) - -// ExportType ... -type ExportType int8 - -const ( - exportTypeFull ExportType = iota - exportTypeLatest - exportTypeMinimal -) - -func parseExportType(exportTypeStr string) (ExportType, error) { - switch exportTypeStr { - case "full": - return exportTypeFull, nil - case "latest": - return exportTypeLatest, nil - case "minimal": - return exportTypeMinimal, nil - } - - var exportType ExportType - return exportType, fmt.Errorf("Invalid export type (%s), available: [full, latest, minimal]", exportTypeStr) -} - -func convertToMinimalSpec(stepLib models.StepCollectionModel) models.StepCollectionModel { - steps := stepLib.Steps - - minimalSteps := models.StepHash{} - for stepID := range steps { - minimalSteps[stepID] = models.StepGroupModel{} - } - - stepLib.Steps = minimalSteps - return stepLib -} - -func convertToLatestSpec(stepLib models.StepCollectionModel) models.StepCollectionModel { - steps := stepLib.Steps - - latestSteps := models.StepHash{} - for stepID, stepGroup := range steps { - groupInfo := stepGroup.Info - versions := stepGroup.Versions - latestVersionStr := stepGroup.LatestVersionNumber - latestStep := versions[latestVersionStr] - - latestSteps[stepID] = models.StepGroupModel{ - Versions: map[string]models.StepModel{ - latestVersionStr: latestStep, - }, - Info: groupInfo, - } - } - - stepLib.Steps = latestSteps - return stepLib -} - -func export(c *cli.Context) error { - // Input validation - steplibURI := c.String("steplib") - outputPth := c.String("output") - exportTypeStr := c.String("export-type") - - if steplibURI == "" { - return fmt.Errorf("Missing required input: steplib") - } - - if outputPth == "" { - return fmt.Errorf("Missing required input: output") - } - - exportType := exportTypeFull - if exportTypeStr != "" { - var err error - exportType, err = parseExportType(exportTypeStr) - if err != nil { - return err - } - } - - log.Infof("Exporting StepLib (%s) spec, export-type: %s, output: %s", steplibURI, exportTypeStr, outputPth) - - // Setup StepLib - if exist, err := stepman.RootExistForLibrary(steplibURI); err != nil { - return fmt.Errorf("Failed to check if setup was done for StepLib, error: %s", err) - } else if !exist { - log.Infof("StepLib does not exist, setup...") - if err := stepman.SetupLibrary(steplibURI); err != nil { - return fmt.Errorf("Failed to setup StepLib, error: %s", err) - } - } - - // Prepare spec - stepLibSpec, err := stepman.ReadStepSpec(steplibURI) - if err != nil { - log.Fatalf("Failed to read StepLib spec, error: %s", err) - } - - switch exportType { - case exportTypeMinimal: - stepLibSpec = convertToMinimalSpec(stepLibSpec) - case exportTypeLatest: - stepLibSpec = convertToLatestSpec(stepLibSpec) - } - - stepLibSpecBytes, err := json.Marshal(stepLibSpec) - if err != nil { - return fmt.Errorf("Failed to marshal StepLib, error: %s", err) - } - - // Export spec - outputDir := filepath.Dir(outputPth) - - exist, err := pathutil.IsDirExists(outputDir) - if err != nil { - return fmt.Errorf("Failed to check if dir (%s) exist, error: %s", outputDir, err) - } - if !exist { - if err := os.MkdirAll(outputDir, 0777); err != nil { - return fmt.Errorf("Failed to create dir (%s), error: %s", outputDir, err) - } - } - - if err := fileutil.WriteBytesToFile(outputPth, stepLibSpecBytes); err != nil { - return fmt.Errorf("Failed to write StepLib spec to: %s, error: %s", outputPth, err) - } - - log.Infof("StepLib spec exported to: %s", outputPth) - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/flags.go b/vendor/github.com/bitrise-io/stepman/cli/flags.go deleted file mode 100644 index 68e52d98..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/flags.go +++ /dev/null @@ -1,168 +0,0 @@ -package cli - -import "github.com/urfave/cli" - -const ( - // DebugEnvKey ... - DebugEnvKey = "STEPMAN_DEBUG" - // LogLevelEnvKey ... - LogLevelEnvKey = "LOGLEVEL" - // CollectionPathEnvKey ... - CollectionPathEnvKey = "STEPMAN_COLLECTION" - - // HelpKey ... - HelpKey = "help" - helpKeyShort = "h" - - // VersionKey ... - VersionKey = "version" - versionKeyShort = "v" - - // CollectionKey ... - CollectionKey = "collection" - collectionKeyShort = "c" - // LocalCollectionKey ... - LocalCollectionKey = "local" - // CopySpecJSONKey ... - CopySpecJSONKey = "copy-spec-json" - - // DebugKey ... - DebugKey = "debug" - debugKeyShort = "d" - - // LogLevelKey ... - LogLevelKey = "loglevel" - logLevelKeyShort = "l" - - // IDKey ... - IDKey = "id" - idKeyShort = "i" - - // PathKey ... - PathKey = "path" - pathKeyShort = "p" - - // CopyYMLKey ... - CopyYMLKey = "copyyml" - copyYMLKeyShort = "y" - - // UpdateKey ... - UpdateKey = "update" - updateKeyShort = "u" - - // TagKey ... - TagKey = "tag" - tagKeyShort = "t" - - // GitKey ... - GitKey = "git" - gitKeyShort = "g" - - // StepIDKEy ... - StepIDKEy = "stepid" - stepIDKeyShort = "s" - - // ShortKey ... - ShortKey = "short" - - // ToolMode ... - ToolMode = "toolmode" - - // FormatKey ... - FormatKey = "format" - formatKeyShort = "f" - // OutputFormatRaw ... - OutputFormatRaw = "raw" - // OutputFormatJSON ... - OutputFormatJSON = "json" - - // StepYMLKey ... - StepYMLKey = "step-yml" -) - -var ( - // App flags - flLogLevel = cli.StringFlag{ - Name: LogLevelKey + ", " + logLevelKeyShort, - Value: "info", - Usage: "Log level (options: debug, info, warn, error, fatal, panic).", - EnvVar: LogLevelEnvKey, - } - flags = []cli.Flag{ - flLogLevel, - } - // Command flags - flCollection = cli.StringFlag{ - Name: CollectionKey + ", " + collectionKeyShort, - Usage: "Collection of step.", - EnvVar: CollectionPathEnvKey, - } - flLocalCollection = cli.BoolFlag{ - Name: LocalCollectionKey, - Usage: "[Deprecated!!!][Use 'file://' in steplib uri instead] Allow the --collection to be a local path.", - } - flCopySpecJSON = cli.StringFlag{ - Name: CopySpecJSONKey, - Usage: "If setup succeeds copy the generates spec.json to this path.", - } - flID = cli.StringFlag{ - Name: IDKey + ", " + idKeyShort, - Usage: "Step id.", - } - flVersion = cli.StringFlag{ - Name: VersionKey + ", " + versionKeyShort, - Usage: "Step version.", - } - flPath = cli.StringFlag{ - Name: PathKey + ", " + pathKeyShort, - Usage: "Path where the step will copied.", - } - flCopyYML = cli.StringFlag{ - Name: CopyYMLKey + ", " + copyYMLKeyShort, - Usage: "Path where the selected/activated step's step.yml will be copied.", - } - flUpdate = cli.BoolFlag{ - Name: UpdateKey + ", " + updateKeyShort, - Usage: "If flag is set, and collection doesn't contains the specified step, the collection will updated.", - } - flTag = cli.StringFlag{ - Name: TagKey + ", " + tagKeyShort, - Usage: "Git (version) tag.", - } - flGit = cli.StringFlag{ - Name: GitKey + ", " + gitKeyShort, - Usage: "Git clone url of the step repository.", - } - flStepID = cli.StringFlag{ - Name: StepIDKEy + ", " + stepIDKeyShort, - Usage: "ID of the step.", - } - flFormat = cli.StringFlag{ - Name: FormatKey + ", " + formatKeyShort, - Usage: "Output format (options: raw, json).", - } - flShort = cli.BoolFlag{ - Name: ShortKey, - Usage: "Show short version of infos.", - } - flStepYML = cli.StringFlag{ - Name: StepYMLKey, - Usage: "Path of step.yml", - } - flToolMode = cli.BoolFlag{ - Name: ToolMode, - Usage: "Stepman called as tool.", - } -) - -func initHelpAndVersionFlags() { - cli.HelpFlag = cli.BoolFlag{ - Name: HelpKey + ", " + helpKeyShort, - Usage: "Show help.", - } - - cli.VersionFlag = cli.BoolFlag{ - Name: VersionKey + ", " + versionKeyShort, - Usage: "Print the version.", - } -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/help.go b/vendor/github.com/bitrise-io/stepman/cli/help.go deleted file mode 100644 index 445ae83e..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/help.go +++ /dev/null @@ -1,26 +0,0 @@ -package cli - -import "github.com/urfave/cli" - -func initAppHelpTemplate() { - cli.AppHelpTemplate = ` -NAME: {{.Name}} - {{.Usage}} - -USAGE: {{.Name}} {{if .Flags}}[OPTIONS] {{end}}COMMAND [arg...] - -VERSION: {{.Version}}{{if or .Author .Email}} - -AUTHOR:{{if .Author}} - {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} - {{.Email}}{{end}}{{end}} -{{if .Flags}} -GLOBAL OPTIONS: - {{range .Flags}}{{.}} - {{end}}{{end}} -COMMANDS: - {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}} -COMMAND HELP: {{.Name}} COMMAND --help/-h - -` -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/setup.go b/vendor/github.com/bitrise-io/stepman/cli/setup.go deleted file mode 100644 index a8c1a5b4..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/setup.go +++ /dev/null @@ -1,58 +0,0 @@ -package cli - -import ( - "fmt" - "strings" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/stepman/stepman" - "github.com/urfave/cli" -) - -func setup(c *cli.Context) error { - // Input validation - steplibURI := c.String(CollectionKey) - if steplibURI == "" { - log.Fatal("No step collection specified") - } - - copySpecJSONPath := c.String(CopySpecJSONKey) - - if c.IsSet(LocalCollectionKey) { - log.Warn("'local' flag is deprecated") - log.Warn("use 'file://' prefix in steplib path instead") - fmt.Println() - } - - if c.Bool(LocalCollectionKey) { - if !strings.HasPrefix(steplibURI, "file://") { - log.Warnf("Appending file path prefix (file://) to StepLib (%s)", steplibURI) - steplibURI = "file://" + steplibURI - log.Warnf("From now you can refer to this StepLib with URI: %s", steplibURI) - log.Warnf("For example, to delete StepLib call: `stepman delete --collection %s`", steplibURI) - } - } - - // Setup - if err := stepman.SetupLibrary(steplibURI); err != nil { - log.Fatalf("Setup failed, error: %s", err) - } - - // Copy spec.json - if copySpecJSONPath != "" { - log.Infof("Copying spec YML to path: %s", copySpecJSONPath) - - route, found := stepman.ReadRoute(steplibURI) - if !found { - log.Fatalf("No route found for steplib (%s)", steplibURI) - } - - sourceSpecJSONPth := stepman.GetStepSpecPath(route) - if err := command.CopyFile(sourceSpecJSONPth, copySpecJSONPath); err != nil { - log.Fatalf("Failed to copy spec.json from (%s) to (%s), error: %s", sourceSpecJSONPth, copySpecJSONPath, err) - } - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/share.go b/vendor/github.com/bitrise-io/stepman/cli/share.go deleted file mode 100644 index 4f07daf2..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/share.go +++ /dev/null @@ -1,188 +0,0 @@ -package cli - -import ( - "encoding/json" - "errors" - "fmt" - "path" - - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/stepman/stepman" - "github.com/bitrise-tools/colorstring" - "github.com/urfave/cli" -) - -const ( - // ShareFilename ... - ShareFilename string = "share.json" -) - -// ShareModel ... -type ShareModel struct { - Collection string - StepID string - StepTag string -} - -// ShareBranchName ... -func (share ShareModel) ShareBranchName() string { - return share.StepID + "-" + share.StepTag -} - -// DeleteShareSteplibFile ... -func DeleteShareSteplibFile() error { - return command.RemoveDir(getShareFilePath()) -} - -// ReadShareSteplibFromFile ... -func ReadShareSteplibFromFile() (ShareModel, error) { - if exist, err := pathutil.IsPathExists(getShareFilePath()); err != nil { - return ShareModel{}, err - } else if !exist { - return ShareModel{}, errors.New("No share steplib found") - } - - bytes, err := fileutil.ReadBytesFromFile(getShareFilePath()) - if err != nil { - return ShareModel{}, err - } - - share := ShareModel{} - if err := json.Unmarshal(bytes, &share); err != nil { - return ShareModel{}, err - } - - return share, nil -} - -// WriteShareSteplibToFile ... -func WriteShareSteplibToFile(share ShareModel) error { - var bytes []byte - bytes, err := json.MarshalIndent(share, "", "\t") - if err != nil { - log.Errorf("Failed to parse json, error: %s", err) - return err - } - - return fileutil.WriteBytesToFile(getShareFilePath(), bytes) -} - -// GuideTextForStepAudit ... -func GuideTextForStepAudit(toolMode bool) string { - name := "stepman" - if toolMode { - name = "bitrise" - } - - b := colorstring.NewBuilder() - b.Plain("First, you need to ensure that your step is stored in a ").Blue("public git repository").NewLine() - b.Plain("and it follows our ").Blue("step development guideline").Plain(": https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md.").NewLine() - b.NewLine() - b.Plain("To audit your step on your local machine call ").Blue("$ %s audit --step-yml path/to/your/step.yml", name) - return b.String() -} - -// GuideTextForStart ... -func GuideTextForStart() string { - b := colorstring.NewBuilder() - b.Blue("Fork the StepLib repository ").Plain("you want to share your Step in.").NewLine() - b.Plain(`You can find the main ("official") StepLib repository at `).Plain("https://github.com/bitrise-io/bitrise-steplib") - return b.String() -} - -// GuideTextForShareStart ... -func GuideTextForShareStart(toolMode bool) string { - name := "stepman" - if toolMode { - name = "bitrise" - } - - b := colorstring.NewBuilder() - b.Plain("Call ").Blue("$ %s share start -c https://github.com/[your-username]/bitrise-steplib.git", name).Plain(", with the git clone URL of your forked StepLib repository.").NewLine() - b.Plain("This will prepare your forked StepLib locally for sharing.") - return b.String() -} - -// GuideTextForShareCreate ... -func GuideTextForShareCreate(toolMode bool) string { - name := "stepman" - if toolMode { - name = "bitrise" - } - - b := colorstring.NewBuilder() - b.Plain("Next, call ").Blue("$ %s share create --tag [step-version-tag] --git [step-git-uri].git --stepid [step-id]", name).Plain(",").NewLine() - b.Plain("to add your Step to your forked StepLib repository (locally).").NewLine() - b.NewLine() - b.Yellow("Important: ").Plain("you have to add the (version) tag to your Step's repository.") - return b.String() -} - -// GuideTextForAudit ... -func GuideTextForAudit(toolMode bool) string { - name := "stepman" - if toolMode { - name = "bitrise" - } - - b := colorstring.NewBuilder() - b.Plain("You can call ").Blue("$ %s audit -c https://github.com/[your-username]/bitrise-steplib.git ", name).NewLine() - b.Plain("to perform a complete health-check on your forked StepLib before submitting your Pull Request.").NewLine() - b.NewLine() - b.Plain("This can help you catch issues which might prevent your Step from being accepted.") - return b.String() -} - -// GuideTextForShareFinish ... -func GuideTextForShareFinish(toolMode bool) string { - name := "stepman" - if toolMode { - name = "bitrise" - } - - b := colorstring.NewBuilder() - b.Plain("Almost done! You should review your Step's step.yml file (the one added to the local StepLib),").NewLine() - b.Plain("and once you're happy with it call ").Blue("$ %s share finish", name).NewLine() - b.NewLine() - b.Plain("This will commit & push the step.yml into your forked StepLib repository.") - return b.String() -} - -// GuideTextForFinish ... -func GuideTextForFinish() string { - b := colorstring.NewBuilder() - b.Plain("The only remaining thing is to ").Blue("create a Pull Request").Plain(" in the original StepLib repository. And you are done!") - return b.String() -} - -func share(c *cli.Context) { - toolMode := c.Bool(ToolMode) - - b := colorstring.NewBuilder() - b.Plain("Do you want to share your own Step with the world? Awesome!").NewLine() - b.NewLine() - b.Plain("Just follow these steps:").NewLine() - b.NewLine() - b.Plain("0. ").Plain(GuideTextForStepAudit(toolMode)).NewLine() - b.NewLine() - b.Plain("1. ").Plain(GuideTextForStart()).NewLine() - b.NewLine() - b.Plain("2. ").Plain(GuideTextForShareStart(toolMode)).NewLine() - b.NewLine() - b.Plain("3. ").Plain(GuideTextForShareCreate(toolMode)).NewLine() - b.NewLine() - b.Plain("4. ").Plain(GuideTextForAudit(toolMode)).NewLine() - b.NewLine() - b.Plain("5. ").Plain(GuideTextForShareFinish(toolMode)).NewLine() - b.NewLine() - b.Plain("6. ").Plain(GuideTextForFinish()).NewLine() - b.NewLine() - fmt.Printf(b.String()) -} - -func getShareFilePath() string { - return path.Join(stepman.GetStepmanDirPath(), ShareFilename) -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/share_audit.go b/vendor/github.com/bitrise-io/stepman/cli/share_audit.go deleted file mode 100644 index 74a6d4e6..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/share_audit.go +++ /dev/null @@ -1,46 +0,0 @@ -package cli - -import ( - "fmt" - - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/stepman/stepman" - "github.com/bitrise-tools/colorstring" - "github.com/urfave/cli" -) - -func printFinishAudit(share ShareModel, toolMode bool) { - b := colorstring.NewBuilder() - b.Green("your step (%s@%s) is valid", share.StepID, share.StepTag).NewLine() - b.NewLine() - b.Plain(GuideTextForShareFinish(toolMode)) - fmt.Println(b.String()) -} - -func shareAudit(c *cli.Context) error { - toolMode := c.Bool(ToolMode) - - log.Infof("Validating Step share params...") - share, err := ReadShareSteplibFromFile() - if err != nil { - log.Errorf(err.Error()) - fail("You have to start sharing with `stepman share start`, or you can read instructions with `stepman share`") - } - log.Donef("all inputs are valid") - - fmt.Println() - log.Infof("Auditing the StepLib...") - _, found := stepman.ReadRoute(share.Collection) - if !found { - fail("No route found for collectionURI (%s)", share.Collection) - } - - if err := auditStepLibBeforeSharePullRequest(share.Collection); err != nil { - fail("Audit Step Collection failed, err: %s", err) - } - - printFinishAudit(share, toolMode) - fmt.Println() - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/share_create.go b/vendor/github.com/bitrise-io/stepman/cli/share_create.go deleted file mode 100644 index 134b5d11..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/share_create.go +++ /dev/null @@ -1,238 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "path" - "regexp" - "strconv" - "strings" - "time" - "unicode/utf8" - - "gopkg.in/yaml.v2" - - "github.com/bitrise-io/go-utils/command/git" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/go-utils/pointers" - "github.com/bitrise-io/go-utils/retry" - "github.com/bitrise-io/goinp/goinp" - "github.com/bitrise-io/stepman/models" - "github.com/bitrise-io/stepman/stepman" - "github.com/urfave/cli" -) - -const maxSummaryLength = 100 - -func getStepIDFromGit(git string) string { - splits := strings.Split(git, "/") - lastPart := splits[len(splits)-1] - splits = strings.Split(lastPart, ".") - return splits[0] -} - -func validateTag(tag string) error { - if tag == "" { - return fmt.Errorf("no Step tag specified") - } - - parts := strings.Split(tag, ".") - n := len(parts) - - if n != 3 { - return fmt.Errorf("invalid semver format %s: %d parts instead of 3", tag, n) - } - - for _, part := range parts { - if _, err := strconv.Atoi(part); err != nil { - return fmt.Errorf("invalid semver format %s: %s", tag, err) - } - } - - return nil -} - -func create(c *cli.Context) error { - toolMode := c.Bool(ToolMode) - - log.Infof("Validating Step share params...") - - share, err := ReadShareSteplibFromFile() - if err != nil { - log.Errorf(err.Error()) - fail("You have to start sharing with `stepman share start`, or you can read instructions with `stepman share`") - } - - // Input validation - tag := c.String(TagKey) - if err := validateTag(tag); err != nil { - fail("validate tag: %s", err) - } - - gitURI := c.String(GitKey) - if gitURI == "" { - fail("No Step url specified") - } - - stepID := c.String(StepIDKEy) - if stepID == "" { - stepID = getStepIDFromGit(gitURI) - } - if stepID == "" { - fail("No Step id specified") - } - r := regexp.MustCompile(`[a-z0-9-]+`) - if find := r.FindString(stepID); find != stepID { - fail("StepID doesn't conforms to: [a-z0-9-]") - } - - route, found := stepman.ReadRoute(share.Collection) - if !found { - fail("No route found for collectionURI (%s)", share.Collection) - } - stepDirInSteplib := stepman.GetStepCollectionDirPath(route, stepID, tag) - stepYMLPathInSteplib := path.Join(stepDirInSteplib, "step.yml") - if exist, err := pathutil.IsPathExists(stepYMLPathInSteplib); err != nil { - fail("Failed to check step.yml path in steplib, err: %s", err) - } else if exist { - log.Printf("Step already exist in path: %s", stepDirInSteplib) - log.Warnf("For sharing it's required to work with a clean Step repository.") - if val, err := goinp.AskForBool("Would you like to overwrite local version of Step?"); err != nil { - fail("Failed to get bool, err: %s", err) - } else { - if !val { - log.Errorf("Unfortunately we can't continue with sharing without an overwrite exist step.yml.") - fail("Please finish your changes, run this command again and allow it to overwrite the exist step.yml!") - } - } - } - log.Donef("all inputs are valid") - - // Clone Step to tmp dir - fmt.Println() - log.Infof("Validating the Step...") - - tmp, err := pathutil.NormalizedOSTempDirPath("") - if err != nil { - fail("Failed to get temp directory, err: %s", err) - } - - log.Printf("cloning Step repo from (%s) with tag (%s) to: %s", gitURI, tag, tmp) - - repo, err := git.New(tmp) - if err != nil { - return err - } - - if err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { - return repo.CloneTagOrBranch(gitURI, tag).Run() - }); err != nil { - fail("Failed to git-clone (url: %s) version (%s), error: %s", - gitURI, tag, err) - } - - // Update step.yml - tmpStepYMLPath := path.Join(tmp, "step.yml") - bytes, err := fileutil.ReadBytesFromFile(tmpStepYMLPath) - if err != nil { - fail("Failed to read Step from file, err: %s", err) - } - var stepModel models.StepModel - if err := yaml.Unmarshal(bytes, &stepModel); err != nil { - fail("Failed to unmarchal Step, err: %s", err) - } - - commit, err := repo.RevParse("HEAD").RunAndReturnTrimmedCombinedOutput() - if err != nil { - fail("Failed to get commit hash, err: %s", err) - } - - stepModel.Source = &models.StepSourceModel{ - Git: gitURI, - Commit: commit, - } - stepModel.PublishedAt = pointers.NewTimePtr(time.Now()) - - // Validate step-yml - if err := stepModel.Audit(); err != nil { - fail("Failed to validate Step, err: %s", err) - } - for _, input := range stepModel.Inputs { - key, value, err := input.GetKeyValuePair() - if err != nil { - fail("Failed to get Step input key-value pair, err: %s", err) - } - - options, err := input.GetOptions() - if err != nil { - fail("Failed to get Step input (%s) options, err: %s", key, err) - } - - if len(options.ValueOptions) > 0 && value == "" { - log.Warnf("Step input with 'value_options', should contain default value!") - fail("Missing default value for Step input (%s).", key) - } - } - if strings.Contains(*stepModel.Summary, "\n") { - log.Warnf("Step summary should be one line!") - } - if utf8.RuneCountInString(*stepModel.Summary) > maxSummaryLength { - log.Warnf("Step summary should contains maximum (%d) characters, actual: (%d)!", maxSummaryLength, utf8.RuneCountInString(*stepModel.Summary)) - } - log.Donef("step is valid") - - // Copy step.yml to steplib - fmt.Println() - log.Infof("Integrating the Step into the Steplib...") - - share.StepID = stepID - share.StepTag = tag - if err := WriteShareSteplibToFile(share); err != nil { - fail("Failed to save share steplib to file, err: %s", err) - } - - log.Printf("step dir in collection: %s", stepDirInSteplib) - if exist, err := pathutil.IsPathExists(stepDirInSteplib); err != nil { - fail("Failed to check path (%s), err: %s", stepDirInSteplib, err) - } else if !exist { - if err := os.MkdirAll(stepDirInSteplib, 0777); err != nil { - fail("Failed to create path (%s), err: %s", stepDirInSteplib, err) - } - } - - collectionDir := stepman.GetLibraryBaseDirPath(route) - steplibRepo, err := git.New(collectionDir) - if err != nil { - fail("Failed to init setplib repo: %s", err) - } - if err := steplibRepo.Checkout(share.ShareBranchName()).Run(); err != nil { - if err := steplibRepo.NewBranch(share.ShareBranchName()).Run(); err != nil { - fail("Git failed to create and checkout branch, err: %s", err) - } - } - - stepBytes, err := yaml.Marshal(stepModel) - if err != nil { - fail("Failed to marcshal Step model, err: %s", err) - } - if err := fileutil.WriteBytesToFile(stepYMLPathInSteplib, stepBytes); err != nil { - fail("Failed to write Step to file, err: %s", err) - } - - log.Printf("your Step (%s@%s) added to the local StepLib (%s).", share.StepID, share.StepTag, stepDirInSteplib) - - // Update spec.json - if err := stepman.ReGenerateLibrarySpec(route); err != nil { - fail("Failed to re-create steplib, err: %s", err) - } - - log.Donef("the StepLib changes prepared on branch: %s", share.ShareBranchName()) - - fmt.Println() - log.Printf(GuideTextForShareFinish(toolMode)) - fmt.Println() - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/share_create_test.go b/vendor/github.com/bitrise-io/stepman/cli/share_create_test.go deleted file mode 100644 index 52713c9a..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/share_create_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package cli - -import "testing" - -func TestValidateTag(t *testing.T) { - cases := []struct { - tag string - valid bool - }{ - {tag: "1.0.0", valid: true}, - {tag: "1.0", valid: false}, - {tag: "v1.0.0", valid: false}, - } - - for _, tc := range cases { - got := validateTag(tc.tag) - valid := got == nil - - if valid != tc.valid { - t.Errorf("validateTag(%s) == nil should be %t but got %s", tc.tag, tc.valid, got) - } - } -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/share_finish.go b/vendor/github.com/bitrise-io/stepman/cli/share_finish.go deleted file mode 100644 index 4ab2bc96..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/share_finish.go +++ /dev/null @@ -1,81 +0,0 @@ -package cli - -import ( - "fmt" - - "github.com/bitrise-io/go-utils/command/git" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/stepman/stepman" - "github.com/bitrise-tools/colorstring" - "github.com/urfave/cli" -) - -func printFinishShare() { - b := colorstring.NewBuilder() - b.Plain(GuideTextForFinish()).NewLine() - b.NewLine() - b.Plain("On GitHub you can find a ").Blue("Compare & pull request").Plain(" button, in the section called ").Blue("Your recently pushed branches:").Plain(",").NewLine() - b.Plain("which will bring you to the page to ").Blue("Open a pull request").Plain(", where you can review and create your Pull Request.") - fmt.Println(b.String()) -} - -func finish(c *cli.Context) error { - log.Infof("Validating Step share params...") - - share, err := ReadShareSteplibFromFile() - if err != nil { - log.Errorf(err.Error()) - fail("You have to start sharing with `stepman share start`, or you can read instructions with `stepman share`") - } - - route, found := stepman.ReadRoute(share.Collection) - if !found { - fail("No route found for collectionURI (%s)", share.Collection) - } - - collectionDir := stepman.GetLibraryBaseDirPath(route) - log.Donef("all inputs are valid") - - fmt.Println() - log.Infof("Checking StepLib changes...") - repo, err := git.New(collectionDir) - if err != nil { - fail(err.Error()) - } - - out, err := repo.Status("--porcelain").RunAndReturnTrimmedCombinedOutput() - if err != nil { - fail(err.Error()) - } - if out == "" { - log.Warnf("No git changes, it seems you already called this command") - fmt.Println() - printFinishShare() - return nil - } - - stepDirInSteplib := stepman.GetStepCollectionDirPath(route, share.StepID, share.StepTag) - stepYMLPathInSteplib := stepDirInSteplib + "/step.yml" - log.Printf("new step.yml: %s", stepYMLPathInSteplib) - if err := repo.Add(stepYMLPathInSteplib).Run(); err != nil { - fail(err.Error()) - } - - fmt.Println() - log.Infof("Submitting the changes...") - msg := share.StepID + " " + share.StepTag - if err := repo.Commit(msg).Run(); err != nil { - fail(err.Error()) - } - - log.Printf("pushing to your fork: %s", share.Collection) - if out, err := repo.Push(share.ShareBranchName()).RunAndReturnTrimmedCombinedOutput(); err != nil { - fail(out) - } - - fmt.Println() - printFinishShare() - fmt.Println() - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/share_start.go b/vendor/github.com/bitrise-io/stepman/cli/share_start.go deleted file mode 100644 index a831f6ba..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/share_start.go +++ /dev/null @@ -1,128 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "time" - - "github.com/bitrise-io/go-utils/command/git" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/retry" - "github.com/bitrise-io/goinp/goinp" - "github.com/bitrise-io/stepman/stepman" - "github.com/urfave/cli" -) - -func fail(format string, v ...interface{}) { - log.Errorf(format, v...) - os.Exit(1) -} - -func showSubcommandHelp(c *cli.Context) { - if err := cli.ShowSubcommandHelp(c); err != nil { - log.Warnf("Failed to show help, error: %s", err) - } -} - -func start(c *cli.Context) error { - // Input validation - log.Infof("Validating Step share params...") - - toolMode := c.Bool(ToolMode) - - collectionURI := c.String(CollectionKey) - if collectionURI == "" { - log.Errorf("No step collection specified\n") - showSubcommandHelp(c) - os.Exit(1) - } - - log.Donef("all inputs are valid") - - fmt.Println() - log.Infof("Preparing StepLib...") - - if route, found := stepman.ReadRoute(collectionURI); found { - collLocalPth := stepman.GetLibraryBaseDirPath(route) - log.Printf("StepLib found locally at: %s", collLocalPth) - log.Warnf("For sharing it's required to work with a clean StepLib repository.") - if val, err := goinp.AskForBool("Would you like to remove the local version (your forked StepLib repository) and re-clone it?"); err != nil { - fail("Failed to ask for input, error: %s", err) - } else { - if !val { - log.Errorf("Unfortunately we can't continue with sharing without a clean StepLib repository.") - fail("Please finish your changes, run this command again and allow it to remove the local StepLib folder!") - } - if err := stepman.CleanupRoute(route); err != nil { - log.Errorf("Failed to cleanup route for uri: %s", collectionURI) - } - } - } - - // cleanup - if err := DeleteShareSteplibFile(); err != nil { - fail("Failed to delete share steplib file, error: %s", err) - } - - var route stepman.SteplibRoute - isSuccess := false - defer func() { - if !isSuccess { - if err := stepman.CleanupRoute(route); err != nil { - log.Errorf("Failed to cleanup route for uri: %s", collectionURI) - } - if err := DeleteShareSteplibFile(); err != nil { - fail("Failed to delete share steplib file, error: %s", err) - } - } - }() - - // Preparing steplib - alias := stepman.GenerateFolderAlias() - route = stepman.SteplibRoute{ - SteplibURI: collectionURI, - FolderAlias: alias, - } - - pth := stepman.GetLibraryBaseDirPath(route) - if err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { - repo, err := git.New(pth) - if err != nil { - return err - } - return repo.Clone(collectionURI).Run() - }); err != nil { - fail("Failed to setup step spec (url: %s) version (%s), error: %s", collectionURI, pth, err) - } - - specPth := pth + "/steplib.yml" - collection, err := stepman.ParseStepCollection(specPth) - if err != nil { - fail("Failed to read step spec, error: %s", err) - } - - if err := stepman.WriteStepSpecToFile(collection, route); err != nil { - fail("Failed to save step spec, error: %s", err) - } - - if err := stepman.AddRoute(route); err != nil { - fail("Failed to setup routing, error: %s", err) - } - - log.Donef("StepLib prepared at: %s", pth) - - share := ShareModel{ - Collection: collectionURI, - } - if err := WriteShareSteplibToFile(share); err != nil { - fail("Failed to save share steplib to file, error: %s", err) - } - - isSuccess = true - - fmt.Println() - fmt.Println(GuideTextForShareCreate(toolMode)) - fmt.Println() - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/step_info.go b/vendor/github.com/bitrise-io/stepman/cli/step_info.go deleted file mode 100644 index e6f71e8a..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/step_info.go +++ /dev/null @@ -1,194 +0,0 @@ -package cli - -import ( - "fmt" - "log" - "time" - - "path/filepath" - - "github.com/bitrise-io/go-utils/command/git" - flog "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/go-utils/retry" - "github.com/bitrise-io/stepman/models" - "github.com/bitrise-io/stepman/stepman" - "github.com/urfave/cli" -) - -var stepInfoCommand = cli.Command{ - Name: "step-info", - Usage: "Prints the step definition (step.yml content).", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "library", - Usage: "Library of the step (options: LIBRARY_URI, git, path).", - EnvVar: "STEPMAN_LIBRARY_URI", - }, - cli.StringFlag{ - Name: "id", - Usage: "ID of the step (options: ID_IN_LIBRARY, GIT_URI, LOCAL_STEP_DIRECTORY_PATH).", - }, - cli.StringFlag{ - Name: "version", - Usage: "Version of the step (options: VERSION_IN_LIBRARY, GIT_BRANCH_OR_TAG).", - }, - cli.StringFlag{ - Name: "format", - Usage: "Output format (options: raw, json).", - }, - cli.StringFlag{ - Name: "collection, c", - Usage: "[DEPRECATED] Collection of step.", - EnvVar: CollectionPathEnvKey, - }, - cli.BoolFlag{ - Name: "short", - Usage: "[DEPRECATED] Show short version of infos.", - }, - cli.StringFlag{ - Name: "step-yml", - Usage: "[DEPRECATED] Path of step.yml", - }, - }, - Action: func(c *cli.Context) error { - if err := stepInfo(c); err != nil { - log.Fatalf("Command failed, error: %s", err) - } - return nil - }, -} - -func stepInfo(c *cli.Context) error { - // Input parsing - library := c.String("library") - if library == "" { - collection := c.String(CollectionKey) - library = collection - } - - id := c.String(IDKey) - if id == "" { - stepYMLPath := c.String(StepYMLKey) - if stepYMLPath != "" { - id = stepYMLPath - library = "path" - } - } - - if library == "" { - return fmt.Errorf("Missing required input: library") - } - if id == "" { - return fmt.Errorf("Missing required input: id") - } - - version := c.String(VersionKey) - - format := c.String(FormatKey) - if format == "" { - format = OutputFormatRaw - } - if format != OutputFormatRaw && format != OutputFormatJSON { - return fmt.Errorf("Invalid input value: format = %s", format) - } - - var log flog.Logger - log = flog.NewDefaultRawLogger() - if format == OutputFormatJSON { - log = flog.NewDefaultJSONLoger() - } - // --- - - stepInfo := models.StepInfoModel{ - Library: library, - ID: id, - Version: version, - } - - switch library { - case "git": - stepGitSourceURI := id - tmpStepDir, err := pathutil.NormalizedOSTempDirPath("__step__") - if err != nil { - return fmt.Errorf("failed to create tmp dir, error: %s", err) - } - - tagOrBranch := version - if tagOrBranch == "" { - tagOrBranch = "master" - } - - if err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { - repo, err := git.New(tmpStepDir) - if err != nil { - return err - } - return repo.CloneTagOrBranch(stepGitSourceURI, tagOrBranch).Run() - }); err != nil { - return fmt.Errorf("failed to clone step from: %s, error: %s", stepGitSourceURI, err) - } - - stepDefinitionPth := filepath.Join(tmpStepDir, "step.yml") - if exist, err := pathutil.IsPathExists(stepDefinitionPth); err != nil { - return fmt.Errorf("failed to check if step definition (step.yml) exist at: %s, error: %s", stepDefinitionPth, err) - } else if !exist { - return fmt.Errorf("step definition (step.yml) does not exist at: %s", stepDefinitionPth) - } - - step, err := stepman.ParseStepDefinition(stepDefinitionPth, false) - if err != nil { - return fmt.Errorf("failed to parse step definition at: %s, error: %s", stepDefinitionPth, err) - } - - stepInfo.Version = tagOrBranch - stepInfo.Step = step - stepInfo.DefinitionPth = stepDefinitionPth - case "path": - stepDir := id - stepDefinitionPth := filepath.Join(stepDir, "step.yml") - if exist, err := pathutil.IsPathExists(stepDefinitionPth); err != nil { - return fmt.Errorf("failed to check if step definition (step.yml) exist at: %s, error: %s", stepDefinitionPth, err) - } else if !exist { - return fmt.Errorf("step definition (step.yml) does not exist at: %s", stepDefinitionPth) - } - - step, err := stepman.ParseStepDefinition(stepDefinitionPth, false) - if err != nil { - return fmt.Errorf("failed to parse step definition at: %s, error: %s", stepDefinitionPth, err) - } - - stepInfo.Step = step - stepInfo.DefinitionPth = stepDefinitionPth - default: // library step - // Check if setup was done for collection - if exist, err := stepman.RootExistForLibrary(library); err != nil { - return fmt.Errorf("Failed to check if setup was done for steplib (%s), error: %s", library, err) - } else if !exist { - if err := stepman.SetupLibrary(library); err != nil { - return fmt.Errorf("Failed to setup steplib (%s), error: %s", library, err) - } - } - - stepVersion, err := stepman.ReadStepVersionInfo(library, id, version) - if err != nil { - return fmt.Errorf("Failed to read Step information, error: %s", err) - } - - route, found := stepman.ReadRoute(library) - if !found { - return fmt.Errorf("No route found for library: %s", library) - } - - stepDir := stepman.GetStepCollectionDirPath(route, id, stepVersion.Version) - stepDefinitionPth := filepath.Join(stepDir, "step.yml") - - stepInfo.Step = stepVersion.Step - stepInfo.Version = stepVersion.Version - stepInfo.LatestVersion = stepVersion.LatestAvailableVersion - stepInfo.DefinitionPth = stepDefinitionPth - } - - log.Print(stepInfo) - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/step_list.go b/vendor/github.com/bitrise-io/stepman/cli/step_list.go deleted file mode 100644 index fab7034e..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/step_list.go +++ /dev/null @@ -1,117 +0,0 @@ -package cli - -import ( - "encoding/json" - "fmt" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/go-utils/colorstring" - "github.com/bitrise-io/go-utils/pointers" - "github.com/bitrise-io/go-utils/stringutil" - "github.com/bitrise-io/stepman/models" - "github.com/bitrise-io/stepman/stepman" - "github.com/urfave/cli" -) - -func printRawStepList(stepLibURI string, stepLib models.StepCollectionModel, isShort bool) { - fmt.Println(colorstring.Bluef("Steps in StepLib (%s):", stepLibURI)) - fmt.Println() - for stepID, stepGroupInfo := range stepLib.Steps { - if isShort { - // print only step IDs - fmt.Printf("%s\n", stepID) - continue - } - - latestStepVerInfos, isFound := stepGroupInfo.LatestVersion() - if !isFound { - log.Errorf("No version found for step: %s", stepID) - continue - } - fmt.Printf(" * %s\n", pointers.String(latestStepVerInfos.Title)) - fmt.Printf(" ID: %s\n", stepID) - fmt.Printf(" Latest Version: %s\n", stepGroupInfo.LatestVersionNumber) - summaryText := "no summary specified" - if latestStepVerInfos.Summary != nil { - stepSumText := *latestStepVerInfos.Summary - // stepSumText = strings.Replace(stepSumText, "\n", " ", -1) - summaryText = stringutil.IndentTextWithMaxLength(stepSumText, " ", 130, false) - } - fmt.Printf(" Summary: %s\n", summaryText) - fmt.Println() - } - fmt.Println() -} - -func printJSONStepList(stepLibURI string, stepLib models.StepCollectionModel, isShort bool) error { - stepList := models.StepListModel{ - StepLib: stepLibURI, - } - for stepID := range stepLib.Steps { - stepList.Steps = append(stepList.Steps, stepID) - } - - bytes, err := json.Marshal(stepList) - if err != nil { - return err - } - - fmt.Println(string(bytes)) - return nil -} - -func listSteps(stepLibURI, format string) error { - // Check if setup was done for collection - if exist, err := stepman.RootExistForLibrary(stepLibURI); err != nil { - return err - } else if !exist { - if err := stepman.SetupLibrary(stepLibURI); err != nil { - log.Fatal("Failed to setup steplib") - } - } - - stepLib, err := stepman.ReadStepSpec(stepLibURI) - if err != nil { - return err - } - - switch format { - case OutputFormatRaw: - printRawStepList(stepLibURI, stepLib, false) - break - case OutputFormatJSON: - if err := printJSONStepList(stepLibURI, stepLib, false); err != nil { - return err - } - break - default: - return fmt.Errorf("Invalid format: %s", format) - } - return nil -} - -func stepList(c *cli.Context) error { - // Input validation - stepLibURIs := []string{} - stepLibURI := c.String(CollectionKey) - if stepLibURI == "" { - stepLibURIs = stepman.GetAllStepCollectionPath() - } else { - stepLibURIs = []string{stepLibURI} - } - - format := c.String(FormatKey) - if format == "" { - format = OutputFormatRaw - } else if !(format == OutputFormatRaw || format == OutputFormatJSON) { - log.Fatalf("Invalid format: %s", format) - } - - for _, URI := range stepLibURIs { - if err := listSteps(URI, format); err != nil { - log.Errorf("Failed to list steps in StepLib (%s), err: %s", URI, err) - } - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/update.go b/vendor/github.com/bitrise-io/stepman/cli/update.go deleted file mode 100644 index 40b2cb3e..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/update.go +++ /dev/null @@ -1,35 +0,0 @@ -package cli - -import ( - "fmt" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/stepman/stepman" - "github.com/urfave/cli" -) - -func update(c *cli.Context) error { - collectionURIs := []string{} - - // StepSpec collection path - collectionURI := c.String(CollectionKey) - if collectionURI == "" { - log.Info("No StepLib specified, update all...") - collectionURIs = stepman.GetAllStepCollectionPath() - } else { - collectionURIs = []string{collectionURI} - } - - if len(collectionURIs) == 0 { - log.Info("No local StepLib found, nothing to update...") - } - - for _, URI := range collectionURIs { - log.Infof("Update StepLib (%s)...", URI) - if _, err := stepman.UpdateLibrary(URI); err != nil { - return fmt.Errorf("Failed to update StepLib (%s), error: %s", URI, err) - } - } - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/cli/version.go b/vendor/github.com/bitrise-io/stepman/cli/version.go deleted file mode 100644 index b6c598a5..00000000 --- a/vendor/github.com/bitrise-io/stepman/cli/version.go +++ /dev/null @@ -1,82 +0,0 @@ -package cli - -import ( - "encoding/json" - "fmt" - "os" - "runtime" - - flog "github.com/bitrise-io/go-utils/log" - ver "github.com/bitrise-io/stepman/version" - "github.com/urfave/cli" -) - -// VersionOutputModel ... -type VersionOutputModel struct { - Version string `json:"version,omitempty"` - OS string `json:"os,omitempty"` - GO string `json:"go,omitempty"` - BuildNumber string `json:"build_number,omitempty"` - Commit string `json:"commit,omitempty"` - - FullVersion bool `json:"-"` -} - -// String ... -func (version VersionOutputModel) String() string { - str := "" - if version.FullVersion { - str += fmt.Sprintf("version: %s\n", version.Version) - str += fmt.Sprintf("os: %s\n", version.OS) - str += fmt.Sprintf("go: %s\n", version.GO) - str += fmt.Sprintf("build_number: %s\n", version.BuildNumber) - str += fmt.Sprintf("commit: %s", version.Commit) - } else { - str = version.Version - } - - return str -} - -// JSON ... -func (version VersionOutputModel) JSON() string { - if version.FullVersion { - bytes, err := json.Marshal(version) - if err != nil { - return fmt.Sprintf(`"Failed to marshal version (%#v), err: %s"`, version, err) - } - return string(bytes) + "\n" - } - - return fmt.Sprintf(`"%s"`+"\n", version.Version) -} - -func printVersionCmd(c *cli.Context) error { - fullVersion := c.Bool("full") - format := c.String("format") - if format == "" { - format = "raw" - } - - var log flog.Logger - if format == "raw" { - log = flog.NewDefaultRawLogger() - } else if format == "json" { - log = flog.NewDefaultJSONLoger() - } else { - flog.Errorf("Invalid format: %s\n", format) - os.Exit(1) - } - - versionOutput := VersionOutputModel{} - versionOutput.Version = ver.VERSION - versionOutput.OS = fmt.Sprintf("%s (%s)", runtime.GOOS, runtime.GOARCH) - versionOutput.GO = runtime.Version() - versionOutput.BuildNumber = ver.BuildNumber - versionOutput.Commit = ver.Commit - versionOutput.FullVersion = fullVersion - - log.Print(versionOutput) - - return nil -} diff --git a/vendor/github.com/bitrise-io/stepman/docker-compose.yml b/vendor/github.com/bitrise-io/stepman/docker-compose.yml deleted file mode 100644 index f9128202..00000000 --- a/vendor/github.com/bitrise-io/stepman/docker-compose.yml +++ /dev/null @@ -1,4 +0,0 @@ -app: - build: . - volumes: - - .:/go/src/github.com/bitrise-io/stepman diff --git a/vendor/github.com/bitrise-io/stepman/gows.yml b/vendor/github.com/bitrise-io/stepman/gows.yml deleted file mode 100644 index 58b5ed7d..00000000 --- a/vendor/github.com/bitrise-io/stepman/gows.yml +++ /dev/null @@ -1 +0,0 @@ -package_name: github.com/bitrise-io/stepman diff --git a/vendor/github.com/bitrise-io/stepman/main.go b/vendor/github.com/bitrise-io/stepman/main.go deleted file mode 100644 index e7688d36..00000000 --- a/vendor/github.com/bitrise-io/stepman/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/bitrise-io/stepman/cli" - -func main() { - cli.Run() -} diff --git a/vendor/github.com/bitrise-io/stepman/models/models_methods_test.go b/vendor/github.com/bitrise-io/stepman/models/models_methods_test.go deleted file mode 100644 index 941f22d9..00000000 --- a/vendor/github.com/bitrise-io/stepman/models/models_methods_test.go +++ /dev/null @@ -1,467 +0,0 @@ -package models - -import ( - "testing" - "time" - - envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/pointers" - "github.com/stretchr/testify/require" -) - -func TestValidate(t *testing.T) { - step := StepModel{ - Title: pointers.NewStringPtr("title"), - Summary: pointers.NewStringPtr("summary"), - Website: pointers.NewStringPtr("website"), - PublishedAt: pointers.NewTimePtr(time.Date(2012, time.January, 1, 0, 0, 0, 0, time.UTC)), - Source: &StepSourceModel{ - Git: "https://github.com/bitrise-io/bitrise.git", - Commit: "1e1482141079fc12def64d88cb7825b8f1cb1dc3", - }, - } - - require.Equal(t, nil, step.Audit()) - - step.Title = nil - require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'title' property") - - step.Title = new(string) - *step.Title = "" - require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'title' property") - *step.Title = "title" - - step.PublishedAt = nil - require.NotEqual(t, nil, step.Audit()) - require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'PublishedAt' property") - step.PublishedAt = new(time.Time) - - *step.PublishedAt = time.Time{} - require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'PublishedAt' property") - step.PublishedAt = pointers.NewTimePtr(time.Date(2012, time.January, 1, 0, 0, 0, 0, time.UTC)) - - step.Website = nil - require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'website' property") - - step.Website = new(string) - *step.Website = "" - require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'website' property") - *step.Website = "website" - - step.Source.Git = "" - require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'source.git' property") - step.Source.Git = "git" - - step.Source.Git = "git@github.com:bitrise-io/bitrise.git" - require.EqualError(t, step.Audit(), "Invalid step: step source should start with http:// or https://") - - step.Source.Git = "https://github.com/bitrise-io/bitrise" - require.EqualError(t, step.Audit(), "Invalid step: step source should end with .git") - step.Source.Git = "https://github.com/bitrise-io/bitrise.git" - - step.Source.Commit = "" - require.EqualError(t, step.Audit(), "Invalid step: missing or empty required 'source.commit' property") - step.Source.Commit = "1e1482141079fc12def64d88cb7825b8f1cb1dc3" - - step.Timeout = new(int) - *step.Timeout = -1 - require.EqualError(t, step.Audit(), "Invalid step: timeout less then 0") -} - -func TestValidateStepInputOutputModel(t *testing.T) { - // Filled env - env := envmanModels.EnvironmentItemModel{ - "test_key": "test_value", - envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - Description: pointers.NewStringPtr("test_description"), - Summary: pointers.NewStringPtr("test_summary"), - ValueOptions: []string{"test_key2", "test_value2"}, - IsRequired: pointers.NewBoolPtr(true), - IsExpand: pointers.NewBoolPtr(false), - IsDontChangeValue: pointers.NewBoolPtr(true), - }, - } - - step := StepModel{ - Inputs: []envmanModels.EnvironmentItemModel{env}, - } - - require.NoError(t, step.ValidateInputAndOutputEnvs(true)) - - // Empty key - env = envmanModels.EnvironmentItemModel{ - "": "test_value", - envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - Description: pointers.NewStringPtr("test_description"), - Summary: pointers.NewStringPtr("test_summary"), - ValueOptions: []string{"test_key2", "test_value2"}, - IsRequired: pointers.NewBoolPtr(true), - IsExpand: pointers.NewBoolPtr(false), - IsDontChangeValue: pointers.NewBoolPtr(true), - }, - } - - step = StepModel{ - Inputs: []envmanModels.EnvironmentItemModel{env}, - } - - require.Error(t, step.ValidateInputAndOutputEnvs(true)) - - // Title is empty - env = envmanModels.EnvironmentItemModel{ - "test_key": "test_value", - envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ - Description: pointers.NewStringPtr("test_description"), - ValueOptions: []string{"test_key2", "test_value2"}, - IsRequired: pointers.NewBoolPtr(true), - IsExpand: pointers.NewBoolPtr(false), - IsDontChangeValue: pointers.NewBoolPtr(true), - }, - } - - step = StepModel{ - Inputs: []envmanModels.EnvironmentItemModel{env}, - } - - require.Error(t, step.ValidateInputAndOutputEnvs(true)) - - // IsSensitive is true but IsExpand is not - env = envmanModels.EnvironmentItemModel{ - "test_key": "test_value", - envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - Description: pointers.NewStringPtr("test_description"), - ValueOptions: []string{"test_key2", "test_value2"}, - IsRequired: pointers.NewBoolPtr(true), - IsExpand: pointers.NewBoolPtr(false), - IsSensitive: pointers.NewBoolPtr(true), - IsDontChangeValue: pointers.NewBoolPtr(true), - }, - } - - step = StepModel{ - Inputs: []envmanModels.EnvironmentItemModel{env}, - } - - require.Error(t, step.ValidateInputAndOutputEnvs(true)) - - // IsSensitive is not set - env = envmanModels.EnvironmentItemModel{ - "test_key": "test_value", - envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - Description: pointers.NewStringPtr("test_description"), - ValueOptions: []string{"test_key2", "test_value2"}, - IsRequired: pointers.NewBoolPtr(true), - IsExpand: pointers.NewBoolPtr(false), - IsDontChangeValue: pointers.NewBoolPtr(true), - }, - } - - step = StepModel{ - Inputs: []envmanModels.EnvironmentItemModel{env}, - } - - require.NoError(t, step.ValidateInputAndOutputEnvs(true)) - - // IsSensitive is set to false - env = envmanModels.EnvironmentItemModel{ - "test_key": "test_value", - envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - Description: pointers.NewStringPtr("test_description"), - ValueOptions: []string{"test_key2", "test_value2"}, - IsRequired: pointers.NewBoolPtr(true), - IsExpand: pointers.NewBoolPtr(false), - IsSensitive: pointers.NewBoolPtr(false), - IsDontChangeValue: pointers.NewBoolPtr(true), - }, - } - - step = StepModel{ - Inputs: []envmanModels.EnvironmentItemModel{env}, - } - - require.NoError(t, step.ValidateInputAndOutputEnvs(true)) - - // IsExpand not set and IsSensitive set - env = envmanModels.EnvironmentItemModel{ - "test_key": "test_value", - envmanModels.OptionsKey: envmanModels.EnvironmentItemOptionsModel{ - Title: pointers.NewStringPtr("test_title"), - Description: pointers.NewStringPtr("test_description"), - ValueOptions: []string{"test_key2", "test_value2"}, - IsRequired: pointers.NewBoolPtr(true), - IsSensitive: pointers.NewBoolPtr(true), - IsDontChangeValue: pointers.NewBoolPtr(true), - }, - } - - step = StepModel{ - Inputs: []envmanModels.EnvironmentItemModel{env}, - } - - require.NoError(t, step.ValidateInputAndOutputEnvs(true)) -} - -func TestFillMissingDefaults(t *testing.T) { - title := "name 1" - // "desc 1" := ""desc 1" 1" - website := "web/1" - git := "https://git.url" - // fork := "fork/1" - - step := StepModel{ - Title: pointers.NewStringPtr(title), - Website: pointers.NewStringPtr(website), - Source: &StepSourceModel{ - Git: git, - }, - HostOsTags: []string{"osx"}, - ProjectTypeTags: []string{"ios"}, - TypeTags: []string{"test"}, - } - - require.Equal(t, nil, step.FillMissingDefaults()) - - if step.Description == nil || *step.Description != "" { - t.Fatal("Description missing") - } - if step.SourceCodeURL == nil || *step.SourceCodeURL != "" { - t.Fatal("SourceCodeURL missing") - } - if step.SupportURL == nil || *step.SupportURL != "" { - t.Fatal("SourceCodeURL missing") - } - if step.IsRequiresAdminUser == nil || *step.IsRequiresAdminUser != DefaultIsRequiresAdminUser { - t.Fatal("IsRequiresAdminUser missing") - } - if step.IsAlwaysRun == nil || *step.IsAlwaysRun != DefaultIsAlwaysRun { - t.Fatal("IsAlwaysRun missing") - } - if step.IsSkippable == nil || *step.IsSkippable != DefaultIsSkippable { - t.Fatal("IsSkippable missing") - } - if step.RunIf == nil || *step.RunIf != "" { - t.Fatal("RunIf missing") - } - if step.Timeout == nil || *step.Timeout != 0 { - t.Fatal("Timeout missing") - } -} - -func TestGetStep(t *testing.T) { - defaultIsRequiresAdminUser := DefaultIsRequiresAdminUser - - step := StepModel{ - Title: pointers.NewStringPtr("name 1"), - Description: pointers.NewStringPtr("desc 1"), - Website: pointers.NewStringPtr("web/1"), - SourceCodeURL: pointers.NewStringPtr("fork/1"), - Source: &StepSourceModel{ - Git: "https://git.url", - }, - HostOsTags: []string{"osx"}, - ProjectTypeTags: []string{"ios"}, - TypeTags: []string{"test"}, - IsRequiresAdminUser: pointers.NewBoolPtr(defaultIsRequiresAdminUser), - Inputs: []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "KEY_1": "Value 1", - }, - envmanModels.EnvironmentItemModel{ - "KEY_2": "Value 2", - }, - }, - Outputs: []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "KEY_3": "Value 3", - }, - }, - } - - collection := StepCollectionModel{ - FormatVersion: "1.0.0", - GeneratedAtTimeStamp: 0, - Steps: StepHash{ - "step": StepGroupModel{ - Versions: map[string]StepModel{ - "1.0.0": step, - }, - }, - }, - SteplibSource: "source", - DownloadLocations: []DownloadLocationModel{ - DownloadLocationModel{ - Type: "zip", - Src: "amazon/", - }, - DownloadLocationModel{ - Type: "git", - Src: "step.git", - }, - }, - } - - _, stepFound, versionFound := collection.GetStep("step", "1.0.0") - require.Equal(t, true, (stepFound && versionFound)) -} - -func TestGetDownloadLocations(t *testing.T) { - defaultIsRequiresAdminUser := DefaultIsRequiresAdminUser - - // Zip & git download locations - step := StepModel{ - Title: pointers.NewStringPtr("name 1"), - Description: pointers.NewStringPtr("desc 1"), - Website: pointers.NewStringPtr("web/1"), - SourceCodeURL: pointers.NewStringPtr("fork/1"), - Source: &StepSourceModel{ - Git: "https://git.url", - }, - HostOsTags: []string{"osx"}, - ProjectTypeTags: []string{"ios"}, - TypeTags: []string{"test"}, - IsRequiresAdminUser: pointers.NewBoolPtr(defaultIsRequiresAdminUser), - Inputs: []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "KEY_1": "Value 1", - }, - envmanModels.EnvironmentItemModel{ - "KEY_2": "Value 2", - }, - }, - Outputs: []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "KEY_3": "Value 3", - }, - }, - } - - collection := StepCollectionModel{ - FormatVersion: "1.0.0", - GeneratedAtTimeStamp: 0, - Steps: StepHash{ - "step": StepGroupModel{ - Versions: map[string]StepModel{ - "1.0.0": step, - }, - }, - }, - SteplibSource: "source", - DownloadLocations: []DownloadLocationModel{ - DownloadLocationModel{ - Type: "zip", - Src: "amazon/", - }, - DownloadLocationModel{ - Type: "git", - Src: "step.git", - }, - }, - } - - locations, err := collection.GetDownloadLocations("step", "1.0.0") - require.Equal(t, nil, err) - - zipFound := false - gitFount := false - zipIdx := -1 - gitIdx := -1 - - for idx, location := range locations { - if location.Type == "zip" { - if location.Src != "amazon/step/1.0.0/step.zip" { - t.Fatalf("Incorrect zip location (%s)", location.Src) - } - zipFound = true - zipIdx = idx - } else if location.Type == "git" { - if location.Src != "https://git.url" { - t.Fatalf("Incorrect git location (%s)", location.Src) - } - gitFount = true - gitIdx = idx - } - } - - require.Equal(t, true, zipFound) - require.Equal(t, true, gitFount) - if gitIdx < zipIdx { - t.Fatal("Incorrect download locations order") - } -} - -func TestGetLatestStepVersion(t *testing.T) { - defaultIsRequiresAdminUser := DefaultIsRequiresAdminUser - - step := StepModel{ - Title: pointers.NewStringPtr("name 1"), - Description: pointers.NewStringPtr("desc 1"), - Website: pointers.NewStringPtr("web/1"), - SourceCodeURL: pointers.NewStringPtr("fork/1"), - Source: &StepSourceModel{ - Git: "https://git.url", - }, - HostOsTags: []string{"osx"}, - ProjectTypeTags: []string{"ios"}, - TypeTags: []string{"test"}, - IsRequiresAdminUser: pointers.NewBoolPtr(defaultIsRequiresAdminUser), - Inputs: []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "KEY_1": "Value 1", - }, - envmanModels.EnvironmentItemModel{ - "KEY_2": "Value 2", - }, - }, - Outputs: []envmanModels.EnvironmentItemModel{ - envmanModels.EnvironmentItemModel{ - "KEY_3": "Value 3", - }, - }, - } - - collection := StepCollectionModel{ - FormatVersion: "1.0.0", - GeneratedAtTimeStamp: 0, - Steps: StepHash{ - "step": StepGroupModel{ - Versions: map[string]StepModel{ - "1.0.0": step, - "2.0.0": step, - }, - LatestVersionNumber: "2.0.0", - }, - }, - SteplibSource: "source", - DownloadLocations: []DownloadLocationModel{ - DownloadLocationModel{ - Type: "zip", - Src: "amazon/", - }, - DownloadLocationModel{ - Type: "git", - Src: "step.git", - }, - }, - } - - latest, err := collection.GetLatestStepVersion("step") - require.Equal(t, nil, err) - require.Equal(t, "2.0.0", latest) -} - -func Test_BrewDepModel_GetBinaryName(t *testing.T) { - require.Equal(t, "", BrewDepModel{}.GetBinaryName()) - require.Equal(t, "awscli", BrewDepModel{Name: "awscli"}.GetBinaryName()) - require.Equal(t, "aws", BrewDepModel{Name: "awscli", BinName: "aws"}.GetBinaryName()) -} - -func Test_AptGetDepModel_GetBinaryName(t *testing.T) { - require.Equal(t, "", AptGetDepModel{}.GetBinaryName()) - require.Equal(t, "awscli", AptGetDepModel{Name: "awscli"}.GetBinaryName()) - require.Equal(t, "aws", AptGetDepModel{Name: "awscli", BinName: "aws"}.GetBinaryName()) -} diff --git a/vendor/github.com/bitrise-io/stepman/models/models_test.go b/vendor/github.com/bitrise-io/stepman/models/models_test.go deleted file mode 100644 index 6f2924cb..00000000 --- a/vendor/github.com/bitrise-io/stepman/models/models_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package models - -import ( - "encoding/json" - "testing" - - "gopkg.in/yaml.v2" - - "github.com/bitrise-io/go-utils/pointers" - "github.com/stretchr/testify/require" -) - -func Test_serialize_StepModel(t *testing.T) { - t.Log("Empty") - { - step := StepModel{} - - // JSON - { - bytes, err := json.Marshal(step) - require.NoError(t, err) - require.Equal(t, `{}`, string(bytes)) - } - - // YAML - { - bytes, err := yaml.Marshal(step) - require.NoError(t, err) - require.Equal(t, `{} -`, - string(bytes)) - } - } - - t.Log("Toolkit") - { - step := StepModel{ - Title: pointers.NewStringPtr("Test Step"), - Toolkit: &StepToolkitModel{ - Go: &GoStepToolkitModel{ - PackageName: "go/package", - }, - Bash: &BashStepToolkitModel{ - EntryFile: "step.sh", - }, - }, - } - - // JSON - { - bytes, err := json.Marshal(step) - require.NoError(t, err) - require.Equal(t, `{"title":"Test Step","toolkit":{"bash":{"entry_file":"step.sh"},"go":{"package_name":"go/package"}}}`, string(bytes)) - } - - // YAML - { - bytes, err := yaml.Marshal(step) - require.NoError(t, err) - require.Equal(t, `title: Test Step -toolkit: - bash: - entry_file: step.sh - go: - package_name: go/package -`, - string(bytes)) - } - } -} diff --git a/vendor/github.com/bitrise-io/stepman/stepman/library.go b/vendor/github.com/bitrise-io/stepman/stepman/library.go deleted file mode 100644 index 9a79ed02..00000000 --- a/vendor/github.com/bitrise-io/stepman/stepman/library.go +++ /dev/null @@ -1,130 +0,0 @@ -package stepman - -import ( - "fmt" - "os" - "strings" - "time" - - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/command/git" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/go-utils/retry" - "github.com/bitrise-io/stepman/models" -) - -const filePathPrefix = "file://" - -// SetupLibrary ... -func SetupLibrary(libraryURI string) error { - if exist, err := RootExistForLibrary(libraryURI); err != nil { - return fmt.Errorf("failed to check if routing exist for library (%s), error: %s", libraryURI, err) - } else if exist { - return nil - } - - alias := GenerateFolderAlias() - route := SteplibRoute{ - SteplibURI: libraryURI, - FolderAlias: alias, - } - - // Cleanup - isSuccess := false - defer func() { - if !isSuccess { - if err := CleanupRoute(route); err != nil { - log.Errorf("Failed to cleanup routing for library (%s), error: %s", libraryURI, err) - } - } - }() - - // Setup - isLocalLibrary := strings.HasPrefix(libraryURI, filePathPrefix) - - pth := GetLibraryBaseDirPath(route) - if !isLocalLibrary { - if err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { - repo, err := git.New(pth) - if err != nil { - return err - } - return repo.Clone(libraryURI).Run() - }); err != nil { - return fmt.Errorf("failed to clone library (%s), error: %s", libraryURI, err) - } - } else { - // Local spec path - if err := os.MkdirAll(pth, 0777); err != nil { - return fmt.Errorf("failed to create library dir (%s), error: %s", pth, err) - } - - libraryFilePath := libraryURI - if strings.HasPrefix(libraryURI, filePathPrefix) { - libraryFilePath = strings.TrimPrefix(libraryURI, filePathPrefix) - } - - if err := command.CopyDir(libraryFilePath, pth, true); err != nil { - return fmt.Errorf("failed to copy dir (%s) to (%s), error: %s", libraryFilePath, pth, err) - } - } - - if err := ReGenerateLibrarySpec(route); err != nil { - return fmt.Errorf("failed to re-generate library (%s), error: %s", libraryURI, err) - } - - if err := AddRoute(route); err != nil { - return fmt.Errorf("failed to add routing, error: %s", err) - } - - isSuccess = true - - return nil -} - -// UpdateLibrary ... -func UpdateLibrary(libraryURI string) (models.StepCollectionModel, error) { - route, found := ReadRoute(libraryURI) - if !found { - if err := CleanupDanglingLibrary(libraryURI); err != nil { - log.Errorf("Failed to cleaning up library (%s), error: %s", libraryURI, err) - } - return models.StepCollectionModel{}, fmt.Errorf("no route found for library: %s", libraryURI) - } - - isLocalLibrary := strings.HasPrefix(libraryURI, filePathPrefix) - - if isLocalLibrary { - if err := CleanupRoute(route); err != nil { - return models.StepCollectionModel{}, fmt.Errorf("failed to cleanup route for library (%s), error: %s", libraryURI, err) - } - - if err := SetupLibrary(libraryURI); err != nil { - return models.StepCollectionModel{}, fmt.Errorf("failed to setup library (%s), error: %s", libraryURI, err) - } - } else { - pth := GetLibraryBaseDirPath(route) - if exists, err := pathutil.IsPathExists(pth); err != nil { - return models.StepCollectionModel{}, fmt.Errorf("failed to check if library (%s) directory (%s) exist, error: %s", libraryURI, pth, err) - } else if !exists { - return models.StepCollectionModel{}, fmt.Errorf("library (%s) not initialized", libraryURI) - } - - if err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { - repo, err := git.New(pth) - if err != nil { - return err - } - return repo.Pull().Run() - }); err != nil { - return models.StepCollectionModel{}, fmt.Errorf("failed to pull library (%s), error: %s", libraryURI, err) - } - - if err := ReGenerateLibrarySpec(route); err != nil { - return models.StepCollectionModel{}, fmt.Errorf("failed to generate spec for library (%s), error: %s", libraryURI, err) - } - } - - return ReadStepSpec(libraryURI) -} diff --git a/vendor/github.com/bitrise-io/stepman/stepman/paths.go b/vendor/github.com/bitrise-io/stepman/stepman/paths.go deleted file mode 100644 index 7cb8f5fc..00000000 --- a/vendor/github.com/bitrise-io/stepman/stepman/paths.go +++ /dev/null @@ -1,248 +0,0 @@ -package stepman - -import ( - "encoding/json" - "errors" - "fmt" - "os" - "path" - "time" - - log "github.com/Sirupsen/logrus" - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" -) - -const ( - // StepmanDirname ... - StepmanDirname = ".stepman" - // RoutingFilename ... - RoutingFilename = "routing.json" - // CollectionsDirname ... - CollectionsDirname = "step_collections" -) - -// SteplibRoute ... -type SteplibRoute struct { - SteplibURI string - FolderAlias string -} - -// SteplibRoutes ... -type SteplibRoutes []SteplibRoute - -// GetRoute ... -func (routes SteplibRoutes) GetRoute(URI string) (route SteplibRoute, found bool) { - for _, route := range routes { - if route.SteplibURI == URI { - pth := path.Join(GetCollectionsDirPath(), route.FolderAlias) - exist, err := pathutil.IsPathExists(pth) - if err != nil { - log.Warnf("Failed to read path %s", pth) - return SteplibRoute{}, false - } else if !exist { - log.Warnf("Failed to read path %s", pth) - return SteplibRoute{}, false - } - return route, true - } - } - return SteplibRoute{}, false -} - -// ReadRoute ... -func ReadRoute(uri string) (route SteplibRoute, found bool) { - routes, err := readRouteMap() - if err != nil { - return SteplibRoute{}, false - } - - return routes.GetRoute(uri) -} - -func (routes SteplibRoutes) writeToFile() error { - routeMap := map[string]string{} - for _, route := range routes { - routeMap[route.SteplibURI] = route.FolderAlias - } - bytes, err := json.MarshalIndent(routeMap, "", "\t") - if err != nil { - return err - } - return fileutil.WriteBytesToFile(getRoutingFilePath(), bytes) -} - -// CleanupRoute ... -func CleanupRoute(route SteplibRoute) error { - pth := path.Join(GetCollectionsDirPath(), route.FolderAlias) - if err := command.RemoveDir(pth); err != nil { - return err - } - return RemoveRoute(route) -} - -// CleanupDanglingLibrary ... -func CleanupDanglingLibrary(URI string) error { - route := SteplibRoute{ - SteplibURI: URI, - FolderAlias: "", - } - return RemoveRoute(route) -} - -// RootExistForLibrary ... -func RootExistForLibrary(collectionURI string) (bool, error) { - routes, err := readRouteMap() - if err != nil { - return false, err - } - - _, found := routes.GetRoute(collectionURI) - return found, nil -} - -func getAlias(uri string) (string, error) { - routes, err := readRouteMap() - if err != nil { - return "", err - } - - route, found := routes.GetRoute(uri) - if found == false { - return "", errors.New("No routes exist for uri:" + uri) - } - return route.FolderAlias, nil -} - -// RemoveRoute ... -func RemoveRoute(route SteplibRoute) error { - routes, err := readRouteMap() - if err != nil { - return err - } - - newRoutes := SteplibRoutes{} - for _, aRoute := range routes { - if aRoute.SteplibURI != route.SteplibURI { - newRoutes = append(newRoutes, aRoute) - } - } - return newRoutes.writeToFile() -} - -// AddRoute ... -func AddRoute(route SteplibRoute) error { - routes, err := readRouteMap() - if err != nil { - return err - } - - routes = append(routes, route) - return routes.writeToFile() -} - -// GenerateFolderAlias ... -func GenerateFolderAlias() string { - return fmt.Sprintf("%v", time.Now().Unix()) -} - -func readRouteMap() (SteplibRoutes, error) { - exist, err := pathutil.IsPathExists(getRoutingFilePath()) - if err != nil { - return SteplibRoutes{}, err - } else if !exist { - return SteplibRoutes{}, nil - } - - bytes, err := fileutil.ReadBytesFromFile(getRoutingFilePath()) - if err != nil { - return SteplibRoutes{}, err - } - var routeMap map[string]string - if err := json.Unmarshal(bytes, &routeMap); err != nil { - return SteplibRoutes{}, err - } - - routes := []SteplibRoute{} - for key, value := range routeMap { - routes = append(routes, SteplibRoute{ - SteplibURI: key, - FolderAlias: value, - }) - } - - return routes, nil -} - -// CreateStepManDirIfNeeded ... -func CreateStepManDirIfNeeded() error { - return os.MkdirAll(GetStepmanDirPath(), 0777) -} - -// GetStepSpecPath ... -func GetStepSpecPath(route SteplibRoute) string { - return path.Join(GetCollectionsDirPath(), route.FolderAlias, "spec", "spec.json") -} - -// GetSlimStepSpecPath ... -func GetSlimStepSpecPath(route SteplibRoute) string { - return path.Join(GetCollectionsDirPath(), route.FolderAlias, "spec", "slim-spec.json") -} - -// GetCacheBaseDir ... -func GetCacheBaseDir(route SteplibRoute) string { - return path.Join(GetCollectionsDirPath(), route.FolderAlias, "cache") -} - -// GetLibraryBaseDirPath ... -func GetLibraryBaseDirPath(route SteplibRoute) string { - return path.Join(GetCollectionsDirPath(), route.FolderAlias, "collection") -} - -// GetAllStepCollectionPath ... -func GetAllStepCollectionPath() []string { - routes, err := readRouteMap() - if err != nil { - log.Errorf("Failed to read step specs path, error: %s", err) - return []string{} - } - - sources := []string{} - for _, route := range routes { - sources = append(sources, route.SteplibURI) - } - - return sources -} - -// GetStepCacheDirPath ... -// Step's Cache dir path, where it's code lives. -func GetStepCacheDirPath(route SteplibRoute, id, version string) string { - return path.Join(GetCacheBaseDir(route), id, version) -} - -// GetStepGlobalInfoPath ... -func GetStepGlobalInfoPath(route SteplibRoute, id string) string { - return path.Join(GetLibraryBaseDirPath(route), "steps", id, "step-info.yml") -} - -// GetStepCollectionDirPath ... -// Step's Collection dir path, where it's spec (step.yml) lives. -func GetStepCollectionDirPath(route SteplibRoute, id, version string) string { - return path.Join(GetLibraryBaseDirPath(route), "steps", id, version) -} - -// GetStepmanDirPath ... -func GetStepmanDirPath() string { - return path.Join(pathutil.UserHomeDir(), StepmanDirname) -} - -// GetCollectionsDirPath ... -func GetCollectionsDirPath() string { - return path.Join(GetStepmanDirPath(), CollectionsDirname) -} - -func getRoutingFilePath() string { - return path.Join(GetStepmanDirPath(), RoutingFilename) -} diff --git a/vendor/github.com/bitrise-io/stepman/stepman/util.go b/vendor/github.com/bitrise-io/stepman/stepman/util.go deleted file mode 100644 index 1699feaa..00000000 --- a/vendor/github.com/bitrise-io/stepman/stepman/util.go +++ /dev/null @@ -1,427 +0,0 @@ -package stepman - -import ( - "encoding/json" - "errors" - "fmt" - "os" - "path" - "path/filepath" - "regexp" - "strings" - "time" - - "github.com/bitrise-io/go-utils/command" - "github.com/bitrise-io/go-utils/command/git" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/bitrise-io/go-utils/retry" - "github.com/bitrise-io/go-utils/urlutil" - "github.com/bitrise-io/go-utils/versions" - "github.com/bitrise-io/stepman/models" - "gopkg.in/yaml.v2" -) - -// ParseStepGroupInfoModel ... -func ParseStepGroupInfoModel(pth string) (models.StepGroupInfoModel, bool, error) { - if exist, err := pathutil.IsPathExists(pth); err != nil { - return models.StepGroupInfoModel{}, false, err - } else if !exist { - return models.StepGroupInfoModel{}, false, nil - } - - bytes, err := fileutil.ReadBytesFromFile(pth) - if err != nil { - return models.StepGroupInfoModel{}, true, err - } - - var globalStepInfo models.StepGroupInfoModel - if err := yaml.Unmarshal(bytes, &globalStepInfo); err != nil { - return models.StepGroupInfoModel{}, true, err - } - - return globalStepInfo, true, nil -} - -// ParseStepDefinition ... -func ParseStepDefinition(pth string, validate bool) (models.StepModel, error) { - bytes, err := fileutil.ReadBytesFromFile(pth) - if err != nil { - return models.StepModel{}, err - } - - var stepModel models.StepModel - if err := yaml.Unmarshal(bytes, &stepModel); err != nil { - return models.StepModel{}, err - } - - if err := stepModel.Normalize(); err != nil { - return models.StepModel{}, err - } - - if validate { - if err := stepModel.Audit(); err != nil { - return models.StepModel{}, err - } - } - - if err := stepModel.FillMissingDefaults(); err != nil { - return models.StepModel{}, err - } - - return stepModel, nil -} - -// ParseStepGroupInfo ... -func ParseStepGroupInfo(pth string) (models.StepGroupInfoModel, error) { - bytes, err := fileutil.ReadBytesFromFile(pth) - if err != nil { - return models.StepGroupInfoModel{}, err - } - - var stepGroupInfo models.StepGroupInfoModel - if err := yaml.Unmarshal(bytes, &stepGroupInfo); err != nil { - return models.StepGroupInfoModel{}, err - } - - return stepGroupInfo, nil -} - -// ParseStepCollection ... -func ParseStepCollection(pth string) (models.StepCollectionModel, error) { - bytes, err := fileutil.ReadBytesFromFile(pth) - if err != nil { - return models.StepCollectionModel{}, err - } - - var stepCollection models.StepCollectionModel - if err := yaml.Unmarshal(bytes, &stepCollection); err != nil { - return models.StepCollectionModel{}, err - } - return stepCollection, nil -} - -// DownloadStep ... -func DownloadStep(collectionURI string, collection models.StepCollectionModel, id, version, commithash string) error { - downloadLocations, err := collection.GetDownloadLocations(id, version) - if err != nil { - return err - } - - route, found := ReadRoute(collectionURI) - if !found { - return fmt.Errorf("No routing found for lib: %s", collectionURI) - } - - stepPth := GetStepCacheDirPath(route, id, version) - if exist, err := pathutil.IsPathExists(stepPth); err != nil { - return err - } else if exist { - return nil - } - - success := false - for _, downloadLocation := range downloadLocations { - switch downloadLocation.Type { - case "zip": - err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { - return command.DownloadAndUnZIP(downloadLocation.Src, stepPth) - }) - - if err != nil { - log.Warnf("Failed to download step.zip: %s", err) - } else { - success = true - return nil - } - case "git": - err := retry.Times(2).Wait(3 * time.Second).Try(func(attempt uint) error { - repo, err := git.New(stepPth) - if err != nil { - return err - } - - if err := repo.CloneTagOrBranch(downloadLocation.Src, version).Run(); err != nil { - return err - } - - hash, err := repo.RevParse("HEAD").RunAndReturnTrimmedCombinedOutput() - if err != nil { - return err - } - - if hash != commithash { - return fmt.Errorf("commit hash (%s) doesn't match the one specified (%s) for the version tag (%s)", hash, commithash, version) - } - return nil - }) - - if err != nil { - log.Warnf("Failed to clone step (%s): %v", downloadLocation.Src, err) - } else { - success = true - return nil - } - default: - return fmt.Errorf("Failed to download: Invalid download location (%#v) for step %#v (%#v)", downloadLocation, id, version) - } - } - - if !success { - return errors.New("Failed to download step") - } - return nil -} - -func addStepVersionToStepGroup(step models.StepModel, version string, stepGroup models.StepGroupModel) (models.StepGroupModel, error) { - if stepGroup.LatestVersionNumber != "" { - r, err := versions.CompareVersions(stepGroup.LatestVersionNumber, version) - if err != nil { - return models.StepGroupModel{}, err - } - if r == 1 { - stepGroup.LatestVersionNumber = version - } - } else { - stepGroup.LatestVersionNumber = version - } - stepGroup.Versions[version] = step - return stepGroup, nil -} - -func generateStepLib(route SteplibRoute, templateCollection models.StepCollectionModel) (models.StepCollectionModel, error) { - collection := models.StepCollectionModel{ - FormatVersion: templateCollection.FormatVersion, - GeneratedAtTimeStamp: time.Now().Unix(), - SteplibSource: templateCollection.SteplibSource, - DownloadLocations: templateCollection.DownloadLocations, - AssetsDownloadBaseURI: templateCollection.AssetsDownloadBaseURI, - } - - stepHash := models.StepHash{} - - stepsSpecDirPth := GetLibraryBaseDirPath(route) - if err := filepath.Walk(stepsSpecDirPth, func(pth string, f os.FileInfo, err error) error { - truncatedPath := strings.Replace(pth, stepsSpecDirPth+"/", "", -1) - match, matchErr := regexp.MatchString("([a-z]+).yml", truncatedPath) - if matchErr != nil { - return matchErr - } - - if match { - components := strings.Split(truncatedPath, "/") - if len(components) == 4 { - stepsDirName := components[0] - stepID := components[1] - stepVersion := components[2] - - step, parseErr := ParseStepDefinition(pth, true) - if parseErr != nil { - return parseErr - } - - stepGroupInfo := models.StepGroupInfoModel{} - - // Check for step-info.yml - STEP_SPEC_DIR/steps/step-id/step-info.yml - stepGroupInfoPth := filepath.Join(stepsSpecDirPth, stepsDirName, stepID, "step-info.yml") - if exist, err := pathutil.IsPathExists(stepGroupInfoPth); err != nil { - return err - } else if exist { - deprecationInfo, err := ParseStepGroupInfo(stepGroupInfoPth) - if err != nil { - return err - } - - stepGroupInfo.RemovalDate = deprecationInfo.RemovalDate - stepGroupInfo.DeprecateNotes = deprecationInfo.DeprecateNotes - } - - // Check for assets - STEP_SPEC_DIR/steps/step-id/assets - if collection.AssetsDownloadBaseURI != "" { - assetsFolderPth := path.Join(stepsSpecDirPth, stepsDirName, stepID, "assets") - exist, err := pathutil.IsPathExists(assetsFolderPth) - if err != nil { - return err - } - if exist { - assetsMap := map[string]string{} - err := filepath.Walk(assetsFolderPth, func(pth string, f os.FileInfo, err error) error { - _, file := filepath.Split(pth) - if pth != assetsFolderPth && file != "" { - assetURI, err := urlutil.Join(collection.AssetsDownloadBaseURI, stepID, "assets", file) - if err != nil { - return err - } - assetsMap[file] = assetURI - } - return nil - }) - - if err != nil { - return err - } - - step.AssetURLs = assetsMap - stepGroupInfo.AssetURLs = assetsMap - } - } - - // Add to stepgroup - stepGroup, found := stepHash[stepID] - if !found { - stepGroup = models.StepGroupModel{ - Versions: map[string]models.StepModel{}, - } - } - stepGroup, err = addStepVersionToStepGroup(step, stepVersion, stepGroup) - if err != nil { - return err - } - - stepGroup.Info = stepGroupInfo - - stepHash[stepID] = stepGroup - } else { - } - } - - return err - }); err != nil { - return models.StepCollectionModel{}, fmt.Errorf("Failed to walk through path, error: %s", err) - } - - collection.Steps = stepHash - - return collection, nil -} - -func generateSlimStepLib(collection models.StepCollectionModel) models.StepCollectionModel { - - slimCollection := models.StepCollectionModel{ - FormatVersion: collection.FormatVersion, - GeneratedAtTimeStamp: collection.GeneratedAtTimeStamp, - SteplibSource: collection.SteplibSource, - DownloadLocations: collection.DownloadLocations, - AssetsDownloadBaseURI: collection.AssetsDownloadBaseURI, - } - steps := models.StepHash{} - - for stepID, stepGroupModel := range collection.Steps { - steps[stepID] = models.StepGroupModel{ - Info: stepGroupModel.Info, - Versions: map[string]models.StepModel{stepGroupModel.LatestVersionNumber: stepGroupModel.Versions[stepGroupModel.LatestVersionNumber]}, - } - } - - slimCollection.Steps = steps - - return slimCollection -} - -// WriteStepSpecToFile ... -func WriteStepSpecToFile(templateCollection models.StepCollectionModel, route SteplibRoute) error { - pth := GetStepSpecPath(route) - - if exist, err := pathutil.IsPathExists(pth); err != nil { - return err - } else if !exist { - dir, _ := path.Split(pth) - err := os.MkdirAll(dir, 0777) - if err != nil { - return err - } - } else { - err := os.Remove(pth) - if err != nil { - return err - } - } - - collection, err := generateStepLib(route, templateCollection) - if err != nil { - return err - } - - bytes, err := json.MarshalIndent(collection, "", "\t") - if err != nil { - return err - } - - if err := fileutil.WriteBytesToFile(pth, bytes); err != nil { - return err - } - - pth = GetSlimStepSpecPath(route) - slimCollection := generateSlimStepLib(collection) - if err != nil { - return err - } - - bytes, err = json.MarshalIndent(slimCollection, "", "\t") - if err != nil { - return err - } - - return fileutil.WriteBytesToFile(pth, bytes) -} - -// ReadStepSpec ... -func ReadStepSpec(uri string) (models.StepCollectionModel, error) { - route, found := ReadRoute(uri) - if !found { - return models.StepCollectionModel{}, errors.New("No route found for lib: " + uri) - } - pth := GetStepSpecPath(route) - bytes, err := fileutil.ReadBytesFromFile(pth) - if err != nil { - return models.StepCollectionModel{}, err - } - var stepLib models.StepCollectionModel - if err := json.Unmarshal(bytes, &stepLib); err != nil { - return models.StepCollectionModel{}, err - } - - return stepLib, nil -} - -// ReadStepVersionInfo ... -func ReadStepVersionInfo(collectionURI, stepID, stepVersionID string) (models.StepVersionModel, error) { - // Input validation - if stepID == "" { - return models.StepVersionModel{}, errors.New("Missing required input: step id") - } - - // Check if step exist in collection - collection, err := ReadStepSpec(collectionURI) - if err != nil { - return models.StepVersionModel{}, fmt.Errorf("Failed to read steps spec (spec.json), err: %s", err) - } - - stepWithVersion, stepFound, versionFound := collection.GetStepVersion(stepID, stepVersionID) - if !stepFound { - return models.StepVersionModel{}, fmt.Errorf("Collection doesn't contain step with id: %s", stepID) - } else if !versionFound { - return models.StepVersionModel{}, fmt.Errorf("Collection doesn't contain step (%s) with version: %s", stepID, stepVersionID) - } - - return stepWithVersion, nil -} - -// ReGenerateLibrarySpec ... -func ReGenerateLibrarySpec(route SteplibRoute) error { - pth := GetLibraryBaseDirPath(route) - if exists, err := pathutil.IsPathExists(pth); err != nil { - return err - } else if !exists { - return errors.New("Not initialized") - } - - specPth := pth + "/steplib.yml" - collection, err := ParseStepCollection(specPth) - if err != nil { - return err - } - - return WriteStepSpecToFile(collection, route) -} diff --git a/vendor/github.com/bitrise-io/stepman/stepman/util_test.go b/vendor/github.com/bitrise-io/stepman/stepman/util_test.go deleted file mode 100644 index ec4c1471..00000000 --- a/vendor/github.com/bitrise-io/stepman/stepman/util_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package stepman - -import ( - "testing" - - "github.com/bitrise-io/go-utils/pointers" - "github.com/bitrise-io/stepman/models" - "github.com/stretchr/testify/require" -) - -func TestAddStepVersionToStepGroup(t *testing.T) { - step := models.StepModel{ - Title: pointers.NewStringPtr("name 1"), - } - - group := models.StepGroupModel{ - Versions: map[string]models.StepModel{ - "1.0.0": step, - "2.0.0": step, - }, - LatestVersionNumber: "2.0.0", - } - - group, err := addStepVersionToStepGroup(step, "2.1.0", group) - require.Equal(t, nil, err) - require.Equal(t, 3, len(group.Versions)) - require.Equal(t, "2.1.0", group.LatestVersionNumber) -} diff --git a/vendor/github.com/bitrise-io/stepman/version/build.go b/vendor/github.com/bitrise-io/stepman/version/build.go deleted file mode 100644 index 06c70c10..00000000 --- a/vendor/github.com/bitrise-io/stepman/version/build.go +++ /dev/null @@ -1,7 +0,0 @@ -package version - -// BuildNumber ... -var BuildNumber = "" - -// Commit ... -var Commit = "" diff --git a/vendor/github.com/bitrise-io/stepman/version/version.go b/vendor/github.com/bitrise-io/stepman/version/version.go deleted file mode 100644 index d33c31ac..00000000 --- a/vendor/github.com/bitrise-io/stepman/version/version.go +++ /dev/null @@ -1,4 +0,0 @@ -package version - -// VERSION ... -const VERSION = "0.11.0" diff --git a/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info.go b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info.go new file mode 100644 index 00000000..0b7337a6 --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info.go @@ -0,0 +1,252 @@ +package xcodeproj + +import ( + "bufio" + "encoding/json" + "fmt" + "path/filepath" + "strings" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/command/rubyscript" + "github.com/bitrise-io/go-utils/errorutil" + "github.com/bitrise-tools/go-xcode/plistutil" + "github.com/pkg/errors" +) + +// CodeSignInfo ... +type CodeSignInfo struct { + CodeSignEntitlementsPath string + BundleIdentifier string + CodeSignIdentity string + ProvisioningProfileSpecifier string + ProvisioningProfile string + DevelopmentTeam string +} + +// TargetMapping ... +type TargetMapping struct { + Configuration string `json:"configuration"` + ProjectTargets map[string][]string `json:"project_targets"` +} + +func clearRubyScriptOutput(out string) string { + reader := strings.NewReader(out) + scanner := bufio.NewScanner(reader) + + jsonLines := []string{} + jsonResponseStart := false + + for scanner.Scan() { + line := scanner.Text() + trimmed := strings.TrimSpace(line) + + if !jsonResponseStart && trimmed == "{" { + jsonResponseStart = true + } + if !jsonResponseStart { + continue + } + + jsonLines = append(jsonLines, line) + } + + if len(jsonLines) == 0 { + return out + } + + return strings.Join(jsonLines, "\n") +} + +func readSchemeTargetMapping(projectPth, scheme, user string) (TargetMapping, error) { + runner := rubyscript.New(codeSignInfoScriptContent) + bundleInstallCmd, err := runner.BundleInstallCommand(gemfileContent, "") + if err != nil { + return TargetMapping{}, fmt.Errorf("failed to create bundle install command, error: %s", err) + } + + if out, err := bundleInstallCmd.RunAndReturnTrimmedCombinedOutput(); err != nil { + return TargetMapping{}, fmt.Errorf("bundle install failed, output: %s, error: %s", out, err) + } + + runCmd, err := runner.RunScriptCommand() + if err != nil { + return TargetMapping{}, fmt.Errorf("failed to create script runner command, error: %s", err) + } + + envsToAppend := []string{ + "project=" + projectPth, + "scheme=" + scheme, + "user=" + user} + envs := append(runCmd.GetCmd().Env, envsToAppend...) + + runCmd.SetEnvs(envs...) + + out, err := runCmd.RunAndReturnTrimmedCombinedOutput() + if err != nil { + return TargetMapping{}, fmt.Errorf("failed to run code signing analyzer script, output: %s, error: %s", out, err) + } + + type OutputModel struct { + Data TargetMapping `json:"data"` + Error string `json:"error"` + } + var output OutputModel + if err := json.Unmarshal([]byte(out), &output); err != nil { + out = clearRubyScriptOutput(out) + if err := json.Unmarshal([]byte(out), &output); err != nil { + return TargetMapping{}, fmt.Errorf("failed to unmarshal output: %s", out) + } + } + + if output.Error != "" { + return TargetMapping{}, fmt.Errorf("failed to get provisioning profile - bundle id mapping, error: %s", output.Error) + } + + return output.Data, nil +} + +func parseBuildSettingsOut(out string) (map[string]string, error) { + reader := strings.NewReader(out) + scanner := bufio.NewScanner(reader) + + buildSettings := map[string]string{} + isBuildSettings := false + for scanner.Scan() { + line := scanner.Text() + + if strings.HasPrefix(line, "Build settings for") { + isBuildSettings = true + continue + } + if !isBuildSettings { + continue + } + + split := strings.Split(line, " = ") + if len(split) > 1 { + key := strings.TrimSpace(split[0]) + value := strings.TrimSpace(strings.Join(split[1:], " = ")) + + buildSettings[key] = value + } + } + if err := scanner.Err(); err != nil { + return map[string]string{}, errors.Wrap(err, "Failed to scan build settings") + } + + return buildSettings, nil +} + +func getTargetBuildSettingsWithXcodebuild(projectPth, target, configuration string) (map[string]string, error) { + args := []string{"-showBuildSettings", "-project", projectPth, "-target", target} + if configuration != "" { + args = append(args, "-configuration", configuration) + } + + cmd := command.New("xcodebuild", args...) + cmd.SetDir(filepath.Dir(projectPth)) + + out, err := cmd.RunAndReturnTrimmedCombinedOutput() + if err != nil { + if errorutil.IsExitStatusError(err) { + return map[string]string{}, errors.Wrapf(err, "%s failed with output: %s", cmd.PrintableCommandArgs(), out) + } + return map[string]string{}, errors.Wrapf(err, "%s failed", cmd.PrintableCommandArgs()) + } + + return parseBuildSettingsOut(out) +} + +func getBundleIDWithPlistbuddy(infoPlistPth string) (string, error) { + plistData, err := plistutil.NewPlistDataFromFile(infoPlistPth) + if err != nil { + return "", err + } + + bundleID, _ := plistData.GetString("CFBundleIdentifier") + return bundleID, nil +} + +func firstNonEmpty(values ...string) string { + for _, value := range values { + if value != "" { + return value + } + } + return "" +} + +// ResolveCodeSignInfo ... +func ResolveCodeSignInfo(projectOrWorkspacePth, scheme, user string) (map[string]CodeSignInfo, error) { + projectTargetsMapping, err := readSchemeTargetMapping(projectOrWorkspacePth, scheme, user) + if err != nil { + return nil, err + } + + resolvedCodeSignInfoMap := map[string]CodeSignInfo{} + for projectPth, targets := range projectTargetsMapping.ProjectTargets { + for _, targetName := range targets { + if targetName == "" { + return nil, errors.New("target name is empty") + } + + if projectPth == "" { + return nil, fmt.Errorf("failed to resolve which project contains target: %s", targetName) + } + + buildSettings, err := getTargetBuildSettingsWithXcodebuild(projectPth, targetName, projectTargetsMapping.Configuration) + if err != nil { + return nil, fmt.Errorf("failed to read project build settings, error: %s", err) + } + + // resolve Info.plist path + infoPlistPth := buildSettings["INFOPLIST_FILE"] + if infoPlistPth != "" { + projectDir := filepath.Dir(projectPth) + infoPlistPth = filepath.Join(projectDir, infoPlistPth) + } + // --- + + // resolve bundle id + // best case if it presents in the buildSettings, since it is expanded + bundleID := buildSettings["PRODUCT_BUNDLE_IDENTIFIER"] + if bundleID == "" && infoPlistPth != "" { + // try to find the bundle id in the Info.plist file, unless it contains env var + id, err := getBundleIDWithPlistbuddy(infoPlistPth) + if err != nil { + return nil, fmt.Errorf("failed to resolve bundle id, error: %s", err) + } + bundleID = id + } + if bundleID == "" { + return nil, fmt.Errorf("failed to resolve bundle id") + } + // --- + + codeSignEntitlementsPth := buildSettings["CODE_SIGN_ENTITLEMENTS"] + if codeSignEntitlementsPth != "" { + projectDir := filepath.Dir(projectPth) + codeSignEntitlementsPth = filepath.Join(projectDir, codeSignEntitlementsPth) + } + + codeSignIdentity := buildSettings["CODE_SIGN_IDENTITY"] + provisioningProfileSpecifier := buildSettings["PROVISIONING_PROFILE_SPECIFIER"] + provisioningProfile := buildSettings["PROVISIONING_PROFILE"] + developmentTeam := buildSettings["DEVELOPMENT_TEAM"] + + resolvedCodeSignInfo := CodeSignInfo{ + CodeSignEntitlementsPath: codeSignEntitlementsPth, + BundleIdentifier: bundleID, + CodeSignIdentity: codeSignIdentity, + ProvisioningProfileSpecifier: provisioningProfileSpecifier, + ProvisioningProfile: provisioningProfile, + DevelopmentTeam: developmentTeam, + } + + resolvedCodeSignInfoMap[targetName] = resolvedCodeSignInfo + } + } + + return resolvedCodeSignInfoMap, nil +} diff --git a/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info_script.go b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info_script.go new file mode 100644 index 00000000..68c87398 --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info_script.go @@ -0,0 +1,188 @@ +package xcodeproj + +const codeSignInfoScriptContent = `require 'xcodeproj' +require 'json' + +def workspace_contained_projects(workspace_pth) + workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_pth) + workspace_dir = File.dirname(workspace_pth) + project_paths = [] + workspace.file_references.each do |ref| + pth = ref.path + next unless File.extname(pth) == '.xcodeproj' + next if pth.end_with?('Pods/Pods.xcodeproj') + + project_path = File.expand_path(pth, workspace_dir) + project_paths << project_path + end + + project_paths +end + +def shared_scheme_path(project_or_workspace_pth, scheme_name) + File.join(project_or_workspace_pth, 'xcshareddata', 'xcschemes', scheme_name + '.xcscheme') +end + +def user_scheme_path(project_or_workspace_pth, scheme_name, user_name) + File.join(project_or_workspace_pth, 'xcuserdata', user_name + '.xcuserdatad', 'xcschemes', scheme_name + '.xcscheme') +end + +def read_scheme(project_or_workspace_pth, scheme_name, user_name) + project_paths = [project_or_workspace_pth] + if File.extname(project_or_workspace_pth) == '.xcworkspace' + project_paths += workspace_contained_projects(project_or_workspace_pth) + end + + project_paths.each do |project_path| + scheme_pth = shared_scheme_path(project_path, scheme_name) + scheme_pth = user_scheme_path(project_path, scheme_name, user_name) unless File.exist?(scheme_pth) + next unless File.exist?(scheme_pth) + + scheme = Xcodeproj::XCScheme.new(scheme_pth) + container_dir = File.dirname(project_path) + + return { + scheme: scheme, + container_dir: container_dir + } + end + + nil +end + +def project_buildable_target_mapping(project_dir, scheme) + build_action = scheme.build_action + return nil unless build_action + + entries = build_action.entries || [] + return nil if entries.empty? + + entries = entries.select(&:build_for_archiving?) || [] + return nil if entries.empty? + + mapping = {} + + entries.each do |entry| + buildable_references = entry.buildable_references || [] + next if buildable_references.empty? + + buildable_references = buildable_references.reject do |r| + r.target_name.to_s.empty? || r.target_referenced_container.to_s.empty? + end + next if buildable_references.empty? + + buildable_reference = entry.buildable_references.first + + target_name = buildable_reference.target_name.to_s + container = buildable_reference.target_referenced_container.to_s.sub(/^container:/, '') + next if target_name.empty? || container.empty? + + project_pth = File.expand_path(container, project_dir) + next unless File.exist?(project_pth) + + project = Xcodeproj::Project.open(project_pth) + next unless project + + target = project.targets.find { |t| t.name == target_name } + next unless target + next unless runnable_target?(target) + + targets = mapping[project_pth] || [] + targets << target + mapping[project_pth] = targets + end + + mapping +end + +def runnable_target?(target) + return false unless target.is_a?(Xcodeproj::Project::Object::PBXNativeTarget) + + product_reference = target.product_reference + return false unless product_reference + + product_reference.path.end_with?('.app', '.appex') +end + +def collect_dependent_targets(target, dependent_targets) + dependent_targets << target + + dependencies = target.dependencies || [] + return dependent_targets if dependencies.empty? + + dependencies.each do |dependency| + dependent_target = dependency.target + next unless dependent_target + next unless runnable_target?(dependent_target) + + collect_dependent_targets(dependent_target, dependent_targets) + end + + dependent_targets +end + +def find_archive_action_build_configuration_name(scheme) + archive_action = scheme.archive_action + return nil unless archive_action + + archive_action.build_configuration +end + +def read_scheme_target_mapping(project_or_workspace_pth, scheme_name, user_name) + scheme_container_dir = read_scheme(project_or_workspace_pth, scheme_name, user_name) + raise "project (#{project_or_workspace_pth}) does not contain scheme: #{scheme_name}" unless scheme_container_dir + scheme = scheme_container_dir[:scheme] + container_dir = scheme_container_dir[:container_dir] + + configuration = find_archive_action_build_configuration_name(scheme) + + target_mapping = project_buildable_target_mapping(container_dir, scheme) || [] + raise 'scheme does not contain buildable target' if target_mapping.empty? + + project_targets = {} + target_mapping.each do |project_pth, targets| + targets.each do |target| + dependent_targets = [] + dependent_targets = collect_dependent_targets(target, dependent_targets) + + project_targets[project_pth] = dependent_targets.collect(&:name) + end + end + raise 'failed to collect buildable targets' if project_targets.empty? + + { + configuration: configuration, + project_targets: project_targets + } +end + +begin + project_path = ENV['project'] + scheme_name = ENV['scheme'] + user_name = ENV['user'] + + raise 'missing project_path' if project_path.to_s.empty? + raise 'missing scheme_name' if scheme_name.to_s.empty? + raise 'missing user_name' if user_name.to_s.empty? + + mapping = read_scheme_target_mapping(project_path, scheme_name, user_name) + result = { + data: mapping + } + result_json = JSON.pretty_generate(result).to_s + puts result_json +rescue => e + error_message = e.to_s + "\n" + e.backtrace.join("\n") + result = { + error: error_message + } + result_json = result.to_json.to_s + puts result_json + exit(1) +end +` + +const gemfileContent = `source "https://rubygems.org" +gem "xcodeproj" +gem "json" +` diff --git a/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info_script.rb b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info_script.rb new file mode 100644 index 00000000..4c825933 --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info_script.rb @@ -0,0 +1,180 @@ +require 'xcodeproj' +require 'json' + +def workspace_contained_projects(workspace_pth) + workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_pth) + workspace_dir = File.dirname(workspace_pth) + project_paths = [] + workspace.file_references.each do |ref| + pth = ref.path + next unless File.extname(pth) == '.xcodeproj' + next if pth.end_with?('Pods/Pods.xcodeproj') + + project_path = File.expand_path(pth, workspace_dir) + project_paths << project_path + end + + project_paths +end + +def shared_scheme_path(project_or_workspace_pth, scheme_name) + File.join(project_or_workspace_pth, 'xcshareddata', 'xcschemes', scheme_name + '.xcscheme') +end + +def user_scheme_path(project_or_workspace_pth, scheme_name, user_name) + File.join(project_or_workspace_pth, 'xcuserdata', user_name + '.xcuserdatad', 'xcschemes', scheme_name + '.xcscheme') +end + +def read_scheme(project_or_workspace_pth, scheme_name, user_name) + project_paths = [project_or_workspace_pth] + if File.extname(project_or_workspace_pth) == '.xcworkspace' + project_paths += workspace_contained_projects(project_or_workspace_pth) + end + + project_paths.each do |project_path| + scheme_pth = shared_scheme_path(project_path, scheme_name) + scheme_pth = user_scheme_path(project_path, scheme_name, user_name) unless File.exist?(scheme_pth) + next unless File.exist?(scheme_pth) + + scheme = Xcodeproj::XCScheme.new(scheme_pth) + container_dir = File.dirname(project_path) + + return { + scheme: scheme, + container_dir: container_dir + } + end + + nil +end + +def project_buildable_target_mapping(project_dir, scheme) + build_action = scheme.build_action + return nil unless build_action + + entries = build_action.entries || [] + return nil if entries.empty? + + entries = entries.select(&:build_for_archiving?) || [] + return nil if entries.empty? + + mapping = {} + + entries.each do |entry| + buildable_references = entry.buildable_references || [] + next if buildable_references.empty? + + buildable_references = buildable_references.reject do |r| + r.target_name.to_s.empty? || r.target_referenced_container.to_s.empty? + end + next if buildable_references.empty? + + buildable_reference = entry.buildable_references.first + + target_name = buildable_reference.target_name.to_s + container = buildable_reference.target_referenced_container.to_s.sub(/^container:/, '') + next if target_name.empty? || container.empty? + + project_pth = File.expand_path(container, project_dir) + next unless File.exist?(project_pth) + + project = Xcodeproj::Project.open(project_pth) + next unless project + + target = project.targets.find { |t| t.name == target_name } + next unless target + next unless runnable_target?(target) + + targets = mapping[project_pth] || [] + targets << target + mapping[project_pth] = targets + end + + mapping +end + +def runnable_target?(target) + return false unless target.is_a?(Xcodeproj::Project::Object::PBXNativeTarget) + + product_reference = target.product_reference + return false unless product_reference + + product_reference.path.end_with?('.app', '.appex') +end + +def collect_dependent_targets(target, dependent_targets) + dependent_targets << target + + dependencies = target.dependencies || [] + return dependent_targets if dependencies.empty? + + dependencies.each do |dependency| + dependent_target = dependency.target + next unless dependent_target + next unless runnable_target?(dependent_target) + + collect_dependent_targets(dependent_target, dependent_targets) + end + + dependent_targets +end + +def find_archive_action_build_configuration_name(scheme) + archive_action = scheme.archive_action + return nil unless archive_action + + archive_action.build_configuration +end + +def read_scheme_target_mapping(project_or_workspace_pth, scheme_name, user_name) + scheme_container_dir = read_scheme(project_or_workspace_pth, scheme_name, user_name) + raise "project (#{project_or_workspace_pth}) does not contain scheme: #{scheme_name}" unless scheme_container_dir + scheme = scheme_container_dir[:scheme] + container_dir = scheme_container_dir[:container_dir] + + configuration = find_archive_action_build_configuration_name(scheme) + + target_mapping = project_buildable_target_mapping(container_dir, scheme) || [] + raise 'scheme does not contain buildable target' if target_mapping.empty? + + project_targets = {} + target_mapping.each do |project_pth, targets| + targets.each do |target| + dependent_targets = [] + dependent_targets = collect_dependent_targets(target, dependent_targets) + + project_targets[project_pth] = dependent_targets.collect(&:name) + end + end + raise 'failed to collect buildable targets' if project_targets.empty? + + { + configuration: configuration, + project_targets: project_targets + } +end + +begin + project_path = ENV['project'] + scheme_name = ENV['scheme'] + user_name = ENV['user'] + + raise 'missing project_path' if project_path.to_s.empty? + raise 'missing scheme_name' if scheme_name.to_s.empty? + raise 'missing user_name' if user_name.to_s.empty? + + mapping = read_scheme_target_mapping(project_path, scheme_name, user_name) + result = { + data: mapping + } + result_json = JSON.pretty_generate(result).to_s + puts result_json +rescue => e + error_message = e.to_s + "\n" + e.backtrace.join("\n") + result = { + error: error_message + } + result_json = result.to_json.to_s + puts result_json + exit(1) +end diff --git a/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info_script.sh b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info_script.sh new file mode 100644 index 00000000..9bfe6410 --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/code_sign_info_script.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# test code_sign_info_script.rb + +export user="" +export project="" +export scheme="" +export configuration="" + +ruby code_sign_info_script.rb \ No newline at end of file diff --git a/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/project.go b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/project.go new file mode 100644 index 00000000..e9fc0554 --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/project.go @@ -0,0 +1,70 @@ +package xcodeproj + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/bitrise-io/go-utils/pathutil" +) + +// ProjectModel ... +type ProjectModel struct { + Pth string + Name string + SDKs []string + SharedSchemes []SchemeModel + Targets []TargetModel +} + +// NewProject ... +func NewProject(xcodeprojPth string) (ProjectModel, error) { + project := ProjectModel{ + Pth: xcodeprojPth, + Name: strings.TrimSuffix(filepath.Base(xcodeprojPth), filepath.Ext(xcodeprojPth)), + } + + // SDK + pbxprojPth := filepath.Join(xcodeprojPth, "project.pbxproj") + + if exist, err := pathutil.IsPathExists(pbxprojPth); err != nil { + return ProjectModel{}, err + } else if !exist { + return ProjectModel{}, fmt.Errorf("Project descriptor not found at: %s", pbxprojPth) + } + + sdks, err := GetBuildConfigSDKs(pbxprojPth) + if err != nil { + return ProjectModel{}, err + } + + project.SDKs = sdks + + // Shared Schemes + schemes, err := ProjectSharedSchemes(xcodeprojPth) + if err != nil { + return ProjectModel{}, err + } + + project.SharedSchemes = schemes + + // Targets + targets, err := ProjectTargets(xcodeprojPth) + if err != nil { + return ProjectModel{}, err + } + + project.Targets = targets + + return project, nil +} + +// ContainsSDK ... +func (p ProjectModel) ContainsSDK(sdk string) bool { + for _, s := range p.SDKs { + if s == sdk { + return true + } + } + return false +} diff --git a/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/workspace.go b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/workspace.go new file mode 100644 index 00000000..1d9d03be --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/workspace.go @@ -0,0 +1,78 @@ +package xcodeproj + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/bitrise-io/go-utils/pathutil" +) + +// WorkspaceModel ... +type WorkspaceModel struct { + Pth string + Name string + Projects []ProjectModel + IsPodWorkspace bool +} + +// NewWorkspace ... +func NewWorkspace(xcworkspacePth string, projectsToCheck ...string) (WorkspaceModel, error) { + workspace := WorkspaceModel{ + Pth: xcworkspacePth, + Name: strings.TrimSuffix(filepath.Base(xcworkspacePth), filepath.Ext(xcworkspacePth)), + } + + projects, err := WorkspaceProjectReferences(xcworkspacePth) + if err != nil { + return WorkspaceModel{}, err + } + + if len(projectsToCheck) > 0 { + filteredProjects := []string{} + for _, project := range projects { + for _, projectToCheck := range projectsToCheck { + if project == projectToCheck { + filteredProjects = append(filteredProjects, project) + } + } + } + projects = filteredProjects + } + + for _, xcodeprojPth := range projects { + + if exist, err := pathutil.IsPathExists(xcodeprojPth); err != nil { + return WorkspaceModel{}, err + } else if !exist { + return WorkspaceModel{}, fmt.Errorf("referred project (%s) not found", xcodeprojPth) + } + + project, err := NewProject(xcodeprojPth) + if err != nil { + return WorkspaceModel{}, err + } + + workspace.Projects = append(workspace.Projects, project) + } + + return workspace, nil +} + +// GetSharedSchemes ... +func (w WorkspaceModel) GetSharedSchemes() []SchemeModel { + schemes := []SchemeModel{} + for _, project := range w.Projects { + schemes = append(schemes, project.SharedSchemes...) + } + return schemes +} + +// GetTargets ... +func (w WorkspaceModel) GetTargets() []TargetModel { + targets := []TargetModel{} + for _, project := range w.Projects { + targets = append(targets, project.Targets...) + } + return targets +} diff --git a/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xcodeproj.go b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xcodeproj.go new file mode 100644 index 00000000..c1b28ca6 --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xcodeproj.go @@ -0,0 +1,84 @@ +package xcodeproj + +import ( + "bufio" + "regexp" + "strings" + + "github.com/bitrise-io/go-utils/fileutil" +) + +// Extensions +const ( + // XCWorkspaceExt ... + XCWorkspaceExt = ".xcworkspace" + // XCodeProjExt ... + XCodeProjExt = ".xcodeproj" + // XCSchemeExt ... + XCSchemeExt = ".xcscheme" +) + +// IsXCodeProj ... +func IsXCodeProj(pth string) bool { + return strings.HasSuffix(pth, XCodeProjExt) +} + +// IsXCWorkspace ... +func IsXCWorkspace(pth string) bool { + return strings.HasSuffix(pth, XCWorkspaceExt) +} + +// GetBuildConfigSDKs ... +func GetBuildConfigSDKs(pbxprojPth string) ([]string, error) { + content, err := fileutil.ReadStringFromFile(pbxprojPth) + if err != nil { + return []string{}, err + } + + return getBuildConfigSDKsFromContent(content) +} + +func getBuildConfigSDKsFromContent(pbxprojContent string) ([]string, error) { + sdkMap := map[string]bool{} + + beginXCBuildConfigurationSection := `/* Begin XCBuildConfiguration section */` + endXCBuildConfigurationSection := `/* End XCBuildConfiguration section */` + isXCBuildConfigurationSection := false + + // SDKROOT = macosx; + pattern := `SDKROOT = (?P.*);` + regexp := regexp.MustCompile(pattern) + + scanner := bufio.NewScanner(strings.NewReader(pbxprojContent)) + for scanner.Scan() { + line := scanner.Text() + + if strings.TrimSpace(line) == endXCBuildConfigurationSection { + break + } + + if strings.TrimSpace(line) == beginXCBuildConfigurationSection { + isXCBuildConfigurationSection = true + continue + } + + if !isXCBuildConfigurationSection { + continue + } + + if match := regexp.FindStringSubmatch(line); len(match) == 2 { + sdk := match[1] + sdkMap[sdk] = true + } + } + if err := scanner.Err(); err != nil { + return []string{}, err + } + + sdks := []string{} + for sdk := range sdkMap { + sdks = append(sdks, sdk) + } + + return sdks, nil +} diff --git a/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xcodeproj_test_files.go b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xcodeproj_test_files.go new file mode 100644 index 00000000..7960bd54 --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xcodeproj_test_files.go @@ -0,0 +1,1326 @@ +package xcodeproj + +import ( + "path/filepath" + + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" +) + +func testIOSPbxprojPth() (string, error) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__bitrise_init__") + if err != nil { + return "", err + } + + pth := filepath.Join(tmpDir, "project.pbxproj") + + if err := fileutil.WriteStringToFile(pth, testIOSPbxprojContent); err != nil { + return "", err + } + + return pth, nil +} + +func testMacOSPbxprojPth() (string, error) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("__bitrise_init__") + if err != nil { + return "", err + } + + pth := filepath.Join(tmpDir, "project.pbxproj") + + if err := fileutil.WriteStringToFile(pth, testMacOSPbxprojContent); err != nil { + return "", err + } + + return pth, nil +} + +const testMacOSPbxprojContent = `// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1302F7441D95BA4A005CE678 /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1302F7431D95BA4A005CE678 /* Session.swift */; }; + 130E6BBE1D95BBB4009D3C78 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138F9EE61D8E7ABC00515FCA /* Command.swift */; }; + 131A3D9A1D90543F002DAF99 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 131A3D961D9053BB002DAF99 /* Realm.framework */; }; + 131A3D9B1D90543F002DAF99 /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 131A3D961D9053BB002DAF99 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 131A3D9C1D90543F002DAF99 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 131A3D971D9053BB002DAF99 /* RealmSwift.framework */; }; + 131A3D9D1D90543F002DAF99 /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 131A3D971D9053BB002DAF99 /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 131A3DA61D9060AA002DAF99 /* Yaml.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 131A3DA11D90609E002DAF99 /* Yaml.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 131A3DA71D9060C0002DAF99 /* Yaml.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 131A3DA11D90609E002DAF99 /* Yaml.framework */; }; + 131A3DAB1D906BFA002DAF99 /* BitriseTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131A3DAA1D906BFA002DAF99 /* BitriseTool.swift */; }; + 131A3DAD1D906D54002DAF99 /* Bitrise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131A3DAC1D906D54002DAF99 /* Bitrise.swift */; }; + 131A3DAF1D906DAB002DAF99 /* Envman.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131A3DAE1D906DAB002DAF99 /* Envman.swift */; }; + 131A3DB11D906DBF002DAF99 /* Stepman.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131A3DB01D906DBF002DAF99 /* Stepman.swift */; }; + 131ACE731D93054B007E71E9 /* ToolManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131ACE721D93054B007E71E9 /* ToolManager.swift */; }; + 131ACE761D9323E3007E71E9 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131ACE751D9323E3007E71E9 /* Version.swift */; }; + 13A95E821D966F040061B54F /* BashSessionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A95E811D966F040061B54F /* BashSessionViewController.swift */; }; + 13AAE1BD1D8426FF00AEE66D /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AAE1BC1D8426FF00AEE66D /* FileManager.swift */; }; + 13B0B8F31D872E93006EA29C /* RealmManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B0B8F21D872E93006EA29C /* RealmManager.swift */; }; + 13B958CD1D89E87600D3310D /* SystemInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B958CC1D89E87600D3310D /* SystemInfoViewController.swift */; }; + 13B958D01D89EC7800D3310D /* RunViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B958CE1D89EC7800D3310D /* RunViewController.swift */; }; + 13C989811D8319600028BA2C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C989801D8319600028BA2C /* AppDelegate.swift */; }; + 13C989851D8319600028BA2C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13C989841D8319600028BA2C /* Assets.xcassets */; }; + 13C989881D8319600028BA2C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13C989861D8319600028BA2C /* Main.storyboard */; }; + 13C989931D8319600028BA2C /* BitriseStudioTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C989921D8319600028BA2C /* BitriseStudioTests.swift */; }; + 13C9899E1D8319600028BA2C /* BitriseStudioUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C9899D1D8319600028BA2C /* BitriseStudioUITests.swift */; }; + 13CA73D61D84A74800B1A323 /* AddProjectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CA73D51D84A74800B1A323 /* AddProjectViewController.swift */; }; + 13CA73DE1D84B67200B1A323 /* Project.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CA73DD1D84B67200B1A323 /* Project.swift */; }; + 13E3F5531D83477300AE7C20 /* ProjectsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C989821D8319600028BA2C /* ProjectsViewController.swift */; }; + 13FF5FCE1D9859EE008C7DFB /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FF5FCD1D9859EE008C7DFB /* Log.swift */; }; + 13FF5FD11D98620A008C7DFB /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FF5FD01D98620A008C7DFB /* String+Extensions.swift */; }; + 13FF5FD31D9862DA008C7DFB /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FF5FD21D9862DA008C7DFB /* Data+Extensions.swift */; }; + 13FF5FD51D9872EC008C7DFB /* Pipe+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FF5FD41D9872EC008C7DFB /* Pipe+Extensions.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 13C9898F1D8319600028BA2C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 13C989751D83195F0028BA2C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13C9897C1D83195F0028BA2C; + remoteInfo = BitriseStudio; + }; + 13C9899A1D8319600028BA2C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 13C989751D83195F0028BA2C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13C9897C1D83195F0028BA2C; + remoteInfo = BitriseStudio; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 131A3D9E1D90543F002DAF99 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 131A3D9D1D90543F002DAF99 /* RealmSwift.framework in Embed Frameworks */, + 131A3D9B1D90543F002DAF99 /* Realm.framework in Embed Frameworks */, + 131A3DA61D9060AA002DAF99 /* Yaml.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1302F7431D95BA4A005CE678 /* Session.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; }; + 131A3D961D9053BB002DAF99 /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Realm.framework; path = Framworks/Realm.framework; sourceTree = ""; }; + 131A3D971D9053BB002DAF99 /* RealmSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RealmSwift.framework; path = Framworks/RealmSwift.framework; sourceTree = ""; }; + 131A3DA11D90609E002DAF99 /* Yaml.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Yaml.framework; path = Framworks/Yaml.framework; sourceTree = ""; }; + 131A3DAA1D906BFA002DAF99 /* BitriseTool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitriseTool.swift; sourceTree = ""; }; + 131A3DAC1D906D54002DAF99 /* Bitrise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bitrise.swift; sourceTree = ""; }; + 131A3DAE1D906DAB002DAF99 /* Envman.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Envman.swift; sourceTree = ""; }; + 131A3DB01D906DBF002DAF99 /* Stepman.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stepman.swift; sourceTree = ""; }; + 131ACE721D93054B007E71E9 /* ToolManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolManager.swift; sourceTree = ""; }; + 131ACE751D9323E3007E71E9 /* Version.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; }; + 138F9EE61D8E7ABC00515FCA /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = ""; }; + 13A95E811D966F040061B54F /* BashSessionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BashSessionViewController.swift; sourceTree = ""; }; + 13AAE1BC1D8426FF00AEE66D /* FileManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = ""; }; + 13B0B8F21D872E93006EA29C /* RealmManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmManager.swift; sourceTree = ""; }; + 13B958CC1D89E87600D3310D /* SystemInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemInfoViewController.swift; sourceTree = ""; }; + 13B958CE1D89EC7800D3310D /* RunViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RunViewController.swift; sourceTree = ""; }; + 13C9897D1D8319600028BA2C /* BitriseStudio.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitriseStudio.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13C989801D8319600028BA2C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 13C989821D8319600028BA2C /* ProjectsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectsViewController.swift; sourceTree = ""; }; + 13C989841D8319600028BA2C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 13C989871D8319600028BA2C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 13C989891D8319600028BA2C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13C9898E1D8319600028BA2C /* BitriseStudioTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitriseStudioTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 13C989921D8319600028BA2C /* BitriseStudioTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitriseStudioTests.swift; sourceTree = ""; }; + 13C989941D8319600028BA2C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13C989991D8319600028BA2C /* BitriseStudioUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitriseStudioUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 13C9899D1D8319600028BA2C /* BitriseStudioUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitriseStudioUITests.swift; sourceTree = ""; }; + 13C9899F1D8319600028BA2C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13CA73D51D84A74800B1A323 /* AddProjectViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddProjectViewController.swift; sourceTree = ""; }; + 13CA73DD1D84B67200B1A323 /* Project.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Project.swift; sourceTree = ""; }; + 13FF5FCD1D9859EE008C7DFB /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; + 13FF5FD01D98620A008C7DFB /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; + 13FF5FD21D9862DA008C7DFB /* Data+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = ""; }; + 13FF5FD41D9872EC008C7DFB /* Pipe+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Pipe+Extensions.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13C9897A1D83195F0028BA2C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 131A3D9C1D90543F002DAF99 /* RealmSwift.framework in Frameworks */, + 131A3D9A1D90543F002DAF99 /* Realm.framework in Frameworks */, + 131A3DA71D9060C0002DAF99 /* Yaml.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C9898B1D8319600028BA2C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C989961D8319600028BA2C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1302F7421D95BA35005CE678 /* Bash */ = { + isa = PBXGroup; + children = ( + 1302F7431D95BA4A005CE678 /* Session.swift */, + 138F9EE61D8E7ABC00515FCA /* Command.swift */, + ); + name = Bash; + sourceTree = ""; + }; + 131ACE741D9323C1007E71E9 /* Version */ = { + isa = PBXGroup; + children = ( + 131ACE751D9323E3007E71E9 /* Version.swift */, + ); + name = Version; + sourceTree = ""; + }; + 13C989741D83195F0028BA2C = { + isa = PBXGroup; + children = ( + 13C9897F1D8319600028BA2C /* BitriseStudio */, + 13C989911D8319600028BA2C /* BitriseStudioTests */, + 13C9899C1D8319600028BA2C /* BitriseStudioUITests */, + 13C9897E1D8319600028BA2C /* Products */, + 13CA73D71D84B5C500B1A323 /* Frameworks */, + ); + sourceTree = ""; + }; + 13C9897E1D8319600028BA2C /* Products */ = { + isa = PBXGroup; + children = ( + 13C9897D1D8319600028BA2C /* BitriseStudio.app */, + 13C9898E1D8319600028BA2C /* BitriseStudioTests.xctest */, + 13C989991D8319600028BA2C /* BitriseStudioUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 13C9897F1D8319600028BA2C /* BitriseStudio */ = { + isa = PBXGroup; + children = ( + 13FF5FCF1D9861E3008C7DFB /* Extensions */, + 13FF5FCC1D9859DB008C7DFB /* Log */, + 1302F7421D95BA35005CE678 /* Bash */, + 131ACE741D9323C1007E71E9 /* Version */, + 13CA73DC1D84B63E00B1A323 /* Models */, + 13E3F5501D8342DD00AE7C20 /* Managers */, + 13E3F54D1D8341DF00AE7C20 /* Controllers */, + 13E3F54E1D83429900AE7C20 /* Supporting Files */, + 13E3F54F1D8342BC00AE7C20 /* Assets */, + 13C989801D8319600028BA2C /* AppDelegate.swift */, + ); + path = BitriseStudio; + sourceTree = ""; + }; + 13C989911D8319600028BA2C /* BitriseStudioTests */ = { + isa = PBXGroup; + children = ( + 13C989921D8319600028BA2C /* BitriseStudioTests.swift */, + 13C989941D8319600028BA2C /* Info.plist */, + ); + path = BitriseStudioTests; + sourceTree = ""; + }; + 13C9899C1D8319600028BA2C /* BitriseStudioUITests */ = { + isa = PBXGroup; + children = ( + 13C9899D1D8319600028BA2C /* BitriseStudioUITests.swift */, + 13C9899F1D8319600028BA2C /* Info.plist */, + ); + path = BitriseStudioUITests; + sourceTree = ""; + }; + 13CA73D71D84B5C500B1A323 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 131A3DA11D90609E002DAF99 /* Yaml.framework */, + 131A3D961D9053BB002DAF99 /* Realm.framework */, + 131A3D971D9053BB002DAF99 /* RealmSwift.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 13CA73DC1D84B63E00B1A323 /* Models */ = { + isa = PBXGroup; + children = ( + 13CA73DD1D84B67200B1A323 /* Project.swift */, + 131A3DAA1D906BFA002DAF99 /* BitriseTool.swift */, + 131A3DAC1D906D54002DAF99 /* Bitrise.swift */, + 131A3DAE1D906DAB002DAF99 /* Envman.swift */, + 131A3DB01D906DBF002DAF99 /* Stepman.swift */, + ); + name = Models; + sourceTree = ""; + }; + 13E3F54D1D8341DF00AE7C20 /* Controllers */ = { + isa = PBXGroup; + children = ( + 13C989861D8319600028BA2C /* Main.storyboard */, + 13C989821D8319600028BA2C /* ProjectsViewController.swift */, + 13CA73D51D84A74800B1A323 /* AddProjectViewController.swift */, + 13B958CC1D89E87600D3310D /* SystemInfoViewController.swift */, + 13B958CE1D89EC7800D3310D /* RunViewController.swift */, + 13A95E811D966F040061B54F /* BashSessionViewController.swift */, + ); + name = Controllers; + sourceTree = ""; + }; + 13E3F54E1D83429900AE7C20 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 13C989891D8319600028BA2C /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 13E3F54F1D8342BC00AE7C20 /* Assets */ = { + isa = PBXGroup; + children = ( + 13C989841D8319600028BA2C /* Assets.xcassets */, + ); + name = Assets; + sourceTree = ""; + }; + 13E3F5501D8342DD00AE7C20 /* Managers */ = { + isa = PBXGroup; + children = ( + 13AAE1BC1D8426FF00AEE66D /* FileManager.swift */, + 13B0B8F21D872E93006EA29C /* RealmManager.swift */, + 131ACE721D93054B007E71E9 /* ToolManager.swift */, + ); + name = Managers; + sourceTree = ""; + }; + 13FF5FCC1D9859DB008C7DFB /* Log */ = { + isa = PBXGroup; + children = ( + 13FF5FCD1D9859EE008C7DFB /* Log.swift */, + ); + name = Log; + sourceTree = ""; + }; + 13FF5FCF1D9861E3008C7DFB /* Extensions */ = { + isa = PBXGroup; + children = ( + 13FF5FD01D98620A008C7DFB /* String+Extensions.swift */, + 13FF5FD21D9862DA008C7DFB /* Data+Extensions.swift */, + 13FF5FD41D9872EC008C7DFB /* Pipe+Extensions.swift */, + ); + name = Extensions; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13C9897C1D83195F0028BA2C /* BitriseStudio */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13C989A21D8319600028BA2C /* Build configuration list for PBXNativeTarget "BitriseStudio" */; + buildPhases = ( + 13C989791D83195F0028BA2C /* Sources */, + 13C9897A1D83195F0028BA2C /* Frameworks */, + 13C9897B1D83195F0028BA2C /* Resources */, + 131A3D9E1D90543F002DAF99 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BitriseStudio; + productName = BitriseStudio; + productReference = 13C9897D1D8319600028BA2C /* BitriseStudio.app */; + productType = "com.apple.product-type.application"; + }; + 13C9898D1D8319600028BA2C /* BitriseStudioTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13C989A51D8319600028BA2C /* Build configuration list for PBXNativeTarget "BitriseStudioTests" */; + buildPhases = ( + 13C9898A1D8319600028BA2C /* Sources */, + 13C9898B1D8319600028BA2C /* Frameworks */, + 13C9898C1D8319600028BA2C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 13C989901D8319600028BA2C /* PBXTargetDependency */, + ); + name = BitriseStudioTests; + productName = BitriseStudioTests; + productReference = 13C9898E1D8319600028BA2C /* BitriseStudioTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13C989981D8319600028BA2C /* BitriseStudioUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13C989A81D8319600028BA2C /* Build configuration list for PBXNativeTarget "BitriseStudioUITests" */; + buildPhases = ( + 13C989951D8319600028BA2C /* Sources */, + 13C989961D8319600028BA2C /* Frameworks */, + 13C989971D8319600028BA2C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 13C9899B1D8319600028BA2C /* PBXTargetDependency */, + ); + name = BitriseStudioUITests; + productName = BitriseStudioUITests; + productReference = 13C989991D8319600028BA2C /* BitriseStudioUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 13C989751D83195F0028BA2C /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0800; + LastUpgradeCheck = 0810; + ORGANIZATIONNAME = "Krisztian Goedrei"; + TargetAttributes = { + 13C9897C1D83195F0028BA2C = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = 9NS44DLTN7; + ProvisioningStyle = Manual; + }; + 13C9898D1D8319600028BA2C = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = L935L4GU3F; + ProvisioningStyle = Automatic; + TestTargetID = 13C9897C1D83195F0028BA2C; + }; + 13C989981D8319600028BA2C = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = L935L4GU3F; + ProvisioningStyle = Automatic; + TestTargetID = 13C9897C1D83195F0028BA2C; + }; + }; + }; + buildConfigurationList = 13C989781D83195F0028BA2C /* Build configuration list for PBXProject "BitriseStudio" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 13C989741D83195F0028BA2C; + productRefGroup = 13C9897E1D8319600028BA2C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13C9897C1D83195F0028BA2C /* BitriseStudio */, + 13C9898D1D8319600028BA2C /* BitriseStudioTests */, + 13C989981D8319600028BA2C /* BitriseStudioUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 13C9897B1D83195F0028BA2C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C989851D8319600028BA2C /* Assets.xcassets in Resources */, + 13C989881D8319600028BA2C /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C9898C1D8319600028BA2C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C989971D8319600028BA2C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13C989791D83195F0028BA2C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13FF5FD11D98620A008C7DFB /* String+Extensions.swift in Sources */, + 131A3DB11D906DBF002DAF99 /* Stepman.swift in Sources */, + 13B0B8F31D872E93006EA29C /* RealmManager.swift in Sources */, + 13FF5FCE1D9859EE008C7DFB /* Log.swift in Sources */, + 131A3DAB1D906BFA002DAF99 /* BitriseTool.swift in Sources */, + 13E3F5531D83477300AE7C20 /* ProjectsViewController.swift in Sources */, + 130E6BBE1D95BBB4009D3C78 /* Command.swift in Sources */, + 13FF5FD51D9872EC008C7DFB /* Pipe+Extensions.swift in Sources */, + 131ACE731D93054B007E71E9 /* ToolManager.swift in Sources */, + 13A95E821D966F040061B54F /* BashSessionViewController.swift in Sources */, + 13C989811D8319600028BA2C /* AppDelegate.swift in Sources */, + 1302F7441D95BA4A005CE678 /* Session.swift in Sources */, + 131ACE761D9323E3007E71E9 /* Version.swift in Sources */, + 13B958CD1D89E87600D3310D /* SystemInfoViewController.swift in Sources */, + 13CA73D61D84A74800B1A323 /* AddProjectViewController.swift in Sources */, + 131A3DAD1D906D54002DAF99 /* Bitrise.swift in Sources */, + 13FF5FD31D9862DA008C7DFB /* Data+Extensions.swift in Sources */, + 13AAE1BD1D8426FF00AEE66D /* FileManager.swift in Sources */, + 13B958D01D89EC7800D3310D /* RunViewController.swift in Sources */, + 131A3DAF1D906DAB002DAF99 /* Envman.swift in Sources */, + 13CA73DE1D84B67200B1A323 /* Project.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C9898A1D8319600028BA2C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C989931D8319600028BA2C /* BitriseStudioTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C989951D8319600028BA2C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C9899E1D8319600028BA2C /* BitriseStudioUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 13C989901D8319600028BA2C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13C9897C1D83195F0028BA2C /* BitriseStudio */; + targetProxy = 13C9898F1D8319600028BA2C /* PBXContainerItemProxy */; + }; + 13C9899B1D8319600028BA2C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13C9897C1D83195F0028BA2C /* BitriseStudio */; + targetProxy = 13C9899A1D8319600028BA2C /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 13C989861D8319600028BA2C /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 13C989871D8319600028BA2C /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 13C989A01D8319600028BA2C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 13C989A11D8319600028BA2C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + }; + name = Release; + }; + 13C989A31D8319600028BA2C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Mac Developer: Some Dude (KYXQXCWE3G)"; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 9NS44DLTN7; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + "$(PROJECT_DIR)/Framworks", + ); + INFOPLIST_FILE = BitriseStudio/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseStudio; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "b17a1b90-9459-4620-9332-347d399f7cd9"; + PROVISIONING_PROFILE_SPECIFIER = "Mac Development Wildcard"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 13C989A41D8319600028BA2C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 9NS44DLTN7; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + "$(PROJECT_DIR)/Framworks", + ); + INFOPLIST_FILE = BitriseStudio/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseStudio; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "1bb807b8-a953-459e-85ca-c86d3fe13645"; + PROVISIONING_PROFILE_SPECIFIER = "Mac App-Store Wildcards"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + 13C989A61D8319600028BA2C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = L935L4GU3F; + INFOPLIST_FILE = BitriseStudioTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = bitrise.BitriseStudioTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BitriseStudio.app/Contents/MacOS/BitriseStudio"; + }; + name = Debug; + }; + 13C989A71D8319600028BA2C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = L935L4GU3F; + INFOPLIST_FILE = BitriseStudioTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = bitrise.BitriseStudioTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BitriseStudio.app/Contents/MacOS/BitriseStudio"; + }; + name = Release; + }; + 13C989A91D8319600028BA2C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = L935L4GU3F; + INFOPLIST_FILE = BitriseStudioUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = bitrise.BitriseStudioUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_TARGET_NAME = BitriseStudio; + }; + name = Debug; + }; + 13C989AA1D8319600028BA2C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = L935L4GU3F; + INFOPLIST_FILE = BitriseStudioUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = bitrise.BitriseStudioUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_TARGET_NAME = BitriseStudio; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13C989781D83195F0028BA2C /* Build configuration list for PBXProject "BitriseStudio" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C989A01D8319600028BA2C /* Debug */, + 13C989A11D8319600028BA2C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13C989A21D8319600028BA2C /* Build configuration list for PBXNativeTarget "BitriseStudio" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C989A31D8319600028BA2C /* Debug */, + 13C989A41D8319600028BA2C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13C989A51D8319600028BA2C /* Build configuration list for PBXNativeTarget "BitriseStudioTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C989A61D8319600028BA2C /* Debug */, + 13C989A71D8319600028BA2C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13C989A81D8319600028BA2C /* Build configuration list for PBXNativeTarget "BitriseStudioUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C989A91D8319600028BA2C /* Debug */, + 13C989AA1D8319600028BA2C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 13C989751D83195F0028BA2C /* Project object */; +} +` + +const testIOSPbxprojContent = ` +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 13C4D5AB1DDDDED300D5DC29 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C4D5AA1DDDDED300D5DC29 /* AppDelegate.swift */; }; + 13C4D5AD1DDDDED300D5DC29 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C4D5AC1DDDDED300D5DC29 /* ViewController.swift */; }; + 13C4D5B01DDDDED300D5DC29 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13C4D5AE1DDDDED300D5DC29 /* Main.storyboard */; }; + 13C4D5B21DDDDED300D5DC29 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13C4D5B11DDDDED300D5DC29 /* Assets.xcassets */; }; + 13C4D5B51DDDDED300D5DC29 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13C4D5B31DDDDED300D5DC29 /* LaunchScreen.storyboard */; }; + 13C4D5C01DDDDED400D5DC29 /* BitriseFastlaneSampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C4D5BF1DDDDED400D5DC29 /* BitriseFastlaneSampleTests.swift */; }; + 13C4D5CB1DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C4D5CA1DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 13C4D5BC1DDDDED400D5DC29 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 13C4D59F1DDDDED300D5DC29 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13C4D5A61DDDDED300D5DC29; + remoteInfo = BitriseFastlaneSample; + }; + 13C4D5C71DDDDED400D5DC29 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 13C4D59F1DDDDED300D5DC29 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13C4D5A61DDDDED300D5DC29; + remoteInfo = BitriseFastlaneSample; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 13C4D5A71DDDDED300D5DC29 /* BitriseFastlaneSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitriseFastlaneSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13C4D5AA1DDDDED300D5DC29 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 13C4D5AC1DDDDED300D5DC29 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 13C4D5AF1DDDDED300D5DC29 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 13C4D5B11DDDDED300D5DC29 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 13C4D5B41DDDDED300D5DC29 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 13C4D5B61DDDDED300D5DC29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13C4D5BB1DDDDED400D5DC29 /* BitriseFastlaneSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitriseFastlaneSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 13C4D5BF1DDDDED400D5DC29 /* BitriseFastlaneSampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitriseFastlaneSampleTests.swift; sourceTree = ""; }; + 13C4D5C11DDDDED400D5DC29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13C4D5C61DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitriseFastlaneSampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 13C4D5CA1DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitriseFastlaneSampleUITests.swift; sourceTree = ""; }; + 13C4D5CC1DDDDED400D5DC29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13C4D5A41DDDDED300D5DC29 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C4D5B81DDDDED400D5DC29 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C4D5C31DDDDED400D5DC29 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 13C4D59E1DDDDED300D5DC29 = { + isa = PBXGroup; + children = ( + 13C4D5A91DDDDED300D5DC29 /* BitriseFastlaneSample */, + 13C4D5BE1DDDDED400D5DC29 /* BitriseFastlaneSampleTests */, + 13C4D5C91DDDDED400D5DC29 /* BitriseFastlaneSampleUITests */, + 13C4D5A81DDDDED300D5DC29 /* Products */, + ); + sourceTree = ""; + }; + 13C4D5A81DDDDED300D5DC29 /* Products */ = { + isa = PBXGroup; + children = ( + 13C4D5A71DDDDED300D5DC29 /* BitriseFastlaneSample.app */, + 13C4D5BB1DDDDED400D5DC29 /* BitriseFastlaneSampleTests.xctest */, + 13C4D5C61DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 13C4D5A91DDDDED300D5DC29 /* BitriseFastlaneSample */ = { + isa = PBXGroup; + children = ( + 13C4D5AA1DDDDED300D5DC29 /* AppDelegate.swift */, + 13C4D5AC1DDDDED300D5DC29 /* ViewController.swift */, + 13C4D5AE1DDDDED300D5DC29 /* Main.storyboard */, + 13C4D5B11DDDDED300D5DC29 /* Assets.xcassets */, + 13C4D5B31DDDDED300D5DC29 /* LaunchScreen.storyboard */, + 13C4D5B61DDDDED300D5DC29 /* Info.plist */, + ); + path = BitriseFastlaneSample; + sourceTree = ""; + }; + 13C4D5BE1DDDDED400D5DC29 /* BitriseFastlaneSampleTests */ = { + isa = PBXGroup; + children = ( + 13C4D5BF1DDDDED400D5DC29 /* BitriseFastlaneSampleTests.swift */, + 13C4D5C11DDDDED400D5DC29 /* Info.plist */, + ); + path = BitriseFastlaneSampleTests; + sourceTree = ""; + }; + 13C4D5C91DDDDED400D5DC29 /* BitriseFastlaneSampleUITests */ = { + isa = PBXGroup; + children = ( + 13C4D5CA1DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.swift */, + 13C4D5CC1DDDDED400D5DC29 /* Info.plist */, + ); + path = BitriseFastlaneSampleUITests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13C4D5A61DDDDED300D5DC29 /* BitriseFastlaneSample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13C4D5CF1DDDDED400D5DC29 /* Build configuration list for PBXNativeTarget "BitriseFastlaneSample" */; + buildPhases = ( + 13C4D5A31DDDDED300D5DC29 /* Sources */, + 13C4D5A41DDDDED300D5DC29 /* Frameworks */, + 13C4D5A51DDDDED300D5DC29 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BitriseFastlaneSample; + productName = BitriseFastlaneSample; + productReference = 13C4D5A71DDDDED300D5DC29 /* BitriseFastlaneSample.app */; + productType = "com.apple.product-type.application"; + }; + 13C4D5BA1DDDDED400D5DC29 /* BitriseFastlaneSampleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13C4D5D21DDDDED400D5DC29 /* Build configuration list for PBXNativeTarget "BitriseFastlaneSampleTests" */; + buildPhases = ( + 13C4D5B71DDDDED400D5DC29 /* Sources */, + 13C4D5B81DDDDED400D5DC29 /* Frameworks */, + 13C4D5B91DDDDED400D5DC29 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 13C4D5BD1DDDDED400D5DC29 /* PBXTargetDependency */, + ); + name = BitriseFastlaneSampleTests; + productName = BitriseFastlaneSampleTests; + productReference = 13C4D5BB1DDDDED400D5DC29 /* BitriseFastlaneSampleTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13C4D5C51DDDDED400D5DC29 /* BitriseFastlaneSampleUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13C4D5D51DDDDED400D5DC29 /* Build configuration list for PBXNativeTarget "BitriseFastlaneSampleUITests" */; + buildPhases = ( + 13C4D5C21DDDDED400D5DC29 /* Sources */, + 13C4D5C31DDDDED400D5DC29 /* Frameworks */, + 13C4D5C41DDDDED400D5DC29 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 13C4D5C81DDDDED400D5DC29 /* PBXTargetDependency */, + ); + name = BitriseFastlaneSampleUITests; + productName = BitriseFastlaneSampleUITests; + productReference = 13C4D5C61DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 13C4D59F1DDDDED300D5DC29 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0810; + LastUpgradeCheck = 0810; + ORGANIZATIONNAME = "Krisztian Goedrei"; + TargetAttributes = { + 13C4D5A61DDDDED300D5DC29 = { + CreatedOnToolsVersion = 8.1; + DevelopmentTeam = 9NS44DLTN7; + ProvisioningStyle = Manual; + }; + 13C4D5BA1DDDDED400D5DC29 = { + CreatedOnToolsVersion = 8.1; + DevelopmentTeam = 72SA8V3WYL; + ProvisioningStyle = Automatic; + TestTargetID = 13C4D5A61DDDDED300D5DC29; + }; + 13C4D5C51DDDDED400D5DC29 = { + CreatedOnToolsVersion = 8.1; + DevelopmentTeam = 72SA8V3WYL; + ProvisioningStyle = Automatic; + TestTargetID = 13C4D5A61DDDDED300D5DC29; + }; + }; + }; + buildConfigurationList = 13C4D5A21DDDDED300D5DC29 /* Build configuration list for PBXProject "BitriseFastlaneSample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 13C4D59E1DDDDED300D5DC29; + productRefGroup = 13C4D5A81DDDDED300D5DC29 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13C4D5A61DDDDED300D5DC29 /* BitriseFastlaneSample */, + 13C4D5BA1DDDDED400D5DC29 /* BitriseFastlaneSampleTests */, + 13C4D5C51DDDDED400D5DC29 /* BitriseFastlaneSampleUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 13C4D5A51DDDDED300D5DC29 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C4D5B51DDDDED300D5DC29 /* LaunchScreen.storyboard in Resources */, + 13C4D5B21DDDDED300D5DC29 /* Assets.xcassets in Resources */, + 13C4D5B01DDDDED300D5DC29 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C4D5B91DDDDED400D5DC29 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C4D5C41DDDDED400D5DC29 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13C4D5A31DDDDED300D5DC29 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C4D5AD1DDDDED300D5DC29 /* ViewController.swift in Sources */, + 13C4D5AB1DDDDED300D5DC29 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C4D5B71DDDDED400D5DC29 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C4D5C01DDDDED400D5DC29 /* BitriseFastlaneSampleTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13C4D5C21DDDDED400D5DC29 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13C4D5CB1DDDDED400D5DC29 /* BitriseFastlaneSampleUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 13C4D5BD1DDDDED400D5DC29 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13C4D5A61DDDDED300D5DC29 /* BitriseFastlaneSample */; + targetProxy = 13C4D5BC1DDDDED400D5DC29 /* PBXContainerItemProxy */; + }; + 13C4D5C81DDDDED400D5DC29 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13C4D5A61DDDDED300D5DC29 /* BitriseFastlaneSample */; + targetProxy = 13C4D5C71DDDDED400D5DC29 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 13C4D5AE1DDDDED300D5DC29 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 13C4D5AF1DDDDED300D5DC29 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 13C4D5B31DDDDED300D5DC29 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 13C4D5B41DDDDED300D5DC29 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 13C4D5CD1DDDDED400D5DC29 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 13C4D5CE1DDDDED400D5DC29 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 13C4D5D01DDDDED400D5DC29 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + DEVELOPMENT_TEAM = 9NS44DLTN7; + INFOPLIST_FILE = BitriseFastlaneSample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseFastlaneSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "8e4701a8-01fb-4467-aad7-5a6c541795f0"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.bitrise.BitriseFastlaneSample"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 13C4D5D11DDDDED400D5DC29 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + DEVELOPMENT_TEAM = 9NS44DLTN7; + INFOPLIST_FILE = BitriseFastlaneSample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseFastlaneSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "8e4701a8-01fb-4467-aad7-5a6c541795f0"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.bitrise.BitriseFastlaneSample"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + 13C4D5D31DDDDED400D5DC29 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = 72SA8V3WYL; + INFOPLIST_FILE = BitriseFastlaneSampleTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseFastlaneSampleTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BitriseFastlaneSample.app/BitriseFastlaneSample"; + }; + name = Debug; + }; + 13C4D5D41DDDDED400D5DC29 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = 72SA8V3WYL; + INFOPLIST_FILE = BitriseFastlaneSampleTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseFastlaneSampleTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BitriseFastlaneSample.app/BitriseFastlaneSample"; + }; + name = Release; + }; + 13C4D5D61DDDDED400D5DC29 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + DEVELOPMENT_TEAM = 72SA8V3WYL; + INFOPLIST_FILE = BitriseFastlaneSampleUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseFastlaneSampleUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_TARGET_NAME = BitriseFastlaneSample; + }; + name = Debug; + }; + 13C4D5D71DDDDED400D5DC29 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + DEVELOPMENT_TEAM = 72SA8V3WYL; + INFOPLIST_FILE = BitriseFastlaneSampleUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.bitrise.BitriseFastlaneSampleUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + TEST_TARGET_NAME = BitriseFastlaneSample; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13C4D5A21DDDDED300D5DC29 /* Build configuration list for PBXProject "BitriseFastlaneSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C4D5CD1DDDDED400D5DC29 /* Debug */, + 13C4D5CE1DDDDED400D5DC29 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13C4D5CF1DDDDED400D5DC29 /* Build configuration list for PBXNativeTarget "BitriseFastlaneSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C4D5D01DDDDED400D5DC29 /* Debug */, + 13C4D5D11DDDDED400D5DC29 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13C4D5D21DDDDED400D5DC29 /* Build configuration list for PBXNativeTarget "BitriseFastlaneSampleTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C4D5D31DDDDED400D5DC29 /* Debug */, + 13C4D5D41DDDDED400D5DC29 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13C4D5D51DDDDED400D5DC29 /* Build configuration list for PBXNativeTarget "BitriseFastlaneSampleUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13C4D5D61DDDDED400D5DC29 /* Debug */, + 13C4D5D71DDDDED400D5DC29 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 13C4D59F1DDDDED300D5DC29 /* Project object */; +} +` diff --git a/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xcscheme.go b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xcscheme.go new file mode 100644 index 00000000..81102847 --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-xcode/xcodeproj/xcscheme.go @@ -0,0 +1,250 @@ +package xcodeproj + +import ( + "bufio" + "fmt" + "os" + "path" + "path/filepath" + "regexp" + "sort" + "strings" + + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" +) + +// SchemeModel ... +type SchemeModel struct { + Name string + HasXCTest bool +} + +func filterSharedSchemeFilePaths(paths []string) []string { + isSharedSchemeFilePath := func(pth string) bool { + regexpPattern := filepath.Join(".*[/]?xcshareddata", "xcschemes", ".+[.]xcscheme") + regexp := regexp.MustCompile(regexpPattern) + return (regexp.FindString(pth) != "") + } + + filteredPaths := []string{} + for _, pth := range paths { + if isSharedSchemeFilePath(pth) { + filteredPaths = append(filteredPaths, pth) + } + } + + sort.Strings(filteredPaths) + + return filteredPaths +} + +func sharedSchemeFilePaths(projectOrWorkspacePth string) ([]string, error) { + filesInDir := func(dir string) ([]string, error) { + files := []string{} + if err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { + files = append(files, path) + return nil + }); err != nil { + return []string{}, err + } + return files, nil + } + + paths, err := filesInDir(projectOrWorkspacePth) + if err != nil { + return []string{}, err + } + return filterSharedSchemeFilePaths(paths), nil +} + +// SchemeNameFromPath ... +func SchemeNameFromPath(schemePth string) string { + basename := filepath.Base(schemePth) + ext := filepath.Ext(schemePth) + if ext != XCSchemeExt { + return "" + } + return strings.TrimSuffix(basename, ext) +} + +func schemeFileContentContainsXCTestBuildAction(schemeFileContent string) (bool, error) { + testActionStartPattern := ".+)"`) + testableReferenceEndPattern := "" + isTestableReference := false + + xctestBuildableReferenceNameRegexp := regexp.MustCompile(`BuildableName = ".+.xctest"`) + + scanner := bufio.NewScanner(strings.NewReader(schemeFileContent)) + for scanner.Scan() { + line := scanner.Text() + + if strings.TrimSpace(line) == testActionEndPattern { + break + } + + if strings.TrimSpace(line) == testActionStartPattern { + isTestableAction = true + continue + } + + if !isTestableAction { + continue + } + + // TestAction + + if strings.TrimSpace(line) == testableReferenceEndPattern { + isTestableReference = false + continue + } + + if strings.TrimSpace(line) == testableReferenceStartPattern { + isTestableReference = true + continue + } + + if !isTestableReference { + continue + } + + // TestableReference + + if matches := testableReferenceSkippedRegexp.FindStringSubmatch(line); len(matches) > 1 { + skipped := matches[1] + if skipped != "NO" { + break + } + } + + if match := xctestBuildableReferenceNameRegexp.FindString(line); match != "" { + return true, nil + } + } + + if err := scanner.Err(); err != nil { + return false, err + } + + return false, nil +} + +// SchemeFileContainsXCTestBuildAction ... +func SchemeFileContainsXCTestBuildAction(schemeFilePth string) (bool, error) { + content, err := fileutil.ReadStringFromFile(schemeFilePth) + if err != nil { + return false, err + } + + return schemeFileContentContainsXCTestBuildAction(content) +} + +func sharedSchemes(projectOrWorkspacePth string) ([]SchemeModel, error) { + schemePaths, err := sharedSchemeFilePaths(projectOrWorkspacePth) + if err != nil { + return []SchemeModel{}, err + } + + schemes := []SchemeModel{} + for _, schemePth := range schemePaths { + schemeName := SchemeNameFromPath(schemePth) + + hasXCTest, err := SchemeFileContainsXCTestBuildAction(schemePth) + if err != nil { + return []SchemeModel{}, err + } + + schemes = append(schemes, SchemeModel{ + Name: schemeName, + HasXCTest: hasXCTest, + }) + } + + return schemes, nil +} + +// ProjectSharedSchemes ... +func ProjectSharedSchemes(projectPth string) ([]SchemeModel, error) { + return sharedSchemes(projectPth) +} + +// WorkspaceProjectReferences ... +func WorkspaceProjectReferences(workspace string) ([]string, error) { + projects := []string{} + + workspaceDir := filepath.Dir(workspace) + + xcworkspacedataPth := path.Join(workspace, "contents.xcworkspacedata") + if exist, err := pathutil.IsPathExists(xcworkspacedataPth); err != nil { + return []string{}, err + } else if !exist { + return []string{}, fmt.Errorf("contents.xcworkspacedata does not exist at: %s", xcworkspacedataPth) + } + + xcworkspacedataStr, err := fileutil.ReadStringFromFile(xcworkspacedataPth) + if err != nil { + return []string{}, err + } + + xcworkspacedataLines := strings.Split(xcworkspacedataStr, "\n") + fileRefStart := false + regexp := regexp.MustCompile(`location = "(.+):(.+).xcodeproj"`) + + for _, line := range xcworkspacedataLines { + if strings.Contains(line, "[A-Z0-9]+) /\* (?P.*) \*/ = {`) + endPBXNativeTargetPattern := `};` + isPBXNativeTarget := false + + // isa = PBXNativeTarget; + isaRegexp := regexp.MustCompile(`\s*isa = (?P.*);`) + + beginDependenciesPattern := `dependencies = (` + dependencieRegexp := regexp.MustCompile(`\s*(?P[A-Z0-9]+) /\* (?P.*) \*/,`) + endDependenciesPattern := `);` + isDependencies := false + + // name = SampleAppWithCocoapods; + nameRegexp := regexp.MustCompile(`\s*name = (?P.*);`) + // productReference = BAAFFEED19EE788800F3AC91 /* SampleAppWithCocoapodsTests.xctest */; + productReferenceRegexp := regexp.MustCompile(`\s*productReference = (?P[A-Z0-9]+) /\* (?P.*) \*/;`) + // productType = "com.apple.product-type.bundle.unit-test"; + productTypeRegexp := regexp.MustCompile(`\s*productType = (?P.*);`) + + scanner := bufio.NewScanner(strings.NewReader(pbxprojContent)) + for scanner.Scan() { + line := scanner.Text() + + if strings.TrimSpace(line) == endPBXNativeTargetSectionPattern { + break + } + + if strings.TrimSpace(line) == beginPBXNativeTargetSectionPattern { + isPBXNativeTargetSection = true + continue + } + + if !isPBXNativeTargetSection { + continue + } + + // PBXNativeTarget section + + if strings.TrimSpace(line) == endPBXNativeTargetPattern { + pbxNativeTarget := PBXNativeTarget{ + id: id, + isa: isa, + dependencies: dependencies, + name: name, + productPath: productPath, + productType: productType, + } + pbxNativeTargets = append(pbxNativeTargets, pbxNativeTarget) + + id = "" + isa = "" + name = "" + productPath = "" + productType = "" + dependencies = []string{} + + isPBXNativeTarget = false + continue + } + + if matches := beginPBXNativeTargetRegexp.FindStringSubmatch(line); len(matches) == 3 { + id = matches[1] + name = matches[2] + + isPBXNativeTarget = true + continue + } + + if !isPBXNativeTarget { + continue + } + + // PBXNativeTarget item + + if matches := isaRegexp.FindStringSubmatch(line); len(matches) == 2 { + isa = strings.Trim(matches[1], `"`) + } + + if matches := nameRegexp.FindStringSubmatch(line); len(matches) == 2 { + name = strings.Trim(matches[1], `"`) + } + + if matches := productTypeRegexp.FindStringSubmatch(line); len(matches) == 2 { + productType = strings.Trim(matches[1], `"`) + } + + if matches := productReferenceRegexp.FindStringSubmatch(line); len(matches) == 3 { + // productId := strings.Trim(matches[1], `"`) + productPath = strings.Trim(matches[2], `"`) + } + + if isDependencies && strings.TrimSpace(line) == endDependenciesPattern { + isDependencies = false + continue + } + + if strings.TrimSpace(line) == beginDependenciesPattern { + isDependencies = true + continue + } + + if !isDependencies { + continue + } + + // dependencies + if matches := dependencieRegexp.FindStringSubmatch(line); len(matches) == 3 { + dependencieID := strings.Trim(matches[1], `"`) + dependencieIsa := strings.Trim(matches[2], `"`) + + if dependencieIsa == "PBXTargetDependency" { + dependencies = append(dependencies, dependencieID) + } + } + } + + if err := scanner.Err(); err != nil { + return []PBXNativeTarget{}, err + } + + return pbxNativeTargets, nil +} + +func parsePBXTargetDependencies(pbxprojContent string) ([]PBXTargetDependency, error) { + pbxTargetDependencies := []PBXTargetDependency{} + + id := "" + isa := "" + target := "" + + beginPBXTargetDependencySectionPattern := `/* Begin PBXTargetDependency section */` + endPBXTargetDependencySectionPattern := `/* End PBXTargetDependency section */` + isPBXTargetDependencySection := false + + // BAAFFEEF19EE788800F3AC91 /* PBXTargetDependency */ = { + beginPBXTargetDependencyRegexp := regexp.MustCompile(`\s*(?P[A-Z0-9]+) /\* (?P.*) \*/ = {`) + endPBXTargetDependencyPattern := `};` + isPBXTargetDependency := false + + // isa = PBXTargetDependency; + isaRegexp := regexp.MustCompile(`\s*isa = (?P.*);`) + // target = BAAFFED019EE788800F3AC91 /* SampleAppWithCocoapods */; + targetRegexp := regexp.MustCompile(`\s*target = (?P[A-Z0-9]+) /\* (?P.*) \*/;`) + + scanner := bufio.NewScanner(strings.NewReader(pbxprojContent)) + for scanner.Scan() { + line := scanner.Text() + + if strings.TrimSpace(line) == endPBXTargetDependencySectionPattern { + break + } + + if strings.TrimSpace(line) == beginPBXTargetDependencySectionPattern { + isPBXTargetDependencySection = true + continue + } + + if !isPBXTargetDependencySection { + continue + } + + // PBXTargetDependency section + + if strings.TrimSpace(line) == endPBXTargetDependencyPattern { + pbxTargetDependency := PBXTargetDependency{ + id: id, + isa: isa, + target: target, + } + pbxTargetDependencies = append(pbxTargetDependencies, pbxTargetDependency) + + id = "" + isa = "" + target = "" + + isPBXTargetDependency = false + continue + } + + if matches := beginPBXTargetDependencyRegexp.FindStringSubmatch(line); len(matches) == 3 { + id = matches[1] + isa = matches[2] + + isPBXTargetDependency = true + continue + } + + if !isPBXTargetDependency { + continue + } + + // PBXTargetDependency item + + if matches := isaRegexp.FindStringSubmatch(line); len(matches) == 2 { + isa = strings.Trim(matches[1], `"`) + } + + if matches := targetRegexp.FindStringSubmatch(line); len(matches) == 3 { + targetID := strings.Trim(matches[1], `"`) + // targetName := strings.Trim(matches[2], `"`) + + target = targetID + } + } + + return pbxTargetDependencies, nil +} + +func targetDependencieWithID(dependencies []PBXTargetDependency, id string) (PBXTargetDependency, bool) { + for _, dependencie := range dependencies { + if dependencie.id == id { + return dependencie, true + } + } + return PBXTargetDependency{}, false +} + +func targetWithID(targets []PBXNativeTarget, id string) (PBXNativeTarget, bool) { + for _, target := range targets { + if target.id == id { + return target, true + } + } + return PBXNativeTarget{}, false +} + +func pbxprojContentTartgets(pbxprojContent string) ([]TargetModel, error) { + targetMap := map[string]TargetModel{} + + nativeTargets, err := parsePBXNativeTargets(pbxprojContent) + if err != nil { + return []TargetModel{}, err + } + + targetDependencies, err := parsePBXTargetDependencies(pbxprojContent) + if err != nil { + return []TargetModel{}, err + } + + // Add targets which has test targets + for _, target := range nativeTargets { + if path.Ext(target.productPath) == ".xctest" { + if len(target.dependencies) > 0 { + for _, dependencieID := range target.dependencies { + dependency, found := targetDependencieWithID(targetDependencies, dependencieID) + if found { + dependentTarget, found := targetWithID(nativeTargets, dependency.target) + if found { + targetMap[dependentTarget.name] = TargetModel{ + Name: dependentTarget.name, + HasXCTest: true, + } + } + } + } + } + } + } + + // Add targets which has NO test targets + for _, target := range nativeTargets { + if path.Ext(target.productPath) != ".xctest" { + _, found := targetMap[target.name] + if !found { + targetMap[target.name] = TargetModel{ + Name: target.name, + HasXCTest: false, + } + } + } + } + + targets := []TargetModel{} + for _, target := range targetMap { + targets = append(targets, target) + } + + return targets, nil +} + +// ProjectTargets ... +func ProjectTargets(projectPth string) ([]TargetModel, error) { + pbxProjPth := filepath.Join(projectPth, "project.pbxproj") + if exist, err := pathutil.IsPathExists(pbxProjPth); err != nil { + return []TargetModel{}, err + } else if !exist { + return []TargetModel{}, fmt.Errorf("project.pbxproj does not exist at: %s", pbxProjPth) + } + + content, err := fileutil.ReadStringFromFile(pbxProjPth) + if err != nil { + return []TargetModel{}, err + } + + return pbxprojContentTartgets(content) +} + +// WorkspaceTargets ... +func WorkspaceTargets(workspacePth string) ([]TargetModel, error) { + projects, err := WorkspaceProjectReferences(workspacePth) + if err != nil { + return []TargetModel{}, err + } + + targets := []TargetModel{} + for _, project := range projects { + projectTargets, err := ProjectTargets(project) + if err != nil { + return []TargetModel{}, err + } + + targets = append(targets, projectTargets...) + } + + return targets, nil +} diff --git a/vendor/golang.org/x/sys/unix/mksyscall.go b/vendor/golang.org/x/sys/unix/mksyscall.go index 890652ca..e06e4253 100644 --- a/vendor/golang.org/x/sys/unix/mksyscall.go +++ b/vendor/golang.org/x/sys/unix/mksyscall.go @@ -88,6 +88,10 @@ func parseParam(p string) Param { func main() { // Get the OS and architecture (using GOARCH_TARGET if it exists) goos := os.Getenv("GOOS") + if goos == "" { + fmt.Fprintln(os.Stderr, "GOOS not defined in environment") + os.Exit(1) + } goarch := os.Getenv("GOARCH_TARGET") if goarch == "" { goarch = os.Getenv("GOARCH") diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin.go b/vendor/golang.org/x/sys/unix/syscall_darwin.go index 04042e44..a2e36888 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin.go @@ -416,6 +416,7 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e //sys Chmod(path string, mode uint32) (err error) //sys Chown(path string, uid int, gid int) (err error) //sys Chroot(path string) (err error) +//sys ClockGettime(clockid int32, time *Timespec) (err error) //sys Close(fd int) (err error) //sys Dup(fd int) (nfd int, err error) //sys Dup2(from int, to int) (err error) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go index b50178d6..c142e33e 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go @@ -943,6 +943,21 @@ func libc_chroot_trampoline() // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func ClockGettime(clockid int32, time *Timespec) (err error) { + _, _, e1 := syscall_syscall(funcPC(libc_clock_gettime_trampoline), uintptr(clockid), uintptr(unsafe.Pointer(time)), 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +func libc_clock_gettime_trampoline() + +//go:linkname libc_clock_gettime libc_clock_gettime +//go:cgo_import_dynamic libc_clock_gettime clock_gettime "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Close(fd int) (err error) { _, _, e1 := syscall_syscall(funcPC(libc_close_trampoline), uintptr(fd), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s index da9b900a..1a391519 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s @@ -108,6 +108,8 @@ TEXT ·libc_chown_trampoline(SB),NOSPLIT,$0-0 JMP libc_chown(SB) TEXT ·libc_chroot_trampoline(SB),NOSPLIT,$0-0 JMP libc_chroot(SB) +TEXT ·libc_clock_gettime_trampoline(SB),NOSPLIT,$0-0 + JMP libc_clock_gettime(SB) TEXT ·libc_close_trampoline(SB),NOSPLIT,$0-0 JMP libc_close(SB) TEXT ·libc_dup_trampoline(SB),NOSPLIT,$0-0 diff --git a/vendor/golang.org/x/sys/unix/zsysnum_darwin_amd64.go b/vendor/golang.org/x/sys/unix/zsysnum_darwin_amd64.go index 9e2837e0..654dd3da 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_darwin_amd64.go @@ -1,4 +1,4 @@ -// go run mksysnum.go /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/usr/include/sys/syscall.h +// go run mksysnum.go /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/sys/syscall.h // Code generated by the command above; see README.md. DO NOT EDIT. // +build amd64,darwin @@ -431,6 +431,8 @@ const ( SYS_NTP_ADJTIME = 527 SYS_NTP_GETTIME = 528 SYS_OS_FAULT_WITH_PAYLOAD = 529 - SYS_MAXSYSCALL = 530 + SYS_KQUEUE_WORKLOOP_CTL = 530 + SYS___MACH_BRIDGE_REMOTE_TIME = 531 + SYS_MAXSYSCALL = 532 SYS_INVALID = 63 ) diff --git a/vendor/gopkg.in/yaml.v2/decode_test.go b/vendor/gopkg.in/yaml.v2/decode_test.go deleted file mode 100644 index b05c466e..00000000 --- a/vendor/gopkg.in/yaml.v2/decode_test.go +++ /dev/null @@ -1,1334 +0,0 @@ -package yaml_test - -import ( - "errors" - "io" - "math" - "reflect" - "strings" - "time" - - . "gopkg.in/check.v1" - "gopkg.in/yaml.v2" -) - -var unmarshalIntTest = 123 - -var unmarshalTests = []struct { - data string - value interface{} -}{ - { - "", - (*struct{})(nil), - }, - { - "{}", &struct{}{}, - }, { - "v: hi", - map[string]string{"v": "hi"}, - }, { - "v: hi", map[string]interface{}{"v": "hi"}, - }, { - "v: true", - map[string]string{"v": "true"}, - }, { - "v: true", - map[string]interface{}{"v": true}, - }, { - "v: 10", - map[string]interface{}{"v": 10}, - }, { - "v: 0b10", - map[string]interface{}{"v": 2}, - }, { - "v: 0xA", - map[string]interface{}{"v": 10}, - }, { - "v: 4294967296", - map[string]int64{"v": 4294967296}, - }, { - "v: 0.1", - map[string]interface{}{"v": 0.1}, - }, { - "v: .1", - map[string]interface{}{"v": 0.1}, - }, { - "v: .Inf", - map[string]interface{}{"v": math.Inf(+1)}, - }, { - "v: -.Inf", - map[string]interface{}{"v": math.Inf(-1)}, - }, { - "v: -10", - map[string]interface{}{"v": -10}, - }, { - "v: -.1", - map[string]interface{}{"v": -0.1}, - }, - - // Simple values. - { - "123", - &unmarshalIntTest, - }, - - // Floats from spec - { - "canonical: 6.8523e+5", - map[string]interface{}{"canonical": 6.8523e+5}, - }, { - "expo: 685.230_15e+03", - map[string]interface{}{"expo": 685.23015e+03}, - }, { - "fixed: 685_230.15", - map[string]interface{}{"fixed": 685230.15}, - }, { - "neginf: -.inf", - map[string]interface{}{"neginf": math.Inf(-1)}, - }, { - "fixed: 685_230.15", - map[string]float64{"fixed": 685230.15}, - }, - //{"sexa: 190:20:30.15", map[string]interface{}{"sexa": 0}}, // Unsupported - //{"notanum: .NaN", map[string]interface{}{"notanum": math.NaN()}}, // Equality of NaN fails. - - // Bools from spec - { - "canonical: y", - map[string]interface{}{"canonical": true}, - }, { - "answer: NO", - map[string]interface{}{"answer": false}, - }, { - "logical: True", - map[string]interface{}{"logical": true}, - }, { - "option: on", - map[string]interface{}{"option": true}, - }, { - "option: on", - map[string]bool{"option": true}, - }, - // Ints from spec - { - "canonical: 685230", - map[string]interface{}{"canonical": 685230}, - }, { - "decimal: +685_230", - map[string]interface{}{"decimal": 685230}, - }, { - "octal: 02472256", - map[string]interface{}{"octal": 685230}, - }, { - "hexa: 0x_0A_74_AE", - map[string]interface{}{"hexa": 685230}, - }, { - "bin: 0b1010_0111_0100_1010_1110", - map[string]interface{}{"bin": 685230}, - }, { - "bin: -0b101010", - map[string]interface{}{"bin": -42}, - }, { - "bin: -0b1000000000000000000000000000000000000000000000000000000000000000", - map[string]interface{}{"bin": -9223372036854775808}, - }, { - "decimal: +685_230", - map[string]int{"decimal": 685230}, - }, - - //{"sexa: 190:20:30", map[string]interface{}{"sexa": 0}}, // Unsupported - - // Nulls from spec - { - "empty:", - map[string]interface{}{"empty": nil}, - }, { - "canonical: ~", - map[string]interface{}{"canonical": nil}, - }, { - "english: null", - map[string]interface{}{"english": nil}, - }, { - "~: null key", - map[interface{}]string{nil: "null key"}, - }, { - "empty:", - map[string]*bool{"empty": nil}, - }, - - // Flow sequence - { - "seq: [A,B]", - map[string]interface{}{"seq": []interface{}{"A", "B"}}, - }, { - "seq: [A,B,C,]", - map[string][]string{"seq": []string{"A", "B", "C"}}, - }, { - "seq: [A,1,C]", - map[string][]string{"seq": []string{"A", "1", "C"}}, - }, { - "seq: [A,1,C]", - map[string][]int{"seq": []int{1}}, - }, { - "seq: [A,1,C]", - map[string]interface{}{"seq": []interface{}{"A", 1, "C"}}, - }, - // Block sequence - { - "seq:\n - A\n - B", - map[string]interface{}{"seq": []interface{}{"A", "B"}}, - }, { - "seq:\n - A\n - B\n - C", - map[string][]string{"seq": []string{"A", "B", "C"}}, - }, { - "seq:\n - A\n - 1\n - C", - map[string][]string{"seq": []string{"A", "1", "C"}}, - }, { - "seq:\n - A\n - 1\n - C", - map[string][]int{"seq": []int{1}}, - }, { - "seq:\n - A\n - 1\n - C", - map[string]interface{}{"seq": []interface{}{"A", 1, "C"}}, - }, - - // Literal block scalar - { - "scalar: | # Comment\n\n literal\n\n \ttext\n\n", - map[string]string{"scalar": "\nliteral\n\n\ttext\n"}, - }, - - // Folded block scalar - { - "scalar: > # Comment\n\n folded\n line\n \n next\n line\n * one\n * two\n\n last\n line\n\n", - map[string]string{"scalar": "\nfolded line\nnext line\n * one\n * two\n\nlast line\n"}, - }, - - // Map inside interface with no type hints. - { - "a: {b: c}", - map[interface{}]interface{}{"a": map[interface{}]interface{}{"b": "c"}}, - }, - - // Structs and type conversions. - { - "hello: world", - &struct{ Hello string }{"world"}, - }, { - "a: {b: c}", - &struct{ A struct{ B string } }{struct{ B string }{"c"}}, - }, { - "a: {b: c}", - &struct{ A *struct{ B string } }{&struct{ B string }{"c"}}, - }, { - "a: {b: c}", - &struct{ A map[string]string }{map[string]string{"b": "c"}}, - }, { - "a: {b: c}", - &struct{ A *map[string]string }{&map[string]string{"b": "c"}}, - }, { - "a:", - &struct{ A map[string]string }{}, - }, { - "a: 1", - &struct{ A int }{1}, - }, { - "a: 1", - &struct{ A float64 }{1}, - }, { - "a: 1.0", - &struct{ A int }{1}, - }, { - "a: 1.0", - &struct{ A uint }{1}, - }, { - "a: [1, 2]", - &struct{ A []int }{[]int{1, 2}}, - }, { - "a: [1, 2]", - &struct{ A [2]int }{[2]int{1, 2}}, - }, { - "a: 1", - &struct{ B int }{0}, - }, { - "a: 1", - &struct { - B int "a" - }{1}, - }, { - "a: y", - &struct{ A bool }{true}, - }, - - // Some cross type conversions - { - "v: 42", - map[string]uint{"v": 42}, - }, { - "v: -42", - map[string]uint{}, - }, { - "v: 4294967296", - map[string]uint64{"v": 4294967296}, - }, { - "v: -4294967296", - map[string]uint64{}, - }, - - // int - { - "int_max: 2147483647", - map[string]int{"int_max": math.MaxInt32}, - }, - { - "int_min: -2147483648", - map[string]int{"int_min": math.MinInt32}, - }, - { - "int_overflow: 9223372036854775808", // math.MaxInt64 + 1 - map[string]int{}, - }, - - // int64 - { - "int64_max: 9223372036854775807", - map[string]int64{"int64_max": math.MaxInt64}, - }, - { - "int64_max_base2: 0b111111111111111111111111111111111111111111111111111111111111111", - map[string]int64{"int64_max_base2": math.MaxInt64}, - }, - { - "int64_min: -9223372036854775808", - map[string]int64{"int64_min": math.MinInt64}, - }, - { - "int64_neg_base2: -0b111111111111111111111111111111111111111111111111111111111111111", - map[string]int64{"int64_neg_base2": -math.MaxInt64}, - }, - { - "int64_overflow: 9223372036854775808", // math.MaxInt64 + 1 - map[string]int64{}, - }, - - // uint - { - "uint_min: 0", - map[string]uint{"uint_min": 0}, - }, - { - "uint_max: 4294967295", - map[string]uint{"uint_max": math.MaxUint32}, - }, - { - "uint_underflow: -1", - map[string]uint{}, - }, - - // uint64 - { - "uint64_min: 0", - map[string]uint{"uint64_min": 0}, - }, - { - "uint64_max: 18446744073709551615", - map[string]uint64{"uint64_max": math.MaxUint64}, - }, - { - "uint64_max_base2: 0b1111111111111111111111111111111111111111111111111111111111111111", - map[string]uint64{"uint64_max_base2": math.MaxUint64}, - }, - { - "uint64_maxint64: 9223372036854775807", - map[string]uint64{"uint64_maxint64": math.MaxInt64}, - }, - { - "uint64_underflow: -1", - map[string]uint64{}, - }, - - // float32 - { - "float32_max: 3.40282346638528859811704183484516925440e+38", - map[string]float32{"float32_max": math.MaxFloat32}, - }, - { - "float32_nonzero: 1.401298464324817070923729583289916131280e-45", - map[string]float32{"float32_nonzero": math.SmallestNonzeroFloat32}, - }, - { - "float32_maxuint64: 18446744073709551615", - map[string]float32{"float32_maxuint64": float32(math.MaxUint64)}, - }, - { - "float32_maxuint64+1: 18446744073709551616", - map[string]float32{"float32_maxuint64+1": float32(math.MaxUint64 + 1)}, - }, - - // float64 - { - "float64_max: 1.797693134862315708145274237317043567981e+308", - map[string]float64{"float64_max": math.MaxFloat64}, - }, - { - "float64_nonzero: 4.940656458412465441765687928682213723651e-324", - map[string]float64{"float64_nonzero": math.SmallestNonzeroFloat64}, - }, - { - "float64_maxuint64: 18446744073709551615", - map[string]float64{"float64_maxuint64": float64(math.MaxUint64)}, - }, - { - "float64_maxuint64+1: 18446744073709551616", - map[string]float64{"float64_maxuint64+1": float64(math.MaxUint64 + 1)}, - }, - - // Overflow cases. - { - "v: 4294967297", - map[string]int32{}, - }, { - "v: 128", - map[string]int8{}, - }, - - // Quoted values. - { - "'1': '\"2\"'", - map[interface{}]interface{}{"1": "\"2\""}, - }, { - "v:\n- A\n- 'B\n\n C'\n", - map[string][]string{"v": []string{"A", "B\nC"}}, - }, - - // Explicit tags. - { - "v: !!float '1.1'", - map[string]interface{}{"v": 1.1}, - }, { - "v: !!float 0", - map[string]interface{}{"v": float64(0)}, - }, { - "v: !!float -1", - map[string]interface{}{"v": float64(-1)}, - }, { - "v: !!null ''", - map[string]interface{}{"v": nil}, - }, { - "%TAG !y! tag:yaml.org,2002:\n---\nv: !y!int '1'", - map[string]interface{}{"v": 1}, - }, - - // Non-specific tag (Issue #75) - { - "v: ! test", - map[string]interface{}{"v": "test"}, - }, - - // Anchors and aliases. - { - "a: &x 1\nb: &y 2\nc: *x\nd: *y\n", - &struct{ A, B, C, D int }{1, 2, 1, 2}, - }, { - "a: &a {c: 1}\nb: *a", - &struct { - A, B struct { - C int - } - }{struct{ C int }{1}, struct{ C int }{1}}, - }, { - "a: &a [1, 2]\nb: *a", - &struct{ B []int }{[]int{1, 2}}, - }, - - // Bug #1133337 - { - "foo: ''", - map[string]*string{"foo": new(string)}, - }, { - "foo: null", - map[string]*string{"foo": nil}, - }, { - "foo: null", - map[string]string{"foo": ""}, - }, { - "foo: null", - map[string]interface{}{"foo": nil}, - }, - - // Support for ~ - { - "foo: ~", - map[string]*string{"foo": nil}, - }, { - "foo: ~", - map[string]string{"foo": ""}, - }, { - "foo: ~", - map[string]interface{}{"foo": nil}, - }, - - // Ignored field - { - "a: 1\nb: 2\n", - &struct { - A int - B int "-" - }{1, 0}, - }, - - // Bug #1191981 - { - "" + - "%YAML 1.1\n" + - "--- !!str\n" + - `"Generic line break (no glyph)\n\` + "\n" + - ` Generic line break (glyphed)\n\` + "\n" + - ` Line separator\u2028\` + "\n" + - ` Paragraph separator\u2029"` + "\n", - "" + - "Generic line break (no glyph)\n" + - "Generic line break (glyphed)\n" + - "Line separator\u2028Paragraph separator\u2029", - }, - - // Struct inlining - { - "a: 1\nb: 2\nc: 3\n", - &struct { - A int - C inlineB `yaml:",inline"` - }{1, inlineB{2, inlineC{3}}}, - }, - - // Map inlining - { - "a: 1\nb: 2\nc: 3\n", - &struct { - A int - C map[string]int `yaml:",inline"` - }{1, map[string]int{"b": 2, "c": 3}}, - }, - - // bug 1243827 - { - "a: -b_c", - map[string]interface{}{"a": "-b_c"}, - }, - { - "a: +b_c", - map[string]interface{}{"a": "+b_c"}, - }, - { - "a: 50cent_of_dollar", - map[string]interface{}{"a": "50cent_of_dollar"}, - }, - - // issue #295 (allow scalars with colons in flow mappings and sequences) - { - "a: {b: https://github.com/go-yaml/yaml}", - map[string]interface{}{"a": map[interface{}]interface{}{ - "b": "https://github.com/go-yaml/yaml", - }}, - }, - { - "a: [https://github.com/go-yaml/yaml]", - map[string]interface{}{"a": []interface{}{"https://github.com/go-yaml/yaml"}}, - }, - - // Duration - { - "a: 3s", - map[string]time.Duration{"a": 3 * time.Second}, - }, - - // Issue #24. - { - "a: ", - map[string]string{"a": ""}, - }, - - // Base 60 floats are obsolete and unsupported. - { - "a: 1:1\n", - map[string]string{"a": "1:1"}, - }, - - // Binary data. - { - "a: !!binary gIGC\n", - map[string]string{"a": "\x80\x81\x82"}, - }, { - "a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n", - map[string]string{"a": strings.Repeat("\x90", 54)}, - }, { - "a: !!binary |\n " + strings.Repeat("A", 70) + "\n ==\n", - map[string]string{"a": strings.Repeat("\x00", 52)}, - }, - - // Ordered maps. - { - "{b: 2, a: 1, d: 4, c: 3, sub: {e: 5}}", - &yaml.MapSlice{{"b", 2}, {"a", 1}, {"d", 4}, {"c", 3}, {"sub", yaml.MapSlice{{"e", 5}}}}, - }, - - // Issue #39. - { - "a:\n b:\n c: d\n", - map[string]struct{ B interface{} }{"a": {map[interface{}]interface{}{"c": "d"}}}, - }, - - // Custom map type. - { - "a: {b: c}", - M{"a": M{"b": "c"}}, - }, - - // Support encoding.TextUnmarshaler. - { - "a: 1.2.3.4\n", - map[string]textUnmarshaler{"a": textUnmarshaler{S: "1.2.3.4"}}, - }, - { - "a: 2015-02-24T18:19:39Z\n", - map[string]textUnmarshaler{"a": textUnmarshaler{"2015-02-24T18:19:39Z"}}, - }, - - // Timestamps - { - // Date only. - "a: 2015-01-01\n", - map[string]time.Time{"a": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, - }, - { - // RFC3339 - "a: 2015-02-24T18:19:39.12Z\n", - map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, .12e9, time.UTC)}, - }, - { - // RFC3339 with short dates. - "a: 2015-2-3T3:4:5Z", - map[string]time.Time{"a": time.Date(2015, 2, 3, 3, 4, 5, 0, time.UTC)}, - }, - { - // ISO8601 lower case t - "a: 2015-02-24t18:19:39Z\n", - map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)}, - }, - { - // space separate, no time zone - "a: 2015-02-24 18:19:39\n", - map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)}, - }, - // Some cases not currently handled. Uncomment these when - // the code is fixed. - // { - // // space separated with time zone - // "a: 2001-12-14 21:59:43.10 -5", - // map[string]interface{}{"a": time.Date(2001, 12, 14, 21, 59, 43, .1e9, time.UTC)}, - // }, - // { - // // arbitrary whitespace between fields - // "a: 2001-12-14 \t\t \t21:59:43.10 \t Z", - // map[string]interface{}{"a": time.Date(2001, 12, 14, 21, 59, 43, .1e9, time.UTC)}, - // }, - { - // explicit string tag - "a: !!str 2015-01-01", - map[string]interface{}{"a": "2015-01-01"}, - }, - { - // explicit timestamp tag on quoted string - "a: !!timestamp \"2015-01-01\"", - map[string]time.Time{"a": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, - }, - { - // explicit timestamp tag on unquoted string - "a: !!timestamp 2015-01-01", - map[string]time.Time{"a": time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, - }, - { - // quoted string that's a valid timestamp - "a: \"2015-01-01\"", - map[string]interface{}{"a": "2015-01-01"}, - }, - { - // explicit timestamp tag into interface. - "a: !!timestamp \"2015-01-01\"", - map[string]interface{}{"a": "2015-01-01"}, - }, - { - // implicit timestamp tag into interface. - "a: 2015-01-01", - map[string]interface{}{"a": "2015-01-01"}, - }, - - // Encode empty lists as zero-length slices. - { - "a: []", - &struct{ A []int }{[]int{}}, - }, - - // UTF-16-LE - { - "\xff\xfe\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00\n\x00", - M{"ñoño": "very yes"}, - }, - // UTF-16-LE with surrogate. - { - "\xff\xfe\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00 \x00=\xd8\xd4\xdf\n\x00", - M{"ñoño": "very yes 🟔"}, - }, - - // UTF-16-BE - { - "\xfe\xff\x00\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00\n", - M{"ñoño": "very yes"}, - }, - // UTF-16-BE with surrogate. - { - "\xfe\xff\x00\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00 \xd8=\xdf\xd4\x00\n", - M{"ñoño": "very yes 🟔"}, - }, - - // YAML Float regex shouldn't match this - { - "a: 123456e1\n", - M{"a": "123456e1"}, - }, { - "a: 123456E1\n", - M{"a": "123456E1"}, - }, - // yaml-test-suite 3GZX: Spec Example 7.1. Alias Nodes - { - "First occurrence: &anchor Foo\nSecond occurrence: *anchor\nOverride anchor: &anchor Bar\nReuse anchor: *anchor\n", - map[interface{}]interface{}{ - "Reuse anchor": "Bar", - "First occurrence": "Foo", - "Second occurrence": "Foo", - "Override anchor": "Bar", - }, - }, - // Single document with garbage following it. - { - "---\nhello\n...\n}not yaml", - "hello", - }, - { - "a: 5\n", - &struct{ A jsonNumberT }{"5"}, - }, - { - "a: 5.5\n", - &struct{ A jsonNumberT }{"5.5"}, - }, -} - -type M map[interface{}]interface{} - -type inlineB struct { - B int - inlineC `yaml:",inline"` -} - -type inlineC struct { - C int -} - -func (s *S) TestUnmarshal(c *C) { - for i, item := range unmarshalTests { - c.Logf("test %d: %q", i, item.data) - t := reflect.ValueOf(item.value).Type() - value := reflect.New(t) - err := yaml.Unmarshal([]byte(item.data), value.Interface()) - if _, ok := err.(*yaml.TypeError); !ok { - c.Assert(err, IsNil) - } - c.Assert(value.Elem().Interface(), DeepEquals, item.value, Commentf("error: %v", err)) - } -} - -// TODO(v3): This test should also work when unmarshaling onto an interface{}. -func (s *S) TestUnmarshalFullTimestamp(c *C) { - // Full timestamp in same format as encoded. This is confirmed to be - // properly decoded by Python as a timestamp as well. - var str = "2015-02-24T18:19:39.123456789-03:00" - var t time.Time - err := yaml.Unmarshal([]byte(str), &t) - c.Assert(err, IsNil) - c.Assert(t, Equals, time.Date(2015, 2, 24, 18, 19, 39, 123456789, t.Location())) - c.Assert(t.In(time.UTC), Equals, time.Date(2015, 2, 24, 21, 19, 39, 123456789, time.UTC)) -} - -func (s *S) TestDecoderSingleDocument(c *C) { - // Test that Decoder.Decode works as expected on - // all the unmarshal tests. - for i, item := range unmarshalTests { - c.Logf("test %d: %q", i, item.data) - if item.data == "" { - // Behaviour differs when there's no YAML. - continue - } - t := reflect.ValueOf(item.value).Type() - value := reflect.New(t) - err := yaml.NewDecoder(strings.NewReader(item.data)).Decode(value.Interface()) - if _, ok := err.(*yaml.TypeError); !ok { - c.Assert(err, IsNil) - } - c.Assert(value.Elem().Interface(), DeepEquals, item.value) - } -} - -var decoderTests = []struct { - data string - values []interface{} -}{{ - "", - nil, -}, { - "a: b", - []interface{}{ - map[interface{}]interface{}{"a": "b"}, - }, -}, { - "---\na: b\n...\n", - []interface{}{ - map[interface{}]interface{}{"a": "b"}, - }, -}, { - "---\n'hello'\n...\n---\ngoodbye\n...\n", - []interface{}{ - "hello", - "goodbye", - }, -}} - -func (s *S) TestDecoder(c *C) { - for i, item := range decoderTests { - c.Logf("test %d: %q", i, item.data) - var values []interface{} - dec := yaml.NewDecoder(strings.NewReader(item.data)) - for { - var value interface{} - err := dec.Decode(&value) - if err == io.EOF { - break - } - c.Assert(err, IsNil) - values = append(values, value) - } - c.Assert(values, DeepEquals, item.values) - } -} - -type errReader struct{} - -func (errReader) Read([]byte) (int, error) { - return 0, errors.New("some read error") -} - -func (s *S) TestDecoderReadError(c *C) { - err := yaml.NewDecoder(errReader{}).Decode(&struct{}{}) - c.Assert(err, ErrorMatches, `yaml: input error: some read error`) -} - -func (s *S) TestUnmarshalNaN(c *C) { - value := map[string]interface{}{} - err := yaml.Unmarshal([]byte("notanum: .NaN"), &value) - c.Assert(err, IsNil) - c.Assert(math.IsNaN(value["notanum"].(float64)), Equals, true) -} - -var unmarshalErrorTests = []struct { - data, error string -}{ - {"v: !!float 'error'", "yaml: cannot decode !!str `error` as a !!float"}, - {"v: [A,", "yaml: line 1: did not find expected node content"}, - {"v:\n- [A,", "yaml: line 2: did not find expected node content"}, - {"a:\n- b: *,", "yaml: line 2: did not find expected alphabetic or numeric character"}, - {"a: *b\n", "yaml: unknown anchor 'b' referenced"}, - {"a: &a\n b: *a\n", "yaml: anchor 'a' value contains itself"}, - {"value: -", "yaml: block sequence entries are not allowed in this context"}, - {"a: !!binary ==", "yaml: !!binary value contains invalid base64 data"}, - {"{[.]}", `yaml: invalid map key: \[\]interface \{\}\{"\."\}`}, - {"{{.}}", `yaml: invalid map key: map\[interface\ \{\}\]interface \{\}\{".":interface \{\}\(nil\)\}`}, - {"b: *a\na: &a {c: 1}", `yaml: unknown anchor 'a' referenced`}, - {"%TAG !%79! tag:yaml.org,2002:\n---\nv: !%79!int '1'", "yaml: did not find expected whitespace"}, -} - -func (s *S) TestUnmarshalErrors(c *C) { - for i, item := range unmarshalErrorTests { - c.Logf("test %d: %q", i, item.data) - var value interface{} - err := yaml.Unmarshal([]byte(item.data), &value) - c.Assert(err, ErrorMatches, item.error, Commentf("Partial unmarshal: %#v", value)) - } -} - -func (s *S) TestDecoderErrors(c *C) { - for _, item := range unmarshalErrorTests { - var value interface{} - err := yaml.NewDecoder(strings.NewReader(item.data)).Decode(&value) - c.Assert(err, ErrorMatches, item.error, Commentf("Partial unmarshal: %#v", value)) - } -} - -var unmarshalerTests = []struct { - data, tag string - value interface{} -}{ - {"_: {hi: there}", "!!map", map[interface{}]interface{}{"hi": "there"}}, - {"_: [1,A]", "!!seq", []interface{}{1, "A"}}, - {"_: 10", "!!int", 10}, - {"_: null", "!!null", nil}, - {`_: BAR!`, "!!str", "BAR!"}, - {`_: "BAR!"`, "!!str", "BAR!"}, - {"_: !!foo 'BAR!'", "!!foo", "BAR!"}, - {`_: ""`, "!!str", ""}, -} - -var unmarshalerResult = map[int]error{} - -type unmarshalerType struct { - value interface{} -} - -func (o *unmarshalerType) UnmarshalYAML(unmarshal func(v interface{}) error) error { - if err := unmarshal(&o.value); err != nil { - return err - } - if i, ok := o.value.(int); ok { - if result, ok := unmarshalerResult[i]; ok { - return result - } - } - return nil -} - -type unmarshalerPointer struct { - Field *unmarshalerType "_" -} - -type unmarshalerValue struct { - Field unmarshalerType "_" -} - -func (s *S) TestUnmarshalerPointerField(c *C) { - for _, item := range unmarshalerTests { - obj := &unmarshalerPointer{} - err := yaml.Unmarshal([]byte(item.data), obj) - c.Assert(err, IsNil) - if item.value == nil { - c.Assert(obj.Field, IsNil) - } else { - c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value)) - c.Assert(obj.Field.value, DeepEquals, item.value) - } - } -} - -func (s *S) TestUnmarshalerValueField(c *C) { - for _, item := range unmarshalerTests { - obj := &unmarshalerValue{} - err := yaml.Unmarshal([]byte(item.data), obj) - c.Assert(err, IsNil) - c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value)) - c.Assert(obj.Field.value, DeepEquals, item.value) - } -} - -func (s *S) TestUnmarshalerWholeDocument(c *C) { - obj := &unmarshalerType{} - err := yaml.Unmarshal([]byte(unmarshalerTests[0].data), obj) - c.Assert(err, IsNil) - value, ok := obj.value.(map[interface{}]interface{}) - c.Assert(ok, Equals, true, Commentf("value: %#v", obj.value)) - c.Assert(value["_"], DeepEquals, unmarshalerTests[0].value) -} - -func (s *S) TestUnmarshalerTypeError(c *C) { - unmarshalerResult[2] = &yaml.TypeError{[]string{"foo"}} - unmarshalerResult[4] = &yaml.TypeError{[]string{"bar"}} - defer func() { - delete(unmarshalerResult, 2) - delete(unmarshalerResult, 4) - }() - - type T struct { - Before int - After int - M map[string]*unmarshalerType - } - var v T - data := `{before: A, m: {abc: 1, def: 2, ghi: 3, jkl: 4}, after: B}` - err := yaml.Unmarshal([]byte(data), &v) - c.Assert(err, ErrorMatches, ""+ - "yaml: unmarshal errors:\n"+ - " line 1: cannot unmarshal !!str `A` into int\n"+ - " foo\n"+ - " bar\n"+ - " line 1: cannot unmarshal !!str `B` into int") - c.Assert(v.M["abc"], NotNil) - c.Assert(v.M["def"], IsNil) - c.Assert(v.M["ghi"], NotNil) - c.Assert(v.M["jkl"], IsNil) - - c.Assert(v.M["abc"].value, Equals, 1) - c.Assert(v.M["ghi"].value, Equals, 3) -} - -type proxyTypeError struct{} - -func (v *proxyTypeError) UnmarshalYAML(unmarshal func(interface{}) error) error { - var s string - var a int32 - var b int64 - if err := unmarshal(&s); err != nil { - panic(err) - } - if s == "a" { - if err := unmarshal(&b); err == nil { - panic("should have failed") - } - return unmarshal(&a) - } - if err := unmarshal(&a); err == nil { - panic("should have failed") - } - return unmarshal(&b) -} - -func (s *S) TestUnmarshalerTypeErrorProxying(c *C) { - type T struct { - Before int - After int - M map[string]*proxyTypeError - } - var v T - data := `{before: A, m: {abc: a, def: b}, after: B}` - err := yaml.Unmarshal([]byte(data), &v) - c.Assert(err, ErrorMatches, ""+ - "yaml: unmarshal errors:\n"+ - " line 1: cannot unmarshal !!str `A` into int\n"+ - " line 1: cannot unmarshal !!str `a` into int32\n"+ - " line 1: cannot unmarshal !!str `b` into int64\n"+ - " line 1: cannot unmarshal !!str `B` into int") -} - -type failingUnmarshaler struct{} - -var failingErr = errors.New("failingErr") - -func (ft *failingUnmarshaler) UnmarshalYAML(unmarshal func(interface{}) error) error { - return failingErr -} - -func (s *S) TestUnmarshalerError(c *C) { - err := yaml.Unmarshal([]byte("a: b"), &failingUnmarshaler{}) - c.Assert(err, Equals, failingErr) -} - -type sliceUnmarshaler []int - -func (su *sliceUnmarshaler) UnmarshalYAML(unmarshal func(interface{}) error) error { - var slice []int - err := unmarshal(&slice) - if err == nil { - *su = slice - return nil - } - - var intVal int - err = unmarshal(&intVal) - if err == nil { - *su = []int{intVal} - return nil - } - - return err -} - -func (s *S) TestUnmarshalerRetry(c *C) { - var su sliceUnmarshaler - err := yaml.Unmarshal([]byte("[1, 2, 3]"), &su) - c.Assert(err, IsNil) - c.Assert(su, DeepEquals, sliceUnmarshaler([]int{1, 2, 3})) - - err = yaml.Unmarshal([]byte("1"), &su) - c.Assert(err, IsNil) - c.Assert(su, DeepEquals, sliceUnmarshaler([]int{1})) -} - -// From http://yaml.org/type/merge.html -var mergeTests = ` -anchors: - list: - - &CENTER { "x": 1, "y": 2 } - - &LEFT { "x": 0, "y": 2 } - - &BIG { "r": 10 } - - &SMALL { "r": 1 } - -# All the following maps are equal: - -plain: - # Explicit keys - "x": 1 - "y": 2 - "r": 10 - label: center/big - -mergeOne: - # Merge one map - << : *CENTER - "r": 10 - label: center/big - -mergeMultiple: - # Merge multiple maps - << : [ *CENTER, *BIG ] - label: center/big - -override: - # Override - << : [ *BIG, *LEFT, *SMALL ] - "x": 1 - label: center/big - -shortTag: - # Explicit short merge tag - !!merge "<<" : [ *CENTER, *BIG ] - label: center/big - -longTag: - # Explicit merge long tag - ! "<<" : [ *CENTER, *BIG ] - label: center/big - -inlineMap: - # Inlined map - << : {"x": 1, "y": 2, "r": 10} - label: center/big - -inlineSequenceMap: - # Inlined map in sequence - << : [ *CENTER, {"r": 10} ] - label: center/big -` - -func (s *S) TestMerge(c *C) { - var want = map[interface{}]interface{}{ - "x": 1, - "y": 2, - "r": 10, - "label": "center/big", - } - - var m map[interface{}]interface{} - err := yaml.Unmarshal([]byte(mergeTests), &m) - c.Assert(err, IsNil) - for name, test := range m { - if name == "anchors" { - continue - } - c.Assert(test, DeepEquals, want, Commentf("test %q failed", name)) - } -} - -func (s *S) TestMergeStruct(c *C) { - type Data struct { - X, Y, R int - Label string - } - want := Data{1, 2, 10, "center/big"} - - var m map[string]Data - err := yaml.Unmarshal([]byte(mergeTests), &m) - c.Assert(err, IsNil) - for name, test := range m { - if name == "anchors" { - continue - } - c.Assert(test, Equals, want, Commentf("test %q failed", name)) - } -} - -var unmarshalNullTests = []func() interface{}{ - func() interface{} { var v interface{}; v = "v"; return &v }, - func() interface{} { var s = "s"; return &s }, - func() interface{} { var s = "s"; sptr := &s; return &sptr }, - func() interface{} { var i = 1; return &i }, - func() interface{} { var i = 1; iptr := &i; return &iptr }, - func() interface{} { m := map[string]int{"s": 1}; return &m }, - func() interface{} { m := map[string]int{"s": 1}; return m }, -} - -func (s *S) TestUnmarshalNull(c *C) { - for _, test := range unmarshalNullTests { - item := test() - zero := reflect.Zero(reflect.TypeOf(item).Elem()).Interface() - err := yaml.Unmarshal([]byte("null"), item) - c.Assert(err, IsNil) - if reflect.TypeOf(item).Kind() == reflect.Map { - c.Assert(reflect.ValueOf(item).Interface(), DeepEquals, reflect.MakeMap(reflect.TypeOf(item)).Interface()) - } else { - c.Assert(reflect.ValueOf(item).Elem().Interface(), DeepEquals, zero) - } - } -} - -func (s *S) TestUnmarshalSliceOnPreset(c *C) { - // Issue #48. - v := struct{ A []int }{[]int{1}} - yaml.Unmarshal([]byte("a: [2]"), &v) - c.Assert(v.A, DeepEquals, []int{2}) -} - -var unmarshalStrictTests = []struct { - data string - value interface{} - error string -}{{ - data: "a: 1\nc: 2\n", - value: struct{ A, B int }{A: 1}, - error: `yaml: unmarshal errors:\n line 2: field c not found in type struct { A int; B int }`, -}, { - data: "a: 1\nb: 2\na: 3\n", - value: struct{ A, B int }{A: 3, B: 2}, - error: `yaml: unmarshal errors:\n line 3: field a already set in type struct { A int; B int }`, -}, { - data: "c: 3\na: 1\nb: 2\nc: 4\n", - value: struct { - A int - inlineB `yaml:",inline"` - }{ - A: 1, - inlineB: inlineB{ - B: 2, - inlineC: inlineC{ - C: 4, - }, - }, - }, - error: `yaml: unmarshal errors:\n line 4: field c already set in type struct { A int; yaml_test.inlineB "yaml:\\",inline\\"" }`, -}, { - data: "c: 0\na: 1\nb: 2\nc: 1\n", - value: struct { - A int - inlineB `yaml:",inline"` - }{ - A: 1, - inlineB: inlineB{ - B: 2, - inlineC: inlineC{ - C: 1, - }, - }, - }, - error: `yaml: unmarshal errors:\n line 4: field c already set in type struct { A int; yaml_test.inlineB "yaml:\\",inline\\"" }`, -}, { - data: "c: 1\na: 1\nb: 2\nc: 3\n", - value: struct { - A int - M map[string]interface{} `yaml:",inline"` - }{ - A: 1, - M: map[string]interface{}{ - "b": 2, - "c": 3, - }, - }, - error: `yaml: unmarshal errors:\n line 4: key "c" already set in map`, -}, { - data: "a: 1\n9: 2\nnull: 3\n9: 4", - value: map[interface{}]interface{}{ - "a": 1, - nil: 3, - 9: 4, - }, - error: `yaml: unmarshal errors:\n line 4: key 9 already set in map`, -}} - -func (s *S) TestUnmarshalStrict(c *C) { - for i, item := range unmarshalStrictTests { - c.Logf("test %d: %q", i, item.data) - // First test that normal Unmarshal unmarshals to the expected value. - t := reflect.ValueOf(item.value).Type() - value := reflect.New(t) - err := yaml.Unmarshal([]byte(item.data), value.Interface()) - c.Assert(err, Equals, nil) - c.Assert(value.Elem().Interface(), DeepEquals, item.value) - - // Then test that UnmarshalStrict fails on the same thing. - t = reflect.ValueOf(item.value).Type() - value = reflect.New(t) - err = yaml.UnmarshalStrict([]byte(item.data), value.Interface()) - c.Assert(err, ErrorMatches, item.error) - } -} - -type textUnmarshaler struct { - S string -} - -func (t *textUnmarshaler) UnmarshalText(s []byte) error { - t.S = string(s) - return nil -} - -func (s *S) TestFuzzCrashers(c *C) { - cases := []string{ - // runtime error: index out of range - "\"\\0\\\r\n", - - // should not happen - " 0: [\n] 0", - "? ? \"\n\" 0", - " - {\n000}0", - "0:\n 0: [0\n] 0", - " - \"\n000\"0", - " - \"\n000\"\"", - "0:\n - {\n000}0", - "0:\n - \"\n000\"0", - "0:\n - \"\n000\"\"", - - // runtime error: index out of range - " \ufeff\n", - "? \ufeff\n", - "? \ufeff:\n", - "0: \ufeff\n", - "? \ufeff: \ufeff\n", - } - for _, data := range cases { - var v interface{} - _ = yaml.Unmarshal([]byte(data), &v) - } -} - -//var data []byte -//func init() { -// var err error -// data, err = ioutil.ReadFile("/tmp/file.yaml") -// if err != nil { -// panic(err) -// } -//} -// -//func (s *S) BenchmarkUnmarshal(c *C) { -// var err error -// for i := 0; i < c.N; i++ { -// var v map[string]interface{} -// err = yaml.Unmarshal(data, &v) -// } -// if err != nil { -// panic(err) -// } -//} -// -//func (s *S) BenchmarkMarshal(c *C) { -// var v map[string]interface{} -// yaml.Unmarshal(data, &v) -// c.ResetTimer() -// for i := 0; i < c.N; i++ { -// yaml.Marshal(&v) -// } -//} diff --git a/vendor/gopkg.in/yaml.v2/encode_test.go b/vendor/gopkg.in/yaml.v2/encode_test.go deleted file mode 100644 index 4a266008..00000000 --- a/vendor/gopkg.in/yaml.v2/encode_test.go +++ /dev/null @@ -1,625 +0,0 @@ -package yaml_test - -import ( - "bytes" - "fmt" - "math" - "strconv" - "strings" - "time" - - "net" - "os" - - . "gopkg.in/check.v1" - "gopkg.in/yaml.v2" -) - -type jsonNumberT string - -func (j jsonNumberT) Int64() (int64, error) { - val, err := strconv.Atoi(string(j)) - if err != nil { - return 0, err - } - return int64(val), nil -} - -func (j jsonNumberT) Float64() (float64, error) { - return strconv.ParseFloat(string(j), 64) -} - -func (j jsonNumberT) String() string { - return string(j) -} - -var marshalIntTest = 123 - -var marshalTests = []struct { - value interface{} - data string -}{ - { - nil, - "null\n", - }, { - (*marshalerType)(nil), - "null\n", - }, { - &struct{}{}, - "{}\n", - }, { - map[string]string{"v": "hi"}, - "v: hi\n", - }, { - map[string]interface{}{"v": "hi"}, - "v: hi\n", - }, { - map[string]string{"v": "true"}, - "v: \"true\"\n", - }, { - map[string]string{"v": "false"}, - "v: \"false\"\n", - }, { - map[string]interface{}{"v": true}, - "v: true\n", - }, { - map[string]interface{}{"v": false}, - "v: false\n", - }, { - map[string]interface{}{"v": 10}, - "v: 10\n", - }, { - map[string]interface{}{"v": -10}, - "v: -10\n", - }, { - map[string]uint{"v": 42}, - "v: 42\n", - }, { - map[string]interface{}{"v": int64(4294967296)}, - "v: 4294967296\n", - }, { - map[string]int64{"v": int64(4294967296)}, - "v: 4294967296\n", - }, { - map[string]uint64{"v": 4294967296}, - "v: 4294967296\n", - }, { - map[string]interface{}{"v": "10"}, - "v: \"10\"\n", - }, { - map[string]interface{}{"v": 0.1}, - "v: 0.1\n", - }, { - map[string]interface{}{"v": float64(0.1)}, - "v: 0.1\n", - }, { - map[string]interface{}{"v": float32(0.99)}, - "v: 0.99\n", - }, { - map[string]interface{}{"v": -0.1}, - "v: -0.1\n", - }, { - map[string]interface{}{"v": math.Inf(+1)}, - "v: .inf\n", - }, { - map[string]interface{}{"v": math.Inf(-1)}, - "v: -.inf\n", - }, { - map[string]interface{}{"v": math.NaN()}, - "v: .nan\n", - }, { - map[string]interface{}{"v": nil}, - "v: null\n", - }, { - map[string]interface{}{"v": ""}, - "v: \"\"\n", - }, { - map[string][]string{"v": []string{"A", "B"}}, - "v:\n- A\n- B\n", - }, { - map[string][]string{"v": []string{"A", "B\nC"}}, - "v:\n- A\n- |-\n B\n C\n", - }, { - map[string][]interface{}{"v": []interface{}{"A", 1, map[string][]int{"B": []int{2, 3}}}}, - "v:\n- A\n- 1\n- B:\n - 2\n - 3\n", - }, { - map[string]interface{}{"a": map[interface{}]interface{}{"b": "c"}}, - "a:\n b: c\n", - }, { - map[string]interface{}{"a": "-"}, - "a: '-'\n", - }, - - // Simple values. - { - &marshalIntTest, - "123\n", - }, - - // Structures - { - &struct{ Hello string }{"world"}, - "hello: world\n", - }, { - &struct { - A struct { - B string - } - }{struct{ B string }{"c"}}, - "a:\n b: c\n", - }, { - &struct { - A *struct { - B string - } - }{&struct{ B string }{"c"}}, - "a:\n b: c\n", - }, { - &struct { - A *struct { - B string - } - }{}, - "a: null\n", - }, { - &struct{ A int }{1}, - "a: 1\n", - }, { - &struct{ A []int }{[]int{1, 2}}, - "a:\n- 1\n- 2\n", - }, { - &struct{ A [2]int }{[2]int{1, 2}}, - "a:\n- 1\n- 2\n", - }, { - &struct { - B int "a" - }{1}, - "a: 1\n", - }, { - &struct{ A bool }{true}, - "a: true\n", - }, - - // Conditional flag - { - &struct { - A int "a,omitempty" - B int "b,omitempty" - }{1, 0}, - "a: 1\n", - }, { - &struct { - A int "a,omitempty" - B int "b,omitempty" - }{0, 0}, - "{}\n", - }, { - &struct { - A *struct{ X, y int } "a,omitempty,flow" - }{&struct{ X, y int }{1, 2}}, - "a: {x: 1}\n", - }, { - &struct { - A *struct{ X, y int } "a,omitempty,flow" - }{nil}, - "{}\n", - }, { - &struct { - A *struct{ X, y int } "a,omitempty,flow" - }{&struct{ X, y int }{}}, - "a: {x: 0}\n", - }, { - &struct { - A struct{ X, y int } "a,omitempty,flow" - }{struct{ X, y int }{1, 2}}, - "a: {x: 1}\n", - }, { - &struct { - A struct{ X, y int } "a,omitempty,flow" - }{struct{ X, y int }{0, 1}}, - "{}\n", - }, { - &struct { - A float64 "a,omitempty" - B float64 "b,omitempty" - }{1, 0}, - "a: 1\n", - }, - { - &struct { - T1 time.Time "t1,omitempty" - T2 time.Time "t2,omitempty" - T3 *time.Time "t3,omitempty" - T4 *time.Time "t4,omitempty" - }{ - T2: time.Date(2018, 1, 9, 10, 40, 47, 0, time.UTC), - T4: newTime(time.Date(2098, 1, 9, 10, 40, 47, 0, time.UTC)), - }, - "t2: 2018-01-09T10:40:47Z\nt4: 2098-01-09T10:40:47Z\n", - }, - // Nil interface that implements Marshaler. - { - map[string]yaml.Marshaler{ - "a": nil, - }, - "a: null\n", - }, - - // Flow flag - { - &struct { - A []int "a,flow" - }{[]int{1, 2}}, - "a: [1, 2]\n", - }, { - &struct { - A map[string]string "a,flow" - }{map[string]string{"b": "c", "d": "e"}}, - "a: {b: c, d: e}\n", - }, { - &struct { - A struct { - B, D string - } "a,flow" - }{struct{ B, D string }{"c", "e"}}, - "a: {b: c, d: e}\n", - }, - - // Unexported field - { - &struct { - u int - A int - }{0, 1}, - "a: 1\n", - }, - - // Ignored field - { - &struct { - A int - B int "-" - }{1, 2}, - "a: 1\n", - }, - - // Struct inlining - { - &struct { - A int - C inlineB `yaml:",inline"` - }{1, inlineB{2, inlineC{3}}}, - "a: 1\nb: 2\nc: 3\n", - }, - - // Map inlining - { - &struct { - A int - C map[string]int `yaml:",inline"` - }{1, map[string]int{"b": 2, "c": 3}}, - "a: 1\nb: 2\nc: 3\n", - }, - - // Duration - { - map[string]time.Duration{"a": 3 * time.Second}, - "a: 3s\n", - }, - - // Issue #24: bug in map merging logic. - { - map[string]string{"a": ""}, - "a: \n", - }, - - // Issue #34: marshal unsupported base 60 floats quoted for compatibility - // with old YAML 1.1 parsers. - { - map[string]string{"a": "1:1"}, - "a: \"1:1\"\n", - }, - - // Binary data. - { - map[string]string{"a": "\x00"}, - "a: \"\\0\"\n", - }, { - map[string]string{"a": "\x80\x81\x82"}, - "a: !!binary gIGC\n", - }, { - map[string]string{"a": strings.Repeat("\x90", 54)}, - "a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n", - }, - - // Ordered maps. - { - &yaml.MapSlice{{"b", 2}, {"a", 1}, {"d", 4}, {"c", 3}, {"sub", yaml.MapSlice{{"e", 5}}}}, - "b: 2\na: 1\nd: 4\nc: 3\nsub:\n e: 5\n", - }, - - // Encode unicode as utf-8 rather than in escaped form. - { - map[string]string{"a": "你好"}, - "a: 你好\n", - }, - - // Support encoding.TextMarshaler. - { - map[string]net.IP{"a": net.IPv4(1, 2, 3, 4)}, - "a: 1.2.3.4\n", - }, - // time.Time gets a timestamp tag. - { - map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)}, - "a: 2015-02-24T18:19:39Z\n", - }, - { - map[string]*time.Time{"a": newTime(time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC))}, - "a: 2015-02-24T18:19:39Z\n", - }, - { - // This is confirmed to be properly decoded in Python (libyaml) without a timestamp tag. - map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 123456789, time.FixedZone("FOO", -3*60*60))}, - "a: 2015-02-24T18:19:39.123456789-03:00\n", - }, - // Ensure timestamp-like strings are quoted. - { - map[string]string{"a": "2015-02-24T18:19:39Z"}, - "a: \"2015-02-24T18:19:39Z\"\n", - }, - - // Ensure strings containing ": " are quoted (reported as PR #43, but not reproducible). - { - map[string]string{"a": "b: c"}, - "a: 'b: c'\n", - }, - - // Containing hash mark ('#') in string should be quoted - { - map[string]string{"a": "Hello #comment"}, - "a: 'Hello #comment'\n", - }, - { - map[string]string{"a": "你好 #comment"}, - "a: '你好 #comment'\n", - }, - { - map[string]interface{}{"a": jsonNumberT("5")}, - "a: 5\n", - }, - { - map[string]interface{}{"a": jsonNumberT("100.5")}, - "a: 100.5\n", - }, - { - map[string]interface{}{"a": jsonNumberT("bogus")}, - "a: bogus\n", - }, -} - -func (s *S) TestMarshal(c *C) { - defer os.Setenv("TZ", os.Getenv("TZ")) - os.Setenv("TZ", "UTC") - for i, item := range marshalTests { - c.Logf("test %d: %q", i, item.data) - data, err := yaml.Marshal(item.value) - c.Assert(err, IsNil) - c.Assert(string(data), Equals, item.data) - } -} - -func (s *S) TestEncoderSingleDocument(c *C) { - for i, item := range marshalTests { - c.Logf("test %d. %q", i, item.data) - var buf bytes.Buffer - enc := yaml.NewEncoder(&buf) - err := enc.Encode(item.value) - c.Assert(err, Equals, nil) - err = enc.Close() - c.Assert(err, Equals, nil) - c.Assert(buf.String(), Equals, item.data) - } -} - -func (s *S) TestEncoderMultipleDocuments(c *C) { - var buf bytes.Buffer - enc := yaml.NewEncoder(&buf) - err := enc.Encode(map[string]string{"a": "b"}) - c.Assert(err, Equals, nil) - err = enc.Encode(map[string]string{"c": "d"}) - c.Assert(err, Equals, nil) - err = enc.Close() - c.Assert(err, Equals, nil) - c.Assert(buf.String(), Equals, "a: b\n---\nc: d\n") -} - -func (s *S) TestEncoderWriteError(c *C) { - enc := yaml.NewEncoder(errorWriter{}) - err := enc.Encode(map[string]string{"a": "b"}) - c.Assert(err, ErrorMatches, `yaml: write error: some write error`) // Data not flushed yet -} - -type errorWriter struct{} - -func (errorWriter) Write([]byte) (int, error) { - return 0, fmt.Errorf("some write error") -} - -var marshalErrorTests = []struct { - value interface{} - error string - panic string -}{{ - value: &struct { - B int - inlineB ",inline" - }{1, inlineB{2, inlineC{3}}}, - panic: `Duplicated key 'b' in struct struct \{ B int; .*`, -}, { - value: &struct { - A int - B map[string]int ",inline" - }{1, map[string]int{"a": 2}}, - panic: `Can't have key "a" in inlined map; conflicts with struct field`, -}} - -func (s *S) TestMarshalErrors(c *C) { - for _, item := range marshalErrorTests { - if item.panic != "" { - c.Assert(func() { yaml.Marshal(item.value) }, PanicMatches, item.panic) - } else { - _, err := yaml.Marshal(item.value) - c.Assert(err, ErrorMatches, item.error) - } - } -} - -func (s *S) TestMarshalTypeCache(c *C) { - var data []byte - var err error - func() { - type T struct{ A int } - data, err = yaml.Marshal(&T{}) - c.Assert(err, IsNil) - }() - func() { - type T struct{ B int } - data, err = yaml.Marshal(&T{}) - c.Assert(err, IsNil) - }() - c.Assert(string(data), Equals, "b: 0\n") -} - -var marshalerTests = []struct { - data string - value interface{} -}{ - {"_:\n hi: there\n", map[interface{}]interface{}{"hi": "there"}}, - {"_:\n- 1\n- A\n", []interface{}{1, "A"}}, - {"_: 10\n", 10}, - {"_: null\n", nil}, - {"_: BAR!\n", "BAR!"}, -} - -type marshalerType struct { - value interface{} -} - -func (o marshalerType) MarshalText() ([]byte, error) { - panic("MarshalText called on type with MarshalYAML") -} - -func (o marshalerType) MarshalYAML() (interface{}, error) { - return o.value, nil -} - -type marshalerValue struct { - Field marshalerType "_" -} - -func (s *S) TestMarshaler(c *C) { - for _, item := range marshalerTests { - obj := &marshalerValue{} - obj.Field.value = item.value - data, err := yaml.Marshal(obj) - c.Assert(err, IsNil) - c.Assert(string(data), Equals, string(item.data)) - } -} - -func (s *S) TestMarshalerWholeDocument(c *C) { - obj := &marshalerType{} - obj.value = map[string]string{"hello": "world!"} - data, err := yaml.Marshal(obj) - c.Assert(err, IsNil) - c.Assert(string(data), Equals, "hello: world!\n") -} - -type failingMarshaler struct{} - -func (ft *failingMarshaler) MarshalYAML() (interface{}, error) { - return nil, failingErr -} - -func (s *S) TestMarshalerError(c *C) { - _, err := yaml.Marshal(&failingMarshaler{}) - c.Assert(err, Equals, failingErr) -} - -func (s *S) TestSortedOutput(c *C) { - order := []interface{}{ - false, - true, - 1, - uint(1), - 1.0, - 1.1, - 1.2, - 2, - uint(2), - 2.0, - 2.1, - "", - ".1", - ".2", - ".a", - "1", - "2", - "a!10", - "a/0001", - "a/002", - "a/3", - "a/10", - "a/11", - "a/0012", - "a/100", - "a~10", - "ab/1", - "b/1", - "b/01", - "b/2", - "b/02", - "b/3", - "b/03", - "b1", - "b01", - "b3", - "c2.10", - "c10.2", - "d1", - "d7", - "d7abc", - "d12", - "d12a", - } - m := make(map[interface{}]int) - for _, k := range order { - m[k] = 1 - } - data, err := yaml.Marshal(m) - c.Assert(err, IsNil) - out := "\n" + string(data) - last := 0 - for i, k := range order { - repr := fmt.Sprint(k) - if s, ok := k.(string); ok { - if _, err = strconv.ParseFloat(repr, 32); s == "" || err == nil { - repr = `"` + repr + `"` - } - } - index := strings.Index(out, "\n"+repr+":") - if index == -1 { - c.Fatalf("%#v is not in the output: %#v", k, out) - } - if index < last { - c.Fatalf("%#v was generated before %#v: %q", k, order[i-1], out) - } - last = index - } -} - -func newTime(t time.Time) *time.Time { - return &t -} diff --git a/vendor/gopkg.in/yaml.v2/example_embedded_test.go b/vendor/gopkg.in/yaml.v2/example_embedded_test.go deleted file mode 100644 index 171c0931..00000000 --- a/vendor/gopkg.in/yaml.v2/example_embedded_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package yaml_test - -import ( - "fmt" - "log" - - "gopkg.in/yaml.v2" -) - -// An example showing how to unmarshal embedded -// structs from YAML. - -type StructA struct { - A string `yaml:"a"` -} - -type StructB struct { - // Embedded structs are not treated as embedded in YAML by default. To do that, - // add the ",inline" annotation below - StructA `yaml:",inline"` - B string `yaml:"b"` -} - -var data = ` -a: a string from struct A -b: a string from struct B -` - -func ExampleUnmarshal_embedded() { - var b StructB - - err := yaml.Unmarshal([]byte(data), &b) - if err != nil { - log.Fatalf("cannot unmarshal data: %v", err) - } - fmt.Println(b.A) - fmt.Println(b.B) - // Output: - // a string from struct A - // a string from struct B -} diff --git a/vendor/gopkg.in/yaml.v2/suite_test.go b/vendor/gopkg.in/yaml.v2/suite_test.go deleted file mode 100644 index c5cf1ed4..00000000 --- a/vendor/gopkg.in/yaml.v2/suite_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package yaml_test - -import ( - . "gopkg.in/check.v1" - "testing" -) - -func Test(t *testing.T) { TestingT(t) } - -type S struct{} - -var _ = Suite(&S{}) From a8188d992bf9be6815dac6247f057ee110999c50 Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Mon, 11 Feb 2019 16:32:30 +0100 Subject: [PATCH 14/18] PR clean --- cmd/xcode.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/cmd/xcode.go b/cmd/xcode.go index e82b65b1..9c000445 100644 --- a/cmd/xcode.go +++ b/cmd/xcode.go @@ -80,17 +80,18 @@ the one you usually open in Xcode, then hit Enter. if err != nil { return "", fmt.Errorf("failed to read input: %s", err) } - } else { - if len(projPaths) == 1 { - log.Printf("Found one project file: %s.", path.Base(projPaths[0])) - projpth = projPaths[0] - } else { - log.Printf("Found multiple project file: %s.", path.Base(projpth)) - projpth, err = goinp.SelectFromStringsWithDefault("Select the project file you want to scan", 1, projPaths) - if err != nil { - return "", fmt.Errorf("failed to select project file: %s", err) - } - } + return projpth, err + } + + if len(projPaths) == 1 { + log.Printf("Found one project file: %s.", path.Base(projPaths[0])) + return projPaths[0], nil + } + + log.Printf("Found multiple project file: %s.", path.Base(projpth)) + projpth, err = goinp.SelectFromStringsWithDefault("Select the project file you want to scan", 1, projPaths) + if err != nil { + return "", fmt.Errorf("failed to select project file: %s", err) } return projpth, nil From 3198ad523fc879f992f0dd07629cb8315282961b Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Mon, 11 Feb 2019 17:35:31 +0100 Subject: [PATCH 15/18] PR: make ProjectType private --- cmd/utils.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/utils.go b/cmd/utils.go index dec783e5..d02ca186 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -9,16 +9,18 @@ import ( "github.com/bitrise-core/bitrise-init/utility" ) -// ProjectType ... -type ProjectType int +// projectType enum. +// Could be iOSProjectType = 0 +// Or xamarinProjectType = 1 +type projectType int const ( - iOSProjectType ProjectType = iota + iOSProjectType projectType = iota xamarinProjectType ) // Scans the root dir for the provided project files -func scanForProjectFiles(projectType ProjectType) ([]string, error) { +func scanForProjectFiles(projType projectType) ([]string, error) { searchDir, err := os.Getwd() if err != nil { return nil, err @@ -31,7 +33,7 @@ func scanForProjectFiles(projectType ProjectType) ([]string, error) { var paths []string { - if projectType == iOSProjectType { + if projType == iOSProjectType { paths, err = ios.FilterRelevantWorkspaceFiles(fileList) if err != nil { return nil, fmt.Errorf("failed to search for workspace files, error: %s", err) @@ -43,7 +45,7 @@ func scanForProjectFiles(projectType ProjectType) ([]string, error) { return nil, fmt.Errorf("failed to search for project files, error: %s", err) } } - } else if projectType == xamarinProjectType { + } else if projType == xamarinProjectType { paths, err = xamarin.FilterSolutionFiles(fileList) if err != nil { return nil, fmt.Errorf("failed to search for solution files, error: %s", err) From 0917f252ca1a6580b62fdad22d0b9645d4777b54 Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Thu, 14 Feb 2019 15:39:53 +0100 Subject: [PATCH 16/18] move the findXcodeProject & findXamarinProject methods to cmd/utils.go; add the auto scan function to the xcodeUITests as well --- cmd/utils.go | 71 +++++++++++++++++++++++++++++++++++++++++++++ cmd/xamarin.go | 35 +--------------------- cmd/xcode.go | 44 +++------------------------- cmd/xcodeUITests.go | 14 +++++---- 4 files changed, 84 insertions(+), 80 deletions(-) diff --git a/cmd/utils.go b/cmd/utils.go index d02ca186..482269fc 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -3,10 +3,14 @@ package cmd import ( "fmt" "os" + "path" "github.com/bitrise-core/bitrise-init/scanners/ios" "github.com/bitrise-core/bitrise-init/scanners/xamarin" "github.com/bitrise-core/bitrise-init/utility" + "github.com/bitrise-io/go-utils/colorstring" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/goinp/goinp" ) // projectType enum. @@ -60,3 +64,70 @@ func scanForProjectFiles(projType projectType) ([]string, error) { } return paths, nil } + +// findProject scans the directory for Xcode Project (.xcworkspace / .xcodeproject) file first +// If can't find any, ask the user to drag-and-drop the file +func findXcodeProject() (string, error) { + var projpth string + + projPaths, err := scanForProjectFiles(iOSProjectType) + if err != nil { + log.Printf("Failed: %s", err) + fmt.Println() + + log.Infof("Provide the project file manually") + askText := `Please drag-and-drop your Xcode Project (` + colorstring.Green(".xcodeproj") + `) or Workspace (` + colorstring.Green(".xcworkspace") + `) file, +the one you usually open in Xcode, then hit Enter. +(Note: if you have a Workspace file you should most likely use that)` + projpth, err = goinp.AskForPath(askText) + if err != nil { + return "", fmt.Errorf("failed to read input: %s", err) + } + return projpth, err + } + + if len(projPaths) == 1 { + log.Printf("Found one project file: %s.", path.Base(projPaths[0])) + return projPaths[0], nil + } + + log.Printf("Found multiple project file: %s.", path.Base(projpth)) + projpth, err = goinp.SelectFromStringsWithDefault("Select the project file you want to scan", 1, projPaths) + if err != nil { + return "", fmt.Errorf("failed to select project file: %s", err) + } + + return projpth, nil +} + +// findSolution scans the directory for Xamarin.Solution file first +// If can't find any, ask the user to drag-and-drop the file +func findXamarinSolution() (string, error) { + var solutionPth string + solPaths, err := scanForProjectFiles(xamarinProjectType) + if err != nil { + log.Printf("Failed: %s", err) + fmt.Println() + + log.Infof("Provide the solution file manually") + askText := `Please drag-and-drop your Xamarin Solution (` + colorstring.Green(".sln") + `) file, +and then hit Enter` + solutionPth, err = goinp.AskForPath(askText) + if err != nil { + return "", fmt.Errorf("failed to read input: %s", err) + } + } else { + if len(solPaths) == 1 { + log.Printf("Found one solution file: %s.", path.Base(solPaths[0])) + solutionPth = solPaths[0] + } else { + log.Printf("Found multiple solution file: %s.", path.Base(solutionPth)) + solutionPth, err = goinp.SelectFromStringsWithDefault("Select the solution file you want to scan", 1, solPaths) + if err != nil { + return "", fmt.Errorf("failed to select solution file: %s", err) + } + } + } + + return solutionPth, nil +} diff --git a/cmd/xamarin.go b/cmd/xamarin.go index 76c454c3..5849e63e 100644 --- a/cmd/xamarin.go +++ b/cmd/xamarin.go @@ -3,7 +3,6 @@ package cmd import ( "encoding/json" "fmt" - "path" "path/filepath" "sort" @@ -76,38 +75,6 @@ func archivableSolutionConfigNames(projectsByID map[string]project.Model) []stri return archivableSolutionConfigNames } -// findSolution scans the directory for Xamarin.Solution file first -// If can't find any, ask the user to drag-and-drop the file -func findSolution() (string, error) { - var solutionPth string - solPaths, err := scanForProjectFiles(xamarinProjectType) - if err != nil { - log.Printf("Failed: %s", err) - fmt.Println() - - log.Infof("Provide the solution file manually") - askText := `Please drag-and-drop your Xamarin Solution (` + colorstring.Green(".sln") + `) file, -and then hit Enter` - solutionPth, err = goinp.AskForPath(askText) - if err != nil { - return "", fmt.Errorf("failed to read input: %s", err) - } - } else { - if len(solPaths) == 1 { - log.Printf("Found one solution file: %s.", path.Base(solPaths[0])) - solutionPth = solPaths[0] - } else { - log.Printf("Found multiple solution file: %s.", path.Base(solutionPth)) - solutionPth, err = goinp.SelectFromStringsWithDefault("Select the solution file you want to scan", 1, solPaths) - if err != nil { - return "", fmt.Errorf("failed to select solution file: %s", err) - } - } - } - - return solutionPth, nil -} - func scanXamarinProject(cmd *cobra.Command, args []string) error { absExportOutputDirPath, err := initExportOutputDir() if err != nil { @@ -127,7 +94,7 @@ func scanXamarinProject(cmd *cobra.Command, args []string) error { // // Scan the directory for Xamarin.Solution file first // If can't find any, ask the user to drag-and-drop the file - xamarinCmd.SolutionFilePath, err = findSolution() + xamarinCmd.SolutionFilePath, err = findXamarinSolution() if err != nil { return err } diff --git a/cmd/xcode.go b/cmd/xcode.go index 9c000445..ffe08a99 100644 --- a/cmd/xcode.go +++ b/cmd/xcode.go @@ -3,7 +3,6 @@ package cmd import ( "fmt" "os" - "path" "path/filepath" "strings" @@ -62,41 +61,6 @@ func initExportOutputDir() (string, error) { return absExportOutputDirPath, nil } -// findProject scans the directory for Xcode Project (.xcworkspace / .xcodeproject) file first -// If can't find any, ask the user to drag-and-drop the file -func findProject() (string, error) { - var projpth string - - projPaths, err := scanForProjectFiles(iOSProjectType) - if err != nil { - log.Printf("Failed: %s", err) - fmt.Println() - - log.Infof("Provide the project file manually") - askText := `Please drag-and-drop your Xcode Project (` + colorstring.Green(".xcodeproj") + `) or Workspace (` + colorstring.Green(".xcworkspace") + `) file, -the one you usually open in Xcode, then hit Enter. -(Note: if you have a Workspace file you should most likely use that)` - projpth, err = goinp.AskForPath(askText) - if err != nil { - return "", fmt.Errorf("failed to read input: %s", err) - } - return projpth, err - } - - if len(projPaths) == 1 { - log.Printf("Found one project file: %s.", path.Base(projPaths[0])) - return projPaths[0], nil - } - - log.Printf("Found multiple project file: %s.", path.Base(projpth)) - projpth, err = goinp.SelectFromStringsWithDefault("Select the project file you want to scan", 1, projPaths) - if err != nil { - return "", fmt.Errorf("failed to select project file: %s", err) - } - - return projpth, nil -} - func scanXcodeProject(cmd *cobra.Command, args []string) error { absExportOutputDirPath, err := initExportOutputDir() if err != nil { @@ -120,14 +84,14 @@ func scanXcodeProject(cmd *cobra.Command, args []string) error { log.Infof("Scan the directory for project files") log.Warnf("You can specify the Xcode project/workscape file to scan with the --file flag.") - projpth, err := findProject() + // + // Scan the directory for Xcode Project (.xcworkspace / .xcodeproject) file first + // If can't find any, ask the user to drag-and-drop the file + projpth, err := findXcodeProject() if err != nil { return err } - // - // Scan the directory for Xcode Project (.xcworkspace / .xcodeproject) file first - // If can't find any, ask the user to drag-and-drop the file projectPath = strings.Trim(strings.TrimSpace(projpth), "'\"") } log.Debugf("projectPath: %s", projectPath) diff --git a/cmd/xcodeUITests.go b/cmd/xcodeUITests.go index ccd8b440..4f2e789b 100644 --- a/cmd/xcodeUITests.go +++ b/cmd/xcodeUITests.go @@ -50,18 +50,20 @@ func scanXcodeUITestsProject(cmd *cobra.Command, args []string) error { projectPath := paramXcodeProjectFilePath if projectPath == "" { - askText := `Please drag-and-drop your Xcode Project (` + colorstring.Green(".xcodeproj") + `) or Workspace (` + colorstring.Green(".xcworkspace") + `) file, -the one you usually open in Xcode, then hit Enter. -(Note: if you have a Workspace file you should most likely use that)` - projpth, err := goinp.AskForPath(askText) + log.Infof("Scan the directory for project files") + log.Warnf("You can specify the Xcode project/workscape file to scan with the --file flag.") + + // + // Scan the directory for Xcode Project (.xcworkspace / .xcodeproject) file first + // If can't find any, ask the user to drag-and-drop the file + projpth, err := findXcodeProject() if err != nil { - return fmt.Errorf("failed to read input: %s", err) + return err } projectPath = strings.Trim(strings.TrimSpace(projpth), "'\"") } log.Debugf("projectPath: %s", projectPath) - xcodeUITestsCmd := xcodeuitest.CommandModel{ProjectFilePath: projectPath} schemeToUse := paramXcodeScheme From f16a378f594099e2f9d1fbcf54822c70d6e2e3ad Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Fri, 15 Feb 2019 09:49:58 +0100 Subject: [PATCH 17/18] PR: fix return --- cmd/utils.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/utils.go b/cmd/utils.go index 482269fc..3895d4fe 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -83,7 +83,8 @@ the one you usually open in Xcode, then hit Enter. if err != nil { return "", fmt.Errorf("failed to read input: %s", err) } - return projpth, err + + return projpth, nil } if len(projPaths) == 1 { From 2eb23e206673d111b817b256db9c493ab8b56703 Mon Sep 17 00:00:00 2001 From: Akos Birmacher Date: Fri, 15 Feb 2019 09:52:55 +0100 Subject: [PATCH 18/18] clean the findXamarinSolution() just like the findXcodeProject() --- cmd/utils.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/cmd/utils.go b/cmd/utils.go index 3895d4fe..70b3c294 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -117,17 +117,19 @@ and then hit Enter` if err != nil { return "", fmt.Errorf("failed to read input: %s", err) } - } else { - if len(solPaths) == 1 { - log.Printf("Found one solution file: %s.", path.Base(solPaths[0])) - solutionPth = solPaths[0] - } else { - log.Printf("Found multiple solution file: %s.", path.Base(solutionPth)) - solutionPth, err = goinp.SelectFromStringsWithDefault("Select the solution file you want to scan", 1, solPaths) - if err != nil { - return "", fmt.Errorf("failed to select solution file: %s", err) - } - } + + return solutionPth, nil + } + + if len(solPaths) == 1 { + log.Printf("Found one solution file: %s.", path.Base(solPaths[0])) + return solPaths[0], nil + } + + log.Printf("Found multiple solution file: %s.", path.Base(solutionPth)) + solutionPth, err = goinp.SelectFromStringsWithDefault("Select the solution file you want to scan", 1, solPaths) + if err != nil { + return "", fmt.Errorf("failed to select solution file: %s", err) } return solutionPth, nil

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