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).
256 lines
12 KiB
Rust
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());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|