From a04df6fcaead97a50b5e78f71b2cfd5b1b2542b9 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Thu, 26 Feb 2026 16:33:23 +0200 Subject: [PATCH] Add a separate tools command for building nightly and remove the old fastlane one. --- .github/workflows/unit_tests_enterprise.yml | 2 +- Tools/Sources/Commands/CI/CI.swift | 3 +- .../Commands/CI/ConfigureNightly.swift | 63 +++++++++++++++++++ ci_scripts/ci_post_clone.sh | 2 +- fastlane/Fastfile | 29 --------- 5 files changed, 67 insertions(+), 32 deletions(-) create mode 100644 Tools/Sources/Commands/CI/ConfigureNightly.swift diff --git a/.github/workflows/unit_tests_enterprise.yml b/.github/workflows/unit_tests_enterprise.yml index ec1da9b95..ac8b35e63 100644 --- a/.github/workflows/unit_tests_enterprise.yml +++ b/.github/workflows/unit_tests_enterprise.yml @@ -39,7 +39,7 @@ jobs: run: source ci_scripts/ci_common.sh && setup_github_actions_environment - name: Configure Enterprise - run: bundle exec fastlane config_element_pro + run: swift run pipeline configure Variants/ElementPro/ElementPro.pkl - name: SwiftFormat run: swiftformat --lint . diff --git a/Tools/Sources/Commands/CI/CI.swift b/Tools/Sources/Commands/CI/CI.swift index e435ec476..ca6165aed 100644 --- a/Tools/Sources/Commands/CI/CI.swift +++ b/Tools/Sources/Commands/CI/CI.swift @@ -6,7 +6,8 @@ struct CI: ParsableCommand { static let configuration = CommandConfiguration(abstract: "CI workflow commands that can be run both locally and in CI environments.", subcommands: [ UnitTests.self, - RunTests.self + RunTests.self, + ConfigureNightly.self ]) static let testOutputDirectory = "test_output" diff --git a/Tools/Sources/Commands/CI/ConfigureNightly.swift b/Tools/Sources/Commands/CI/ConfigureNightly.swift new file mode 100644 index 000000000..176a14b2a --- /dev/null +++ b/Tools/Sources/Commands/CI/ConfigureNightly.swift @@ -0,0 +1,63 @@ +import ArgumentParser +import CommandLineTools +import Foundation +import Yams + +struct ConfigureNightly: AsyncParsableCommand { + static let configuration = CommandConfiguration(abstract: "Configures the project for a Nightly build.", + discussion: "Adds the Nightly variant to project.yml, updates secrets, runs xcodegen, and generates the app icon banner.") + + @Option(help: "The build number to display on the app icon banner.") + var buildNumber: String + + func run() async throws { + guard !buildNumber.isEmpty else { + throw ValidationError("Invalid build number.") + } + + try addNightlyVariant() + + try Zsh.run(command: "swift run pipeline update-foss-secrets") + try Zsh.run(command: "xcodegen") + + let releaseVersion = try readMarketingVersion() + try await generateAppIconBanner(version: releaseVersion, buildNumber: buildNumber) + } + + /// Adds the Nightly variant include path to `project.yml` if it isn't already present. + private func addNightlyVariant() throws { + let projectURL = URL.projectDirectory.appending(component: "project.yml") + let projectString = try String(contentsOf: projectURL) + guard var projectConfig = try Yams.compose(yaml: projectString) else { + throw ValidationError("Failed to parse project.yml.") + } + + // Check if the nightly variant is already included + if projectConfig["include"]?.sequence?.contains(where: { $0.mapping?["path"] == "Variants/Nightly/nightly.yml" }) == false { + projectConfig["include"]?.sequence?.append(["path": "Variants/Nightly/nightly.yml"]) + } + + let updatedYAMLString = try Yams.serialize(node: projectConfig) + try updatedYAMLString.write(to: projectURL, atomically: true, encoding: .utf8) + } + + /// Reads the MARKETING_VERSION from `project.yml`. + private func readMarketingVersion() throws -> String { + let projectURL = URL.projectDirectory.appending(component: "project.yml") + let projectString = try String(contentsOf: projectURL) + + let marketingVersionRegex = /MARKETING_VERSION:\s*([^\s]+)/ + guard let match = projectString.firstMatch(of: marketingVersionRegex) else { + throw ValidationError("Could not find MARKETING_VERSION in project.yml.") + } + + return String(match.1) + } + + /// Generates the app icon banner with version and build number. + private func generateAppIconBanner(version: String, buildNumber: String) async throws { + let bannerText = "\(version) (\(buildNumber))" + let iconPath = "Variants/Nightly/Resources/NightlyAppIcon.icon/Assets/Version.png" + try await AppIconBanner.parse([iconPath, "--banner-text", bannerText]).run() + } +} diff --git a/ci_scripts/ci_post_clone.sh b/ci_scripts/ci_post_clone.sh index f1f6a3205..c4d85ce04 100755 --- a/ci_scripts/ci_post_clone.sh +++ b/ci_scripts/ci_post_clone.sh @@ -7,7 +7,7 @@ setup_xcode_cloud_environment install_xcode_cloud_brew_dependencies if [ "$CI_WORKFLOW" = "Nightly" ]; then - bundle exec fastlane config_nightly build_number:"$CI_BUILD_NUMBER" + swift run tools ci configure-nightly elif [ "$CI_WORKFLOW" = "Element Pro" ]; then # Xcode Cloud automatically fetches the submodules. swift run pipeline configure Variants/ElementPro/ElementPro.pkl diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 12fbdd8f4..0ac690f1f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -124,35 +124,6 @@ lane :config_production do xcodegen(spec: "project.yml") end -lane :config_nightly do |options| - build_number = options[:build_number] - UI.user_error!("Invalid build number.") unless !build_number.to_s.empty? - - target_file_path = "../project.yml" - data = YAML.load_file target_file_path - - # Check if the "path" already exists in the "include" array - if !data["include"].any? { |item| item["path"] == "Variants/Nightly/nightly.yml" } - data["include"].append({ "path" => "Variants/Nightly/nightly.yml" }) - end - - sh("(cd .. && swift run pipeline update-foss-secrets)") - - File.open(target_file_path, 'w') { |f| YAML.dump(data, f) } - - xcodegen(spec: "project.yml") - - # Automatically done by Xcode Cloud. Cannot override - # https://developer.apple.com/documentation/xcode/setting-the-next-build-number-for-xcode-cloud-builds - # bump_build_number() - - release_version = get_version_number(target: "ElementX") - - Dir.chdir ".." do - sh("swift run tools app-icon-banner Variants/Nightly/Resources/NightlyAppIcon.icon/Assets/Version.png --banner-text '#{release_version} (#{build_number})'") - end -end - $sentry_retry=0 lane :upload_dsyms_to_sentry do |options| auth_token = ENV["SENTRY_AUTH_TOKEN"]