Files
lime/tools/platforms/AndroidPlatform.hx
Josh Tynjala b021dbeae7 lime display: if the project file is newer than an existing debug/release/final.hxml file, don't consider the .hxml file valid anymore
Code intelligence should always use the newest hxml content, so the fallback mode where the hxml content is generated, instead of loaded from an existing .hxml file, should be used when the project file is newer.

For instance, if the user changes any file/dir paths in their project file, continuing to use the existing .hxml file could lead to confusing error messages that still reference the old/cached file paths. It should always use the latest paths or other values from the project file. It should be considered a bug to use the old cached paths.

Previously, as a workaround, the user would need to clean or build their project again to get updated .hxml files. It might also require restarting their editor/IDE too. Bad developer experience when we can detect this case automatically.
2024-01-05 13:54:36 -08:00

618 lines
18 KiB
Haxe

package;
import hxp.ArrayTools;
import hxp.Haxelib;
import hxp.HXML;
import hxp.Log;
import hxp.Path;
import hxp.System;
import lime.tools.AndroidHelper;
import lime.tools.Architecture;
import lime.tools.AssetHelper;
import lime.tools.AssetType;
import lime.tools.CPPHelper;
import lime.tools.DeploymentHelper;
import lime.tools.HXProject;
import lime.tools.Icon;
import lime.tools.IconHelper;
import lime.tools.Orientation;
import lime.tools.PlatformTarget;
import lime.tools.ProjectHelper;
import sys.io.File;
import sys.FileSystem;
class AndroidPlatform extends PlatformTarget
{
private var deviceID:String;
public function new(command:String, _project:HXProject, targetFlags:Map<String, String>)
{
super(command, _project, targetFlags);
var defaults = new HXProject();
defaults.meta =
{
title: "MyApplication",
description: "",
packageName: "com.example.myapp",
version: "1.0.0",
company: "",
companyUrl: "",
buildNumber: null,
companyId: ""
};
defaults.app =
{
main: "Main",
file: "MyApplication",
path: "bin",
preloader: "",
swfVersion: 17,
url: "",
init: null
};
defaults.window =
{
width: 800,
height: 600,
parameters: "{}",
background: 0xFFFFFF,
fps: 30,
hardware: true,
display: 0,
resizable: true,
borderless: false,
orientation: Orientation.AUTO,
vsync: false,
fullscreen: false,
allowHighDPI: true,
alwaysOnTop: false,
antialiasing: 0,
allowShaders: true,
requireShaders: false,
depthBuffer: true,
stencilBuffer: true,
colorDepth: 32,
maximized: false,
minimized: false,
hidden: false,
title: ""
};
if (project.targetFlags.exists("simulator") || project.targetFlags.exists("emulator"))
{
defaults.architectures = [Architecture.X86];
}
else
{
defaults.architectures = [Architecture.ARMV7, Architecture.ARM64];
}
defaults.window.width = 0;
defaults.window.height = 0;
defaults.window.fullscreen = true;
defaults.window.requireShaders = true;
for (i in 1...project.windows.length)
{
defaults.windows.push(defaults.window);
}
defaults.merge(project);
project = defaults;
for (excludeArchitecture in project.excludeArchitectures)
{
project.architectures.remove(excludeArchitecture);
}
if (command != "display" && command != "clean")
{
// project = project.clone ();
if (!project.environment.exists("ANDROID_SETUP"))
{
Log.error("You need to run \"lime setup android\" before you can use the Android target");
}
AndroidHelper.initialize(project);
if (deviceID == null && project.targetFlags.exists("device"))
{
deviceID = project.targetFlags.get("device") + ":5555";
}
}
targetDirectory = Path.combine(project.app.path, project.config.getString("android.output-directory", "android"));
}
public override function build():Void
{
var destination = targetDirectory + "/bin";
var hxml = targetDirectory + "/haxe/" + buildType + ".hxml";
var sourceSet = destination + "/app/src/main";
var hasARMV5 = (ArrayTools.containsValue(project.architectures, Architecture.ARMV5)
|| ArrayTools.containsValue(project.architectures, Architecture.ARMV6));
var hasARMV7 = ArrayTools.containsValue(project.architectures, Architecture.ARMV7);
var hasARM64 = ArrayTools.containsValue(project.architectures, Architecture.ARM64);
var hasX86 = ArrayTools.containsValue(project.architectures, Architecture.X86);
var hasX64 = ArrayTools.containsValue(project.architectures, Architecture.X64);
var architectures = [];
if (hasARMV5) architectures.push(Architecture.ARMV5);
if (hasARMV7 || (!hasARMV5 && !hasX86)) architectures.push(Architecture.ARMV7);
if (hasARM64) architectures.push(Architecture.ARM64);
if (hasX86) architectures.push(Architecture.X86);
if (hasX64) architectures.push(Architecture.X64);
for (architecture in architectures)
{
var haxeParams = [hxml, "-D", "android", "-D", "PLATFORM=android-21"];
var cppParams = ["-Dandroid", "-DPLATFORM=android-21"];
var path = sourceSet + "/jniLibs/armeabi";
var suffix = ".so";
if (architecture == Architecture.ARMV7)
{
haxeParams.push("-D");
haxeParams.push("HXCPP_ARMV7");
cppParams.push("-DHXCPP_ARMV7");
path = sourceSet + "/jniLibs/armeabi-v7a";
suffix = "-v7.so";
}
else if (architecture == Architecture.ARM64)
{
haxeParams.push("-D");
haxeParams.push("HXCPP_ARM64");
cppParams.push("-DHXCPP_ARM64");
path = sourceSet + "/jniLibs/arm64-v8a";
suffix = "-64.so";
}
else if (architecture == Architecture.X86)
{
haxeParams.push("-D");
haxeParams.push("HXCPP_X86");
cppParams.push("-DHXCPP_X86");
path = sourceSet + "/jniLibs/x86";
suffix = "-x86.so";
}
else if (architecture == Architecture.X64)
{
haxeParams.push("-D");
haxeParams.push("HXCPP_X86_64");
cppParams.push("-DHXCPP_X86_64");
path = sourceSet + "/jniLibs/x86_64";
suffix = "-x86_64.so";
}
for (ndll in project.ndlls)
{
ProjectHelper.copyLibrary(project, ndll, "Android", "lib", suffix, path, project.debug, ".so");
}
System.runCommand("", "haxe", haxeParams);
if (noOutput) return;
CPPHelper.compile(project, targetDirectory + "/obj", cppParams);
System.copyIfNewer(targetDirectory + "/obj/libApplicationMain" + (project.debug ? "-debug" : "") + suffix, path + "/libApplicationMain.so");
}
if (!hasARMV5)
{
if (FileSystem.exists(sourceSet + "/jniLibs/armeabi"))
{
System.removeDirectory(sourceSet + "/jniLibs/armeabi");
}
}
if (!hasARMV7)
{
if (FileSystem.exists(sourceSet + "/jniLibs/armeabi-v7a"))
{
System.removeDirectory(sourceSet + "/jniLibs/armeabi-v7a");
}
}
if (!hasARM64)
{
if (FileSystem.exists(sourceSet + "/jniLibs/arm64-v8a"))
{
System.removeDirectory(sourceSet + "/jniLibs/arm64-v8a");
}
}
if (!hasX86)
{
if (FileSystem.exists(sourceSet + "/jniLibs/x86"))
{
System.removeDirectory(sourceSet + "/jniLibs/x86");
}
}
if (!hasX64)
{
if (FileSystem.exists(sourceSet + "/jniLibs/x86_64"))
{
System.removeDirectory(sourceSet + "/jniLibs/x86_64");
}
}
if (noOutput) return;
AndroidHelper.build(project, destination);
}
public override function clean():Void
{
if (FileSystem.exists(targetDirectory))
{
System.removeDirectory(targetDirectory);
}
}
public override function deploy():Void
{
DeploymentHelper.deploy(project, targetFlags, targetDirectory, "Android");
}
public override function display():Void
{
if (project.targetFlags.exists("output-file"))
{
var build = "-debug";
if (project.keystore != null)
{
build = "-release";
}
var outputDirectory = null;
if (project.config.exists("android.gradle-build-directory"))
{
outputDirectory = Path.combine(project.config.getString("android.gradle-build-directory"), project.app.file + "/app/outputs/apk");
}
else
{
outputDirectory = Path.combine(FileSystem.fullPath(targetDirectory), "bin/app/build/outputs/apk");
}
Sys.println(Path.combine(outputDirectory, project.app.file + build + ".apk"));
}
else
{
Sys.println(getDisplayHXML().toString());
}
}
private function getDisplayHXML():HXML
{
var path = targetDirectory + "/haxe/" + buildType + ".hxml";
// try to use the existing .hxml file. however, if the project file was
// modified more recently than the .hxml, then the .hxml cannot be
// considered valid anymore. it may cause errors in editors like vscode.
if (FileSystem.exists(path)
&& (project.projectFilePath == null || !FileSystem.exists(project.projectFilePath)
|| (FileSystem.stat(path).mtime.getTime() > FileSystem.stat(project.projectFilePath).mtime.getTime())))
{
return File.getContent(path);
}
else
{
var context = project.templateContext;
var hxml = HXML.fromString(context.HAXE_FLAGS);
hxml.addClassName(context.APP_MAIN);
hxml.cpp = "_";
hxml.noOutput = true;
return hxml;
}
}
public override function install():Void
{
var build = "debug";
if (project.keystore != null)
{
build = "release";
}
if (project.environment.exists("ANDROID_GRADLE_TASK"))
{
var task = project.environment.get("ANDROID_GRADLE_TASK");
if (task == "assembleDebug")
{
build = "debug";
}
else
{
build = "release";
}
}
var outputDirectory = null;
if (project.config.exists("android.gradle-build-directory"))
{
outputDirectory = Path.combine(project.config.getString("android.gradle-build-directory"), project.app.file + "/app/outputs/apk/" + build);
}
else
{
outputDirectory = Path.combine(FileSystem.fullPath(targetDirectory), "bin/app/build/outputs/apk/" + build);
}
var apkPath = Path.combine(outputDirectory, project.app.file + "-" + build + ".apk");
deviceID = AndroidHelper.install(project, apkPath, deviceID);
}
public override function rebuild():Void
{
var armv5 = (/*command == "rebuild" ||*/
ArrayTools.containsValue(project.architectures, Architecture.ARMV5)
|| ArrayTools.containsValue(project.architectures, Architecture.ARMV6));
var armv7 = (command == "rebuild" || ArrayTools.containsValue(project.architectures, Architecture.ARMV7));
var arm64 = (command == "rebuild" || ArrayTools.containsValue(project.architectures, Architecture.ARM64));
var x86 = (command == "rebuild" || ArrayTools.containsValue(project.architectures, Architecture.X86));
var x64 = (/*command == "rebuild" ||*/ ArrayTools.containsValue(project.architectures, Architecture.X64));
var commands = [];
if (armv5) commands.push(["-Dandroid", "-DPLATFORM=android-21"]);
if (armv7) commands.push(["-Dandroid", "-DHXCPP_ARMV7", "-DPLATFORM=android-21"]);
if (arm64) commands.push(["-Dandroid", "-DHXCPP_ARM64", "-DPLATFORM=android-21"]);
if (x86) commands.push(["-Dandroid", "-DHXCPP_X86", "-DPLATFORM=android-21"]);
if (x64) commands.push(["-Dandroid", "-DHXCPP_X86_64", "-DPLATFORM=android-21"]);
CPPHelper.rebuild(project, commands);
}
public override function run():Void
{
AndroidHelper.run(project.meta.packageName + "/" + project.meta.packageName + ".MainActivity", deviceID);
}
public override function trace():Void
{
AndroidHelper.trace(project, project.debug, deviceID);
}
public override function uninstall():Void
{
AndroidHelper.uninstall(project.meta.packageName, deviceID);
}
public override function update():Void
{
AssetHelper.processLibraries(project, targetDirectory);
// project = project.clone ();
for (asset in project.assets)
{
if (asset.embed && asset.sourcePath == "")
{
var path = Path.combine(targetDirectory + "/obj/tmp", asset.targetPath);
System.mkdir(Path.directory(path));
AssetHelper.copyAsset(asset, path);
asset.sourcePath = path;
}
}
// initialize (project);
var destination = targetDirectory + "/bin";
var sourceSet = destination + "/app/src/main";
System.mkdir(sourceSet);
System.mkdir(sourceSet + "/res/drawable-ldpi/");
System.mkdir(sourceSet + "/res/drawable-mdpi/");
System.mkdir(sourceSet + "/res/drawable-hdpi/");
System.mkdir(sourceSet + "/res/drawable-xhdpi/");
for (asset in project.assets)
{
if (asset.type != AssetType.TEMPLATE)
{
var targetPath = "";
switch (asset.type)
{
default:
// case SOUND, MUSIC:
// var extension = Path.extension (asset.sourcePath);
// asset.flatName += ((extension != "") ? "." + extension : "");
// asset.resourceName = asset.flatName;
targetPath = Path.combine(sourceSet + "/assets/", asset.resourceName);
// asset.resourceName = asset.id;
// targetPath = sourceSet + "/res/raw/" + asset.flatName + "." + Path.extension (asset.targetPath);
// default:
// asset.resourceName = asset.flatName;
// targetPath = sourceSet + "/assets/" + asset.resourceName;
}
AssetHelper.copyAssetIfNewer(asset, targetPath);
}
}
if (project.targetFlags.exists("xml"))
{
project.haxeflags.push("-xml " + targetDirectory + "/types.xml");
}
var context = project.templateContext;
context.CPP_DIR = targetDirectory + "/obj";
context.OUTPUT_DIR = targetDirectory;
context.ANDROID_INSTALL_LOCATION = project.config.getString("android.install-location", "auto");
context.ANDROID_MINIMUM_SDK_VERSION = project.config.getInt("android.minimum-sdk-version", 21);
context.ANDROID_TARGET_SDK_VERSION = project.config.getInt("android.target-sdk-version", 30);
context.ANDROID_EXTENSIONS = project.config.getArrayString("android.extension");
context.ANDROID_PERMISSIONS = project.config.getArrayString("android.permission", [
"android.permission.WAKE_LOCK",
"android.permission.INTERNET",
"android.permission.VIBRATE",
"android.permission.ACCESS_NETWORK_STATE"
]);
context.ANDROID_GRADLE_VERSION = project.config.getString("android.gradle-version", "7.4.2");
context.ANDROID_GRADLE_PLUGIN = project.config.getString("android.gradle-plugin", "7.3.1");
context.ANDROID_USE_ANDROIDX = project.config.getString("android.useAndroidX", "true");
context.ANDROID_ENABLE_JETIFIER = project.config.getString("android.enableJetifier", "false");
context.ANDROID_LIBRARY_PROJECTS = [];
if (!project.environment.exists("ANDROID_SDK") || !project.environment.exists("ANDROID_NDK_ROOT"))
{
var command = #if lime "lime" #else "hxp" #end;
var toolsBase = Type.resolveClass("CommandLineTools");
if (toolsBase != null) command = Reflect.field(toolsBase, "commandName");
Log.error("You must define ANDROID_SDK and ANDROID_NDK_ROOT to target Android, please run '" + command + " setup android' first");
Sys.exit(1);
}
if (project.config.exists("android.gradle-build-directory"))
{
context.ANDROID_GRADLE_BUILD_DIRECTORY = project.config.getString("android.gradle-build-directory");
}
if (project.config.exists("android.build-tools-version"))
{
context.ANDROID_BUILD_TOOLS_VERSION = project.config.getString("android.build-tools-version");
}
else
{
context.ANDROID_BUILD_TOOLS_VERSION = AndroidHelper.getBuildToolsVersion(project);
}
var escaped = ~/([ #!=\\:])/g;
context.ANDROID_SDK_ESCAPED = escaped.replace(context.ENV_ANDROID_SDK, "\\$1");
context.ANDROID_NDK_ROOT_ESCAPED = escaped.replace(context.ENV_ANDROID_NDK_ROOT, "\\$1");
if (Reflect.hasField(context, "KEY_STORE")) context.KEY_STORE = StringTools.replace(context.KEY_STORE, "\\", "\\\\");
if (Reflect.hasField(context, "KEY_STORE_ALIAS")) context.KEY_STORE_ALIAS = StringTools.replace(context.KEY_STORE_ALIAS, "\\", "\\\\");
if (Reflect.hasField(context, "KEY_STORE_PASSWORD")) context.KEY_STORE_PASSWORD = StringTools.replace(context.KEY_STORE_PASSWORD, "\\", "\\\\");
if (Reflect.hasField(context,
"KEY_STORE_ALIAS_PASSWORD")) context.KEY_STORE_ALIAS_PASSWORD = StringTools.replace(context.KEY_STORE_ALIAS_PASSWORD, "\\", "\\\\");
var index = 1;
for (dependency in project.dependencies)
{
if (dependency.path != ""
&& FileSystem.exists(dependency.path)
&& FileSystem.isDirectory(dependency.path)
&& (FileSystem.exists(Path.combine(dependency.path, "project.properties"))
|| FileSystem.exists(Path.combine(dependency.path, "build.gradle"))))
{
var name = dependency.name;
if (name == "") name = "project" + index;
context.ANDROID_LIBRARY_PROJECTS.push(
{
name: name,
index: index,
path: "deps/" + name,
source: dependency.path
});
index++;
}
}
var iconTypes = ["ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"];
var iconSizes = [36, 48, 72, 96, 144, 192];
var icons = project.icons;
if (icons.length == 0)
{
icons = [new Icon(System.findTemplate(project.templatePaths, "default/icon.svg"))];
}
for (i in 0...iconTypes.length)
{
if (IconHelper.createIcon(icons, iconSizes[i], iconSizes[i], sourceSet + "/res/drawable-" + iconTypes[i] + "/icon.png"))
{
context.HAS_ICON = true;
}
}
IconHelper.createIcon(icons, 732, 412, sourceSet + "/res/drawable-xhdpi/ouya_icon.png");
var packageDirectory = project.meta.packageName;
packageDirectory = sourceSet + "/java/" + packageDirectory.split(".").join("/");
System.mkdir(packageDirectory);
for (javaPath in project.javaPaths)
{
try
{
if (FileSystem.isDirectory(javaPath))
{
System.recursiveCopy(javaPath, sourceSet + "/java", context, true);
}
else
{
if (Path.extension(javaPath) == "jar")
{
System.copyIfNewer(javaPath, destination + "/app/libs/" + Path.withoutDirectory(javaPath));
}
else
{
System.copyIfNewer(javaPath, sourceSet + "/java/" + Path.withoutDirectory(javaPath));
}
}
}
catch (e:Dynamic) {}
// throw"Could not find javaPath " + javaPath +" required by extension.";
// }
}
for (library in cast(context.ANDROID_LIBRARY_PROJECTS, Array<Dynamic>))
{
System.recursiveCopy(library.source, destination + "/deps/" + library.name, context, true);
}
ProjectHelper.recursiveSmartCopyTemplate(project, "android/template", destination, context);
System.copyFileTemplate(project.templatePaths, "android/MainActivity.java", packageDirectory + "/MainActivity.java", context);
ProjectHelper.recursiveSmartCopyTemplate(project, "haxe", targetDirectory + "/haxe", context);
ProjectHelper.recursiveSmartCopyTemplate(project, "android/hxml", targetDirectory + "/haxe", context);
for (asset in project.assets)
{
if (asset.type == AssetType.TEMPLATE)
{
var targetPath = Path.combine(destination, asset.targetPath);
System.mkdir(Path.directory(targetPath));
AssetHelper.copyAsset(asset, targetPath, context);
}
}
}
public override function watch():Void
{
var hxml = getDisplayHXML();
var dirs = hxml.getClassPaths(true);
var outputPath = Path.combine(Sys.getCwd(), project.app.path);
dirs = dirs.filter(function(dir)
{
return (!Path.startsWith(dir, outputPath));
});
var command = ProjectHelper.getCurrentCommand();
System.watch(command, dirs);
}
}