makepad/platform/build.rs
andodeki 28044a7367 feat(test): Android makepad_test via adb + native-activity, standalone terminal mode
Apply the makepad_test workstream onto the fork's portallist pin:
- makepad_test: runtime support for terminal (standalone) + Android (adb) runners,
  dropping the Studio-hub-only requirement for headless UI tests
- platform: native_activity Android path (looper_wrapper.c, android_native.rs,
  android_jni_native.rs), standalone terminal app_main path
- cargo-makepad: native_activity + profile-dependent prefer-dynamic in android
  build; keystore-create/build-aab packaging inputs
- studio hub: standalone hub_server binary for headless run
- examples/counter: terminal UI test entry (wait_text/click via Selector) ready
  for Android + desktop parity

Base: fork portallist_flow_adaptive_view d82756a (keeps map work + re-exports).
2026-08-16 02:17:48 +03:00

256 lines
12 KiB
Rust

use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
/// Best-effort default for the macOS bundle name when MAKEPAD_BUNDLE_NAME isn't
/// set. Cargo doesn't expose the consuming binary's package name to a
/// dependency's build script (`CARGO_PKG_NAME` here is "makepad-platform"), so
/// we walk up from `OUT_DIR` (which is always
/// `<root>/target/<profile>/build/<crate>-<hash>/out`) to the directory that
/// contains `target/` and use that directory's name. For a typical project
/// that's the package or workspace root, which is almost always a meaningful
/// label. Capitalize the first letter so the menu bar shows "Sample app" rather
/// than "sample app". Returns `None` if the path doesn't have the expected shape
/// or the directory name isn't valid UTF-8.
fn detect_app_name(out_dir: &Path) -> Option<String> {
let workspace_root = out_dir.ancestors().nth(5)?;
let dir_name = workspace_root.file_name()?.to_str()?;
if dir_name.is_empty() {
return None;
}
let mut chars = dir_name.chars();
let first = chars.next()?;
Some(first.to_uppercase().collect::<String>() + chars.as_str())
}
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let path = Path::new(&out_dir)
.parent()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap();
let cwd = std::env::current_dir().unwrap();
let mut file = File::create(path.join("makepad-platform.path")).unwrap();
file.write_all(format!("{}", cwd.display()).as_bytes())
.unwrap();
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let target = env::var("TARGET").unwrap();
if target_os == "macos" {
// The downstream app can override the bundle name shown in the macOS
// application menu by setting MAKEPAD_BUNDLE_NAME — typically via its
// `.cargo/config.toml` `[env]` section with `force = true`. macOS uses
// CFBundleName from this Info.plist as the first menu bar item title
// for unbundled `cargo run` launches, and it overrides whatever NSMenu
// title we pass to setMainMenu:. When the env var isn't set, we fall
// back to the workspace/package directory name (capitalized), which
// is almost always more meaningful than a hardcoded placeholder.
let bundle_name = env::var("MAKEPAD_BUNDLE_NAME")
.ok()
.or_else(|| detect_app_name(Path::new(&out_dir)))
.unwrap_or_else(|| "Makepad App".to_string());
let bundle_id = env::var("MAKEPAD_BUNDLE_IDENTIFIER")
.unwrap_or_else(|_| format!("dev.makepad.{}", bundle_name.to_lowercase().replace(' ', "-")));
let command_line_plist = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>{bundle_id}</string>
<key>CFBundleName</key>
<string>{bundle_name}</string>
<key>CFBundleDisplayName</key>
<string>{bundle_name}</string>
<key>GCSupportsControllerUserInteraction</key>
<true/>
<key>GCSupportedGameControllers</key>
<array>
<dict>
<key>ProfileName</key>
<string>ExtendedGamepad</string>
</dict>
</array>
<key>NSLocationUsageDescription</key>
<string>Used to show your position on the map.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Used to show your position on the map.</string>
</dict>
</plist>
"#
);
std::fs::write(path.join("Info.plist"), command_line_plist).unwrap();
}
// Per slot: env-var override → auto-discovery in `<workspace_root>/resources/`.
// Workspace root is the dir containing `target/` (5 ancestors up from
// OUT_DIR), same heuristic as `detect_app_name`.
let icons: &[(&str, &str, &str)] = &[
("MAKEPAD_APP_ICON_32", "icon_32.png", "CUSTOM_ICON_PNG_32"),
("MAKEPAD_APP_ICON_64", "icon_64.png", "CUSTOM_ICON_PNG_64"),
("MAKEPAD_APP_ICON_128", "icon_128.png", "CUSTOM_ICON_PNG_128"),
("MAKEPAD_APP_ICON_256", "icon_256.png", "CUSTOM_ICON_PNG_256"),
("MAKEPAD_APP_ICON_512", "icon_512.png", "CUSTOM_ICON_PNG_512"),
("MAKEPAD_APP_ICON_1024", "icon_1024.png", "CUSTOM_ICON_PNG_1024"),
("MAKEPAD_APP_ICON_ICO", "icon.ico", "CUSTOM_ICON_ICO"),
];
let resources_dir = Path::new(&out_dir).ancestors().nth(5).map(|r| r.join("resources"));
let mut icon_gen = String::new();
for &(var, filename, const_name) in icons {
println!("cargo:rerun-if-env-changed={var}");
let path = env::var(var).ok().or_else(|| {
let p = resources_dir.as_ref()?.join(filename);
p.is_file().then(|| p.to_string_lossy().into_owned())
});
let value = match &path {
Some(p) => {
println!("cargo:rerun-if-changed={p}");
format!("include_bytes!(r#\"{p}\"#)")
}
None => "&[]".to_string(),
};
icon_gen.push_str(&format!(
"#[allow(dead_code)] pub static {const_name}: &'static [u8] = {value};\n"
));
}
// Watch the resources dir so new/removed icon files trigger a rebuild
// (rerun-if-changed on a non-existent file is a no-op).
if let Some(dir) = resources_dir.as_ref().filter(|d| d.is_dir()) {
println!("cargo:rerun-if-changed={}", dir.display());
}
std::fs::write(Path::new(&out_dir).join("app_icon_gen.rs"), icon_gen).unwrap();
println!("cargo:rustc-check-cfg=cfg(apple_bundle,apple_sim,lines,use_gles_3,use_vulkan,linux_direct,quest,no_android_choreographer,ohos_sim,headless,use_unstable_unix_socket_ancillary_data_2021)");
println!("cargo:rerun-if-env-changed=MAKEPAD");
println!("cargo:rerun-if-env-changed=MAKEPAD_PACKAGE_DIR");
println!("cargo:rerun-if-env-changed=MAKEPAD_BUNDLE_NAME");
println!("cargo:rerun-if-env-changed=MAKEPAD_BUNDLE_IDENTIFIER");
println!("cargo:rerun-if-env-changed=IPHONEOS_DEPLOYMENT_TARGET");
if let Ok(configs) = env::var("MAKEPAD") {
for config in configs.split(['+', ',']) {
match config {
"lines" => println!("cargo:rustc-cfg=lines"),
"linux_direct" => println!("cargo:rustc-cfg=linux_direct"),
"no_android_choreographer" => println!("cargo:rustc-cfg=no_android_choreographer"),
"quest" => {
println!("cargo:rustc-cfg=quest");
println!("cargo:rustc-cfg=use_gles_3");
println!("cargo:rustc-cfg=use_vulkan");
}
"apple_bundle" => println!("cargo:rustc-cfg=apple_bundle"),
"ohos_sim" => println!("cargo:rustc-cfg=ohos_sim"),
"headless" => println!("cargo:rustc-cfg=headless"),
"use_gles_3" => println!("cargo:rustc-cfg=use_gles_3"),
"vulkan" | "use_vulkan" => println!("cargo:rustc-cfg=use_vulkan"),
_ => {}
}
}
}
match target_os.as_str() {
"macos" => {
println!("cargo:rustc-link-lib=framework=GameController");
println!("cargo:rustc-link-lib=framework=CoreLocation");
}
"ios" => {
if target == "aarch64-apple-ios-sim" {
println!("cargo:rustc-cfg=apple_sim");
}
println!("cargo:rustc-link-lib=framework=MetalKit");
println!("cargo:rustc-link-lib=framework=GameController");
println!("cargo:rustc-link-lib=framework=CoreLocation");
}
"tvos" => {
if target == "aarch64-apple-tvos-sim" {
println!("cargo:rustc-cfg=apple_sim");
}
println!("cargo:rustc-link-lib=framework=MetalKit");
println!("cargo:rustc-link-lib=framework=GameController");
}
"linux" => {
println!("cargo:rustc-cfg=use_gles_3");
println!("cargo:rustc-link-lib=xkbcommon");
}
"android" => {
println!("cargo:rustc-cfg=use_gles_3");
// Compile the looper C wrapper so that ALooper_prepare and
// ALooper_pollAll happen in the same C++ translation unit,
// ensuring the thread_local used by ALooper::getForThread()
// is consistent.
let src = std::path::Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap())
.join("src").join("os").join("linux").join("android").join("looper_wrapper.c");
let out_dir_path = std::path::Path::new(&out_dir);
let obj = out_dir_path.join("looper_wrapper.o");
let lib = out_dir_path.join("liblooper_wrapper.a");
// Discover the NDK clang from the Makepad-managed SDK or NDK_DIR env.
let ndk_dir = env::var("NDK_DIR").ok().or_else(|| {
// Search for ndk_path.txt at various ancestor levels
let p = std::path::Path::new(&out_dir);
for depth in 3..=8 {
if let Some(ancestor) = p.ancestors().nth(depth) {
let candidate = ancestor.join("makepad-android-wrapper").join("ndk_path.txt");
if let Ok(s) = std::fs::read_to_string(&candidate) {
let s = s.trim().to_string();
if !s.is_empty() { return Some(s); }
}
}
}
// Also try under CARGO_MANIFEST_DIR's target directory
if let Ok(manifest) = env::var("CARGO_MANIFEST_DIR") {
let candidate = std::path::PathBuf::from(&manifest)
.join("..").join("target").join("makepad-android-wrapper").join("ndk_path.txt");
if let Ok(s) = std::fs::read_to_string(&candidate) {
let s = s.trim().to_string();
if !s.is_empty() { return Some(s); }
}
}
None
});
if let Some(ndk) = ndk_dir {
let host = if cfg!(target_os = "macos") { "darwin-x86_64" }
else if cfg!(target_os = "linux") { "linux-x86_64" }
else { "windows-x86_64" };
let clang = std::path::PathBuf::from(&ndk)
.join("toolchains").join("llvm").join("prebuilt")
.join(host).join("bin").join("aarch64-linux-android26-clang");
let ar = std::path::PathBuf::from(&ndk)
.join("toolchains").join("llvm").join("prebuilt")
.join(host).join("bin").join("llvm-ar");
let sysroot = std::path::PathBuf::from(&ndk)
.join("toolchains").join("llvm").join("prebuilt")
.join(host).join("sysroot");
if clang.exists() {
let cc_ok = std::process::Command::new(&clang)
.args(["-c", "-O2", "-fPIC", "-target", "aarch64-linux-android26"])
.arg("--sysroot").arg(&sysroot)
.arg("-o").arg(&obj)
.arg(&src)
.status()
.map(|s| s.success())
.unwrap_or(false);
if cc_ok && ar.exists() {
let _ = std::process::Command::new(&ar)
.args(["rcs", lib.to_str().unwrap(), obj.to_str().unwrap()])
.status();
if lib.exists() {
println!("cargo:rustc-link-search=native={}", out_dir_path.display());
println!("cargo:rustc-link-lib=static=looper_wrapper");
println!("cargo:rerun-if-changed={}", src.display());
}
}
}
}
}
_ => (),
}
}