From 12d0c54a0a49efeb3df68cf8cf7fe4f4ad92a0c8 Mon Sep 17 00:00:00 2001 From: kari-ts Date: Tue, 4 Mar 2025 16:51:39 -0800 Subject: [PATCH] Makefile: make unstripped libtailscale and debug symbols - Added Makefile targets to build an unstripped version of the libtailscale AAR. - Extracted the native .so (libgojni.so) from the unstripped AAR. - Generated a debug symbols file (libgojni.so.debug) using llvm-objcopy with --only-keep-debug. - Stripped the native library (libgojni.so.stripped) with --strip-debug and repackaged the final AAR. This allows the build chain to produce a stripped AAR for the app and a separate debug symbols file that can be uploaded to Google Play for crash deobfuscation. Signed-off-by: kari-ts Updates tailscale/tailscale#15210 --- .gitignore | 7 ++ Makefile | 227 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 153 insertions(+), 81 deletions(-) diff --git a/.gitignore b/.gitignore index dd0dde50a8..517b9195a9 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,13 @@ tailscale.jks .vscode .idea +# Native libraries +*.stripped +*.unstripped + +# Debug symbols +*.debug + libtailscale.aar libtailscale-sources.jar .DS_Store diff --git a/Makefile b/Makefile index d1a48afcf0..e2b51d9df4 100644 --- a/Makefile +++ b/Makefile @@ -10,38 +10,57 @@ # with this name, it will be used. # # The convention here is tailscale-android-build-amd64- -DOCKER_IMAGE=tailscale-android-build-amd64-191124 +DOCKER_IMAGE := tailscale-android-build-amd64-191124 export TS_USE_TOOLCHAIN=1 -DEBUG_APK=tailscale-debug.apk -RELEASE_AAB=tailscale-release.aab -RELEASE_TV_AAB=tailscale-tv-release.aab -LIBTAILSCALE=android/libs/libtailscale.aar -# Extract the version code from build.gradle. +# Auto-select an NDK from ANDROID_HOME (choose highest version available) +NDK_ROOT ?= $(shell ls -1d $(ANDROID_HOME)/ndk/* 2>/dev/null | sort -V | tail -n 1) + +HOST_OS := $(shell uname | tr A-Z a-z) +ifeq ($(HOST_OS),linux) + STRIP_TOOL := $(NDK_ROOT)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objcopy +else ifeq ($(HOST_OS),darwin) + STRIP_TOOL := $(NDK_ROOT)/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-objcopy +endif + +$(info Using NDK_ROOT: $(NDK_ROOT)) +$(info Using STRIP_TOOL: $(STRIP_TOOL)) + +DEBUG_APK := tailscale-debug.apk +RELEASE_AAB := tailscale-release.aab +RELEASE_TV_AAB := tailscale-tv-release.aab + +# Define output filenames. +LIBTAILSCALE_AAR := android/libs/libtailscale.aar +UNSTRIPPED_AAR := android/libs/libtailscale_unstripped.aar +ARM64_SO_PATH := jni/arm64-v8a/libgojni.so + +# Compute an absolute path for the unstripped AAR. +ABS_UNSTRIPPED_AAR := $(shell pwd)/$(UNSTRIPPED_AAR) + +# Android SDK & Tools settings. ifeq ($(shell uname),Linux) - ANDROID_TOOLS_URL="https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip" - ANDROID_TOOLS_SUM="bd1aa17c7ef10066949c88dc6c9c8d536be27f992a1f3b5a584f9bd2ba5646a0 commandlinetools-linux-9477386_latest.zip" + ANDROID_TOOLS_URL := "https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip" + ANDROID_TOOLS_SUM := "bd1aa17c7ef10066949c88dc6c9c8d536be27f992a1f3b5a584f9bd2ba5646a0 commandlinetools-linux-9477386_latest.zip" else - ANDROID_TOOLS_URL="https://dl.google.com/android/repository/commandlinetools-mac-9477386_latest.zip" - ANDROID_TOOLS_SUM="2072ffce4f54cdc0e6d2074d2f381e7e579b7d63e915c220b96a7db95b2900ee commandlinetools-mac-9477386_latest.zip" + ANDROID_TOOLS_URL := "https://dl.google.com/android/repository/commandlinetools-mac-9477386_latest.zip" + ANDROID_TOOLS_SUM := "2072ffce4f54cdc0e6d2074d2f381e7e579b7d63e915c220b96a7db95b2900ee commandlinetools-mac-9477386_latest.zip" endif -ANDROID_SDK_PACKAGES='platforms;android-31' 'extras;android;m2repository' 'ndk;23.1.7779620' 'platform-tools' 'build-tools;33.0.2' +ANDROID_SDK_PACKAGES := 'platforms;android-31' 'extras;android;m2repository' 'ndk;23.1.7779620' 'platform-tools' 'build-tools;33.0.2' # Attempt to find an ANDROID_SDK_ROOT / ANDROID_HOME based either from # preexisting environment or common locations. export ANDROID_SDK_ROOT ?= $(shell find $$ANDROID_SDK_ROOT $$ANDROID_HOME $$HOME/Library/Android/sdk $$HOME/Android/Sdk $$HOME/AppData/Local/Android/Sdk /usr/lib/android-sdk -maxdepth 1 -type d 2>/dev/null | head -n 1) - -# If ANDROID_SDK_ROOT is still unset, set it to a default location by platform. ifeq ($(ANDROID_SDK_ROOT),) - ifeq ($(shell uname),Linux) - export ANDROID_SDK_ROOT=$(HOME)/Android/Sdk - else ifeq ($(shell uname),Darwin) - export ANDROID_SDK_ROOT=$(HOME)/Library/Android/sdk - else ifneq ($(WINDIR),)) - export ANDROID_SDK_ROOT=$(HOME)/AppData/Local/Android/sdk - else - export ANDROID_SDK_ROOT=$(PWD)/android-sdk - endif + ifeq ($(shell uname),Linux) + export ANDROID_SDK_ROOT := $(HOME)/Android/Sdk + else ifeq ($(shell uname),Darwin) + export ANDROID_SDK_ROOT := $(HOME)/Library/Android/sdk + else ifneq ($(WINDIR),) + export ANDROID_SDK_ROOT := $(HOME)/AppData/Local/Android/sdk + else + export ANDROID_SDK_ROOT := $(PWD)/android-sdk + endif endif export ANDROID_HOME ?= $(ANDROID_SDK_ROOT) @@ -51,67 +70,71 @@ ANDROID_STUDIO_ROOT ?= $(shell find ~/android-studio /usr/local/android-studio / # Set JAVA_HOME to the Android Studio bundled JDK. export JAVA_HOME ?= $(shell find "$(ANDROID_STUDIO_ROOT)/jbr" "$(ANDROID_STUDIO_ROOT)/jre" "$(ANDROID_STUDIO_ROOT)/Contents/jbr/Contents/Home" "$(ANDROID_STUDIO_ROOT)/Contents/jre/Contents/Home" -maxdepth 1 -type d 2>/dev/null | head -n 1) -# If JAVA_HOME is still unset, remove it, because SDK tools go into a CPU spin if it is set and empty. ifeq ($(JAVA_HOME),) - unexport JAVA_HOME + unexport JAVA_HOME else - export PATH := $(JAVA_HOME)/bin:$(PATH) + export PATH := $(JAVA_HOME)/bin:$(PATH) endif AVD_BASE_IMAGE := "system-images;android-33;google_apis;" -export HOST_ARCH=$(shell uname -m) +export HOST_ARCH := $(shell uname -m) ifeq ($(HOST_ARCH),aarch64) - AVD_IMAGE := "$(AVD_BASE_IMAGE)arm64-v8a" + AVD_IMAGE := "$(AVD_BASE_IMAGE)arm64-v8a" else ifeq ($(HOST_ARCH),arm64) - AVD_IMAGE := "$(AVD_BASE_IMAGE)arm64-v8a" + AVD_IMAGE := "$(AVD_BASE_IMAGE)arm64-v8a" else - AVD_IMAGE := "$(AVD_BASE_IMAGE)x86_64" + AVD_IMAGE := "$(AVD_BASE_IMAGE)x86_64" endif AVD ?= tailscale-$(HOST_ARCH) export AVD_IMAGE export AVD # Use our toolchain or the one that is specified, do not perform dynamic toolchain switching. -GOTOOLCHAIN=local +GOTOOLCHAIN := local export GOTOOLCHAIN -# TOOLCHAINDIR is set by fdoid CI and used by tool/* scripts. TOOLCHAINDIR ?= export TOOLCHAINDIR -GOBIN ?= $(PWD)/android/build/go/bin +GOBIN := $(PWD)/android/build/go/bin export GOBIN export PATH := $(PWD)/tool:$(GOBIN):$(ANDROID_HOME)/cmdline-tools/latest/bin:$(ANDROID_HOME)/platform-tools:$(PATH) export GOROOT := # Unset -# -# Android Builds: -# +# ------------------------------------------------------------------------------ +# Android Build Targets +# ------------------------------------------------------------------------------ + + +.PHONY: debug-unstripped +debug-unstripped: build-unstripped-aar + @echo "Listing contents of $(ABS_UNSTRIPPED_AAR):" + unzip -l $(ABS_UNSTRIPPED_AAR) .PHONY: apk -apk: $(DEBUG_APK) ## Build the debug APK +apk: $(DEBUG_APK) .PHONY: tailscale-debug -tailscale-debug: $(DEBUG_APK) ## Build the debug APK +tailscale-debug: $(DEBUG_APK) + +$(DEBUG_APK): libtailscale debug-symbols version gradle-dependencies build-unstripped-aar + (cd android && ./gradlew test assembleDebug) + install -C android/build/outputs/apk/debug/android-debug.apk $@ # Builds the release AAB and signs it (phone/tablet/chromeOS variant) .PHONY: release -release: jarsign-env $(RELEASE_AAB) ## Build the release AAB +release: jarsign-env $(RELEASE_AAB) @jarsigner -sigalg SHA256withRSA -digestalg SHA-256 -keystore $(JKS_PATH) -storepass $(JKS_PASSWORD) $(RELEASE_AAB) tailscale # Builds the release AAB and signs it (androidTV variant) .PHONY: release-tv -release-tv: jarsign-env $(RELEASE_TV_AAB) ## Build the release AAB +release-tv: jarsign-env $(RELEASE_TV_AAB) @jarsigner -sigalg SHA256withRSA -digestalg SHA-256 -keystore $(JKS_PATH) -storepass $(JKS_PASSWORD) $(RELEASE_TV_AAB) tailscale # gradle-dependencies groups together the android sources and libtailscale needed to assemble tests/debug/release builds. .PHONY: gradle-dependencies -gradle-dependencies: $(shell find android -type f -not -path "android/build/*" -not -path '*/.*') $(LIBTAILSCALE) tailscale.version - -$(DEBUG_APK): version gradle-dependencies - (cd android && ./gradlew test assembleDebug) - install -C android/build/outputs/apk/debug/android-debug.apk $@ +gradle-dependencies: $(shell find android -type f -not -path "android/build/*" -not -path '*/.*') $(LIBTAILSCALE_AAR) tailscale.version $(RELEASE_AAB): version gradle-dependencies @echo "Building release AAB" @@ -128,50 +151,93 @@ tailscale-test.apk: version gradle-dependencies install -C ./android/build/outputs/apk/androidTest/applicationTest/android-applicationTest-androidTest.apk $@ tailscale.version: go.mod go.sum $(wildcard .git/HEAD) - $(shell ./tool/go run tailscale.com/cmd/mkversion > tailscale.version) + @bash -c "./tool/go run tailscale.com/cmd/mkversion > tailscale.version" .PHONY: version -version: tailscale.version ## print the current version information - cat tailscale.version +version: tailscale.version + @cat tailscale.version -# -# Go Builds: -# +# ------------------------------------------------------------------------------ +# Go Build Targets (Unstripped AAR, Debug Symbols, Stripped SO, Packaging) +# ------------------------------------------------------------------------------ android/libs: mkdir -p android/libs -$(GOBIN)/gomobile: $(GOBIN)/gobind go.mod go.sum +$(GOBIN): + mkdir -p $(GOBIN) + +$(GOBIN)/gomobile: $(GOBIN)/gobind go.mod go.sum | $(GOBIN) ./tool/go install golang.org/x/mobile/cmd/gomobile $(GOBIN)/gobind: go.mod go.sum ./tool/go install golang.org/x/mobile/cmd/gobind -$(LIBTAILSCALE): Makefile android/libs $(shell find libtailscale -name *.go) go.mod go.sum $(GOBIN)/gomobile tailscale.version +.PHONY: build-unstripped-aar +build-unstripped-aar: tailscale.version $(GOBIN)/gomobile + @echo "Running gomobile bind to generate unstripped AAR..." + @echo "Output file: $(ABS_UNSTRIPPED_AAR)" + mkdir -p $(dir $(ABS_UNSTRIPPED_AAR)) + rm -f $(ABS_UNSTRIPPED_AAR) $(GOBIN)/gomobile bind -target android -androidapi 26 \ -tags "$$(./build-tags.sh)" \ - -ldflags "-w $$(./version-ldflags.sh)" \ - -o $@ ./libtailscale + -ldflags "$$(./version-ldflags.sh)" \ + -o $(ABS_UNSTRIPPED_AAR) ./libtailscale || { echo "gomobile bind failed"; exit 1; } + @if [ ! -f $(ABS_UNSTRIPPED_AAR) ]; then \ + echo "Error: $(ABS_UNSTRIPPED_AAR) was not created"; exit 1; \ + fi + @echo "Generated unstripped AAR: $(ABS_UNSTRIPPED_AAR)" + +$(UNSTRIPPED_AAR): build-unstripped-aar + +libgojni.so.unstripped: $(UNSTRIPPED_AAR) + @echo "Extracting libgojni.so from unstripped AAR..." + @if unzip -p $(ABS_UNSTRIPPED_AAR) jni/arm64-v8a/libgojni.so > libgojni.so.unstripped; then \ + echo "Found arm64-v8a libgojni.so"; \ + elif unzip -p $(ABS_UNSTRIPPED_AAR) jni/armeabi-v7a/libgojni.so > libgojni.so.unstripped; then \ + echo "Found armeabi-v7a libgojni.so"; \ + else \ + echo "Neither jni/arm64-v8a/libgojni.so nor jni/armeabi-v7a/libgojni.so was found."; \ + echo "Listing contents of $(ABS_UNSTRIPPED_AAR):"; \ + unzip -l $(ABS_UNSTRIPPED_AAR); exit 1; \ + fi + +libgojni.so.debug: libgojni.so.unstripped + @echo "Extracting debug symbols from libgojni.so..." + $(STRIP_TOOL) --only-keep-debug libgojni.so.unstripped libgojni.so.debug + +libgojni.so.stripped: libgojni.so.unstripped + @echo "Stripping debug symbols from libgojni.so..." + $(STRIP_TOOL) --strip-debug libgojni.so.unstripped libgojni.so.stripped + +$(LIBTAILSCALE_AAR): libgojni.so.stripped $(UNSTRIPPED_AAR) + @echo "Repackaging AAR with stripped libgojni.so..." + rm -rf temp_aar + mkdir temp_aar + unzip $(ABS_UNSTRIPPED_AAR) -d temp_aar + cp libgojni.so.stripped temp_aar/$(ARM64_SO_PATH) + (cd temp_aar && zip -r ../$(LIBTAILSCALE_AAR) .) + rm -rf temp_aar .PHONY: libtailscale -libtailscale: $(LIBTAILSCALE) ## Build the libtailscale AAR +libtailscale: $(LIBTAILSCALE_AAR) ## Build the stripped libtailscale AAR -# -# Utility tasks: -# +.PHONY: debug-symbols +debug-symbols: libgojni.so.debug -.PHONY: all -all: test $(DEBUG_APK) ## Build and test everything +# ------------------------------------------------------------------------------ +# Utility Targets +# ------------------------------------------------------------------------------ .PHONY: env env: - @echo PATH=$(PATH) - @echo ANDROID_SDK_ROOT=$(ANDROID_SDK_ROOT) - @echo ANDROID_HOME=$(ANDROID_HOME) - @echo ANDROID_STUDIO_ROOT=$(ANDROID_STUDIO_ROOT) - @echo JAVA_HOME=$(JAVA_HOME) - @echo TOOLCHAINDIR=$(TOOLCHAINDIR) - @echo AVD_IMAGE="$(AVD_IMAGE)" + @echo "PATH=$(PATH)" + @echo "ANDROID_SDK_ROOT=$(ANDROID_SDK_ROOT)" + @echo "ANDROID_HOME=$(ANDROID_HOME)" + @echo "ANDROID_STUDIO_ROOT=$(ANDROID_STUDIO_ROOT)" + @echo "JAVA_HOME=$(JAVA_HOME)" + @echo "TOOLCHAINDIR=$(TOOLCHAINDIR)" + @echo "AVD_IMAGE=$(AVD_IMAGE)" # Ensure that JKS_PATH and JKS_PASSWORD are set before we attempt a build # that requires signing. @@ -195,21 +261,20 @@ androidpath: @echo 'export PATH=$(ANDROID_HOME)/cmdline-tools/latest/bin:$(ANDROID_HOME)/platform-tools:$$PATH' .PHONY: tag_release -tag_release: tailscale.version ## Tag the current commit with the current version +tag_release: debug-symbols tailscale.version ## Tag the current commit with the current version source tailscale.version && git tag -a "$${VERSION_LONG}" -m "OSS and Version updated to $${VERSION_LONG}" - .PHONY: bumposs ## Bump to the latest oss and update the versions. bumposs: update-oss tailscale.version 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 source tailscale.version && git tag -a "$${VERSION_LONG}" -m "OSS and Version updated to $${VERSION_LONG}" -.PHONY: bump_version_code -bump_version_code: ## Bump the version code in build.gradle +.PHONY: bump_version_code ## Bump the version code in build.gradle +bump_version_code: 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 -.PHONY: update-oss -update-oss: ## Update the tailscale.com go module +.PHONY: update-oss ## Update the tailscale.com go module +update-oss: GOPROXY=direct ./tool/go get tailscale.com@main ./tool/go mod tidy -compat=1.24 ./tool/go run tailscale.com/cmd/printdep --go > go.toolchain.rev.new @@ -247,7 +312,7 @@ checkandroidsdk: ## Check that Android SDK is installed test: gradle-dependencies ## Run the Android tests (cd android && ./gradlew test) -.PHONY: emulator +.PHONY: emulator emulator: ## Start an android emulator instance @echo "Checking installed SDK packages..." @if ! $(ANDROID_HOME)/cmdline-tools/latest/bin/sdkmanager --list_installed | grep -q "$(AVD_IMAGE)"; then \ @@ -262,7 +327,7 @@ emulator: ## Start an android emulator instance @echo "Starting emulator..." @$(ANDROID_HOME)/emulator/emulator -avd "$(AVD)" -logcat-output /dev/stdout -netdelay none -netspeed full -.PHONY: install +.PHONY: install install: $(DEBUG_APK) ## Install the debug APK on a connected device adb install -r $< @@ -270,7 +335,7 @@ install: $(DEBUG_APK) ## Install the debug APK on a connected device run: install ## Run the debug APK on a connected device adb shell am start -n com.tailscale.ipn/com.tailscale.ipn.MainActivity -.PHONY: docker-build-image +.PHONY: docker-build-image docker-build-image: ## Builds the docker image for the android build environment if it does not exist @echo "Checking if docker image $(DOCKER_IMAGE) already exists..." @if ! docker images $(DOCKER_IMAGE) -q | grep -q . ; then \ @@ -279,19 +344,19 @@ docker-build-image: ## Builds the docker image for the android build environment fi .PHONY: docker-run-build -docker-run-build: clean jarsign-env docker-build-image ## Runs the docker image for the android build environment and builds release +docker-run-build: clean jarsign-env docker-build-image ## Runs the docker image for the android build environment and builds release @docker run --rm -v $(CURDIR):/build/tailscale-android --env JKS_PASSWORD=$(JKS_PASSWORD) --env JKS_PATH=$(JKS_PATH) $(DOCKER_IMAGE) .PHONY: docker-remove-build-image docker-remove-build-image: ## Removes the current docker build image docker rmi --force $(DOCKER_IMAGE) -.PHONY: docker-all ## Makes a fresh docker environment, builds docker and cleans up. For CI. +.PHONY: docker-all ## Makes a fresh docker environment, builds docker and cleans up. For CI. docker-all: docker-build-image docker-run-build $(DOCKER_IMAGE) .PHONY: docker-shell docker-shell: ## Builds a docker image with the android build env and opens a shell - docker build -f docker/DockerFile.amd64-shell -t tailscale-android-shell-amd64 . + docker build -f docker/DockerFile.amd64-shell -t tailscale-android-shell-amd64 . docker run --rm -v $(CURDIR):/build/tailscale-android -it tailscale-android-shell-amd64 .PHONY: docker-remove-shell-image @@ -299,9 +364,9 @@ docker-remove-shell-image: ## Removes all docker shell image docker rmi --force tailscale-android-shell-amd64 .PHONY: clean -clean: ## Remove build artifacts. Does not purge docker build envs. Use dockerRemoveEnv for that. +clean: ## Remove build artifacts. Does not purge docker build envs. Use dockerRemoveEnv for that. @echo "Cleaning up old build artifacts" - -rm -rf android/build $(DEBUG_APK) $(RELEASE_AAB) $(RELEASE_TV_AAB) $(LIBTAILSCALE) android/libs *.apk *.aab + -rm -rf android/build $(DEBUG_APK) $(RELEASE_AAB) $(RELEASE_TV_AAB) $(LIBTAILSCALE_AAR) android/libs *.apk *.aab @echo "Cleaning cached toolchain" -rm -rf $(HOME)/.cache/tailscale-go{,.extracted} -pkill -f gradle