Skip to content

Comments

feat(server,cli): Android APK previews with cross-platform share and run#9509

Merged
fortmarek merged 45 commits intomainfrom
feat/android-previews
Feb 20, 2026
Merged

feat(server,cli): Android APK previews with cross-platform share and run#9509
fortmarek merged 45 commits intomainfrom
feat/android-previews

Conversation

@fortmarek
Copy link
Member

@fortmarek fortmarek commented Feb 18, 2026

image image

What this means for users

tuist share now supports Android APKs. Developers can share Android app previews with their team using tuist share app.apk, just like they already can with iOS .ipa and .app bundles. The APK metadata (package name, version, icon) is automatically extracted and uploaded alongside the build.

tuist run now supports Android previews. Running tuist run <preview-url> with an Android preview will discover available Android emulators/devices via ADB, install the APK, and launch the app — mirroring the existing iOS simulator flow.

The Tuist dashboard handles Android as a first-class platform. Android previews show the correct platform icon, display "Package name" instead of "Bundle identifier", and provide a QR code for direct APK download on Android devices.

Follow-up

Running Android previews from the macOS menu bar app will be added in a follow-up.

Summary

  • Add Android APK support for Previews (server + CLI)
  • Extract tuist share and tuist run into cross-platform TuistShareCommand and TuistRunCommand modules (following the TuistBuildCommand pattern)
  • Create TuistAndroid module with ADB device discovery, APK metadata parsing (via aapt2), install, and app launch
  • Android preview flow: download APK → install via adb install → launch via am start
  • Extract and upload APK icons during preview upload (parity with Apple previews)
  • Restructure PreviewsUploadService so APK upload path compiles cross-platform
  • Make UploadPreviewIconService cross-platform
  • Simplify android_only? checks across server controller and LiveView templates
  • Exclude APK builds from binary_id uniqueness constraint (APKs use SHA256 hash which can repeat across builds)
  • Add DestinationType.android to the CLI and app

Test plan

  • Xcode build passes
  • SwiftPM build passes (swift build --replace-scm-with-registry)
  • tuist share app.apk uploads APK preview with icon successfully
  • tuist run <android-preview-url> discovers emulator, installs APK, launches app
  • E2E tested against local server
  • tuist run <apple-preview-url> still works with iOS simulators
  • Unit tests pass

🤖 Generated with Claude Code

fortmarek and others added 3 commits February 18, 2026 13:04
Users can now share Android APK builds through Tuist Previews, the same
way they share iOS .ipa files today. Running `tuist share app.apk`
extracts metadata via aapt2 and uploads the APK for distribution.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The Android SDK's build-tools directory is never added to PATH by
standard installation methods (mise, Homebrew). Instead of requiring
users to manually add it, look for aapt2 in ANDROID_HOME, ANDROID_SDK_ROOT,
and well-known installation paths before falling back to PATH.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Android APK builds use a SHA256 hash as binary_id, and there's no
strict requirement to prevent re-uploading the same APK. Replace the
unconditional unique index with a partial one that only applies to
non-APK build types.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@fortmarek fortmarek force-pushed the feat/android-previews branch from 55f4e5d to a07ea97 Compare February 18, 2026 14:44
… tuist run

Extract share and run commands into cross-platform TuistShareCommand and
TuistRunCommand modules (following the TuistBuildCommand pattern). Create
TuistAndroid module with ADB device discovery, APK install, and app launch.

- TuistAndroid: AdbController with device discovery, app install/launch
  using am start instead of monkey for reliable activity resolution
- TuistShareCommand: moved from TuistKit, APK sharing always compiled,
  Apple builds behind #if os(macOS)
- TuistRunCommand: moved from TuistKit, Android device selection and
  APK install/launch cross-platform, Apple simulator/device behind
  #if os(macOS)
- Restructured PreviewsUploadService for cross-platform APK upload
- Added GetPreviewInfoService, ServerPreviewInfo, PreviewUploadResult
- Better error messages from adb commands (extracts stderr from
  CommandError)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@fortmarek fortmarek changed the title feat(server,cli): add Android APK support for Previews feat(server,cli): Android APK previews with cross-platform share and run Feb 18, 2026
