# This file contains the fastlane.tools configuration # You can find the documentation at https://docs.fastlane.tools # # For a list of all available actions, check out # # https://docs.fastlane.tools/actions # # For a list of all available plugins, check out # # https://docs.fastlane.tools/plugins/available-plugins # # Uncomment the line if you want fastlane to automatically update itself # update_fastlane # CONSTANTS releaseNotesBodyHeader = "What's new:" releaseNotesFileBody = "../app/version/release-notes" releaseNotesLocales= ["en-US", "en-GB", "en-CA"] releaseNotesMaxLength = 500 errorMessageCancelled = "Release cancelled 😢" aarbExecutable = "AndroidAsanaBridge" asanaBridgeInstallationProblem = "Android Asana Release Bridge not installed or configured correctly - see https://app.asana.com/0/0/1203116937958001/f for instructions" default_platform(:android) platform :android do desc "Generate release notes for the Play Store" private_lane :release_notes_playstore do releaseNotesBody = File.read(releaseNotesFileBody) formatted = "#{releaseNotesBodyHeader}\n#{releaseNotesBody}" validateReleaseNotes(releaseNotes: formatted) UI.message("\n#{formatted}") formatted end desc "Generate release notes for GitHub" private_lane :release_notes_github do releaseNotesBody = File.read(releaseNotesFileBody) formatted = "\#\# #{releaseNotesBodyHeader}\n#{releaseNotesBody}" UI.message("\n#{formatted}") formatted end desc "Upload APK to Play Store, in production track with a very small rollout percentage" lane :deploy_playstore do update_fastlane_release_notes() props = property_file_read(file: "app/version/version.properties") version = props["VERSION"] apkPath = "app/build/outputs/apk/play/release/duckduckgo-#{version}-play-release.apk" upload_to_play_store( apk: apkPath, track: 'production', rollout: '0.001', # ie. 0.1% skip_upload_screenshots: true, skip_upload_images: true, validate_only: false ) cleanup_fastlane_release_notes() annotate_release() end desc "Annotate release" private_lane :annotate_release do props = property_file_read(file: "app/version/version.properties") version = props["VERSION"] http_status = sh("curl -s -o /dev/null -w '%{http_code}' https://improving.duckduckgo.com/t/m_new_release_android?appVersion=#{version}", log: false).to_i if http_status >= 200 && http_status < 300 # Successful response puts "Release annotation successful with status code #{http_status}" else # Unsuccessful response puts "Release annotation failed with status code #{http_status}" end end desc "Upload APK to ad-hoc internal app sharing" private_lane :deploy_adhoc do props = property_file_read(file: "app/version/version.properties") version = props["VERSION"] apkPath = "app/build/outputs/apk/play/release/duckduckgo-#{version}-play-release.apk" upload_to_play_store_internal_app_sharing( apk: apkPath ) end desc "Upload APK to Play Store internal testing track" lane :deploy_dogfood do |options| UI.message("Apk path: #{options[:apk_path]}") upload_to_play_store( apk: options[:apk_path], track: 'internal', skip_upload_screenshots: true, skip_upload_images: true, validate_only: false ) end desc "Deploy APK to GitHub" lane :deploy_github do props = property_file_read(file: "app/version/version.properties") version = props["VERSION"] releaseNotes = release_notes_github() apkPath = "app/build/outputs/apk/play/release/duckduckgo-#{version}-play-release.apk" token = ENV["GITHUB_UPLOAD_TOKEN"] UI.message ("Upload new app version to GitHub\nVersion: #{version}\nRelease Notes:\n=====\n#{releaseNotes}\n=====\n") set_github_release( repository_name: "DuckDuckGo/Android", api_token: token, name: version, tag_name: version, description: releaseNotes, upload_assets: [apkPath], is_draft: false, is_prerelease: false) end desc "Update local changelist metadata" private_lane :update_fastlane_release_notes do releaseNotes = release_notes_playstore() flversion=gradle(task: '-q fastlaneVersionCode').lines.map(&:chomp)[0] UI.message("App version for fastlane is #{flversion}.\nRelease notes for Play Store:\n\n#{releaseNotes}") releaseNotesLocales.each do |locale| File.open("../fastlane/metadata/android/#{locale}/changelogs/#{flversion}.txt", 'w') do |file| file.write("#{releaseNotes}") end end end desc "Clean up local changelist metadata" private_lane :cleanup_fastlane_release_notes do flversion=gradle(task: '-q fastlaneVersionCode').lines.map(&:chomp)[0] releaseNotesLocales.each do |locale| sh("rm '../fastlane/metadata/android/#{locale}/changelogs/#{flversion}.txt'") end end # Note, this currently relies on having `git flow` tools installed. # This dependency could be removed with a little more time to tidy up this script to do the branching/merging manually. desc "Create new release" lane :release do |options| ensure_git_status_clean ensure_git_branch( branch: 'develop' ) options_release_number = options[:release_number] options_release_notes = options[:release_notes] options_notes_type = options[:notes_type] newVersion = determine_version_number( release_number: options_release_number ) releaseNotes = determine_release_notes( release_notes: options_release_notes, notes_type: options_notes_type ) isInteractiveMode = options_release_number == nil || (options_notes_type == nil && options_release_notes == nil) || (options_notes_type == "CUSTOM" && options_release_notes == nil) # For interactive flows, we want to confirm the data inputted by the user before proceeding if !isInteractiveMode UI.message("This are the release information:\n\nVersion=#{newVersion}\nRelease Notes:\n#{releaseNotes}\n") do_create_release_branch(newVersion: newVersion, releaseNotes: releaseNotes) do_create_and_push_tags(newVersion: newVersion) else if UI.confirm("Are you sure you're happy with this release?\n\nVersion=#{newVersion}\nRelease Notes:\n#{releaseNotes}\n") UI.success "Creating release branch for release/#{newVersion}" do_create_release_branch(newVersion: newVersion, releaseNotes: releaseNotes) if UI.confirm(text:"If you have any other changes to make to the release branch, do them now. Enter `y` when ready to create and push tags") do_create_and_push_tags(newVersion: newVersion) else UI.error errorMessageCancelled end else UI.error errorMessageCancelled end end end private_lane :do_create_release_branch do |options| newVersion = options[:newVersion] releaseNotes = options[:releaseNotes] sh "git flow release start #{newVersion}" File.open('../app/version/version.properties', 'w') do |file| file.write("VERSION=#{newVersion}") end File.open('../app/version/release-notes', 'w') do |file| file.write("""#{releaseNotes}""") end end private_lane :do_create_and_push_tags do |options| newVersion = options[:newVersion] git_commit( message: "Updated release notes and version number for new release - #{newVersion}", path: "*", allow_nothing_to_commit: true, skip_git_hooks: true ) sh "git flow release finish -mnFS '#{newVersion}' '#{newVersion}'" push_git_tags(tag: newVersion) sh "git push origin main" sh "git push origin develop" UI.header("#{newVersion} tag has been successfully created. 🎉") end private_lane :determine_release_notes do |options| notes_type = options[:notes_type] custom_release_notes = options[:release_notes] existingReleaseNotes = File.read(releaseNotesFileBody) # This handles the flow when no options were passed. We will prompt the user for input. releaseNotes = if (notes_type == nil && custom_release_notes == nil) then commits = changelog_from_git_commits( between: [last_git_tag, "HEAD"], pretty: "- %s", date_format: "short", match_lightweight_tag: false, merge_commit_filtering: "exclude_merges" ) UI.important("Existing release notes:\n") UI.message("#{existingReleaseNotes}\n") choice = UI.select "What do you want to do for release notes?", ["KEEP EXISTING", "CUSTOM", "Bug fixes and other improvements", ] retrieve_notes_for_type( notes_type: choice, existingReleaseNotes: existingReleaseNotes ) else # Force notes_type to custom when custom_release_notes is present. if custom_release_notes != nil retrieve_notes_for_type( notes_type: "CUSTOM", release_notes: custom_release_notes, existingReleaseNotes: existingReleaseNotes ) else retrieve_notes_for_type( notes_type: notes_type, release_notes: custom_release_notes, existingReleaseNotes: existingReleaseNotes ) end end validateReleaseNotes(releaseNotes: releaseNotes) releaseNotes end desc "Validates the release notes to ensure they are suitable for the Play Store" private_lane :validateReleaseNotes do |options| releaseNotesLength = options[:releaseNotes].length if (releaseNotesLength > releaseNotesMaxLength) UI.user_error!("Release notes are too long for Play Store (#{releaseNotesLength} characters). Max size allowed: #{releaseNotesMaxLength}") end end private_lane :retrieve_notes_for_type do |options| notes_type = options[:notes_type] custom_release_notes = options[:release_notes] rl = case notes_type when "KEEP EXISTING" options[:existingReleaseNotes] when "CUSTOM" if custom_release_notes == nil then prompt(text: "Release Notes: ", multi_line_end_keyword: "END") else custom_release_notes end else notes_type end end desc "Start new hotfix" lane :"hotfix-start" do UI.important("Starting a new hotfix") ensure_git_status_clean sh('git checkout develop && git pull') sh('git checkout main && git pull') newVersion = determine_version_number() releaseNotes = determine_release_notes() sh("git flow hotfix start #{newVersion}") File.open('../app/version/version.properties', 'w') do |file| file.write("VERSION=#{newVersion}") end File.open('../app/version/release-notes', 'w') do |file| file.write("""#{releaseNotes}""") end git_commit( message: "Updated release notes and version number for new release - #{newVersion}", path: "*", allow_nothing_to_commit: true, skip_git_hooks: true ) UI.important(text:"Hotfix branch created. Apply your changes that need to be included in the hotfix now. Run `fastlane hotfix-finish` when you've made your changes and are happy it all works.") end desc "Finish a hotfix in progress" lane :"hotfix-finish" do ensure_git_status_clean ensure_git_branch( branch: '^hotfix/*' ) version = property_file_read(file: "app/version/version.properties")["VERSION"] sh("git flow hotfix finish -m '#{version}' '#{version}'") sh "git push origin #{version}" sh "git push origin main" sh "git push origin develop" UI.header("🎉 #{version} tag has been successfully created, and hotfix has been merged back into main and develop. Everything has been pushed.") end desc "Prompt for version number" private_lane :"determine_version_number" do |options| release_number = options[:release_number] if release_number == nil prompt(text: "\nLast release was: #{last_git_tag}\nEnter New Version Number:") else release_number end end desc "Prepares the Asana release board with a new release task, tags tasks waiting for release etc.." lane :asana_release_prep do begin sh aarbExecutable, "action=verify" rescue => ex UI.user_error!("#{aarbExecutable} not installed. Install the tool and ensure it can be executed by executing `#{aarbExecutable}`") end newVersion = determine_version_number() if UI.confirm("About to create a new release task for #{newVersion}. Ready to continue?") UI.message("Creating release task...") sh aarbExecutable, "version=#{newVersion}", "action=createRelease,tagPendingTasks,addLinksToDescription,removePendingTasks", "board=real" else UI.error(errorMessageCancelled) end end end