1010# with this name, it will be used.
1111#
1212# The convention here is tailscale-android-build-amd64-<date>
13- DOCKER_IMAGE := tailscale-android-build-amd64-041425-1
13+ DOCKER_IMAGE := tailscale-android-build-amd64-072226-3
14+
15+ # The integration test image contains the Android emulator, system image, SDK,
16+ # build-tools, NDK, adb, and helper tools needed to run the emulator-backed Go
17+ # integration tests. Bump this tag when docker/Dockerfile.android-integration
18+ # or the required tool versions change, using:
19+ # tailscale-android-integration-amd64-YYYYMMDD-N
20+ ANDROID_INTEGRATION_DOCKER_IMAGE := tailscale-android-integration-amd64-20260609-1
1421export TS_USE_TOOLCHAIN =1
1522
1623# If set, additional comma-separated build tags passed to the libtailscale Go
@@ -20,6 +27,15 @@ export TS_USE_TOOLCHAIN=1
2027# affordances for debugging it.
2128GOMOBILE_BUILD_TAGS := ts_omit_cachenetmap
2229
30+ # Pull androidApiLevel from gradle.properties.
31+ ANDROID_API_LEVEL := $(shell grep '^androidApiLevel=' android/gradle.properties | cut -d'=' -f2)
32+
33+ ifeq ($(ANDROID_API_LEVEL ) ,)
34+ $(error androidApiLevel missing from android/gradle.properties)
35+ endif
36+
37+ ANDROID_BUILD_TOOLS_VERSION := $(shell grep '^androidBuildToolsVersion=' android/gradle.properties | cut -d'=' -f2)
38+
2339DEBUG_APK := tailscale-debug.apk
2440RELEASE_AAB := tailscale-release.aab
2541RELEASE_TV_AAB := tailscale-tv-release.aab
4056 ANDROID_TOOLS_URL := "https://dl.google.com/android/repository/commandlinetools-mac-9477386_latest.zip"
4157 ANDROID_TOOLS_SUM := "2072ffce4f54cdc0e6d2074d2f381e7e579b7d63e915c220b96a7db95b2900ee commandlinetools-mac-9477386_latest.zip"
4258endif
43- ANDROID_SDK_PACKAGES := 'platforms;android-34 ' 'extras;android;m2repository' 'ndk;23.1.7779620' 'platform-tools' 'build-tools;34.0.0 '
59+ ANDROID_SDK_PACKAGES := 'platforms;android-$( ANDROID_API_LEVEL ) ' 'extras;android;m2repository' 'ndk;23.1.7779620' 'platform-tools' 'build-tools;$( ANDROID_BUILD_TOOLS_VERSION ) '
4460
4561# Attempt to find an ANDROID_SDK_ROOT / ANDROID_HOME based either from
4662# preexisting environment or common locations.
84100 export PATH := $(JAVA_HOME ) /bin:$(PATH )
85101endif
86102
87- AVD_BASE_IMAGE := " system-images;android-33 ;google_apis;"
103+ AVD_BASE_IMAGE := ' system-images;android-$( ANDROID_API_LEVEL ) ;google_apis;'
88104export HOST_ARCH := $(shell uname -m)
89105ifeq ($(HOST_ARCH ) ,aarch64)
90106 AVD_IMAGE := "$(AVD_BASE_IMAGE ) arm64-v8a"
@@ -142,23 +158,35 @@ release-tv: jarsign-env $(RELEASE_TV_AAB)
142158
143159# gradle-dependencies groups together the android sources and libtailscale needed to assemble tests/debug/release builds.
144160.PHONY : gradle-dependencies
145- gradle-dependencies : $(shell find android -type f -not -path "android/build/* " -not -path '* /.* ') $(LIBTAILSCALE_AAR ) tailscale.version
161+ gradle-dependencies : $(shell find android -type f -not -path "android/build/* " -not -path "android/libs/ * " -not -path '* /.* ') $(LIBTAILSCALE_AAR ) tailscale.version
146162
147163$(RELEASE_AAB ) : version gradle-dependencies
148164 @echo " Building release AAB"
149165 (cd android && ./gradlew test bundleRelease)
150166 install -C ./android/build/outputs/bundle/release/android-release.aab $@
151167
168+ # PLATFORM=tv signals to gradle that we should build for AndroidTV. To
169+ # distinguish the TV variant from the phone/tablet build in the Play Store,
170+ # we temporarily increment the versionCode in android/build.gradle by 1 for
171+ # the duration of the build, then restore the original value via a shell trap
172+ # so the working tree is left clean even if the gradle build fails.
152173$(RELEASE_TV_AAB ) : version gradle-dependencies
153174 @echo " Building TV release AAB"
154- (cd android && ./gradlew test bundleRelease_tv)
155- install -C ./android/build/outputs/bundle/release_tv/android-release_tv.aab $@
175+ @set -e; \
176+ ORIG_VC=$$(grep -oE 'versionCode [0-9]+' android/build.gradle | awk '{print $$2}' ) ; \
177+ TV_VC=$$((ORIG_VC + 1 ) ); \
178+ echo " TV versionCode: $$ ORIG_VC -> $$ TV_VC" ; \
179+ trap " sed -i.bak -E 's/versionCode [0-9]+/versionCode $$ ORIG_VC/' android/build.gradle && rm -f android/build.gradle.bak" EXIT INT TERM HUP; \
180+ sed -i.bak -E " s/versionCode [0-9]+/versionCode $$ TV_VC/" android/build.gradle; \
181+ rm -f android/build.gradle.bak; \
182+ (cd android && ./gradlew test bundleRelease -PPLATFORM=tv)
183+ install -C ./android/build/outputs/bundle/release/android-release.aab $@
156184
157185tailscale-test.apk : version gradle-dependencies
158186 (cd android && ./gradlew assembleApplicationTestAndroidTest)
159187 install -C ./android/build/outputs/apk/androidTest/applicationTest/android-applicationTest-androidTest.apk $@
160188
161- tailscale.version : go.mod go.sum $(wildcard .git/HEAD)
189+ tailscale.version : go.mod go.sum go.toolchain.rev $(wildcard .git/HEAD)
162190 @bash -c " ./tool/go run tailscale.com/cmd/mkversion > tailscale.version"
163191
164192.PHONY : version
@@ -175,10 +203,10 @@ android/libs:
175203$(GOBIN ) :
176204 mkdir -p $(GOBIN )
177205
178- $(GOBIN ) /gomobile : $(GOBIN ) /gobind go.mod go.sum | $(GOBIN )
206+ $(GOBIN ) /gomobile : $(GOBIN ) /gobind go.mod go.sum go.toolchain.rev | $(GOBIN )
179207 ./tool/go install golang.org/x/mobile/cmd/gomobile
180208
181- $(GOBIN ) /gobind : go.mod go.sum
209+ $(GOBIN ) /gobind : go.mod go.sum go.toolchain.rev
182210 ./tool/go install golang.org/x/mobile/cmd/gobind
183211
184212.PHONY : build-unstripped-aar
@@ -271,17 +299,26 @@ androidpath:
271299 @echo ' export PATH=$(ANDROID_HOME)/cmdline-tools/latest/bin:$(ANDROID_HOME)/platform-tools:$$PATH'
272300
273301.PHONY : tag_release
274- tag_release : debug-symbols tailscale.version # # Tag the current commit with the current version
275- source tailscale.version && git tag -a " $$ {VERSION_LONG}" -m " OSS and Version updated to $$ {VERSION_LONG}"
302+ tag_release : tailscale.version bump-version-code # # Tag the current commit with the current version
303+ @if ! git diff --quiet -- android/build.gradle; then \
304+ source tailscale.version && git commit -sm " android: bump versionCode for $$ {VERSION_LONG}" android/build.gradle; \
305+ fi
306+ source tailscale.version && git tag -a " $$ {VERSION_LONG}" -m " Version updated to $$ {VERSION_LONG}"
276307
277308.PHONY : bumposs # # Bump to the latest oss and update the versions.
278- bumposs : update-oss tailscale.version
309+ bumposs : update-oss tailscale.version bump-version-code
279310 source tailscale.version && git commit -sm " android: bump OSS" -m " OSS and Version updated to $$ {VERSION_LONG}" go.toolchain.rev android/build.gradle go.mod go.sum
280311 source tailscale.version && git tag -a " $$ {VERSION_LONG}" -m " OSS and Version updated to $$ {VERSION_LONG}"
281312
282- .PHONY : bump_version_code # # Bump the version code in build.gradle
283- bump_version_code :
284- sed -i' .bak' " s/versionCode .*/versionCode $$ (expr $$ (awk '/versionCode ([0-9]+)/{print $$ 2}' android/build.gradle) + 1)/" android/build.gradle && rm android/build.gradle.bak
313+ # Recomputes the base versionCode from tailscale.version and rewrites the
314+ # `versionCode <n>` line in android/build.gradled
315+ .PHONY : bump-version-code
316+ bump-version-code : tailscale.version
317+ @source tailscale.version && \
318+ BASE_VERSION_CODE=$$((VERSION_MAJOR * 100000000 + VERSION_MINOR * 100000 + VERSION_PATCH * 10 ) ) && \
319+ echo " Setting android/build.gradle versionCode to $$ BASE_VERSION_CODE" && \
320+ sed -i.bak -E " s/versionCode [0-9]+/versionCode $$ BASE_VERSION_CODE/" android/build.gradle && \
321+ rm android/build.gradle.bak
285322
286323.PHONY : update-oss # # Update the tailscale.com go module
287324update-oss :
@@ -365,6 +402,14 @@ docker-build-image: ## Builds the docker image for the android build environment
365402 docker build -f docker/DockerFile.amd64-build -t $(DOCKER_IMAGE ) . ; \
366403 fi
367404
405+ .PHONY : docker-build-android-integration-image
406+ docker-build-android-integration-image : # # Build the Docker image used to run Android integration tests
407+ @echo " Checking if docker image $( ANDROID_INTEGRATION_DOCKER_IMAGE) already exists..."
408+ @if ! docker images $(ANDROID_INTEGRATION_DOCKER_IMAGE ) -q | grep -q . ; then \
409+ echo " Image does not exist. Building..." ; \
410+ docker build -f docker/Dockerfile.android-integration -t $(ANDROID_INTEGRATION_DOCKER_IMAGE ) . ; \
411+ fi
412+
368413# DOCKER_ANDROID_DIR is bind-mounted as /root/.android inside the container
369414# so the Gradle-generated debug keystore (and anything else under ~/.android)
370415# persists across docker runs. Without this, every docker-based debug build
@@ -373,12 +418,19 @@ docker-build-image: ## Builds the docker image for the android build environment
373418# JVM's user.home resolves to /root for the container's root user, regardless
374419# of the Dockerfile's HOME=/build env.
375420DOCKER_ANDROID_DIR := $(CURDIR ) /.android-docker
421+ DOCKER_ANDROID_INTEGRATION_DIR := $(CURDIR ) /.android-integration-docker
422+ DOCKER_GRADLE_DIR := $(CURDIR ) /.gradle-docker
423+ DOCKER_GO_CACHE_DIR := $(CURDIR ) /.cache-docker
376424
377425.PHONY : docker-android-dir
378426docker-android-dir :
379- @mkdir -p $(DOCKER_ANDROID_DIR )
427+ @mkdir -p $(DOCKER_ANDROID_DIR ) $(DOCKER_GRADLE_DIR ) $(DOCKER_GO_CACHE_DIR )
428+
429+ .PHONY : docker-android-integration-dir
430+ docker-android-integration-dir :
431+ @mkdir -p $(DOCKER_ANDROID_INTEGRATION_DIR ) $(DOCKER_GO_CACHE_DIR )
380432
381- DOCKER_RUN_VOLS := -v $(CURDIR ) :/build/tailscale-android -v $(DOCKER_ANDROID_DIR ) :/root/.android
433+ DOCKER_RUN_VOLS := -v $(CURDIR ) :/build/tailscale-android -v $(DOCKER_ANDROID_DIR ) :/root/.android -v $( DOCKER_GRADLE_DIR ) :/build/.gradle -v $( DOCKER_GO_CACHE_DIR ) :/build/.cache --env GOPATH=/build/.cache/go --env GOMODCACHE=/build/.cache/go/pkg/mod
382434
383435.PHONY : docker-run-build
384436docker-run-build : clean jarsign-env docker-build-image docker-android-dir # # Runs the docker image for the android build environment and builds release
@@ -388,6 +440,21 @@ docker-run-build: clean jarsign-env docker-build-image docker-android-dir ## Run
388440docker-tailscale-debug : docker-build-image docker-android-dir # # Build tailscale-debug.apk inside the docker env (stable signer across runs)
389441 @docker run --rm $(DOCKER_RUN_VOLS ) $(DOCKER_IMAGE ) make tailscale-debug
390442
443+ .PHONY : android-integration-test
444+ android-integration-test : docker-tailscale-debug android-integration-test-run # # Build APK and run adb-backed Android integration tests in Docker
445+
446+ .PHONY : android-integration-test-run
447+ android-integration-test-run : docker-build-android-integration-image docker-android-integration-dir # # Run adb-backed Android integration tests in Docker using existing APK
448+ @docker run --rm --device /dev/kvm \
449+ -v $(CURDIR ) :/workspace \
450+ -v $(DOCKER_ANDROID_INTEGRATION_DIR ) :/root/.android \
451+ -v $(DOCKER_GO_CACHE_DIR ) :/root/.cache \
452+ --env GOPATH=/root/.cache/go \
453+ --env GOMODCACHE=/root/.cache/go/pkg/mod \
454+ -w /workspace \
455+ $(ANDROID_INTEGRATION_DOCKER_IMAGE ) \
456+ /usr/local/bin/run-android-integration-test /workspace/$(DEBUG_APK )
457+
391458.PHONY : docker-remove-build-image
392459docker-remove-build-image : # # Removes the current docker build image
393460 docker rmi --force $(DOCKER_IMAGE )
0 commit comments