fortmarek and others added 3 commits February 18, 2026 18:20
# Conflicts:
#	server/lib/tuist_web/components/previews/platform_icon.ex
#	server/priv/gettext/dashboard.pot
Remove hardcoded fallback paths (mise, Android Studio, Homebrew) — rely
on the standard ANDROID_HOME / ANDROID_SDK_ROOT environment variables
and bare PATH lookup instead.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
let output: String
do {
output = try await commandRunner
.run(arguments: [adb, "devices", "-l"])
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I also find it crazy that adb doesn't have a way to output json 🙃 at least not that I can tell

fortmarek and others added 20 commits February 18, 2026 19:20
…solution

Removes the `servicePlatform` computed property from `Target+PlatformResolution.swift`
and inlines the platform resolution logic at each call site in BuildService, TestService,
and RunCommandService.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…ommandService

Replaces platform-specific FileHandler/AbsolutePath.current branching with
the cross-platform Environment.pathRelativeToWorkingDirectory helper.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
On Linux only preview URLs are supported, so --generate, --clean,
--configuration, --os, --rosetta, and passthrough arguments are now
only available on macOS.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Show platform-appropriate help for the runnable argument and reorder
to list the most common options first.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…hods

The cross-platform method only accepts path, runnable, and device.
The macOS-only method adds generate, clean, configuration, osVersion,
rosetta, and arguments parameters.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…types

Replace the macOS-only booted simulator shortcut with a cross-platform
isReady check. Android devices and physical devices are always ready,
simulators only when booted. If exactly one ready device exists, it is
selected automatically without prompting.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Inline the platform checks directly at the call sites.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Remove custom ServerPreviewInfo and PreviewUploadResult wrapper types,
using Components.Schemas.Preview from the generated OpenAPI client instead.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…eview

Rename the server operationId from downloadPreview to getPreview and
consolidate into a single cross-platform GetPreviewService returning
Components.Schemas.Preview. Remove the duplicate macOS-only service
and update the app to convert at call sites.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Eliminate the separate APKPreviewUploadService by making
PreviewsUploadService cross-platform. APK upload methods are always
compiled while IPA/appBundle methods remain behind #if canImport(TuistCore).
Git info is now passed as parameters instead of resolved internally,
removing the TuistGit dependency from TuistServer.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace the failable `init?` with a throwing `init` that reports
exactly which URL or date failed to parse via ServerPreviewError,
instead of silently returning nil.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Move configuration, platforms, and derivedDataPath options behind
the platform guard since they only apply to Apple builds.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@fortmarek fortmarek force-pushed the feat/android-previews branch from f4e4f15 to 6f5abc2 Compare February 19, 2026 17:53
@fortmarek fortmarek force-pushed the feat/android-previews branch from 6f5abc2 to 39fe3f2 Compare February 19, 2026 17:57
@fortmarek fortmarek marked this pull request as ready for review February 19, 2026 18:06
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. type:enhancement New feature or request labels Feb 19, 2026
@dosubot
Copy link

dosubot bot commented Feb 19, 2026

Related Documentation

Checked 1 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

@tuist
Copy link

tuist bot commented Feb 19, 2026

🛠️ Tuist Run Report 🛠️

Previews 📦

App Commit Open on device
Tuist 042db007c

Tests 🧪

Scheme Status Cache hit rate Tests Skipped Ran Commit
TuistApp 77 % 21 0 21 b7f3b98fd
TuistCacheEEAcceptanceTests 76 % 14 0 14 eec682c2f
TuistCacheEEUnitTests 76 % 121 0 121 eec682c2f
TuistDependenciesAcceptanceTests 77 % 17 0 17 eec682c2f
TuistGeneratorAcceptanceTests 77 % 71 0 71 042db007c
TuistKitAcceptanceTests 77 % 43 0 43 eec682c2f
TuistUnitTests 77 % 2287 1 2286 b7f3b98fd

Builds 🔨

Scheme Status Duration Commit
TuistApp 48.5s 042db007c
TuistAutomationAcceptanceTests 2m 11s 042db007c
TuistCacheEEAcceptanceTests 45.8s eec682c2f
TuistCacheEEUnitTests 33.5s eec682c2f
TuistDependenciesAcceptanceTests 1m 35s eec682c2f
TuistGeneratorAcceptanceTests 1m 31s 042db007c
TuistKitAcceptanceTests 1m 48s eec682c2f
TuistUnitTests 44.6s eec682c2f
tuist 4m 39s 8ba9e6ac9

Bundles 🧰

Bundle Commit Install size Download size
Tuist 042db007c
28.0 MB
Δ +111.1 KB (+0.40%)
15.0 MB
Δ +52.4 KB (+0.35%)

fortmarek and others added 5 commits February 19, 2026 19:20
- Update unique_constraint name in AppBuild to match new partial index
- Add Credo safety-assured comment for migration drop_if_exists
- Add APK/Android tests for app builds, previews controller, and run command
- Use compactMap for supported platform mapping to handle unknown platforms
- Fix DestinationType Android case string to "Android"
- Add missing Path import in PreviewsUploadServiceTests
- Extract gettext strings for dashboard_previews

Co-Authored-By: Claude Opus 4.6 <[email protected]>
… hashing and fix share test mock

CryptoKit is not available on Linux. Also reset gitController mock before
re-registering to prevent stale return values in share_apk_with_git_info test.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…APK hashing

CryptoSwift was an implicit dependency. Use apple/swift-crypto (already a
declared package dependency) which provides the same SHA256 API as CryptoKit
but works cross-platform including Linux.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…conService

On Linux, URLSession/URLRequest live in FoundationNetworking, not Foundation.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…mandService init

The non-macOS designated init does not accept a commandRunner parameter.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
else { return nil }
self.url = url
supportedPlatforms = appBuild.supported_platforms.map(DestinationType.init)
supportedPlatforms = appBuild.supported_platforms.compactMap(DestinationType.init)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a cool function

ℹ︎ Uploading My App ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ 0%
✔︎ My App uploaded [0.0s]
✔ Success
Share My App with others using the following link: https://test.tuist.io No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blasphemy

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's the other tests that it was following ... might fix it later, but not worth retriggering a CI 🙈

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Feb 19, 2026
fortmarek and others added 4 commits February 19, 2026 21:35
…Linux compatibility

The Command package only applies @mockable on macOS, so MockCommandRunning
doesn't exist on Linux. Replace it with a local StubCommandRunner.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The Command package only applies @mockable on macOS, so
MockCommandRunning is unavailable on Linux. Wrap the entire test file
in #if os(macOS) to skip it on Linux instead of using a stub.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
@fortmarek fortmarek merged commit 87b509b into main Feb 20, 2026
46 of 48 checks passed
@fortmarek fortmarek deleted the feat/android-previews branch February 20, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files. type:enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants