Skip to content

Makefile: make unstripped libtailscale and debug symbols #619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ tailscale.jks
.vscode
.idea

# Native libraries
*.stripped
*.unstripped

# Debug symbols
*.debug

libtailscale.aar
libtailscale-sources.jar
.DS_Store
Expand Down
227 changes: 146 additions & 81 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,57 @@
# with this name, it will be used.
#
# The convention here is tailscale-android-build-amd64-<date>
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)

Expand All @@ -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"
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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 \
Expand All @@ -262,15 +327,15 @@ 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 $<

.PHONY: run
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 \
Expand All @@ -279,29 +344,29 @@ 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
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
Expand Down