diff --git a/deplock.json b/deplock.json index 6fe77b0e..cfab7a61 100644 --- a/deplock.json +++ b/deplock.json @@ -2,7 +2,7 @@ "dep_locks": [ { "url": "https://github.com/bitrise-core/bitrise-init.git", - "revision": "fd5850437e18db75178693eabe0b489e35fa0ac5" + "revision": "b327a3a86c3123e74185f9078b9960fb08b3e990" } ] } \ No newline at end of file diff --git a/go/src/github.com/bitrise-core/bitrise-init/CHANGELOG.md b/go/src/github.com/bitrise-core/bitrise-init/CHANGELOG.md index ae8b75b6..94fcca08 100644 --- a/go/src/github.com/bitrise-core/bitrise-init/CHANGELOG.md +++ b/go/src/github.com/bitrise-core/bitrise-init/CHANGELOG.md @@ -1,7 +1,15 @@ -## Changelog (Current version: 0.9.11) +## Changelog (Current version: 0.9.12) ----------------- +### 0.9.12 (2016 Aug 10) + +* [405e2ef] step versions +* [25f83f0] prepare for 0.9.12 +* [c080c79] godep update ios fixes (#40) +* [6e2dd87] add script step to default workflow (#39) +* [a1cecc1] xcodeproj and xcworkspace should be a dir (#38) + ### 0.9.11 (2016 Jul 29) * [1bad745] prepare for 0.9.11 @@ -109,4 +117,4 @@ ----------------- -Updated: 2016 Jul 29 \ No newline at end of file +Updated: 2016 Aug 10 \ No newline at end of file diff --git a/go/src/github.com/bitrise-core/bitrise-init/Godeps/Godeps.json b/go/src/github.com/bitrise-core/bitrise-init/Godeps/Godeps.json index 40c215ff..0db1a0ff 100644 --- a/go/src/github.com/bitrise-core/bitrise-init/Godeps/Godeps.json +++ b/go/src/github.com/bitrise-core/bitrise-init/Godeps/Godeps.json @@ -13,8 +13,8 @@ }, { "ImportPath": "github.com/bitrise-io/bitrise/models", - "Comment": "1.3.6-2-gc15a006", - "Rev": "c15a0067c6c7eedc9844a8db997a027d8e902a9d" + "Comment": "1.3.7-6-gfb4aad0", + "Rev": "fb4aad0315a98cd1f0862ea7005dad6352a87327" }, { "ImportPath": "github.com/bitrise-io/envman/models", @@ -23,31 +23,31 @@ }, { "ImportPath": "github.com/bitrise-io/go-utils/cmdex", - "Rev": "62166e88f0470a6280c62191660f0f0cb2066a2b" + "Rev": "78d47e6cf747c2b335eb7714ffd71d0ea9ad3cbb" }, { "ImportPath": "github.com/bitrise-io/go-utils/colorstring", - "Rev": "62166e88f0470a6280c62191660f0f0cb2066a2b" + "Rev": "78d47e6cf747c2b335eb7714ffd71d0ea9ad3cbb" }, { "ImportPath": "github.com/bitrise-io/go-utils/errorutil", - "Rev": "62166e88f0470a6280c62191660f0f0cb2066a2b" + "Rev": "78d47e6cf747c2b335eb7714ffd71d0ea9ad3cbb" }, { "ImportPath": "github.com/bitrise-io/go-utils/fileutil", - "Rev": "62166e88f0470a6280c62191660f0f0cb2066a2b" + "Rev": "78d47e6cf747c2b335eb7714ffd71d0ea9ad3cbb" }, { "ImportPath": "github.com/bitrise-io/go-utils/parseutil", - "Rev": "62166e88f0470a6280c62191660f0f0cb2066a2b" + "Rev": "78d47e6cf747c2b335eb7714ffd71d0ea9ad3cbb" }, { "ImportPath": "github.com/bitrise-io/go-utils/pathutil", - "Rev": "62166e88f0470a6280c62191660f0f0cb2066a2b" + "Rev": "78d47e6cf747c2b335eb7714ffd71d0ea9ad3cbb" }, { "ImportPath": "github.com/bitrise-io/go-utils/pointers", - "Rev": "62166e88f0470a6280c62191660f0f0cb2066a2b" + "Rev": "78d47e6cf747c2b335eb7714ffd71d0ea9ad3cbb" }, { "ImportPath": "github.com/bitrise-io/goinp/goinp", @@ -60,7 +60,7 @@ }, { "ImportPath": "github.com/bitrise-io/xcode-utils/xcodeproj", - "Rev": "fe27904d6d8e569bd481c1a1115bd93585489c6d" + "Rev": "46f427a655b9b5c2ec7d058d1707efc40cf8d520" }, { "ImportPath": "github.com/davecgh/go-spew/spew", diff --git a/go/src/github.com/bitrise-core/bitrise-init/bitrise.yml b/go/src/github.com/bitrise-core/bitrise-init/bitrise.yml index 420853c3..946ddd50 100644 --- a/go/src/github.com/bitrise-core/bitrise-init/bitrise.yml +++ b/go/src/github.com/bitrise-core/bitrise-init/bitrise.yml @@ -212,7 +212,7 @@ workflows: 2, Export RELEASE_VERSION 3, Create binaries envs: - - RELEASE_VERSION: 0.9.11 + - RELEASE_VERSION: 0.9.12 after_run: - _export_release_version - create_binaries diff --git a/go/src/github.com/bitrise-core/bitrise-init/scanners/ios/ios.go b/go/src/github.com/bitrise-core/bitrise-init/scanners/ios/ios.go index 2945d858..d159acd4 100644 --- a/go/src/github.com/bitrise-core/bitrise-init/scanners/ios/ios.go +++ b/go/src/github.com/bitrise-core/bitrise-init/scanners/ios/ios.go @@ -3,6 +3,7 @@ package ios import ( "errors" "fmt" + "os" "path/filepath" "regexp" "sort" @@ -57,12 +58,6 @@ var ( scanFolderExtBlackList = []string{frameworkExt} ) -// SchemeModel ... -type SchemeModel struct { - Name string - HasTest bool -} - //-------------------------------------------------- // Utility //-------------------------------------------------- @@ -92,34 +87,56 @@ func isPathContainsComponentWithExtension(pth, ext string) bool { return false } -func isRelevantProject(pth string) bool { +func isDir(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 fileInf.IsDir(), nil +} + +func isRelevantProject(pth string, isTest bool) (bool, error) { + // xcodeproj & xcworkspace should be a dir + if !isTest { + if is, err := isDir(pth); err != nil { + return false, err + } else if !is { + return false, nil + } + } + for _, regexp := range scanProjectPathRegexpBlackList { if isPathMatchRegexp(pth, regexp) { - return false + return false, nil } } for _, folderName := range scanFolderNameBlackList { if isPathContainsComponent(pth, folderName) { - return false + return false, nil } } for _, folderExt := range scanFolderExtBlackList { if isPathContainsComponentWithExtension(pth, folderExt) { - return false + return false, nil } } - return true + return true, nil } -func filterXcodeprojectFiles(fileList []string) []string { +func filterXcodeprojectFiles(fileList []string, isTest bool) ([]string, error) { filteredFiles := utility.FilterFilesWithExtensions(fileList, xcodeprojExtension, xcworkspaceExtension) relevantFiles := []string{} for _, file := range filteredFiles { - if !isRelevantProject(file) { + if is, err := isRelevantProject(file, isTest); err != nil { + return []string{}, err + } else if !is { continue } @@ -128,7 +145,7 @@ func filterXcodeprojectFiles(fileList []string) []string { sort.Sort(utility.ByComponents(relevantFiles)) - return relevantFiles + return relevantFiles, nil } func isRelevantPodfile(pth string) bool { @@ -243,7 +260,10 @@ func (scanner *Scanner) DetectPlatform() (bool, error) { // Search for xcodeproj file log.Info("Searching for .xcodeproj & .xcworkspace files") - xcodeProjectFiles := filterXcodeprojectFiles(fileList) + xcodeProjectFiles, err := filterXcodeprojectFiles(fileList, false) + if err != nil { + return false, fmt.Errorf("failed to collect .xcodeproj & .xcworkspace files, error: %s", err) + } scanner.XcodeProjectAndWorkspaceFiles = xcodeProjectFiles log.Details("%d project file(s) detected", len(xcodeProjectFiles)) @@ -264,7 +284,8 @@ func (scanner *Scanner) DetectPlatform() (bool, error) { // Options ... func (scanner *Scanner) Options() (models.OptionModel, models.Warnings, error) { - // Check for Podfiles + // + // Create Pod workspace - project mapping log.Info("Searching for Podfiles") warnings := models.Warnings{} @@ -297,151 +318,266 @@ func (scanner *Scanner) Options() (models.OptionModel, models.Warnings, error) { log.Details("- %s -> %s", workspace, linkedProject) } } + // ----- + + // + // Separate projects and workspaces + log.Info("Separate projects and workspaces") + projects := []ProjectModel{} + workspaces := []WorkspaceModel{} + + for _, workspaceOrProjectPth := range scanner.XcodeProjectAndWorkspaceFiles { + if xcodeproj.IsXCodeProj(workspaceOrProjectPth) { + project := ProjectModel{Pth: workspaceOrProjectPth} + projects = append(projects, project) + } else { + workspace := WorkspaceModel{Pth: workspaceOrProjectPth} + workspaces = append(workspaces, workspace) + } + } + // ----- + + // + // Separate standalone projects, standalone workspaces and pod projects + standaloneProjects := []ProjectModel{} + standaloneWorkspaces := []WorkspaceModel{} + podProjects := []ProjectModel{} - // Remove CocoaPods workspaces - cleanProjectFiles := []string{} - for _, projectOrWorkspace := range scanner.XcodeProjectAndWorkspaceFiles { - // workspace will generated by CocoaPods - _, found := podfileWorkspaceProjectMap[projectOrWorkspace] + for _, project := range projects { + if !utility.MapStringStringHasValue(podfileWorkspaceProjectMap, project.Pth) { + standaloneProjects = append(standaloneProjects, project) + } + } + + log.Details("%d Standalone project(s) detected", len(standaloneProjects)) + for _, project := range standaloneProjects { + log.Details("- %s", project.Pth) + } + + for _, workspace := range workspaces { + if _, found := podfileWorkspaceProjectMap[workspace.Pth]; !found { + standaloneWorkspaces = append(standaloneWorkspaces, workspace) + } + } + + log.Details("%d Standalone workspace(s) detected", len(standaloneWorkspaces)) + for _, workspace := range standaloneWorkspaces { + log.Details("- %s", workspace.Pth) + } + + for podWorkspacePth, linkedProjectPth := range podfileWorkspaceProjectMap { + project, found := FindProjectWithPth(projects, linkedProjectPth) + if !found { + log.Warn("workspace mapping contains project (%s), but not found in project list", linkedProjectPth) + continue + } + + workspace, found := FindWorkspaceWithPth(workspaces, podWorkspacePth) if !found { - cleanProjectFiles = append(cleanProjectFiles, projectOrWorkspace) + workspace = WorkspaceModel{Pth: podWorkspacePth} } + + workspace.GeneratedByPod = true + + project.PodWorkspace = workspace + podProjects = append(podProjects, project) } - // Inspect Projects - configDescriptors := []ConfigDescriptor{} - projectPathOption := models.NewOptionModel(projectPathTitle, projectPathEnvKey) + log.Details("%d Pod project(s) detected", len(podProjects)) + for _, project := range podProjects { + log.Details("- %s -> %s", project.Pth, project.PodWorkspace.Pth) + } + // ----- - for _, project := range cleanProjectFiles { - isWorkspace := xcodeproj.IsXCWorkspace(project) + // + // Analyze projects and workspaces + analyzedProjects := []ProjectModel{} + analyzedWorkspaces := []WorkspaceModel{} - if isWorkspace { - log.Info("Inspecting workspace file: %s", project) - } else { - log.Info("Inspecting project file: %s", project) + for _, project := range standaloneProjects { + log.Info("Inspecting standalone project file: %s", project.Pth) + + schemes := []SchemeModel{} + + schemeXCtestMap, err := xcodeproj.ProjectSharedSchemes(project.Pth) + if err != nil { + log.Warn("Failed to get shared schemes, error: %s", err) + continue } - validProjectMap := map[string]bool{} - schemeXCTestMap := map[string]bool{} + log.Details("%d shared scheme(s) detected", len(schemeXCtestMap)) + for scheme, hasXCTest := range schemeXCtestMap { + log.Details("- %s", scheme) - missingSharedSchemes := false - hasPodFile := false + schemes = append(schemes, SchemeModel{Name: scheme, HasXCTest: hasXCTest, Shared: true}) + } + + if len(schemeXCtestMap) == 0 { + log.Details("") + log.Error("No shared schemes found, adding recreate-user-schemes step...") + log.Error("The newly generated schemes, may differs from the ones in your project.") + log.Error("Make sure to share your schemes, to have the expected behaviour.") + log.Details("") + + warnings = append(warnings, fmt.Sprintf("no shared scheme found for project: %s", project.Pth)) - // --- - if isWorkspace { - // Collect workspace shared scehemes - workspaceSchemeXCTestMap, err := xcodeproj.WorkspaceSharedSchemes(project) + targetXCTestMap, err := xcodeproj.ProjectTargets(project.Pth) if err != nil { - return models.OptionModel{}, models.Warnings{}, err + log.Warn("Failed to get targets, error: %s", err) + continue } - log.Details("%d workspace shared scheme(s) detected", len(workspaceSchemeXCTestMap)) - for scheme := range workspaceSchemeXCTestMap { - log.Details("- %s", scheme) + log.Warn("%d user scheme(s) will be generated", len(targetXCTestMap)) + for target, hasXCTest := range targetXCTestMap { + log.Warn("- %s", target) + + schemes = append(schemes, SchemeModel{Name: target, HasXCTest: hasXCTest, Shared: false}) } + } - if len(workspaceSchemeXCTestMap) == 0 { - log.Details("") - log.Error("No shared schemes found, adding recreate-user-schemes step...") - log.Error("The newly generated schemes, may differs from the ones in your project.") - log.Error("Make sure to share your schemes, to have the expected behaviour.") - log.Details("") + project.Schemes = schemes + analyzedProjects = append(analyzedProjects, project) + } - warnings = append(warnings, fmt.Sprintf("no shared scheme found for project: %s", project)) - missingSharedSchemes = true + for _, workspace := range standaloneWorkspaces { + log.Info("Inspecting standalone workspace file: %s", workspace.Pth) - targetXCTestMap, err := xcodeproj.WorkspaceTargets(project) - if err != nil { - return models.OptionModel{}, models.Warnings{}, err - } + schemes := []SchemeModel{} - log.Warn("%d user scheme(s) will be generated", len(targetXCTestMap)) - for target := range targetXCTestMap { - log.Warn("- %s", target) - } + schemeXCtestMap, err := xcodeproj.WorkspaceSharedSchemes(workspace.Pth) + if err != nil { + log.Warn("Failed to get shared schemes, error: %s", err) + continue + } - workspaceSchemeXCTestMap = targetXCTestMap - } + log.Details("%d shared scheme(s) detected", len(schemeXCtestMap)) + for scheme, hasXCTest := range schemeXCtestMap { + log.Details("- %s", scheme) - validProjectMap[project] = true - schemeXCTestMap = workspaceSchemeXCTestMap - } else { - found := utility.MapStringStringHasValue(podfileWorkspaceProjectMap, project) - if found { - // CocoaPods will generate a workspace for this project - hasPodFile = true - - for workspace, linkedProject := range podfileWorkspaceProjectMap { - if linkedProject == project { - log.Details("workspace (%s) will be generated by CocoaPods", workspace) - // We should use the generated workspace instead of the project - validProjectMap[workspace] = true - } - } - } else { - // Standalone project - validProjectMap[project] = true - } + schemes = append(schemes, SchemeModel{Name: scheme, HasXCTest: hasXCTest, Shared: true}) + } - projectSchemeXCtestMap, err := xcodeproj.ProjectSharedSchemes(project) + if len(schemeXCtestMap) == 0 { + log.Details("") + log.Error("No shared schemes found, adding recreate-user-schemes step...") + log.Error("The newly generated schemes, may differs from the ones in your project.") + log.Error("Make sure to share your schemes, to have the expected behaviour.") + log.Details("") + + warnings = append(warnings, fmt.Sprintf("no shared scheme found for project: %s", workspace.Pth)) + + targetXCTestMap, err := xcodeproj.WorkspaceTargets(workspace.Pth) if err != nil { - return models.OptionModel{}, models.Warnings{}, err + log.Warn("Failed to get targets, error: %s", err) + continue } - log.Details("%d project shared scheme(s) detected", len(projectSchemeXCtestMap)) - for scheme := range projectSchemeXCtestMap { - log.Details("- %s", scheme) + log.Warn("%d user scheme(s) will be generated", len(targetXCTestMap)) + for target, hasXCTest := range targetXCTestMap { + log.Warn("- %s", target) + + schemes = append(schemes, SchemeModel{Name: target, HasXCTest: hasXCTest, Shared: false}) } + } + + workspace.Schemes = schemes + analyzedWorkspaces = append(analyzedWorkspaces, workspace) + } + + for _, project := range podProjects { + log.Info("Inspecting pod project file: %s", project.Pth) + + schemes := []SchemeModel{} - if len(projectSchemeXCtestMap) == 0 { - log.Details("") - log.Error("No shared schemes found, adding recreate-user-schemes step...") - log.Error("The newly generated schemes, may differs from the ones in your project.") - log.Error("Make sure to share your schemes, to have the expected behaviour.") - log.Details("") + schemeXCtestMap, err := xcodeproj.ProjectSharedSchemes(project.Pth) + if err != nil { + log.Warn("Failed to get shared schemes, error: %s", err) + continue + } - warnings = append(warnings, fmt.Sprintf("no shared scheme found for project: %s", project)) - missingSharedSchemes = true + log.Details("%d shared scheme(s) detected", len(schemeXCtestMap)) + for scheme, hasXCTest := range schemeXCtestMap { + log.Details("- %s", scheme) - targetXCTestMap, err := xcodeproj.ProjectTargets(project) - if err != nil { - return models.OptionModel{}, models.Warnings{}, err - } + schemes = append(schemes, SchemeModel{Name: scheme, HasXCTest: hasXCTest, Shared: true}) + } + + if len(schemeXCtestMap) == 0 { + log.Details("") + log.Error("No shared schemes found, adding recreate-user-schemes step...") + log.Error("The newly generated schemes, may differs from the ones in your project.") + log.Error("Make sure to share your schemes, to have the expected behaviour.") + log.Details("") - log.Warn("%d user scheme(s) will be generated", len(targetXCTestMap)) - for target := range targetXCTestMap { - log.Warn("- %s", target) - } + warnings = append(warnings, fmt.Sprintf("no shared scheme found for project: %s", project.Pth)) - projectSchemeXCtestMap = targetXCTestMap + targetXCTestMap, err := xcodeproj.ProjectTargets(project.Pth) + if err != nil { + log.Warn("Failed to get targets, error: %s", err) + continue } - schemeXCTestMap = projectSchemeXCtestMap + log.Warn("%d user scheme(s) will be generated", len(targetXCTestMap)) + for target, hasXCTest := range targetXCTestMap { + log.Warn("- %s", target) + + schemes = append(schemes, SchemeModel{Name: target, HasXCTest: hasXCTest, Shared: false}) + } } - // --- - if len(schemeXCTestMap) == 0 { - return models.OptionModel{}, models.Warnings{}, errors.New("No shared schemes found, or failed to create user schemes") + project.PodWorkspace.Schemes = schemes + analyzedWorkspaces = append(analyzedWorkspaces, project.PodWorkspace) + } + // ----- + + // + // Create config descriptors + configDescriptors := []ConfigDescriptor{} + projectPathOption := models.NewOptionModel(projectPathTitle, projectPathEnvKey) + + for _, project := range analyzedProjects { + schemeOption := models.NewOptionModel(schemeTitle, schemeEnvKey) + + for _, scheme := range project.Schemes { + configDescriptor := ConfigDescriptor{ + HasPodfile: false, + HasTest: scheme.HasXCTest, + MissingSharedSchemes: !scheme.Shared, + } + configDescriptors = append(configDescriptors, configDescriptor) + + configOption := models.NewEmptyOptionModel() + configOption.Config = configDescriptor.String() + + schemeOption.ValueMap[scheme.Name] = configOption } - for validProject := range validProjectMap { - schemeOption := models.NewOptionModel(schemeTitle, schemeEnvKey) - for schemeName, hasXCtest := range schemeXCTestMap { - configDescriptor := ConfigDescriptor{ - HasPodfile: hasPodFile, - HasTest: hasXCtest, - MissingSharedSchemes: missingSharedSchemes, - } - configDescriptors = append(configDescriptors, configDescriptor) + projectPathOption.ValueMap[project.Pth] = schemeOption + } - configOption := models.NewEmptyOptionModel() - configOption.Config = configDescriptor.String() + for _, workspace := range analyzedWorkspaces { + schemeOption := models.NewOptionModel(schemeTitle, schemeEnvKey) - schemeOption.ValueMap[schemeName] = configOption + for _, scheme := range workspace.Schemes { + configDescriptor := ConfigDescriptor{ + HasPodfile: workspace.GeneratedByPod, + HasTest: scheme.HasXCTest, + MissingSharedSchemes: !scheme.Shared, } + configDescriptors = append(configDescriptors, configDescriptor) + + configOption := models.NewEmptyOptionModel() + configOption.Config = configDescriptor.String() - projectPathOption.ValueMap[validProject] = schemeOption + schemeOption.ValueMap[scheme.Name] = configOption } + + projectPathOption.ValueMap[workspace.Pth] = schemeOption + } + // ----- + + if len(configDescriptors) == 0 { + return models.OptionModel{}, warnings, errors.New("No valid config found") } scanner.configDescriptors = configDescriptors diff --git a/go/src/github.com/bitrise-core/bitrise-init/scanners/ios/ios_test.go b/go/src/github.com/bitrise-core/bitrise-init/scanners/ios/ios_test.go index 3a5ce947..a3602bb5 100644 --- a/go/src/github.com/bitrise-core/bitrise-init/scanners/ios/ios_test.go +++ b/go/src/github.com/bitrise-core/bitrise-init/scanners/ios/ios_test.go @@ -1,8 +1,11 @@ package ios import ( + "os" + "path" "testing" + "github.com/bitrise-io/go-utils/pathutil" "github.com/stretchr/testify/require" ) @@ -209,7 +212,9 @@ func TestIsRelevantProject(t *testing.T) { } for _, file := range fileList { - require.Equal(t, false, isRelevantProject(file)) + is, err := isRelevantProject(file, true) + require.NoError(t, err) + require.Equal(t, false, is) } } @@ -220,9 +225,29 @@ func TestIsRelevantProject(t *testing.T) { } for _, file := range fileList { - require.Equal(t, true, isRelevantProject(file)) + is, err := isRelevantProject(file, true) + require.NoError(t, err) + require.Equal(t, true, is) } } + + t.Log("symlink is not valid") + { + tmpDir, err := pathutil.NormalizedOSTempDirPath("test") + require.NoError(t, err) + + pth := path.Join(tmpDir, "SampleAppWithCocoapods.xcodeproj") + require.NoError(t, os.MkdirAll(pth, 0777)) + is, err := isRelevantProject(pth, false) + require.NoError(t, err) + require.Equal(t, true, is) + + symlinkPth := path.Join(tmpDir, "symlink-SampleAppWithCocoapods.xcodeproj") + require.NoError(t, os.Symlink(pth, symlinkPth)) + is, err = isRelevantProject(symlinkPth, false) + require.NoError(t, err) + require.Equal(t, false, is) + } } func TestFilterXcodeprojectFiles(t *testing.T) { @@ -237,7 +262,8 @@ func TestFilterXcodeprojectFiles(t *testing.T) { "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj", } - files := filterXcodeprojectFiles(fileList) + files, err := filterXcodeprojectFiles(fileList, true) + require.NoError(t, err) require.Equal(t, 1, len(files)) require.Equal(t, "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcodeproj", files[0]) } @@ -253,7 +279,8 @@ func TestFilterXcodeprojectFiles(t *testing.T) { "SampleAppWithCocoapods.xcodeproj", } - files := filterXcodeprojectFiles(fileList) + files, err := filterXcodeprojectFiles(fileList, true) + require.NoError(t, err) require.Equal(t, 1, len(files)) require.Equal(t, "SampleAppWithCocoapods.xcodeproj", files[0]) } @@ -265,7 +292,8 @@ func TestFilterXcodeprojectFiles(t *testing.T) { "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcworkspace", } - files := filterXcodeprojectFiles(fileList) + files, err := filterXcodeprojectFiles(fileList, true) + require.NoError(t, err) require.Equal(t, 2, len(files)) require.Equal(t, "/Users/bitrise/sample-apps-ios-cocoapods/SampleAppWithCocoapods.xcworkspace", files[0]) @@ -280,7 +308,8 @@ func TestFilterXcodeprojectFiles(t *testing.T) { "build.gradle", } - files := filterXcodeprojectFiles(fileList) + files, err := filterXcodeprojectFiles(fileList, true) + require.NoError(t, err) require.Equal(t, 0, len(files)) } } diff --git a/go/src/github.com/bitrise-core/bitrise-init/scanners/ios/models.go b/go/src/github.com/bitrise-core/bitrise-init/scanners/ios/models.go new file mode 100644 index 00000000..a8fa12e6 --- /dev/null +++ b/go/src/github.com/bitrise-core/bitrise-init/scanners/ios/models.go @@ -0,0 +1,44 @@ +package ios + +// SchemeModel ... +type SchemeModel struct { + Name string + Shared bool + HasXCTest bool +} + +// ProjectModel ... +type ProjectModel struct { + Pth string + Schemes []SchemeModel + + PodWorkspace WorkspaceModel +} + +// WorkspaceModel ... +type WorkspaceModel struct { + Pth string + Schemes []SchemeModel + + GeneratedByPod bool +} + +// FindProjectWithPth ... +func FindProjectWithPth(projects []ProjectModel, pth string) (ProjectModel, bool) { + for _, project := range projects { + if project.Pth == pth { + return project, true + } + } + return ProjectModel{}, false +} + +// FindWorkspaceWithPth ... +func FindWorkspaceWithPth(workspaces []WorkspaceModel, pth string) (WorkspaceModel, bool) { + for _, workspace := range workspaces { + if workspace.Pth == pth { + return workspace, true + } + } + return WorkspaceModel{}, false +} diff --git a/go/src/github.com/bitrise-core/bitrise-init/scanners/scanners.go b/go/src/github.com/bitrise-core/bitrise-init/scanners/scanners.go index 7b954085..3256c36e 100644 --- a/go/src/github.com/bitrise-core/bitrise-init/scanners/scanners.go +++ b/go/src/github.com/bitrise-core/bitrise-init/scanners/scanners.go @@ -36,6 +36,9 @@ func CustomConfig() (models.BitriseConfigMap, error) { // GitClone stepList = append(stepList, steps.GitCloneStepListItem()) + // Script + stepList = append(stepList, steps.ScriptSteplistItem()) + bitriseData := models.BitriseDataWithDefaultTriggerMapAndPrimaryWorkflowSteps(stepList) data, err := yaml.Marshal(bitriseData) if err != nil { diff --git a/go/src/github.com/bitrise-core/bitrise-init/steps/steps.go b/go/src/github.com/bitrise-core/bitrise-init/steps/steps.go index 65fbaa7d..1c740d10 100644 --- a/go/src/github.com/bitrise-core/bitrise-init/steps/steps.go +++ b/go/src/github.com/bitrise-core/bitrise-init/steps/steps.go @@ -10,10 +10,10 @@ import ( const ( // Common Step IDs activateSSHKeyID = "activate-ssh-key" - activateSSHKeyVersion = "3.1.0" + activateSSHKeyVersion = "3.1.1" gitCloneID = "git-clone" - gitCloneVersion = "3.3.0" + gitCloneVersion = "3.3.3" certificateAndProfileInstallerID = "certificate-and-profile-installer" certificateAndProfileInstallerVersion = "1.6.0" @@ -22,7 +22,7 @@ const ( deployToBitriseIoVersion = "1.2.4" scriptID = "script" - scriptVersion = "1.1.1" + scriptVersion = "1.1.2" // Android Step IDs gradleRunnerID = "gradle-runner" diff --git a/go/src/github.com/bitrise-core/bitrise-init/vendor/github.com/bitrise-io/go-utils/cmdex/run.go b/go/src/github.com/bitrise-core/bitrise-init/vendor/github.com/bitrise-io/go-utils/cmdex/run.go index 554e27af..cec96ae0 100644 --- a/go/src/github.com/bitrise-core/bitrise-init/vendor/github.com/bitrise-io/go-utils/cmdex/run.go +++ b/go/src/github.com/bitrise-core/bitrise-init/vendor/github.com/bitrise-io/go-utils/cmdex/run.go @@ -1,6 +1,7 @@ package cmdex import ( + "errors" "fmt" "io" "os" @@ -25,6 +26,17 @@ func NewCommand(name string, args ...string) *CommandModel { } } +// NewCommandFromSlice ... +func NewCommandFromSlice(cmdSlice []string) (*CommandModel, error) { + if len(cmdSlice) == 0 { + return nil, errors.New("no command provided") + } else if len(cmdSlice) == 1 { + return NewCommand(cmdSlice[0]), nil + } + + return NewCommand(cmdSlice[0], cmdSlice[1:]...), nil +} + // NewCommandWithCmd ... func NewCommandWithCmd(cmd *exec.Cmd) *CommandModel { return &CommandModel{ diff --git a/go/src/github.com/bitrise-core/bitrise-init/vendor/github.com/bitrise-io/xcode-utils/xcodeproj/xcodeproj.go b/go/src/github.com/bitrise-core/bitrise-init/vendor/github.com/bitrise-io/xcode-utils/xcodeproj/xcodeproj.go index 7169a0c8..38722c12 100644 --- a/go/src/github.com/bitrise-core/bitrise-init/vendor/github.com/bitrise-io/xcode-utils/xcodeproj/xcodeproj.go +++ b/go/src/github.com/bitrise-core/bitrise-init/vendor/github.com/bitrise-io/xcode-utils/xcodeproj/xcodeproj.go @@ -499,13 +499,60 @@ func sharedSchemes(projectOrWorkspacePth string) (map[string]bool, error) { } func schemeFileContentContainsXCTestBuildAction(schemeFileContent string) (bool, error) { - regexpPattern := `BuildableName = ".+.xctest"` - regexp := regexp.MustCompile(regexpPattern) + testActionStartPattern := ".+)"`) + testableReferenceEndPattern := "" + isTestableReference := false + + xctestBuildableReferenceNameRegexp := regexp.MustCompile(`BuildableName = ".+.xctest"`) scanner := bufio.NewScanner(strings.NewReader(schemeFileContent)) for scanner.Scan() { line := scanner.Text() - if regexp.FindString(line) != "" { + + 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 } } @@ -517,78 +564,297 @@ func schemeFileContentContainsXCTestBuildAction(schemeFileContent string) (bool, return false, nil } -func pbxprojContentTartgets(pbxprojContent string) (map[string]bool, error) { - nativeTargetSectionStart := "/* Begin PBXNativeTarget section */" - nativeTargetSectionEnd := "/* End PBXNativeTarget section */" +// PBXTargetDependency ... +type PBXTargetDependency struct { + id string + isa string + target string +} - targetStartRegexpPattern := `\s*[A-Z0-9]+ /\* .* \*/ = {` - targetStartRegexp := regexp.MustCompile(targetStartRegexpPattern) - targetEnd := "};" +func parsePBXTargetDependencies(pbxprojContent string) ([]PBXTargetDependency, error) { + pbxTargetDependencies := []PBXTargetDependency{} - xcTestRegexpPattern := `\s*productReference = .* /\* .*.xctest \*/;` - xcTestRegexp := regexp.MustCompile(xcTestRegexpPattern) + id := "" + isa := "" + target := "" - nameRegexpPattern := `\s*name = (?P.+);` - nameRegexp := regexp.MustCompile(nameRegexpPattern) + beginPBXTargetDependencySectionPattern := `/* Begin PBXTargetDependency section */` + endPBXTargetDependencySectionPattern := `/* End PBXTargetDependency section */` + isPBXTargetDependencySection := false - isTargetSection := false - isTarget := false + // BAAFFEEF19EE788800F3AC91 /* PBXTargetDependency */ = { + beginPBXTargetDependencyRegexp := regexp.MustCompile(`\s*(?P[A-Z0-9]+) /\* (?P.*) \*/ = {`) + endPBXTargetDependencyPattern := `};` + isPBXTargetDependency := false - targetMap := map[string]bool{} - targetName := "" - targetHasXCTest := 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 +} + +// PBXNativeTarget ... +type PBXNativeTarget struct { + id string + isa string + dependencies []string + name string + productPath string + productType string +} + +func parsePBXNativeTargets(pbxprojContent string) ([]PBXNativeTarget, error) { + pbxNativeTargets := []PBXNativeTarget{} + + id := "" + isa := "" + dependencies := []string{} + name := "" + productPath := "" + productType := "" + + beginPBXNativeTargetSectionPattern := `/* Begin PBXNativeTarget section */` + endPBXNativeTargetSectionPattern := `/* End PBXNativeTarget section */` + isPBXNativeTargetSection := false + + // BAAFFED019EE788800F3AC91 /* SampleAppWithCocoapods */ = { + beginPBXNativeTargetRegexp := regexp.MustCompile(`\s*(?P[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() - // End PBXNativeTarget section - if strings.TrimSpace(line) == nativeTargetSectionEnd { + if strings.TrimSpace(line) == endPBXNativeTargetSectionPattern { break } - // Begin PBXNativeTarget section - if strings.TrimSpace(line) == nativeTargetSectionStart { - isTargetSection = true + if strings.TrimSpace(line) == beginPBXNativeTargetSectionPattern { + isPBXNativeTargetSection = true continue } - if !isTargetSection { + if !isPBXNativeTargetSection { continue } - if strings.TrimSpace(line) == targetEnd { - isTarget = false + // PBXNativeTarget section - targetMap[targetName] = targetHasXCTest + if strings.TrimSpace(line) == endPBXNativeTargetPattern { + pbxNativeTarget := PBXNativeTarget{ + id: id, + isa: isa, + dependencies: dependencies, + name: name, + productPath: productPath, + productType: productType, + } + pbxNativeTargets = append(pbxNativeTargets, pbxNativeTarget) - targetName = "" - targetHasXCTest = false + id = "" + isa = "" + name = "" + productPath = "" + productType = "" + dependencies = []string{} + isPBXNativeTarget = false continue } - if targetStartRegexp.FindString(line) != "" { - isTarget = true + if matches := beginPBXNativeTargetRegexp.FindStringSubmatch(line); len(matches) == 3 { + id = matches[1] + name = matches[2] + + isPBXNativeTarget = true + continue } - if !isTarget { + if !isPBXNativeTarget { continue } - if match := nameRegexp.FindStringSubmatch(line); len(match) == 2 { - targetName = match[1] - targetName = strings.Trim(targetName, `"`) + // 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 match := xcTestRegexp.FindString(line); match != "" { - targetHasXCTest = true + 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 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) (map[string]bool, error) { + targetMap := map[string]bool{} + + targets, err := parsePBXNativeTargets(pbxprojContent) + if err != nil { return map[string]bool{}, err } + targetDependencies, err := parsePBXTargetDependencies(pbxprojContent) + if err != nil { + return map[string]bool{}, err + } + + for _, target := range targets { + 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(targets, dependency.target) + if found { + targetMap[dependentTarget.name] = true + } + } + } + } + } + + _, found := targetMap[target.name] + if !found { + targetMap[target.name] = false + } + } + return targetMap, nil } diff --git a/go/src/github.com/bitrise-core/bitrise-init/vendor/github.com/bitrise-io/xcode-utils/xcodeproj/xcodeproj_test_file_contents.go b/go/src/github.com/bitrise-core/bitrise-init/vendor/github.com/bitrise-io/xcode-utils/xcodeproj/xcodeproj_test_file_contents.go new file mode 100644 index 00000000..2adfd13e --- /dev/null +++ b/go/src/github.com/bitrise-core/bitrise-init/vendor/github.com/bitrise-io/xcode-utils/xcodeproj/xcodeproj_test_file_contents.go @@ -0,0 +1,349 @@ +package xcodeproj + +const ( + schemeContentWithXCTestBuildAction = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +` + + schemeContentWithoutXCTestBuildAction = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +` + + pbxNativeTargetSectionWithSpace = `/* Begin PBXTargetDependency section */ + BADDFA051A703F87004C3526 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BADDF9E61A703F87004C3526 /* BitriseSampleAppsiOS With Spaces */; + targetProxy = BADDFA041A703F87004C3526 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXNativeTarget section */ + BADDF9E61A703F87004C3526 /* BitriseSampleAppsiOS With Spaces */ = { + isa = PBXNativeTarget; + buildConfigurationList = BADDFA0D1A703F87004C3526 /* Build configuration list for PBXNativeTarget "BitriseSampleAppsiOS With Spaces" */; + buildPhases = ( + BADDF9E31A703F87004C3526 /* Sources */, + BADDF9E41A703F87004C3526 /* Frameworks */, + BADDF9E51A703F87004C3526 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "BitriseSampleAppsiOS With Spaces"; + productName = "BitriseSampleAppsiOS With Spaces"; + productReference = BADDF9E71A703F87004C3526 /* BitriseSampleAppsiOS With Spaces.app */; + productType = "com.apple.product-type.application"; + }; + BADDFA021A703F87004C3526 /* BitriseSampleAppsiOS With SpacesTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = BADDFA101A703F87004C3526 /* Build configuration list for PBXNativeTarget "BitriseSampleAppsiOS With SpacesTests" */; + buildPhases = ( + BADDF9FF1A703F87004C3526 /* Sources */, + BADDFA001A703F87004C3526 /* Frameworks */, + BADDFA011A703F87004C3526 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + BADDFA051A703F87004C3526 /* PBXTargetDependency */, + ); + name = "BitriseSampleAppsiOS With SpacesTests"; + productName = "BitriseSampleAppsiOS With SpacesTests"; + productReference = BADDFA031A703F87004C3526 /* BitriseSampleAppsiOS With SpacesTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ +` + + pbxProjContentChunk = `// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXTargetDependency section */ + BAAFFEEF19EE788800F3AC91 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BAAFFED019EE788800F3AC91 /* SampleAppWithCocoapods */; + targetProxy = BAAFFEEE19EE788800F3AC91 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXNativeTarget section */ + BAAFFED019EE788800F3AC91 /* SampleAppWithCocoapods */ = { + isa = PBXNativeTarget; + buildConfigurationList = BAAFFEF719EE788800F3AC91 /* Build configuration list for PBXNativeTarget "SampleAppWithCocoapods" */; + buildPhases = ( + BAAFFECD19EE788800F3AC91 /* Sources */, + BAAFFECE19EE788800F3AC91 /* Frameworks */, + BAAFFECF19EE788800F3AC91 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SampleAppWithCocoapods; + productName = SampleAppWithCocoapods; + productReference = BAAFFED119EE788800F3AC91 /* SampleAppWithCocoapods.app */; + productType = "com.apple.product-type.application"; + }; + BAAFFEEC19EE788800F3AC91 /* SampleAppWithCocoapodsTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = BAAFFEFA19EE788800F3AC91 /* Build configuration list for PBXNativeTarget "SampleAppWithCocoapodsTests" */; + buildPhases = ( + 75ACE584234D974D15C5CAE9 /* Check Pods Manifest.lock */, + BAAFFEE919EE788800F3AC91 /* Sources */, + BAAFFEEA19EE788800F3AC91 /* Frameworks */, + BAAFFEEB19EE788800F3AC91 /* Resources */, + D0F06DBF2FED4262AA6DE7DB /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + BAAFFEEF19EE788800F3AC91 /* PBXTargetDependency */, + ); + name = SampleAppWithCocoapodsTests; + productName = SampleAppWithCocoapodsTests; + productReference = BAAFFEED19EE788800F3AC91 /* SampleAppWithCocoapodsTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXVariantGroup section */ + BAAFFEE119EE788800F3AC91 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + BAAFFEE219EE788800F3AC91 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + BAAFFEE619EE788800F3AC91 /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + BAAFFEE719EE788800F3AC91 /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + + rootObject = BAAFFEC919EE788800F3AC91 /* Project object */; +} +` + + pbxTargetDependencies = ` + /* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + BAAFFEEF19EE788800F3AC91 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BAAFFED019EE788800F3AC91 /* SampleAppWithCocoapods */; + targetProxy = BAAFFEEE19EE788800F3AC91 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ +` +) diff --git a/go/src/github.com/bitrise-core/bitrise-init/version/version.go b/go/src/github.com/bitrise-core/bitrise-init/version/version.go index 4e34a18e..02bcc15f 100644 --- a/go/src/github.com/bitrise-core/bitrise-init/version/version.go +++ b/go/src/github.com/bitrise-core/bitrise-init/version/version.go @@ -1,4 +1,4 @@ package version // VERSION ... -const VERSION = "0.9.11" +const VERSION = "0.9.12"