diff --git a/_tests/integration/manual_config_test.go b/_tests/integration/manual_config_test.go index 29f9cf88..87022f40 100644 --- a/_tests/integration/manual_config_test.go +++ b/_tests/integration/manual_config_test.go @@ -502,7 +502,7 @@ var customConfigResultYML = fmt.Sprintf(`options: env_key: PROJECT_LOCATION type: user_input value_map: - "": + android: title: Module summary: Modules provide a container for your Android project's source code, resource files, and app level settings, such as the module-level @@ -512,14 +512,14 @@ var customConfigResultYML = fmt.Sprintf(`options: env_key: MODULE type: user_input value_map: - "": + app: title: Variant summary: Your Android build variant. You can add variants at any time, as well as further configure your existing variants later. env_key: VARIANT type: user_input_optional value_map: - "": + Debug: title: Project or Workspace path summary: The location of your Xcode project or Xcode workspace files, stored as an Environment Variable. In your Workflows, @@ -1239,6 +1239,8 @@ configs: - android-build@%s: inputs: - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT - xcode-archive@%s: inputs: - project_path: $BITRISE_PROJECT_PATH diff --git a/_tests/integration/reactnative_test.go b/_tests/integration/reactnative_test.go index a3f92ce4..acae842a 100644 --- a/_tests/integration/reactnative_test.go +++ b/_tests/integration/reactnative_test.go @@ -45,6 +45,21 @@ func TestReactNative(t *testing.T) { const simpleSample = "https://github.com/bitrise-samples/sample-apps-react-native-ios-and-android.git" + t.Log("joplin") + { + sampleAppDir := filepath.Join(tmpDir, "joplin") + gitClone(t, sampleAppDir, "https://github.com/bitrise-io/joplin.git") + + 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, "joplin", strings.TrimSpace(sampleAppsReactNativeJoplinResultYML), strings.TrimSpace(result)) + } + t.Log("sample-apps-react-native-ios-and-android") { sampleAppDir := filepath.Join(tmpDir, "sample-apps-react-native-ios-and-android") @@ -147,81 +162,90 @@ var sampleAppsReactNativeSubdirVersions = []interface{}{ var sampleAppsReactNativeSubdirResultYML = fmt.Sprintf(`options: react-native: - title: The root directory of an Android project - summary: The root directory of your Android project, stored as an Environment - Variable. In your Workflows, you can specify paths relative to this path. You - can change this at any time. - env_key: PROJECT_LOCATION + title: React Native project directory + summary: Path of the directory containing the project's `+"`package.json`"+` file. + env_key: WORKDIR type: selector value_map: - project/android: - title: Module - summary: Modules provide a container for your Android project's source code, - resource files, and app level settings, such as the module-level build file - and Android manifest file. Each module can be independently built, tested, - and debugged. You can add new modules to your Bitrise builds at any time. - env_key: MODULE - type: user_input + project: + title: The root directory of an Android project + summary: The root directory of your Android project, stored as an Environment + Variable. In your Workflows, you can specify paths relative to this path. + You can change this at any time. + env_key: PROJECT_LOCATION + type: selector value_map: - app: - title: Variant - summary: Your Android build variant. You can add variants at any time, - as well as further configure your existing variants later. - env_key: VARIANT - type: user_input_optional + project/android: + title: Module + summary: Modules provide a container for your Android project's source + code, resource files, and app level settings, such as the module-level + build file and Android manifest file. Each module can be independently + built, tested, and debugged. You can add new modules to your Bitrise + builds at any time. + env_key: MODULE + type: user_input value_map: - "": - title: Project or Workspace path - summary: The location of your Xcode project or Xcode workspace files, - stored as an Environment Variable. In your Workflows, you can specify - paths relative to this path. - env_key: BITRISE_PROJECT_PATH - type: selector + app: + title: Variant + summary: Your Android build variant. You can add variants at any time, + as well as further configure your existing variants later. + env_key: VARIANT + type: user_input_optional value_map: - project/ios/SampleAppsReactNativeAndroid.xcodeproj: - title: Scheme name - summary: An Xcode scheme defines a collection of targets to build, - a configuration to use when building, and a collection of tests - to execute. Only shared schemes are detected automatically but - you can use any scheme as a target on Bitrise. You can change - the scheme at any time in your Env Vars. - env_key: BITRISE_SCHEME + Debug: + title: Project or Workspace path + summary: The location of your Xcode project or Xcode workspace + files, stored as an Environment Variable. In your Workflows, + you can specify paths relative to this path. + env_key: BITRISE_PROJECT_PATH type: selector value_map: - SampleAppsReactNativeAndroid: - title: Distribution method - summary: The export method used to create an .ipa file in - your builds, stored as an Environment Variable. You can - change this at any time, or even create several .ipa files - with different export methods in the same build. - env_key: BITRISE_DISTRIBUTION_METHOD - type: selector - 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: Distribution method - summary: The export method used to create an .ipa file in - your builds, stored as an Environment Variable. You can - change this at any time, or even create several .ipa files - with different export methods in the same build. - env_key: BITRISE_DISTRIBUTION_METHOD + project/ios/SampleAppsReactNativeAndroid.xcodeproj: + title: Scheme name + summary: An Xcode scheme defines a collection of targets to + build, a configuration to use when building, and a collection + of tests to execute. Only shared schemes are detected automatically + but you can use any scheme as a target on Bitrise. You can + change the scheme at any time in your Env Vars. + env_key: BITRISE_SCHEME type: selector 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: + title: Distribution method + summary: The export method used to create an .ipa file + in your builds, stored as an Environment Variable. You + can change this at any time, or even create several + .ipa files with different export methods in the same + build. + env_key: BITRISE_DISTRIBUTION_METHOD + type: selector + 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: Distribution method + summary: The export method used to create an .ipa file + in your builds, stored as an Environment Variable. You + can change this at any time, or even create several + .ipa files with different export methods in the same + build. + env_key: BITRISE_DISTRIBUTION_METHOD + type: selector + 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: | @@ -246,11 +270,11 @@ configs: - git-clone@%s: {} - npm@%s: inputs: - - workdir: project + - workdir: $WORKDIR - command: install - npm@%s: inputs: - - workdir: project + - workdir: $WORKDIR - command: test - install-missing-android-tools@%s: inputs: @@ -258,6 +282,8 @@ configs: - android-build@%s: inputs: - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT - xcode-archive@%s: inputs: - project_path: $BITRISE_PROJECT_PATH @@ -277,11 +303,11 @@ configs: - git-clone@%s: {} - npm@%s: inputs: - - workdir: project + - workdir: $WORKDIR - command: install - npm@%s: inputs: - - workdir: project + - workdir: $WORKDIR - command: test - deploy-to-bitrise-io@%s: {} warnings: @@ -311,81 +337,90 @@ var sampleAppsReactNativeIosAndAndroidVersions = []interface{}{ var sampleAppsReactNativeIosAndAndroidResultYML = fmt.Sprintf(`options: react-native: - title: The root directory of an Android project - summary: The root directory of your Android project, stored as an Environment - Variable. In your Workflows, you can specify paths relative to this path. You - can change this at any time. - env_key: PROJECT_LOCATION + title: React Native project directory + summary: Path of the directory containing the project's `+"`package.json`"+` file. + env_key: WORKDIR type: selector value_map: - android: - title: Module - summary: Modules provide a container for your Android project's source code, - resource files, and app level settings, such as the module-level build file - and Android manifest file. Each module can be independently built, tested, - and debugged. You can add new modules to your Bitrise builds at any time. - env_key: MODULE - type: user_input + .: + title: The root directory of an Android project + summary: The root directory of your Android project, stored as an Environment + Variable. In your Workflows, you can specify paths relative to this path. + You can change this at any time. + env_key: PROJECT_LOCATION + type: selector value_map: - app: - title: Variant - summary: Your Android build variant. You can add variants at any time, - as well as further configure your existing variants later. - env_key: VARIANT - type: user_input_optional + android: + title: Module + summary: Modules provide a container for your Android project's source + code, resource files, and app level settings, such as the module-level + build file and Android manifest file. Each module can be independently + built, tested, and debugged. You can add new modules to your Bitrise + builds at any time. + env_key: MODULE + type: user_input value_map: - "": - title: Project or Workspace path - summary: The location of your Xcode project or Xcode workspace files, - stored as an Environment Variable. In your Workflows, you can specify - paths relative to this path. - env_key: BITRISE_PROJECT_PATH - type: selector + app: + title: Variant + summary: Your Android build variant. You can add variants at any time, + as well as further configure your existing variants later. + env_key: VARIANT + type: user_input_optional value_map: - ios/SampleAppsReactNativeAndroid.xcodeproj: - title: Scheme name - summary: An Xcode scheme defines a collection of targets to build, - a configuration to use when building, and a collection of tests - to execute. Only shared schemes are detected automatically but - you can use any scheme as a target on Bitrise. You can change - the scheme at any time in your Env Vars. - env_key: BITRISE_SCHEME + Debug: + title: Project or Workspace path + summary: The location of your Xcode project or Xcode workspace + files, stored as an Environment Variable. In your Workflows, + you can specify paths relative to this path. + env_key: BITRISE_PROJECT_PATH type: selector value_map: - SampleAppsReactNativeAndroid: - title: Distribution method - summary: The export method used to create an .ipa file in - your builds, stored as an Environment Variable. You can - change this at any time, or even create several .ipa files - with different export methods in the same build. - env_key: BITRISE_DISTRIBUTION_METHOD + ios/SampleAppsReactNativeAndroid.xcodeproj: + title: Scheme name + summary: An Xcode scheme defines a collection of targets to + build, a configuration to use when building, and a collection + of tests to execute. Only shared schemes are detected automatically + but you can use any scheme as a target on Bitrise. You can + change the scheme at any time in your Env Vars. + env_key: BITRISE_SCHEME type: selector 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: Distribution method - summary: The export method used to create an .ipa file in - your builds, stored as an Environment Variable. You can - change this at any time, or even create several .ipa files - with different export methods in the same build. - env_key: BITRISE_DISTRIBUTION_METHOD - type: selector - 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: + title: Distribution method + summary: The export method used to create an .ipa file + in your builds, stored as an Environment Variable. You + can change this at any time, or even create several + .ipa files with different export methods in the same + build. + env_key: BITRISE_DISTRIBUTION_METHOD + type: selector + 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: Distribution method + summary: The export method used to create an .ipa file + in your builds, stored as an Environment Variable. You + can change this at any time, or even create several + .ipa files with different export methods in the same + build. + env_key: BITRISE_DISTRIBUTION_METHOD + type: selector + 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: | @@ -410,9 +445,11 @@ configs: - git-clone@%s: {} - npm@%s: inputs: + - workdir: $WORKDIR - command: install - npm@%s: inputs: + - workdir: $WORKDIR - command: test - install-missing-android-tools@%s: inputs: @@ -420,6 +457,8 @@ configs: - android-build@%s: inputs: - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT - xcode-archive@%s: inputs: - project_path: $BITRISE_PROJECT_PATH @@ -439,9 +478,11 @@ configs: - git-clone@%s: {} - npm@%s: inputs: + - workdir: $WORKDIR - command: install - npm@%s: inputs: + - workdir: $WORKDIR - command: test - deploy-to-bitrise-io@%s: {} warnings: @@ -469,81 +510,90 @@ var sampleAppsReactNativeIosAndAndroidNoTestVersions = []interface{}{ var sampleAppsReactNativeIosAndAndroidNoTestResultYML = fmt.Sprintf(`options: react-native: - title: The root directory of an Android project - summary: The root directory of your Android project, stored as an Environment - Variable. In your Workflows, you can specify paths relative to this path. You - can change this at any time. - env_key: PROJECT_LOCATION + title: React Native project directory + summary: Path of the directory containing the project's `+"`package.json`"+` file. + env_key: WORKDIR type: selector value_map: - android: - title: Module - summary: Modules provide a container for your Android project's source code, - resource files, and app level settings, such as the module-level build file - and Android manifest file. Each module can be independently built, tested, - and debugged. You can add new modules to your Bitrise builds at any time. - env_key: MODULE - type: user_input + .: + title: The root directory of an Android project + summary: The root directory of your Android project, stored as an Environment + Variable. In your Workflows, you can specify paths relative to this path. + You can change this at any time. + env_key: PROJECT_LOCATION + type: selector value_map: - app: - title: Variant - summary: Your Android build variant. You can add variants at any time, - as well as further configure your existing variants later. - env_key: VARIANT - type: user_input_optional + android: + title: Module + summary: Modules provide a container for your Android project's source + code, resource files, and app level settings, such as the module-level + build file and Android manifest file. Each module can be independently + built, tested, and debugged. You can add new modules to your Bitrise + builds at any time. + env_key: MODULE + type: user_input value_map: - "": - title: Project or Workspace path - summary: The location of your Xcode project or Xcode workspace files, - stored as an Environment Variable. In your Workflows, you can specify - paths relative to this path. - env_key: BITRISE_PROJECT_PATH - type: selector + app: + title: Variant + summary: Your Android build variant. You can add variants at any time, + as well as further configure your existing variants later. + env_key: VARIANT + type: user_input_optional value_map: - ios/SampleAppsReactNativeAndroid.xcodeproj: - title: Scheme name - summary: An Xcode scheme defines a collection of targets to build, - a configuration to use when building, and a collection of tests - to execute. Only shared schemes are detected automatically but - you can use any scheme as a target on Bitrise. You can change - the scheme at any time in your Env Vars. - env_key: BITRISE_SCHEME + Debug: + title: Project or Workspace path + summary: The location of your Xcode project or Xcode workspace + files, stored as an Environment Variable. In your Workflows, + you can specify paths relative to this path. + env_key: BITRISE_PROJECT_PATH type: selector value_map: - SampleAppsReactNativeAndroid: - title: Distribution method - summary: The export method used to create an .ipa file in - your builds, stored as an Environment Variable. You can - change this at any time, or even create several .ipa files - with different export methods in the same build. - env_key: BITRISE_DISTRIBUTION_METHOD - type: selector - value_map: - ad-hoc: - config: react-native-android-ios-config - app-store: - config: react-native-android-ios-config - development: - config: react-native-android-ios-config - enterprise: - config: react-native-android-ios-config - SampleAppsReactNativeAndroid-tvOS: - title: Distribution method - summary: The export method used to create an .ipa file in - your builds, stored as an Environment Variable. You can - change this at any time, or even create several .ipa files - with different export methods in the same build. - env_key: BITRISE_DISTRIBUTION_METHOD + ios/SampleAppsReactNativeAndroid.xcodeproj: + title: Scheme name + summary: An Xcode scheme defines a collection of targets to + build, a configuration to use when building, and a collection + of tests to execute. Only shared schemes are detected automatically + but you can use any scheme as a target on Bitrise. You can + change the scheme at any time in your Env Vars. + env_key: BITRISE_SCHEME type: selector value_map: - ad-hoc: - config: react-native-android-ios-config - app-store: - config: react-native-android-ios-config - development: - config: react-native-android-ios-config - enterprise: - config: react-native-android-ios-config + SampleAppsReactNativeAndroid: + title: Distribution method + summary: The export method used to create an .ipa file + in your builds, stored as an Environment Variable. You + can change this at any time, or even create several + .ipa files with different export methods in the same + build. + env_key: BITRISE_DISTRIBUTION_METHOD + type: selector + value_map: + ad-hoc: + config: react-native-android-ios-config + app-store: + config: react-native-android-ios-config + development: + config: react-native-android-ios-config + enterprise: + config: react-native-android-ios-config + SampleAppsReactNativeAndroid-tvOS: + title: Distribution method + summary: The export method used to create an .ipa file + in your builds, stored as an Environment Variable. You + can change this at any time, or even create several + .ipa files with different export methods in the same + build. + env_key: BITRISE_DISTRIBUTION_METHOD + type: selector + value_map: + ad-hoc: + config: react-native-android-ios-config + app-store: + config: react-native-android-ios-config + development: + config: react-native-android-ios-config + enterprise: + config: react-native-android-ios-config configs: react-native: react-native-android-ios-config: | @@ -568,6 +618,7 @@ configs: - git-clone@%s: {} - npm@%s: inputs: + - workdir: $WORKDIR - command: install - install-missing-android-tools@%s: inputs: @@ -575,6 +626,8 @@ configs: - android-build@%s: inputs: - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT - xcode-archive@%s: inputs: - project_path: $BITRISE_PROJECT_PATH @@ -595,6 +648,7 @@ configs: - git-clone@%s: {} - npm@%s: inputs: + - workdir: $WORKDIR - command: install - deploy-to-bitrise-io@%s: {} warnings: @@ -624,84 +678,93 @@ var sampleAppsReactNativeIosAndAndroidYarnVersions = []interface{}{ var sampleAppsReactNativeIosAndAndroidYarnResultYML = fmt.Sprintf(`options: react-native: - title: The root directory of an Android project - summary: The root directory of your Android project, stored as an Environment - Variable. In your Workflows, you can specify paths relative to this path. You - can change this at any time. - env_key: PROJECT_LOCATION + title: React Native project directory + summary: Path of the directory containing the project's `+"`package.json`"+` file. + env_key: WORKDIR type: selector value_map: - android: - title: Module - summary: Modules provide a container for your Android project's source code, - resource files, and app level settings, such as the module-level build file - and Android manifest file. Each module can be independently built, tested, - and debugged. You can add new modules to your Bitrise builds at any time. - env_key: MODULE - type: user_input + .: + title: The root directory of an Android project + summary: The root directory of your Android project, stored as an Environment + Variable. In your Workflows, you can specify paths relative to this path. + You can change this at any time. + env_key: PROJECT_LOCATION + type: selector value_map: - app: - title: Variant - summary: Your Android build variant. You can add variants at any time, - as well as further configure your existing variants later. - env_key: VARIANT - type: user_input_optional + android: + title: Module + summary: Modules provide a container for your Android project's source + code, resource files, and app level settings, such as the module-level + build file and Android manifest file. Each module can be independently + built, tested, and debugged. You can add new modules to your Bitrise + builds at any time. + env_key: MODULE + type: user_input value_map: - "": - title: Project or Workspace path - summary: The location of your Xcode project or Xcode workspace files, - stored as an Environment Variable. In your Workflows, you can specify - paths relative to this path. - env_key: BITRISE_PROJECT_PATH - type: selector + app: + title: Variant + summary: Your Android build variant. You can add variants at any time, + as well as further configure your existing variants later. + env_key: VARIANT + type: user_input_optional value_map: - ios/SampleAppsReactNativeAndroid.xcodeproj: - title: Scheme name - summary: An Xcode scheme defines a collection of targets to build, - a configuration to use when building, and a collection of tests - to execute. Only shared schemes are detected automatically but - you can use any scheme as a target on Bitrise. You can change - the scheme at any time in your Env Vars. - env_key: BITRISE_SCHEME + Debug: + title: Project or Workspace path + summary: The location of your Xcode project or Xcode workspace + files, stored as an Environment Variable. In your Workflows, + you can specify paths relative to this path. + env_key: BITRISE_PROJECT_PATH type: selector value_map: - SampleAppsReactNativeAndroid: - title: Distribution method - summary: The export method used to create an .ipa file in - your builds, stored as an Environment Variable. You can - change this at any time, or even create several .ipa files - with different export methods in the same build. - env_key: BITRISE_DISTRIBUTION_METHOD + ios/SampleAppsReactNativeAndroid.xcodeproj: + title: Scheme name + summary: An Xcode scheme defines a collection of targets to + build, a configuration to use when building, and a collection + of tests to execute. Only shared schemes are detected automatically + but you can use any scheme as a target on Bitrise. You can + change the scheme at any time in your Env Vars. + env_key: BITRISE_SCHEME type: selector 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: Distribution method - summary: The export method used to create an .ipa file in - your builds, stored as an Environment Variable. You can - change this at any time, or even create several .ipa files - with different export methods in the same build. - env_key: BITRISE_DISTRIBUTION_METHOD - type: selector - 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: + title: Distribution method + summary: The export method used to create an .ipa file + in your builds, stored as an Environment Variable. You + can change this at any time, or even create several + .ipa files with different export methods in the same + build. + env_key: BITRISE_DISTRIBUTION_METHOD + type: selector + value_map: + ad-hoc: + config: react-native-android-ios-test-yarn-config + app-store: + config: react-native-android-ios-test-yarn-config + development: + config: react-native-android-ios-test-yarn-config + enterprise: + config: react-native-android-ios-test-yarn-config + SampleAppsReactNativeAndroid-tvOS: + title: Distribution method + summary: The export method used to create an .ipa file + in your builds, stored as an Environment Variable. You + can change this at any time, or even create several + .ipa files with different export methods in the same + build. + env_key: BITRISE_DISTRIBUTION_METHOD + type: selector + value_map: + ad-hoc: + config: react-native-android-ios-test-yarn-config + app-store: + config: react-native-android-ios-test-yarn-config + development: + config: react-native-android-ios-test-yarn-config + enterprise: + config: react-native-android-ios-test-yarn-config configs: react-native: - react-native-android-ios-test-config: | + react-native-android-ios-test-yarn-config: | format_version: "%s" default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git project_type: react-native @@ -723,9 +786,11 @@ configs: - git-clone@%s: {} - yarn@%s: inputs: + - workdir: $WORKDIR - command: install - yarn@%s: inputs: + - workdir: $WORKDIR - command: test - install-missing-android-tools@%s: inputs: @@ -733,6 +798,8 @@ configs: - android-build@%s: inputs: - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT - xcode-archive@%s: inputs: - project_path: $BITRISE_PROJECT_PATH @@ -752,9 +819,11 @@ configs: - git-clone@%s: {} - yarn@%s: inputs: + - workdir: $WORKDIR - command: install - yarn@%s: inputs: + - workdir: $WORKDIR - command: test - deploy-to-bitrise-io@%s: {} warnings: @@ -762,3 +831,189 @@ warnings: warnings_with_recommendations: react-native: [] `, sampleAppsReactNativeIosAndAndroidYarnVersions...) + +var sampleAppsReactNativeJoplinVersions = []interface{}{ + models.FormatVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.NpmVersion, + steps.InstallMissingAndroidToolsVersion, + steps.AndroidBuildVersion, + steps.CocoapodsInstallVersion, + steps.XcodeArchiveVersion, + steps.DeployToBitriseIoVersion, + + steps.ActivateSSHKeyVersion, + steps.GitCloneVersion, + steps.NpmVersion, + steps.DeployToBitriseIoVersion, +} + +var sampleAppsReactNativeJoplinResultYML = fmt.Sprintf(`options: + react-native: + title: React Native project directory + summary: Path of the directory containing the project's `+"`package.json`"+` file. + env_key: WORKDIR + type: selector + value_map: + packages/app-mobile: + title: The root directory of an Android project + summary: The root directory of your Android project, stored as an Environment + Variable. In your Workflows, you can specify paths relative to this path. + You can change this at any time. + env_key: PROJECT_LOCATION + type: selector + value_map: + packages/app-mobile/android: + title: Module + summary: Modules provide a container for your Android project's source + code, resource files, and app level settings, such as the module-level + build file and Android manifest file. Each module can be independently + built, tested, and debugged. You can add new modules to your Bitrise + builds at any time. + env_key: MODULE + type: user_input + value_map: + app: + title: Variant + summary: Your Android build variant. You can add variants at any time, + as well as further configure your existing variants later. + env_key: VARIANT + type: user_input_optional + value_map: + Debug: + title: Project or Workspace path + summary: The location of your Xcode project or Xcode workspace + files, stored as an Environment Variable. In your Workflows, + you can specify paths relative to this path. + env_key: BITRISE_PROJECT_PATH + type: selector + value_map: + packages/app-mobile/ios/Joplin.xcworkspace: + title: Scheme name + summary: An Xcode scheme defines a collection of targets to + build, a configuration to use when building, and a collection + of tests to execute. Only shared schemes are detected automatically + but you can use any scheme as a target on Bitrise. You can + change the scheme at any time in your Env Vars. + env_key: BITRISE_SCHEME + type: selector + value_map: + Joplin: + title: Distribution method + summary: The export method used to create an .ipa file + in your builds, stored as an Environment Variable. You + can change this at any time, or even create several + .ipa files with different export methods in the same + build. + env_key: BITRISE_DISTRIBUTION_METHOD + type: selector + value_map: + ad-hoc: + config: react-native-android-ios-pod-config + app-store: + config: react-native-android-ios-pod-config + development: + config: react-native-android-ios-pod-config + enterprise: + config: react-native-android-ios-pod-config + Joplin-tvOS: + title: Distribution method + summary: The export method used to create an .ipa file + in your builds, stored as an Environment Variable. You + can change this at any time, or even create several + .ipa files with different export methods in the same + build. + env_key: BITRISE_DISTRIBUTION_METHOD + type: selector + value_map: + ad-hoc: + config: react-native-android-ios-pod-config + app-store: + config: react-native-android-ios-pod-config + development: + config: react-native-android-ios-pod-config + enterprise: + config: react-native-android-ios-pod-config + ShareExtension: + title: Distribution method + summary: The export method used to create an .ipa file + in your builds, stored as an Environment Variable. You + can change this at any time, or even create several + .ipa files with different export methods in the same + build. + env_key: BITRISE_DISTRIBUTION_METHOD + type: selector + value_map: + ad-hoc: + config: react-native-android-ios-pod-config + app-store: + config: react-native-android-ios-pod-config + development: + config: react-native-android-ios-pod-config + enterprise: + config: react-native-android-ios-pod-config +configs: + react-native: + react-native-android-ios-pod-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: + description: | + Tests, builds and deploys the app using *Deploy to bitrise.io* Step. + + Next steps: + - Set up an [Apple service with API key](https://devcenter.bitrise.io/en/accounts/connecting-to-services/connecting-to-an-apple-service-with-api-key.html). + - Check out [Getting started with React Native apps](https://devcenter.bitrise.io/en/getting-started/getting-started-with-react-native-apps.html). + steps: + - activate-ssh-key@%s: {} + - git-clone@%s: {} + - npm@%s: + inputs: + - workdir: $WORKDIR + - command: install + - install-missing-android-tools@%s: + inputs: + - gradlew_path: $PROJECT_LOCATION/gradlew + - android-build@%s: + inputs: + - project_location: $PROJECT_LOCATION + - module: $MODULE + - variant: $VARIANT + - cocoapods-install@%s: {} + - xcode-archive@%s: + inputs: + - project_path: $BITRISE_PROJECT_PATH + - scheme: $BITRISE_SCHEME + - distribution_method: $BITRISE_DISTRIBUTION_METHOD + - configuration: Release + - automatic_code_signing: api-key + - deploy-to-bitrise-io@%s: {} + primary: + description: | + Installs dependencies. + + Next steps: + - Add tests to your project and configure the workflow to run them. + - Check out [Getting started with React Native apps](https://devcenter.bitrise.io/en/getting-started/getting-started-with-react-native-apps.html). + steps: + - activate-ssh-key@%s: {} + - git-clone@%s: {} + - npm@%s: + inputs: + - workdir: $WORKDIR + - command: install + - deploy-to-bitrise-io@%s: {} +warnings: + react-native: [] +warnings_with_recommendations: + react-native: [] +`, sampleAppsReactNativeJoplinVersions...) diff --git a/scanners/reactnative/expo.go b/scanners/reactnative/expo.go index 8288486f..d8e23c40 100644 --- a/scanners/reactnative/expo.go +++ b/scanners/reactnative/expo.go @@ -1,13 +1,8 @@ package reactnative import ( - "fmt" - "path/filepath" - "github.com/bitrise-io/bitrise-init/models" "github.com/bitrise-io/bitrise-init/steps" - "github.com/bitrise-io/bitrise-init/utility" - "github.com/bitrise-io/go-utils/log" "gopkg.in/yaml.v2" ) @@ -27,7 +22,7 @@ const ( ) // expoOptions implements ScannerInterface.Options function for Expo based React Native projects. -func (scanner *Scanner) expoOptions() (models.OptionNode, models.Warnings, error) { +func (scanner *Scanner) expoOptions() models.OptionNode { platformOption := models.NewOption(expoPlatformInputTitle, expoPlatformInputSummary, expoPlatformInputEnvKey, models.TypeSelector) configOption := models.NewConfigOption(expoConfigName, nil) @@ -35,28 +30,22 @@ func (scanner *Scanner) expoOptions() (models.OptionNode, models.Warnings, error platformOption.AddConfig(platform, configOption) } - return *platformOption, nil, nil + return *platformOption } // expoConfigs implements ScannerInterface.Configs function for Expo based React Native projects. -func (scanner *Scanner) expoConfigs(isPrivateRepo bool) (models.BitriseConfigMap, error) { +func (scanner *Scanner) expoConfigs(project project, isPrivateRepo bool) (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 == "." { + if project.projectRelDir == "." { // package.json placed in the search dir, no need to change-dir in the workflows - relPackageJSONDir = "" + project.projectRelDir = "" } - log.TPrintf("Working directory: %v", relPackageJSONDir) + testSteps := getTestSteps(project.projectRelDir, project.hasYarnLockFile, project.hasTest) // primary workflow primaryDescription := expoPrimaryWorkflowDescription - if !scanner.hasTest { + if !project.hasTest { primaryDescription = expoPrimaryWorkflowNoTestsDescription } @@ -66,13 +55,13 @@ func (scanner *Scanner) expoConfigs(isPrivateRepo bool) (models.BitriseConfigMap ShouldIncludeCache: false, ShouldIncludeActivateSSH: isPrivateRepo, })...) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, scanner.getTestSteps(relPackageJSONDir)...) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, testSteps...) configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepListV2(false)...) // deploy workflow deployDescription := expoDeployWorkflowDescription - if !scanner.hasTest { + if !project.hasTest { deployDescription = expoDeployWorkflowNoTestsDescription } @@ -81,8 +70,8 @@ func (scanner *Scanner) expoConfigs(isPrivateRepo bool) (models.BitriseConfigMap ShouldIncludeCache: false, ShouldIncludeActivateSSH: isPrivateRepo, })...) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, scanner.getTestSteps(relPackageJSONDir)...) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.RunEASBuildStepListItem(relPackageJSONDir, "$"+expoPlatformInputEnvKey)) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, testSteps...) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.RunEASBuildStepListItem(project.projectRelDir, "$"+expoPlatformInputEnvKey)) configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepList(false)...) // generate bitrise.yml diff --git a/scanners/reactnative/plain.go b/scanners/reactnative/plain.go index d0eb3faa..8cbf4c8e 100644 --- a/scanners/reactnative/plain.go +++ b/scanners/reactnative/plain.go @@ -1,226 +1,236 @@ package reactnative import ( - "errors" "fmt" - "path/filepath" "github.com/bitrise-io/bitrise-init/models" "github.com/bitrise-io/bitrise-init/scanners/android" "github.com/bitrise-io/bitrise-init/scanners/ios" "github.com/bitrise-io/bitrise-init/steps" - "github.com/bitrise-io/bitrise-init/utility" bitriseModels "github.com/bitrise-io/bitrise/models" envmanModels "github.com/bitrise-io/envman/models" - "github.com/bitrise-io/go-utils/pathutil" "gopkg.in/yaml.v2" ) const ( defaultConfigName = "default-react-native-config" + + defaultModule = "app" + defaultVariant = "Debug" ) -// configName generates a config name based on the inputs. -func configName(hasAndroidProject, hasIosProject, hasTest bool) string { +type configDescriptor struct { + hasIOS, hasAndroid bool + hasTest bool + hasYarnLockFile bool + ios ios.ConfigDescriptor +} + +func (d configDescriptor) configName() string { name := "react-native" - if hasAndroidProject { + if d.hasAndroid { name += "-android" } - if hasIosProject { + if d.hasIOS { name += "-ios" + if d.ios.MissingSharedSchemes { + name += "-missing-shared-schemes" + } + if d.ios.HasPodfile { + name += "-pod" + } + if d.ios.CarthageCommand != "" { + name += "-carthage" + } } - if hasTest { + if d.hasTest { name += "-test" } + if d.hasYarnLockFile { + name += "-yarn" + } + return name + "-config" } -// options implements ScannerInterface.Options function for plain React Native projects. -func (scanner *Scanner) options() (models.OptionNode, models.Warnings, error) { - warnings := models.Warnings{} - var rootOption models.OptionNode - projectDir := filepath.Dir(scanner.packageJSONPth) - - // android options - var androidOptions *models.OptionNode - if len(scanner.androidProjects) > 0 { - androidOptions = models.NewOption(android.ProjectLocationInputTitle, android.ProjectLocationInputSummary, android.ProjectLocationInputEnvKey, models.TypeSelector) - for _, project := range scanner.androidProjects { - warnings = append(warnings, project.Warnings...) - - // This config option is removed when merging with ios config. This way no change is needed for the working options merging. - configOption := models.NewConfigOption("glue-only", nil) - moduleOption := models.NewOption(android.ModuleInputTitle, android.ModuleInputSummary, android.ModuleInputEnvKey, models.TypeUserInput) - variantOption := models.NewOption(android.VariantInputTitle, android.VariantInputSummary, android.VariantInputEnvKey, models.TypeOptionalUserInput) - - androidOptions.AddOption(project.RelPath, moduleOption) - moduleOption.AddOption("app", variantOption) - variantOption.AddConfig("", configOption) - } - } +func generateIOSOptions(result ios.DetectResult, hasAndroid, hasTests, hasYarnLockFile bool) (*models.OptionNode, models.Warnings, []configDescriptor) { + var ( + warnings models.Warnings + descriptors []configDescriptor + ) + + projectPathOption := models.NewOption(ios.ProjectPathInputTitle, ios.ProjectPathInputSummary, ios.ProjectPathInputEnvKey, models.TypeSelector) + for _, project := range result.Projects { + warnings = append(warnings, project.Warnings...) + + schemeOption := models.NewOption(ios.SchemeInputTitle, ios.SchemeInputSummary, ios.SchemeInputEnvKey, models.TypeSelector) + projectPathOption.AddOption(project.RelPath, schemeOption) + + for _, scheme := range project.Schemes { + exportMethodOption := models.NewOption(ios.DistributionMethodInputTitle, ios.DistributionMethodInputSummary, ios.DistributionMethodEnvKey, models.TypeSelector) + schemeOption.AddOption(scheme.Name, exportMethodOption) + + for _, exportMethod := range ios.IosExportMethods { + iosConfig := ios.NewConfigDescriptor(project.IsPodWorkspace, project.CarthageCommand, scheme.HasXCTests, scheme.HasAppClip, exportMethod, scheme.Missing) + descriptor := configDescriptor{ + hasIOS: true, + hasAndroid: hasAndroid, + hasTest: hasTests, + hasYarnLockFile: hasYarnLockFile, + ios: iosConfig, + } + descriptors = append(descriptors, descriptor) - // 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 { - scanner.iosScanner.SuppressPodFileParseError = true - if detected, err := scanner.iosScanner.DetectPlatform(scanner.searchDir); err != nil { - return models.OptionNode{}, warnings, err - } else if detected { - options, warns, _, err := scanner.iosScanner.Options() - warnings = append(warnings, warns...) - if err != nil { - return models.OptionNode{}, warnings, err + exportMethodOption.AddConfig(exportMethod, models.NewConfigOption(descriptor.configName(), nil)) } - - iosOptions = &options } } - 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, scanner.hasTest) - 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() - } + return projectPathOption, warnings, descriptors +} +// options implements ScannerInterface.Options function for plain React Native projects. +func (scanner *Scanner) options(project project) (models.OptionNode, models.Warnings) { + var ( + rootOption models.OptionNode + allDescriptors []configDescriptor + warnings models.Warnings + ) + + // Android + if len(project.androidProjects) > 0 { + androidOptions := models.NewOption(android.ProjectLocationInputTitle, android.ProjectLocationInputSummary, android.ProjectLocationInputEnvKey, models.TypeSelector) 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()) + for _, androidProject := range project.androidProjects { + warnings = append(warnings, androidProject.Warnings...) + + moduleOption := models.NewOption(android.ModuleInputTitle, android.ModuleInputSummary, android.ModuleInputEnvKey, models.TypeUserInput) + variantOption := models.NewOption(android.VariantInputTitle, android.VariantInputSummary, android.VariantInputEnvKey, models.TypeOptionalUserInput) + + androidOptions.AddOption(androidProject.RelPath, moduleOption) + moduleOption.AddOption(defaultModule, variantOption) + + if len(project.iosProjects.Projects) == 0 { + descriptor := configDescriptor{ + hasAndroid: true, + hasTest: project.hasTest, + hasYarnLockFile: project.hasYarnLockFile, } + allDescriptors = append(allDescriptors, descriptor) + + variantOption.AddConfig(defaultVariant, models.NewConfigOption(descriptor.configName(), nil)) - configName := configName(androidOptions != nil, true, scanner.hasTest) - child.Config = configName + continue } - } - 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) - } + iosOptions, iosWarnings, descriptors := generateIOSOptions(project.iosProjects, true, project.hasTest, project.hasYarnLockFile) + warnings = append(warnings, iosWarnings...) + allDescriptors = append(allDescriptors, descriptors...) + variantOption.AddOption(defaultVariant, iosOptions) + } + } else { + options, iosWarnings, descriptors := generateIOSOptions(project.iosProjects, false, project.hasTest, project.hasYarnLockFile) + rootOption = *options + warnings = append(warnings, iosWarnings...) + allDescriptors = append(allDescriptors, descriptors...) } - return rootOption, warnings, nil + scanner.configDescriptors = removeDuplicatedConfigDescriptors(append(scanner.configDescriptors, allDescriptors...)) + + return rootOption, warnings } // defaultOptions implements ScannerInterface.DefaultOptions function for plain React Native projects. func (scanner *Scanner) defaultOptions() models.OptionNode { - androidOptions := (&android.Scanner{}).DefaultOptions() - androidOptions.RemoveConfigs() + androidOptions := models.NewOption(android.ProjectLocationInputTitle, android.ProjectLocationInputSummary, android.ProjectLocationInputEnvKey, models.TypeUserInput) + moduleOption := models.NewOption(android.ModuleInputTitle, android.ModuleInputSummary, android.ModuleInputEnvKey, models.TypeUserInput) + variantOption := models.NewOption(android.VariantInputTitle, android.VariantInputSummary, android.VariantInputEnvKey, models.TypeOptionalUserInput) - iosOptions := (&ios.Scanner{}).DefaultOptions() - for _, child := range iosOptions.LastChilds() { - for _, child := range child.ChildOptionMap { - child.Config = defaultConfigName - } - } + androidOptions.AddOption("android", moduleOption) + moduleOption.AddOption(defaultModule, variantOption) - androidOptions.AttachToLastChilds(&iosOptions) + projectPathOption := models.NewOption(ios.ProjectPathInputTitle, ios.ProjectPathInputSummary, ios.ProjectPathInputEnvKey, models.TypeUserInput) + schemeOption := models.NewOption(ios.SchemeInputTitle, ios.SchemeInputSummary, ios.SchemeInputEnvKey, models.TypeUserInput) - return androidOptions + variantOption.AddOption(defaultVariant, projectPathOption) + projectPathOption.AddOption("", schemeOption) + + exportMethodOption := models.NewOption(ios.DistributionMethodInputTitle, ios.DistributionMethodInputSummary, ios.DistributionMethodEnvKey, models.TypeSelector) + for _, exportMethod := range ios.IosExportMethods { + schemeOption.AddOption("", exportMethodOption) + + exportMethodOption.AddConfig(exportMethod, models.NewConfigOption(defaultConfigName, nil)) + } + + return *androidOptions } // configs implements ScannerInterface.Configs function for plain React Native projects. func (scanner *Scanner) configs(isPrivateRepo bool) (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 = "" - } - - configBuilder := models.NewDefaultConfigBuilder() - - // ci - primaryDescription := primaryWorkflowNoTestsDescription - if scanner.hasTest { - primaryDescription = primaryWorkflowDescription + if len(scanner.configDescriptors) == 0 { + return models.BitriseConfigMap{}, fmt.Errorf("invalid state, no config descriptors found") } - configBuilder.SetWorkflowDescriptionTo(models.PrimaryWorkflowID, primaryDescription) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepListV2(steps.PrepareListParams{ - ShouldIncludeCache: false, - ShouldIncludeActivateSSH: isPrivateRepo, - })...) - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, scanner.getTestSteps(relPackageJSONDir)...) + for _, descriptor := range scanner.configDescriptors { + configBuilder := models.NewDefaultConfigBuilder() - configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepListV2(false)...) + testSteps := getTestSteps("$"+projectDirInputEnvKey, descriptor.hasYarnLockFile, descriptor.hasTest) + // ci + primaryDescription := primaryWorkflowNoTestsDescription + if descriptor.hasTest { + primaryDescription = primaryWorkflowDescription + } - // cd - configBuilder.SetWorkflowDescriptionTo(models.DeployWorkflowID, deployWorkflowDescription) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepListV2(steps.PrepareListParams{ - ShouldIncludeCache: false, - ShouldIncludeActivateSSH: isPrivateRepo, - })...) - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, scanner.getTestSteps(relPackageJSONDir)...) - - // android cd - hasAndroidProject := len(scanner.androidProjects) > 0 - if hasAndroidProject { - 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}, - )) - } + configBuilder.SetWorkflowDescriptionTo(models.PrimaryWorkflowID, primaryDescription) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultPrepareStepListV2(steps.PrepareListParams{ + ShouldIncludeCache: false, + ShouldIncludeActivateSSH: isPrivateRepo, + })...) + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, testSteps...) + + configBuilder.AppendStepListItemsTo(models.PrimaryWorkflowID, steps.DefaultDeployStepListV2(false)...) + + // cd + configBuilder.SetWorkflowDescriptionTo(models.DeployWorkflowID, deployWorkflowDescription) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultPrepareStepListV2(steps.PrepareListParams{ + ShouldIncludeCache: false, + ShouldIncludeActivateSSH: isPrivateRepo, + })...) + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, testSteps...) + + // android cd + if descriptor.hasAndroid { + 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}, + envmanModels.EnvironmentItemModel{android.ModuleInputKey: "$" + android.ModuleInputEnvKey}, + envmanModels.EnvironmentItemModel{android.VariantInputKey: "$" + android.VariantInputEnvKey}, + )) + } - // ios cd - if scanner.iosScanner != nil { - for _, descriptor := range scanner.iosScanner.ConfigDescriptors { - if descriptor.MissingSharedSchemes { + // ios cd + if descriptor.hasIOS { + if descriptor.ios.MissingSharedSchemes { configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.RecreateUserSchemesStepListItem( envmanModels.EnvironmentItemModel{ios.ProjectPathInputKey: "$" + ios.ProjectPathInputEnvKey}, )) } - if descriptor.HasPodfile { + if descriptor.ios.HasPodfile { configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CocoapodsInstallStepListItem()) } - if descriptor.CarthageCommand != "" { + if descriptor.ios.CarthageCommand != "" { configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.CarthageStepListItem( - envmanModels.EnvironmentItemModel{ios.CarthageCommandInputKey: descriptor.CarthageCommand}, + envmanModels.EnvironmentItemModel{ios.CarthageCommandInputKey: descriptor.ios.CarthageCommand}, )) } @@ -231,23 +241,8 @@ func (scanner *Scanner) configs(isPrivateRepo bool) (models.BitriseConfigMap, er envmanModels.EnvironmentItemModel{ios.ConfigurationInputKey: "Release"}, envmanModels.EnvironmentItemModel{ios.AutomaticCodeSigningInputKey: ios.AutomaticCodeSigningInputAPIKeyValue}, )) - - configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepListV2(false)...) - - bitriseDataModel, err := configBuilder.Generate(scannerName) - if err != nil { - return models.BitriseConfigMap{}, err - } - - data, err := yaml.Marshal(bitriseDataModel) - if err != nil { - return models.BitriseConfigMap{}, err - } - - configName := configName(hasAndroidProject, true, scanner.hasTest) - configMap[configName] = string(data) } - } else { + configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.DefaultDeployStepListV2(false)...) bitriseDataModel, err := configBuilder.Generate(scannerName) @@ -260,8 +255,7 @@ func (scanner *Scanner) configs(isPrivateRepo bool) (models.BitriseConfigMap, er return models.BitriseConfigMap{}, err } - configName := configName(hasAndroidProject, false, scanner.hasTest) - configMap[configName] = string(data) + configMap[descriptor.configName()] = string(data) } return configMap, nil @@ -297,6 +291,8 @@ func (scanner *Scanner) defaultConfigs() (models.BitriseConfigMap, error) { )) configBuilder.AppendStepListItemsTo(models.DeployWorkflowID, steps.AndroidBuildStepListItem( envmanModels.EnvironmentItemModel{android.ProjectLocationInputKey: projectLocationEnv}, + envmanModels.EnvironmentItemModel{android.ModuleInputKey: "$" + android.ModuleInputEnvKey}, + envmanModels.EnvironmentItemModel{android.VariantInputKey: "$" + android.VariantInputEnvKey}, )) // ios @@ -346,6 +342,17 @@ func getTestSteps(workDir string, hasYarnLockFile, hasTest bool) []bitriseModels return testSteps } -func (scanner *Scanner) getTestSteps(workDir string) []bitriseModels.StepListItemModel { - return getTestSteps(workDir, scanner.hasYarnLockFile, scanner.hasTest) +func removeDuplicatedConfigDescriptors(configDescriptors []configDescriptor) []configDescriptor { + descritorNameMap := map[string]configDescriptor{} + for _, descriptor := range configDescriptors { + name := descriptor.configName() + descritorNameMap[name] = descriptor + } + + descriptors := []configDescriptor{} + for _, descriptor := range descritorNameMap { + descriptors = append(descriptors, descriptor) + } + + return descriptors } diff --git a/scanners/reactnative/reactnative.go b/scanners/reactnative/reactnative.go index 7c0ddd7c..c7d70e26 100644 --- a/scanners/reactnative/reactnative.go +++ b/scanners/reactnative/reactnative.go @@ -15,26 +15,31 @@ import ( const scannerName = "react-native" const ( - // workDirInputKey is a key of the working directory step input. - workDirInputKey = "workdir" -) + projectDirInputTitle = "React Native project directory" + projectDirInputSummary = "Path of the directory containing the project's `package.json` file." + projectDirInputEnvKey = "WORKDIR" -const ( isExpoBasedProjectInputTitle = "Is this an [Expo](https://expo.dev)-based React Native project?" isExpoBasedProjectInputSummary = "Default deploy workflow runs builds on Expo Application Services (EAS) for Expo-based React Native projects.\nOtherwise native iOS and Android build steps will be used." ) -// Scanner implements the project scanner for plain React Native and Expo based projects. -type Scanner struct { - searchDir string - iosScanner *ios.Scanner - androidProjects []android.Project +type project struct { + projectRelDir string hasTest bool hasYarnLockFile bool - packageJSONPth string + // non-Expo; native projects + iosProjects ios.DetectResult + androidProjects []android.Project +} + +// Scanner implements the project scanner for plain React Native and Expo based projects. +type Scanner struct { isExpoBased bool + projects []project + + configDescriptors []configDescriptor } // NewScanner creates a new scanner instance. @@ -72,21 +77,23 @@ func isExpoBasedProject(packageJSONPth string) (bool, error) { return false, nil } -func hasNativeIOSProject(searchDir, projectDir string, iosScanner *ios.Scanner) (bool, error) { +func hasNativeIOSProject(projectDir string, iosScanner *ios.Scanner) (bool, ios.DetectResult, error) { absProjectDir, err := pathutil.AbsPath(projectDir) if err != nil { - return false, err + return false, ios.DetectResult{}, err } iosDir := filepath.Join(absProjectDir, "ios") if exist, err := pathutil.IsDirExists(iosDir); err != nil || !exist { - return false, err + return false, ios.DetectResult{}, err } - return iosScanner.DetectPlatform(searchDir) + detected, err := iosScanner.DetectPlatform(projectDir) + + return detected, iosScanner.DetectResult, err } -func hasNativeAndroidProject(searchDir, projectDir string, androidScanner *android.Scanner) (bool, []android.Project, error) { +func hasNativeAndroidProject(projectDir string, androidScanner *android.Scanner) (bool, []android.Project, error) { absProjectDir, err := pathutil.AbsPath(projectDir) if err != nil { return false, nil, err @@ -97,18 +104,55 @@ func hasNativeAndroidProject(searchDir, projectDir string, androidScanner *andro return false, nil, err } - if detected, err := androidScanner.DetectPlatform(searchDir); err != nil || !detected { + if detected, err := androidScanner.DetectPlatform(projectDir); err != nil || !detected { return false, nil, err } return true, androidScanner.Projects, nil } +func getNativeProjects(packageJSONPth, relPackageJSONDir string) (ios.DetectResult, []android.Project) { + var ( + iosScanner = ios.NewScanner() + androidScanner = android.NewScanner() + ) + iosScanner.ExcludeAppIcon = true + iosScanner.SuppressPodFileParseError = true + + projectDir := filepath.Dir(packageJSONPth) + isIOSProject, iosProjects, err := hasNativeIOSProject(projectDir, iosScanner) + if err != nil { + log.TWarnf("failed to check native iOS projects: %s", err) + } + log.TPrintf("Found native ios project: %v", isIOSProject) + + isAndroidProject, androidProjects, err := hasNativeAndroidProject(projectDir, androidScanner) + if err != nil { + log.TWarnf("failed to check native Android projects: %s", err) + } + log.TPrintf("Found native android project: %v", isAndroidProject) + + // Update native projects paths relative to search dir (otherwise would be relative to package.json dir). + var newIosProjects []ios.Project + for _, p := range iosProjects.Projects { + p.RelPath = filepath.Join(relPackageJSONDir, p.RelPath) + newIosProjects = append(newIosProjects, p) + } + iosProjects.Projects = newIosProjects + + var newAndroidProjects []android.Project + for _, p := range androidProjects { + p.RelPath = filepath.Join(relPackageJSONDir, p.RelPath) + newAndroidProjects = append(newAndroidProjects, p) + } + androidProjects = newAndroidProjects + + return iosProjects, androidProjects +} + // DetectPlatform implements ScannerInterface.DetectPlatform function. func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { - scanner.searchDir = searchDir - - log.TInfof("Collect package.json files") + log.TInfof("Collecting package.json files") packageJSONPths, err := CollectPackageJSONFiles(searchDir) if err != nil { @@ -116,89 +160,93 @@ func (scanner *Scanner) DetectPlatform(searchDir string) (bool, error) { } log.TPrintf("%d package.json file detected", len(packageJSONPths)) - log.TPrintf("Filter relevant package.json files") - - isExpoBased := false - var packageFile string + for _, path := range packageJSONPths { + log.TPrintf("- %s", path) + } + log.TPrintf("Filtering relevant package.json files") for _, packageJSONPth := range packageJSONPths { log.TPrintf("Checking: %s", packageJSONPth) - expoBased, err := isExpoBasedProject(packageJSONPth) + isExpoBased, err := isExpoBasedProject(packageJSONPth) if err != nil { log.TWarnf("failed to determine if project is Expo based: %s", err) - } else if expoBased { - log.TPrintf("Project uses expo: %v", expoBased) - isExpoBased = true - packageFile = packageJSONPth - // TODO: This break drops other package.json files - break } - log.TPrintf("Project uses expo: %v", expoBased) - - if scanner.iosScanner == nil { - scanner.iosScanner = ios.NewScanner() - scanner.iosScanner.ExcludeAppIcon = true - } - androidScanner := android.NewScanner() + log.TPrintf("Project uses expo: %v", isExpoBased) - projectDir := filepath.Dir(packageJSONPth) - isIOSProject, err := hasNativeIOSProject(searchDir, projectDir, scanner.iosScanner) + // determine workdir + packageJSONDir := filepath.Dir(packageJSONPth) + relPackageJSONDir, err := utility.RelPath(searchDir, packageJSONDir) if err != nil { - log.TWarnf("failed to check native iOS projects: %s", err) + return false, fmt.Errorf("failed to get relative package.json dir path: %s", err) } - log.TPrintf("Found native ios project: %v", isIOSProject) - if !isIOSProject { - scanner.iosScanner = nil + + var ( + iosProjects ios.DetectResult + androidProjects []android.Project + ) + if !isExpoBased { + iosProjects, androidProjects = getNativeProjects(packageJSONPth, relPackageJSONDir) + if len(iosProjects.Projects) == 0 && len(androidProjects) == 0 { + continue + } } - isAndroidProject, androidProjects, err := hasNativeAndroidProject(searchDir, projectDir, androidScanner) + // determine Js dependency manager + hasYarnLockFile, err := containsYarnLock(filepath.Dir(packageJSONPth)) if err != nil { - log.TWarnf("failed to check native Android projects: %s", err) + return false, err } - log.TPrintf("Found native android project: %v", isAndroidProject) - scanner.androidProjects = androidProjects + log.TPrintf("Js dependency manager for %s is yarn: %t", packageJSONPth, hasYarnLockFile) - if isIOSProject || isAndroidProject { - // Treating the project as a plain React Native project - packageFile = packageJSONPth - break + packages, err := utility.ParsePackagesJSON(packageJSONPth) + if err != nil { + return false, err } - } - if packageFile == "" { - return false, nil - } + _, hasTests := packages.Scripts["test"] + log.TPrintf("Test script found in package.json: %v", hasTests) - scanner.isExpoBased = isExpoBased - scanner.packageJSONPth = packageFile + result := project{ + projectRelDir: relPackageJSONDir, + hasTest: hasTests, + hasYarnLockFile: hasYarnLockFile, + iosProjects: iosProjects, + androidProjects: androidProjects, + } - // determine Js dependency manager - if scanner.hasYarnLockFile, err = containsYarnLock(filepath.Dir(scanner.packageJSONPth)); err != nil { - return false, err - } - log.TPrintf("Js dependency manager for %s is yarn: %t", scanner.packageJSONPth, scanner.hasYarnLockFile) + if isExpoBased { + scanner.projects = []project{result} + scanner.isExpoBased = true - packages, err := utility.ParsePackagesJSON(scanner.packageJSONPth) - if err != nil { - return false, err + break + } + + scanner.projects = append(scanner.projects, result) } - if _, found := packages.Scripts["test"]; found { - scanner.hasTest = true + if len(scanner.projects) == 0 { + return false, nil } - log.TPrintf("Test script found in package.json: %v", scanner.hasTest) return true, nil } // Options implements ScannerInterface.Options function. -func (scanner *Scanner) Options() (options models.OptionNode, warnings models.Warnings, icons models.Icons, err error) { +func (scanner *Scanner) Options() (options models.OptionNode, allWarnings models.Warnings, icons models.Icons, err error) { if scanner.isExpoBased { - options, warnings, err = scanner.expoOptions() + options = scanner.expoOptions() } else { - options, warnings, err = scanner.options() + projectRootOption := models.NewOption(projectDirInputTitle, projectDirInputSummary, projectDirInputEnvKey, models.TypeSelector) + options = *projectRootOption + + for _, project := range scanner.projects { + options, warnings := scanner.options(project) + allWarnings = append(allWarnings, warnings...) + + projectRootOption.AddOption(project.projectRelDir, &options) + } } return @@ -207,7 +255,7 @@ func (scanner *Scanner) Options() (options models.OptionNode, warnings models.Wa // Configs implements ScannerInterface.Configs function. func (scanner *Scanner) Configs(isPrivateRepo bool) (models.BitriseConfigMap, error) { if scanner.isExpoBased { - return scanner.expoConfigs(isPrivateRepo) + return scanner.expoConfigs(scanner.projects[0], isPrivateRepo) } return scanner.configs(isPrivateRepo) diff --git a/scanners/reactnative/utility.go b/scanners/reactnative/utility.go index a12716c3..2846525e 100644 --- a/scanners/reactnative/utility.go +++ b/scanners/reactnative/utility.go @@ -10,7 +10,7 @@ import ( // CollectPackageJSONFiles collects package.json files, with react-native dependency. func CollectPackageJSONFiles(searchDir string) ([]string, error) { - fileList, err := pathutil.ListPathInDirSortedByComponents(searchDir, true) + fileList, err := pathutil.ListPathInDirSortedByComponents(searchDir, false) if err != nil { return nil, err }