///|
/// Write a file with default permissions
async fn write_file(path : String, content : String) -> Unit {
@fs.write_file(path, content, create=0o644)
}
///|
/// Write a binary file with default permissions
async fn write_bytes(path : String, content : Bytes) -> Unit {
@fs.write_file(path, content, create=0o644)
}
///|
/// Write a file with executable permissions
async fn write_executable(path : String, content : String) -> Unit {
@fs.write_file(path, content, create=0o755)
}
///|
/// Detect the Android SDK directory by checking environment variables
/// and well-known default installation paths.
async fn detect_android_sdk() -> String? {
match @sys.get_env_var("ANDROID_HOME") {
Some(v) => return Some(v)
None => ()
}
match @sys.get_env_var("ANDROID_SDK_ROOT") {
Some(v) => return Some(v)
None => ()
}
// Probe well-known default paths
match @sys.get_env_var("HOME") {
Some(home) => {
let macos_path = "\{home}/Library/Android/sdk"
if @fs.exists(macos_path) {
return Some(macos_path)
}
let linux_path = "\{home}/Android/Sdk"
if @fs.exists(linux_path) {
return Some(linux_path)
}
}
None => ()
}
match @sys.get_env_var("LOCALAPPDATA") {
Some(appdata) => {
let win_path = "\{appdata}/Android/Sdk"
if @fs.exists(win_path) {
return Some(win_path)
}
}
None => ()
}
match @sys.get_env_var("USERPROFILE") {
Some(profile) => {
let win_path = "\{profile}/AppData/Local/Android/Sdk"
if @fs.exists(win_path) {
return Some(win_path)
}
}
None => ()
}
None
}
///|
/// Generate the complete project
async fn generate(config : ProjectConfig) -> Unit raise Error {
let name = config.project_name
let pkg = config.package_id
// Check if output directory already exists
if @fs.exists(name) {
println("Error: Directory '\{name}' already exists.")
return
}
println("Creating project '\{name}'...")
// Create directory tree
let dirs = [
"\{name}",
"\{name}/app",
"\{name}/app/src/main/cpp",
"\{name}/app/src/main/java/com/example/\{pkg}",
"\{name}/app/src/main/moonbit",
"\{name}/app/src/main/res/drawable",
"\{name}/app/src/main/res/mipmap-anydpi",
"\{name}/app/src/main/res/values",
"\{name}/app/src/main/res/values-night",
"\{name}/app/src/main/res/xml",
"\{name}/gradle/wrapper",
]
for dir in dirs {
@fs.mkdir(dir, permission=0o755, recursive=true)
}
// Write all project files
// Root files
write_file("\{name}/.gitignore", gitignore())
write_file("\{name}/settings.gradle.kts", settings_gradle_kts(config))
write_file("\{name}/build.gradle.kts", root_build_gradle_kts())
write_file("\{name}/gradle.properties", gradle_properties())
write_executable("\{name}/gradlew", gradlew_script())
write_file("\{name}/gradlew.bat", gradlew_bat_script())
// Gradle wrapper
write_file("\{name}/gradle/libs.versions.toml", libs_versions_toml())
write_file(
"\{name}/gradle/wrapper/gradle-wrapper.properties",
gradle_wrapper_properties(),
)
// Decode and write gradle-wrapper.jar
let jar_bytes = base64_decode(gradle_wrapper_jar_base64)
write_bytes("\{name}/gradle/wrapper/gradle-wrapper.jar", jar_bytes)
// App build files
write_file("\{name}/app/build.gradle.kts", app_build_gradle_kts(config))
write_file("\{name}/app/proguard-rules.pro", proguard_rules())
// Android manifest and Kotlin sources
write_file(
"\{name}/app/src/main/AndroidManifest.xml",
android_manifest_xml(config),
)
write_file(
"\{name}/app/src/main/java/com/example/\{pkg}/MainActivity.kt",
main_activity_kt(config),
)
// Resources
write_file("\{name}/app/src/main/res/values/colors.xml", colors_xml())
write_file("\{name}/app/src/main/res/values/strings.xml", strings_xml(config))
write_file("\{name}/app/src/main/res/values/themes.xml", themes_xml(config))
write_file(
"\{name}/app/src/main/res/values-night/themes.xml",
themes_night_xml(config),
)
write_file(
"\{name}/app/src/main/res/xml/backup_rules.xml",
backup_rules_xml(),
)
write_file(
"\{name}/app/src/main/res/xml/data_extraction_rules.xml",
data_extraction_rules_xml(),
)
write_file(
"\{name}/app/src/main/res/drawable/ic_launcher_background.xml",
ic_launcher_background_xml(),
)
write_file(
"\{name}/app/src/main/res/drawable/ic_launcher_foreground.xml",
ic_launcher_foreground_xml(),
)
write_file(
"\{name}/app/src/main/res/mipmap-anydpi/ic_launcher.xml",
ic_launcher_xml(),
)
write_file(
"\{name}/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml",
ic_launcher_round_xml(),
)
// CMakeLists.txt
write_file("\{name}/app/src/main/cpp/CMakeLists.txt", cmake_lists_txt(config))
// MoonBit sources
let moonbit_dir = "\{name}/app/src/main/moonbit"
write_file("\{moonbit_dir}/main.mbt", starter_main_mbt(config))
write_file("\{moonbit_dir}/moon.mod.json", gen_moon_mod_json(config))
write_file("\{moonbit_dir}/moon.pkg", gen_moon_pkg_content())
// Run moon install to fetch dependencies
println("Installing MoonBit dependencies...")
let exit_code = @process.run("moon", ["install"], cwd=moonbit_dir)
if exit_code != 0 {
println(
"Warning: 'moon install' exited with code \{exit_code}. You may need to run it manually.",
)
}
// Try to detect Android SDK and write local.properties
let lp_path = "\{name}/local.properties"
match detect_android_sdk() {
Some(dir) => {
write_file(lp_path, "sdk.dir=\{dir}\n")
println("Detected Android SDK, wrote local.properties")
}
None =>
raise Failure::Failure(
"Could not detect Android SDK. Please install Android Studio (https://developer.android.com/studio) or set the ANDROID_HOME environment variable.",
)
}
// Initialize git repository
println("Initializing git repository...")
let git_init_code = @process.run("git", ["init"], cwd=name)
if git_init_code != 0 {
println(
"Warning: 'git init' exited with code \{git_init_code}. You may need to run it manually.",
)
}
// Success message
println("")
println("Project '\{name}' created successfully!")
println("")
println("Next steps:")
println(" cd \{name}")
println(" ./gradlew assembleDebug --no-daemon")
println("")
println("Install on device:")
println(" adb install -r app/build/outputs/apk/debug/app-debug.apk")
println(
" adb shell am start -n com.example.\{pkg}/com.example.\{pkg}.MainActivity",
)
}