diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 538182a09..35aab1d1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,14 +18,11 @@ jobs: - name: Clone Flutter SDK run: tools/ci/clone-flutter-sdk - - name: Download Flutter SDK artifacts (flutter precache) - run: flutter precache --universal - - name: Download our dependencies (flutter pub get) run: flutter pub get - name: Run tools/check - run: TERM=dumb tools/check --no-pub --all --output ci --exclude android + run: TERM=dumb tools/check --no-pub --all --output ci --exclude android --exclude ios android: name: "Android build and lint" @@ -42,11 +39,28 @@ jobs: - name: Clone Flutter SDK run: tools/ci/clone-flutter-sdk - - name: Download Flutter SDK artifacts (flutter precache) - run: flutter precache --universal - - name: Download our dependencies (flutter pub get) run: flutter pub get - name: Run tools/check run: TERM=dumb tools/check --all --output ci android + + ios: + name: "iOS build and lint" + runs-on: macos-26 + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + # Modern Bash and coreutils don't come with macOS; get those. + # (See tools/lib/ensure-shell-deps.sh.) + run: brew install coreutils bash + + - name: Clone Flutter SDK + run: tools/ci/clone-flutter-sdk + + - name: Download our dependencies (flutter pub get) + run: flutter pub get + + - name: Run tools/check + run: TERM=dumb tools/check --no-pub --all --output ci ios diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index d8e91eb4d..7203a3619 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1,3 +1,7 @@ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" #include "Zulip.xcconfig" + +// Compile Swift with "-warnings-as-errors" but only in release builds, so that +// it doesn't get in the way of quick local experiments for debugging. +SWIFT_TREAT_WARNINGS_AS_ERRORS = YES diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift index 474362a54..5a29f5e2d 100644 --- a/ios/NotificationService/NotificationService.swift +++ b/ios/NotificationService/NotificationService.swift @@ -65,7 +65,7 @@ class NotificationService: UNNotificationServiceExtension { bestAttemptContent.title = improvedNotificationContent.title bestAttemptContent.subtitle = improvedNotificationContent.subtitle bestAttemptContent.body = improvedNotificationContent.body - bestAttemptContent.userInfo = improvedNotificationContent.userInfo as [AnyHashable : Any] + bestAttemptContent.userInfo = improvedNotificationContent.userInfo as [AnyHashable: Any] contentHandler(bestAttemptContent) case .failure(let error): // TODO(log) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index d8010d6c3..9732e946e 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -18,7 +18,8 @@ import UIKit let controller = window?.rootViewController as! FlutterViewController - IosNativeHostApiSetup.setUp(binaryMessenger: controller.binaryMessenger, api: IosNativeHostApiImpl()) + IosNativeHostApiSetup.setUp( + binaryMessenger: controller.binaryMessenger, api: IosNativeHostApiImpl()) // Retrieve the remote notification payload from launch options; // this will be null if the launch wasn't triggered by a notification. diff --git a/tools/check b/tools/check index 09edf2ea3..53eda083d 100755 --- a/tools/check +++ b/tools/check @@ -34,7 +34,7 @@ default_suites=( analyze test flutter_version build_runner l10n drift pigeon icons - android # This takes multiple minutes in CI, so do it last. + android ios # These take multiple minutes in CI, so do them last. ) extra_suites=( @@ -517,6 +517,78 @@ run_android() { flutter build appbundle } +run_ios() { + # Omitted from this check: + # pubspec.{yaml,lock} tools/check + files_check ios/ \ + || return 0 + + # iOS builds require macOS and Xcode. + if [ "$(uname -s)" != "Darwin" ]; then + echo "Skipping ios suite (not on macOS)." + return 0 + fi + + local build_opts=( + ${opt_no_pub:+--no-pub} + ) + + check_no_uncommitted_or_untracked ios/'*'.swift \ + || return + + if_verbose echo "Checking for style issues in Swift code..." + + # Generated code (*.g.swift) is omitted from this check. + # shellcheck disable=SC2207 # filenames in our own tree, assume well-behaved + local swift_targets=( + $(git ls-files -- ios/'*'.swift :!:ios/'*'.g.swift) + ) + + # --in-place enables `tools/check --fix ios`; see `check_no_changes`. + # TODO: Once we have a dedicated code formatting check, + # move this swift-format check there. + xcrun swift-format format --in-place \ + --configuration ios/.swift-format \ + "${swift_targets[@]}" \ + || return + + check_no_changes "changes to Swift files (swift-format)" ios/'*'.swift \ + || return + + check_no_uncommitted_or_untracked ios/ macos/ \ + || return + + if_verbose echo "Checking for automated changes from Flutter tooling..." + + flutter build ios "${build_opts[@]}" --config-only \ + || return + + # Check macos/ too, because desktop platforms are supported for dev: + # https://github.com/zulip/zulip-flutter#desktop-support + # (This could live in its own suite. If doing that, we'd probably + # still want to run it in the same GitHub CI workflow as the iOS build, + # since GitHub's macOS-based runners use a lot of our free quota: + # https://github.com/zulip/zulip-flutter/issues/329#issue-1950704934 + # .) + flutter build macos "${build_opts[@]}" --config-only \ + || return + + check_no_changes "changes to ios" ios/ \ + || return + + check_no_changes "changes to macos" macos/ \ + || return + + if_verbose echo "Building for iOS (in release mode)..." + + # Skip codesigning in CI. + if [ "${opt_output}" = ci ]; then + build_opts+=( --no-codesign ) + fi + + flutter build ipa "${build_opts[@]}" +} + run_shellcheck() { # Omitted from this check: nothing (nothing known, anyway). files_check tools/ '!*.'{dart,js,json} \ @@ -568,7 +640,9 @@ print_header() { # We avoid `flutter --version` because, weirdly, when run in a # GitHub Actions step it takes about 30 seconds. (The first time; - # it's fast subsequent times.) That's even after `flutter precache`. + # it's fast subsequent times.) That's even when the Flutter tool's + # binary artifacts have already been downloaded (which, in CI, + # happens during the `flutter pub get` step). describe_git_head "flutter/flutter" "$(flutter_tree)" dart --version @@ -590,6 +664,7 @@ for suite in "${opt_suites[@]}"; do pigeon) maybe_time "$suite" run_pigeon ;; icons) maybe_time "$suite" run_icons ;; android) maybe_time "$suite" run_android ;; + ios) maybe_time "$suite" run_ios ;; shellcheck) maybe_time "$suite" run_shellcheck ;; *) echo >&2 "Internal error: unknown suite $suite" ;; esac || failed+=( "$suite" )