require 'json' fastlane_version '2.71.0' fastlane_require 'aws-sdk-s3' fastlane_require 'erb' skip_docs configured = false # Executes before anything else use to setup the script before_all do # Raises an error is git is not clean if ENV['COMMIT_CHANGES_TO_GIT'] == 'true' ensure_git_status_clean end # Block to ensure we are on the right branch branch = ENV['BRANCH_TO_BUILD'] || 'master' begin ensure_git_branch( branch: branch ) rescue sh "git checkout #{branch}" UI.success("Using branch \"#{branch}\"") end # If we are going to commit changes to git create a separate branch # so no changes are done in the branch that is being built if ENV['COMMIT_CHANGES_TO_GIT'] == 'true' local_branch = ENV['GIT_LOCAL_BRANCH'] || 'build' sh "git checkout -b #{local_branch}" UI.success("Creating branch \"#{local_branch}\"") end end after_all do if ENV['RESET_GIT_BRANCH'] == 'true' branch = ENV['BRANCH_TO_BUILD'] || 'master' package_id = ENV['MAIN_APP_IDENTIFIER'] || 'com.mattermost.rnbeta' beta_dir = '../android/app/src/main/java/com/mattermost/rnbeta' release_dir = "../android/app/src/main/java/#{package_id.gsub '.', '/'}" if beta_dir != release_dir sh "rm -rf #{release_dir}" end reset_git_repo( force: true, skip_clean: true ) sh "git checkout #{branch}" if ENV['COMMIT_CHANGES_TO_GIT'] == 'true' local_branch = ENV['GIT_LOCAL_BRANCH'] || 'build' sh "git branch -D #{local_branch}" UI.success("Deleted working branch \"#{local_branch}\"") end end end desc 'Increment version number for both Android and iOS' lane :set_app_version do version_number = ENV['VERSION_NUMBER'] unless version_number.nil? || version_number.empty? android_set_version_name( gradle_file: './android/app/build.gradle', version_name: version_number ) increment_version_number( xcodeproj: './ios/Mattermost.xcodeproj', version_number: version_number ) if ENV['COMMIT_CHANGES_TO_GIT'] == 'true' msg = ENV['INCREMENT_VERSION_NUMBER_MESSAGE'] || 'Bump app version number to ' commit_message = "#{msg} #{version_number.to_s}" build_folder_path = Dir[File.expand_path('..')].first repo_path = (sh "git -C #{build_folder_path} rev-parse --show-toplevel").strip git_dirty_files = (sh "git -C #{repo_path} diff --name-only HEAD").split(" ").join(' ') unless git_dirty_files.empty? sh "git -C #{repo_path} commit -m \"#{commit_message}\" #{git_dirty_files}" UI.success("Successfully committed \"#{git_dirty_files}\" 💾.") end end end end desc 'Increments the build number for both Android and iOS' lane :set_app_build_number do ## set the build number for both platforms # use the one for iOS if no env variable set if ENV['INCREMENT_BUILD_NUMBER'] === 'true' build_number = ENV['BUILD_NUMBER'] || (get_build_number(xcodeproj: './ios/Mattermost.xcodeproj').to_i + 1) increment_build_number( xcodeproj: './ios/Mattermost.xcodeproj', build_number: build_number ) android_set_version_code( gradle_file: './android/app/build.gradle', version_code: build_number ) if ENV['COMMIT_CHANGES_TO_GIT'] == 'true' msg = ENV['INCREMENT_BUILD_NUMBER_MESSAGE'] || 'Bump app build number to ' commit_message = "#{msg} #{build_number.to_s}" build_folder_path = Dir[File.expand_path('..')].first repo_path = (sh "git -C #{build_folder_path} rev-parse --show-toplevel").strip git_dirty_files = (sh "git -C #{repo_path} diff --name-only HEAD").split(" ").join(' ') unless git_dirty_files.empty? sh "git -C #{repo_path} commit -m \"#{commit_message}\" #{git_dirty_files}" UI.success("Successfully committed \"#{git_dirty_files}\" 💾.") end end end end desc 'Configure the app before building' lane :configure do json = load_config_json # Set the Segment API Key unless ENV['SEGMENT_API_KEY'].nil? || ENV['SEGMENT_API_KEY'].empty? json['SegmentApiKey'] = ENV['SEGMENT_API_KEY'] end # Configure Sentry if enabled if ENV['SENTRY_ENABLED'] == 'true' json['SentryEnabled'] = true json['SentryOrg'] = ENV['SENTRY_ORG'] json['SentryProjectIos'] = ENV['SENTRY_PROJECT_IOS'] json['SentryProjectAndroid'] = ENV['SENTRY_PROJECT_ANDROID'] json['SentryDsnIos'] = ENV['SENTRY_DSN_IOS'] json['SentryDsnAndroid'] = ENV['SENTRY_DSN_ANDROID'] end # Save the config.json file save_config_json(json) configured = true end desc 'Build the app for Android and iOS' lane :build do |options| configure set_app_version set_app_build_number # Build the android app self.runner.current_platform = :android build # Build the ios app self.runner.current_platform = :ios build end desc 'Buid the app for Android and iOS unsigned' lane :unsigned do configure # Build the android app self.runner.current_platform = :android unsigned # Build the ios app self.runner.current_platform = :ios unsigned end desc 'Build the app using a PR so QA can test' lane :build_qa do configure # Build the android app self.runner.current_platform = :android build_qa # Build the ios app self.runner.current_platform = :ios build_qa end platform :ios do desc 'Build iOS app' lane :build do unless configured configure end update_identifiers replace_assets build_ios # Submit to TestFlight if required if ENV['SUBMIT_IOS_TO_TESTFLIGHT'] == 'true' pilot # Send a build message to Mattermost pretext = '#### New iOS released ready to be published' msg = 'release has been cut and is on TestFlight ready to be published.' if ENV['BETA_BUILD'] == 'true' pretext = '#### New iOS beta published to TestFlight' msg = 'Sign up as a beta tester [here](https://mattermost-fastlane.herokuapp.com/)' end version = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost') build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj') send_message_to_mattermost( version, build_number, pretext, '', msg, [], true ) end end desc 'Build an unsigned ipa' lane :unsigned do unless configured configure end update_identifiers replace_assets end lane :build_qa do match(type: 'adhoc') build_ios plist_template = File.read('plist.erb') abbreviated_commit_hash = `git rev-parse --short head` abbreviated_commit_hash.strip! plist_body = ERB.new(plist_template).result(binding) s3 = Aws::S3::Resource.new(region: ENV['AWS_REGION']) plist_obj = s3.bucket(ENV['AWS_BUCKET_NAME']).object("ios/#{abbreviated_commit_hash}.plist") plist_obj.put(body: plist_body) ipa_obj = s3.bucket(ENV['AWS_BUCKET_NAME']).object("ios/#{abbreviated_commit_hash}.ipa") ipa_obj.upload_file('../Mattermost.ipa') ios_install_url = "itms-services://?action=download-manifest&url=https://s3.#{ENV['AWS_REGION']}.amazonaws.com/#{ENV['AWS_BUCKET_NAME']}/ios/#{abbreviated_commit_hash}.plist" unless ENV['MATTERMOST_WEBHOOK_URL'].nil? || ENV['MATTERMOST_WEBHOOK_URL'].empty? msg = "QA build [#{abbreviated_commit_hash}](https://github.com/mattermost/mattermost-mobile/commit/#{abbreviated_commit_hash}) — iOS: #{ios_install_url}" mattermost(message: msg, username: 'Fastlane', icon_url: 'https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/move-to-ios-icon.png') end end lane :update_identifiers do # Set the name for the app app_name = ENV['APP_NAME'] || 'Mattermost Beta' update_info_plist( xcodeproj: './ios/Mattermost.xcodeproj', plist_path: 'Mattermost/Info.plist', display_name: app_name ) # Set the share extension bundle identifier extension_bundle_id = ENV['EXTENSION_APP_IDENTIFIER'] || 'com.mattermost.rnbeta.MattermostShare' update_app_identifier( xcodeproj: './ios/Mattermost.xcodeproj', plist_path: 'MattermostShare/Info.plist', app_identifier: extension_bundle_id ) find_replace_string( path_to_file: './ios/Mattermost.xcodeproj/project.pbxproj', old_string: 'com.mattermost.rnbeta.MattermostShare', new_string: extension_bundle_id ) # Set the app bundle id app_bundle_id = ENV['MAIN_APP_IDENTIFIER'] || 'com.mattermost.rnbeta' update_app_identifier( xcodeproj: './ios/Mattermost.xcodeproj', plist_path: 'Mattermost/Info.plist', app_identifier: app_bundle_id ) find_replace_string( path_to_file: './ios/Mattermost.xcodeproj/project.pbxproj', old_string: 'com.mattermost.rnbeta', new_string: app_bundle_id ) # If set update the development team unless ENV['FASTLANE_TEAM_ID'].nil? || ENV['FASTLANE_TEAM_ID'].empty? update_project_team( path: './ios/Mattermost.xcodeproj', teamid: ENV['FASTLANE_TEAM_ID'] ) end # Set the ICloud container icloud_container = ENV['IOS_ICLOUD_CONTAINER'] || 'iCloud.com.mattermost.rnbeta' update_icloud_container_identifiers( entitlements_file: './ios/Mattermost/Mattermost.entitlements', icloud_container_identifiers: [icloud_container] ) # Set the app group id to share data between the app and the extension app_group_id = ENV['IOS_APP_GROUP'] || 'group.com.mattermost.rnbeta' update_app_group_identifiers( entitlements_file: './ios/Mattermost/Mattermost.entitlements', app_group_identifiers: [app_group_id] ) update_app_group_identifiers( entitlements_file: './ios/MattermostShare/MattermostShare.entitlements', app_group_identifiers: [app_group_id] ) # Save changes to the config.json file config_json = load_config_json config_json['AppGroupId'] = app_group_id save_config_json(config_json) # Sync the provisioning profiles using match if ENV['SYNC_PROVISIONING_PROFILES'] == 'true' match(type: ENV['MATCH_TYPE'] || 'adhoc') end end lane :replace_assets do if ENV['REPLACE_ASSETS'] == 'true' sh 'cp -R ../dist/assets/release/icons/ios/* ../ios/Mattermost/Images.xcassets/AppIcon.appiconset/' sh 'cp -R ../dist/assets/release/splash_screen/ios/* ../ios/SplashScreenResource/' end end error do |lane, exception| version = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost') build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj') send_message_to_mattermost( version, build_number, '', 'Unsuccessful Build', exception.message, [:lane], false ) end def build_ios() app_name = ENV['APP_NAME'] || 'Mattermost Beta' config_mode = ENV['BUILD_FOR_RELEASE'] == 'true' ? 'Release' : 'Debug' method = ENV['IOS_BUILD_EXPORT_METHOD'].nil? || ENV['IOS_BUILD_EXPORT_METHOD'].empty? ? 'ad-hoc' : ENV['IOS_BUILD_EXPORT_METHOD'] gym( clean: true, scheme: 'Mattermost', configuration: config_mode, workspace: './ios/Mattermost.xcworkspace', export_method: method, skip_profile_detection: true, output_name: "#{app_name}.ipa", export_options: { signingStyle: 'manual', iCloudContainerEnvironment: 'Production' } ) end end platform :android do desc 'Build Android app' lane :build do unless configured configure end update_identifiers replace_assets link_sentry_android build_android app_name = ENV['APP_NAME'] || 'Mattermost Beta' apk_path = "#{lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]}" if ENV['SUBMIT_ANDROID_TO_GOOGLE_PLAY'] == 'true' supply( track: ENV['SUPPLY_TRACK'] || 'alpha', apk: apk_path ) build_number = android_get_version_code( gradle_file: './android/app/build.gradle' ) version_number = android_get_version_name( gradle_file: './android/app/build.gradle' ) # Send a build message to Mattermost pretext = '#### New Android released ready to be published' msg = 'release has been cut and is on the Beta track ready to be published.' if ENV['BETA_BUILD'] == 'true' pretext = '#### New Android beta published to Google Play' msg = 'Sign up as a beta tester [here](https://play.google.com/apps/testing/com.mattermost.rnbeta)' end send_message_to_mattermost( version_number, build_number, pretext, '', msg, [], true ) end sh "mv #{apk_path} \"./#{app_name}.apk\"" end desc 'Build an unsigned apk' lane :unsigned do unless configured configure end update_identifiers replace_assets gradle( task: 'assemble', build_type: 'Unsigned', project_dir: 'android/' ) end lane :build_qa do build_android abbreviated_commit_hash = last_git_commit[:abbreviated_commit_hash] s3 = Aws::S3::Resource.new(region: ENV['AWS_REGION']) apk_obj = s3.bucket(ENV['AWS_BUCKET_NAME']).object("android/#{abbreviated_commit_hash}.apk") apk_obj.upload_file("#{lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]}") android_install_path = "https://s3.#{ENV['AWS_REGION']}.amazonaws.com/#{ENV['AWS_BUCKET_NAME']}/android/#{abbreviated_commit_hash}.apk" if ENV['MATTERMOST_WEBHOOK_URL'] && !ENV['MATTERMOST_WEBHOOK_URL'].nil? && !ENV['MATTERMOST_WEBHOOK_URL'].empty? msg = "QA build [#{abbreviated_commit_hash}](https://github.com/mattermost/mattermost-mobile/commit/#{abbreviated_commit_hash}) — Android: #{android_install_path}" mattermost(message: msg, username: 'Fastlane', icon_url: 'https://lh3.ggpht.com/XL0CrI8skkxnboGct-duyg-bZ_MxJDTrjczyjdU8OP2PM1dmj7SP4jL1K8JQeMIB3AM=w300') end end lane :update_identifiers do # Set the name for the app app_name = ENV['APP_NAME'] || 'Mattermost Beta' sh "echo '#{app_name}' > ../fastlane/metadata/android/en-US/title.txt" android_change_string_app_name( newName: app_name, stringsFile: './android/app/src/main/res/values/strings.xml' ) package_id = ENV['MAIN_APP_IDENTIFIER'] || 'com.mattermost.rnbeta' android_change_package_identifier(newIdentifier: package_id, manifest: './android/app/src/main/AndroidManifest.xml') android_update_application_id(app_folder_name: 'android/app', application_id: package_id) beta_dir = './android/app/src/main/java/com/mattermost/rnbeta/' release_dir = "./android/app/src/main/java/#{package_id.gsub '.', '/'}/" if ENV['BUILD_FOR_RELEASE'] == 'true' find_replace_string( path_to_file: "#{beta_dir}MainApplication.java", old_string: 'return BuildConfig.DEBUG;', new_string: 'return false;' ) end if release_dir != beta_dir unless Dir.exist?(".#{release_dir}") FileUtils.mkdir_p ".#{release_dir}" end sh "mv .#{beta_dir}* .#{release_dir}" find_replace_string( path_to_file: './android/app/BUCK', old_string: 'package com.mattermost.rnbeta;', new_string: "package #{package_id};" ) Dir.glob(".#{release_dir}*.java") do |item| find_replace_string( path_to_file: item[1..-1], old_string: 'package com.mattermost.rnbeta;', new_string: "package #{package_id};" ) end Dir.glob('../android/app/src/main/java/com/mattermost/share/*.java') do |item| find_replace_string( path_to_file: item[1..-1], old_string: 'import com.mattermost.rnbeta.MainApplication;', new_string: "import #{package_id}.MainApplication;" ) end end end lane :replace_assets do if ENV['REPLACE_ASSETS'] == 'true' sh 'cp -R ../dist/assets/release/icons/android/* ../android/app/src/main/res/' sh 'cp -R ../dist/assets/release/splash_screen/android/* ../android/app/src/main/res/' end end error do |lane, exception| build_number = android_get_version_code( gradle_file: './android/app/build.gradle' ) version_number = android_get_version_name( gradle_file: './android/app/build.gradle' ) send_message_to_mattermost(version_number, build_number, '', 'Unsuccessful Build', exception.message, [:lane], false) end def build_android() config_mode = ENV['BUILD_FOR_RELEASE'] == 'true' ? 'Release' : 'Debug' gradle( task: 'assemble', build_type: config_mode, project_dir: 'android/' ) end def link_sentry_android if ENV['SENTRY_ENABLED'] == 'true' url = 'https://sentry.io' File.open('../android/sentry.properties', 'w+') do |f| UI.message('Creating sentry.properties from environment') f.write( "defaults.url=#{url}\n"\ "defaults.org=#{ENV['SENTRY_ORG']}\n"\ "defaults.project=#{ENV['SENTRY_PROJECT_ANDROID']}\n"\ "auth.token=#{ENV['SENTRY_AUTH_TOKEN']}\n" ) end else UI.message('Not creating sentry.properties because Sentry is disabled') end end end def load_config_json config_path = '../dist/assets/config.json' config_file = File.read(config_path) JSON.parse(config_file) end def save_config_json(json) config_path = '../dist/assets/config.json' File.open(config_path, 'w') do |f| f.write(JSON.pretty_generate(json)) end end def send_message_to_mattermost(version_number, build_number, pretext, title, msg, default_payloads, success) unless ENV['MATTERMOST_WEBHOOK_URL'].nil? || ENV['MATTERMOST_WEBHOOK_URL'].empty? mattermost( pretext: pretext, message: msg, default_payloads: default_payloads, username: 'Fastlane', icon_url: 'https://s3-eu-west-1.amazonaws.com/fastlane.tools/fastlane.png', payload: {}, attachment_properties: { title: title, thumb_url: 'https://lh3.ggpht.com/XL0CrI8skkxnboGct-duyg-bZ_MxJDTrjczyjdU8OP2PM1dmj7SP4jL1K8JQeMIB3AM=w300', fields: [{ title: 'Version', value: version_number, short: true }, { title: 'Build Number', value: build_number, short: true }, { title: 'Built by', value: 'Jenkins', short: true }] }, success: success ) end end