VERSION := $(shell grep '^version' Cargo.toml | head -1 | cut -d'"' -f2)
APP := {{nome_projeto}}
BOLD := \033[1m
RESET := \033[0m
GREEN := \033[32m
CYAN := \033[36m
RED := \033[31m
export CARGO_TERM_COLOR := never
.DEFAULT_GOAL := help
JAVA_MIN := 17
JAVA_SDKMAN := 17.0.13-tem
NDK_VERSION ?= 25.2.9519653
API_LEVEL ?= 33
BUILD_TOOLS ?= 34.0.0
CLANG_API ?= 26
CMDLINE_URL ?= https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
ANDROID_HOME ?= $(HOME)/android-sdk
ANDROID_NDK_ROOT ?= $(ANDROID_HOME)/ndk/$(NDK_VERSION)
SDKMAN_DIR ?= $(HOME)/.sdkman
ANDROID_TARGETS := aarch64-linux-android armv7-linux-androideabi \
x86_64-linux-android i686-linux-android
ABI ?= aarch64
ifeq ($(ABI),aarch64)
TARGET := aarch64-linux-android
else ifeq ($(ABI),armv7)
TARGET := armv7-linux-androideabi
else ifeq ($(ABI),x86_64)
TARGET := x86_64-linux-android
else ifeq ($(ABI),x86)
TARGET := i686-linux-android
else
$(error ABI desconhecida: '$(ABI)' — use aarch64, armv7, x86_64 ou x86)
endif
PACKAGE := com.example.{{nome_crate}}
ACTIVITY := android.app.NativeActivity
APK_DEBUG := target/debug/apk/$(APP).apk
APK_RELEASE := target/release/apk/$(APP).apk
export ANDROID_HOME
export ANDROID_NDK_ROOT
export PATH := $(ANDROID_HOME)/cmdline-tools/latest/bin:$(ANDROID_HOME)/platform-tools:$(ANDROID_HOME)/emulator:$(PATH)
ifneq ($(wildcard $(SDKMAN_DIR)/candidates/java/current),)
JAVA_HOME ?= $(SDKMAN_DIR)/candidates/java/current
export JAVA_HOME
export PATH := $(JAVA_HOME)/bin:$(PATH)
endif
NDK_BIN := $(ANDROID_NDK_ROOT)/toolchains/llvm/prebuilt/linux-x86_64/bin
export CC_aarch64_linux_android := $(NDK_BIN)/aarch64-linux-android$(CLANG_API)-clang
export CXX_aarch64_linux_android := $(NDK_BIN)/aarch64-linux-android$(CLANG_API)-clang++
export AR_aarch64_linux_android := $(NDK_BIN)/llvm-ar
export CC_armv7_linux_androideabi := $(NDK_BIN)/armv7a-linux-androideabi$(CLANG_API)-clang
export CXX_armv7_linux_androideabi := $(NDK_BIN)/armv7a-linux-androideabi$(CLANG_API)-clang++
export AR_armv7_linux_androideabi := $(NDK_BIN)/llvm-ar
export CC_x86_64_linux_android := $(NDK_BIN)/x86_64-linux-android$(CLANG_API)-clang
export CXX_x86_64_linux_android := $(NDK_BIN)/x86_64-linux-android$(CLANG_API)-clang++
export AR_x86_64_linux_android := $(NDK_BIN)/llvm-ar
export CC_i686_linux_android := $(NDK_BIN)/i686-linux-android$(CLANG_API)-clang
export CXX_i686_linux_android := $(NDK_BIN)/i686-linux-android$(CLANG_API)-clang++
export AR_i686_linux_android := $(NDK_BIN)/llvm-ar
.PHONY: setup
setup: setup-host setup-java setup-android-sdk setup-rust keystore
@echo ""
@echo "$(GREEN)Setup completo.$(RESET) Próximo passo: make build"
@echo "Confira com: make doctor"
.PHONY: setup-host
setup-host:
@echo "$(BOLD)── Pacotes do sistema ──$(RESET)"
@if command -v cc >/dev/null 2>&1 && command -v curl >/dev/null 2>&1 \
&& command -v unzip >/dev/null 2>&1 && command -v pkg-config >/dev/null 2>&1; then \
echo " já instalados ✓"; \
elif command -v apt-get >/dev/null 2>&1; then \
sudo apt-get update && sudo apt-get install -y build-essential pkg-config curl unzip zip git; \
elif command -v dnf >/dev/null 2>&1; then \
sudo dnf install -y gcc gcc-c++ make pkgconf-pkg-config curl unzip zip git; \
elif command -v pacman >/dev/null 2>&1; then \
sudo pacman -S --needed --noconfirm base-devel pkgconf curl unzip zip git; \
else \
echo "$(RED)gerenciador de pacotes não reconhecido$(RESET) — instale à mão: compilador C/C++, pkg-config, curl, unzip"; \
exit 1; \
fi
.PHONY: setup-java
setup-java:
@echo "$(BOLD)── Java $(JAVA_MIN)+ ──$(RESET)"
@major=$$(java -version 2>&1 | sed -n '1s/.*"\([0-9]*\)[."].*/\1/p'); \
if [ -n "$$major" ] && [ "$$major" -ge $(JAVA_MIN) ] 2>/dev/null; then \
echo " Java $$major já instalado ✓"; \
else \
if [ ! -f "$(SDKMAN_DIR)/bin/sdkman-init.sh" ]; then \
echo " instalando o SDKMAN…"; \
curl -fsSL "https://get.sdkman.io" | bash; \
fi; \
. "$(SDKMAN_DIR)/bin/sdkman-init.sh" && sdk install java $(JAVA_SDKMAN) < /dev/null; \
echo " Java $(JAVA_SDKMAN) instalado ✓"; \
fi
.PHONY: setup-android-sdk
setup-android-sdk:
@echo "$(BOLD)── Android SDK / NDK ──$(RESET)"
@if [ ! -x "$(ANDROID_HOME)/cmdline-tools/latest/bin/sdkmanager" ]; then \
echo " baixando as command-line tools…"; \
mkdir -p "$(ANDROID_HOME)/cmdline-tools"; \
curl -fL --progress-bar "$(CMDLINE_URL)" -o "$(ANDROID_HOME)/cmdline-tools.zip"; \
unzip -q -o "$(ANDROID_HOME)/cmdline-tools.zip" -d "$(ANDROID_HOME)/cmdline-tools"; \
rm -rf "$(ANDROID_HOME)/cmdline-tools/latest"; \
mv "$(ANDROID_HOME)/cmdline-tools/cmdline-tools" "$(ANDROID_HOME)/cmdline-tools/latest"; \
rm -f "$(ANDROID_HOME)/cmdline-tools.zip"; \
fi
@yes | sdkmanager --licenses >/dev/null 2>&1 || true
sdkmanager "platform-tools" "build-tools;$(BUILD_TOOLS)" "platforms;android-$(API_LEVEL)" "ndk;$(NDK_VERSION)"
@echo " SDK em $(ANDROID_HOME) ✓"
.PHONY: setup-rust
setup-rust:
@echo "$(BOLD)── Rust ──$(RESET)"
@command -v rustup >/dev/null 2>&1 || \
(echo "$(RED)rustup não encontrado$(RESET) — https://rustup.rs" && exit 1)
rustup target add $(ANDROID_TARGETS)
@command -v cargo-apk >/dev/null 2>&1 || \
(echo " instalando o cargo-apk…" && cargo install cargo-apk --locked)
@echo " toolchain Rust ✓"
.PHONY: keystore
keystore:
@if [ -f release.keystore ]; then \
echo " release.keystore já existe ✓"; \
else \
command -v keytool >/dev/null 2>&1 || \
(echo "$(RED)keytool não encontrado$(RESET) — rode 'make setup-java'" && exit 1); \
keytool -genkeypair -keystore release.keystore -alias {{nome_crate}} \
-keyalg RSA -keysize 2048 -validity 10000 \
-storepass {{nome_crate}} -keypass {{nome_crate}} \
-dname "CN={{titulo}}, OU=Dev, O=Dev, L=Unknown, ST=Unknown, C=BR"; \
echo " release.keystore criada ✓ (chave de teste — não publique com ela)"; \
fi
.PHONY: doctor
doctor:
@ok=0; \
check() { if eval "$$2" >/dev/null 2>&1; then printf " $(GREEN)✔$(RESET) %s\n" "$$1"; else printf " $(RED)✘$(RESET) %s → %s\n" "$$1" "$$3"; ok=1; fi; }; \
check "rustup" "command -v rustup" "https://rustup.rs"; \
check "cargo-apk" "command -v cargo-apk" "make setup-rust"; \
check "alvos Rust Android" "[ \$$(rustup target list --installed | grep -c android) -ge 4 ]" "make setup-rust"; \
check "Java $(JAVA_MIN)+" "[ \"\$$(java -version 2>&1 | sed -n '1s/.*\"\\([0-9]*\\)[.\"].*/\\1/p')\" -ge $(JAVA_MIN) ]" "make setup-java"; \
check "sdkmanager" "command -v sdkmanager" "make setup-android-sdk"; \
check "adb" "command -v adb" "make setup-android-sdk"; \
check "NDK $(NDK_VERSION)" "[ -d '$(ANDROID_NDK_ROOT)' ]" "make setup-android-sdk"; \
check "clang do NDK" "[ -x '$(CC_aarch64_linux_android)' ]" "make setup-android-sdk"; \
check "plataforma android-$(API_LEVEL)" "[ -d '$(ANDROID_HOME)/platforms/android-$(API_LEVEL)' ]" "make setup-android-sdk"; \
check "build-tools $(BUILD_TOOLS)" "[ -d '$(ANDROID_HOME)/build-tools/$(BUILD_TOOLS)' ]" "make setup-android-sdk"; \
check "release.keystore" "[ -f release.keystore ]" "make keystore"; \
check "cc / pkg-config" "command -v cc && command -v pkg-config" "make setup-host"; \
echo ""; \
if [ $$ok = 0 ]; then echo "$(GREEN)Ambiente pronto.$(RESET)"; else echo "$(RED)Falta algo — rode make setup.$(RESET)"; fi; \
exit $$ok
.PHONY: run
run:
cargo run
.PHONY: check
check:
cargo check
.PHONY: check-android
check-android:
cargo check --lib --target $(TARGET)
.PHONY: fmt
fmt:
cargo fmt --all
.PHONY: clippy
clippy:
cargo clippy --all-targets -- -D warnings
.PHONY: build
build: ## Compila o APK de debug (ABI=aarch64|armv7|x86_64|x86)
@echo "$(BOLD)── APK debug ($(TARGET)) ──$(RESET)"
cargo apk build --lib --target $(TARGET)
@echo ""
@echo "$(GREEN)APK:$(RESET) $(APK_DEBUG)"
@ls -lh $(APK_DEBUG)
.PHONY: release
release: keystore
@echo "$(BOLD)── APK release ($(TARGET)) ──$(RESET)"
cargo apk build --lib --release --target $(TARGET)
@echo ""
@echo "$(GREEN)APK:$(RESET) $(APK_RELEASE)"
@ls -lh $(APK_RELEASE)
.PHONY: devices
devices:
adb devices -l
.PHONY: install
install: build
adb install -r $(APK_DEBUG)
.PHONY: launch
launch: install
adb shell am start -n $(PACKAGE)/$(ACTIVITY)
@$(MAKE) --no-print-directory logcat
.PHONY: logcat
logcat:
adb logcat -c
adb logcat -s {{nome_crate}}:V RustStdoutStderr:V AndroidRuntime:E
.PHONY: stop
stop:
adb shell am force-stop $(PACKAGE)
.PHONY: uninstall
uninstall:
adb uninstall $(PACKAGE)
AVD_NAME ?= {{nome_crate}}_avd
AVD_IMAGE ?= system-images;android-$(API_LEVEL);google_apis;x86_64
.PHONY: setup-emulator
setup-emulator:
@test -e /dev/kvm || echo "$(RED)aviso:$(RESET) sem /dev/kvm o emulador x86_64 é inutilmente lento"
sdkmanager "emulator" "$(AVD_IMAGE)"
@echo no | avdmanager create avd -n $(AVD_NAME) -k "$(AVD_IMAGE)" --force
@echo "$(GREEN)AVD '$(AVD_NAME)' criado.$(RESET) Rode: make emulator, depois: make launch ABI=x86_64"
.PHONY: emulator
emulator:
emulator -avd $(AVD_NAME) -gpu host
.PHONY: clean
clean:
cargo clean
.PHONY: version
version:
@echo "$(VERSION)"
.PHONY: help
help:
@echo ""
@echo "$(BOLD)$(APP) $(VERSION) — Android — targets disponíveis$(RESET)"
@echo ""
@awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z_-]+:.*##/ { \
printf " $(CYAN)%-19s$(RESET) %s\n", $$1, $$2 \
}' $(MAKEFILE_LIST)
@echo ""
@echo " $(BOLD)No Windows:$(RESET) use fazer.bat (não há make lá)"
@echo " $(BOLD)O que ainda não funciona no Android:$(RESET) ANDROID_TODO.md"
@echo ""