332 lines
12 KiB
Haxe
332 lines
12 KiB
Haxe
package macros;
|
|
|
|
import haxe.macro.Context;
|
|
import haxe.macro.Expr;
|
|
import haxe.macro.Type;
|
|
import sys.FileSystem;
|
|
import sys.io.File;
|
|
import haxe.Json;
|
|
using StringTools;
|
|
|
|
/**
|
|
* Macro for processing @:Assets meta tags and building asset manifests
|
|
*/
|
|
@:keep
|
|
class AssetMacro {
|
|
// Track all assets processed
|
|
private static var assetMap:Map<String, Array<{id:String, path:String, size:Int, type:String}>> = new Map();
|
|
|
|
/**
|
|
* Entry point for the macro - called during compilation
|
|
*/
|
|
public static function build():Array<Field> {
|
|
#if macro
|
|
Sys.println("AssetMacro.build() called - macro is running");
|
|
Context.warning("AssetMacro is running", Context.currentPos());
|
|
#end
|
|
|
|
// A compilation server can retain macro statics between builds.
|
|
assetMap = new Map();
|
|
|
|
// Get current build context
|
|
var fields = Context.getBuildFields();
|
|
var localClass = Context.getLocalClass().get();
|
|
|
|
#if macro
|
|
Sys.println("Processing class: " + localClass.name);
|
|
#end
|
|
|
|
// Get the output directory from build.hxml
|
|
var outputDir = getOutputDir();
|
|
var manifestDir = outputDir + "/manifest";
|
|
var assetsOutputDir = outputDir + "/assets";
|
|
|
|
// These directories are owned by this macro. Recreate them so removed or
|
|
// renamed source assets cannot survive as stale build output.
|
|
removeDirectory(manifestDir);
|
|
removeDirectory(assetsOutputDir);
|
|
createDirectory(manifestDir);
|
|
createDirectory(assetsOutputDir);
|
|
copyLimeNativeLibrary(outputDir);
|
|
|
|
// Process asset fields
|
|
for (field in fields) {
|
|
processField(field, assetsOutputDir);
|
|
}
|
|
|
|
// Build the manifest files
|
|
buildManifests(manifestDir);
|
|
|
|
return fields;
|
|
}
|
|
|
|
/**
|
|
* Process a field for @:Assets meta
|
|
*/
|
|
private static function processField(field:Field, assetsOutputDir:String) {
|
|
// Look for our custom meta
|
|
var assetsMeta = findAssetsMeta(field.meta);
|
|
if (assetsMeta == null) return;
|
|
|
|
var params = assetsMeta.params;
|
|
if (params.length < 2) {
|
|
Context.error("@:Assets meta requires at least 2 parameters: source path and library name", assetsMeta.pos);
|
|
return;
|
|
}
|
|
|
|
// Extract parameters
|
|
var sourcePath = extractString(params[0]);
|
|
var libraryName = extractString(params[1]);
|
|
var targetPath = params.length > 2 ? extractString(params[2]) : null;
|
|
|
|
// Process directory or single file
|
|
if (FileSystem.exists(sourcePath)) {
|
|
if (FileSystem.isDirectory(sourcePath)) {
|
|
processDirectory(sourcePath, libraryName, assetsOutputDir);
|
|
} else {
|
|
processSingleFile(sourcePath, libraryName, targetPath, assetsOutputDir);
|
|
}
|
|
} else {
|
|
Context.warning('Asset source not found: ${sourcePath}', field.pos);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a directory of assets
|
|
*/
|
|
private static function processDirectory(sourcePath:String, libraryName:String, assetsOutputDir:String) {
|
|
if (!sourcePath.endsWith("/")) sourcePath += "/";
|
|
var targetDir = assetsOutputDir + "/" + libraryName + "/";
|
|
createDirectory(targetDir);
|
|
|
|
function scanDir(path:String, relPath:String = "") {
|
|
if (!FileSystem.exists(path)) return;
|
|
|
|
for (entry in FileSystem.readDirectory(path)) {
|
|
var fullPath = path + entry;
|
|
var relAssetPath = relPath + entry;
|
|
|
|
if (FileSystem.isDirectory(fullPath)) {
|
|
var newRelPath = relPath + entry + "/";
|
|
var newTargetDir = targetDir + newRelPath;
|
|
createDirectory(newTargetDir);
|
|
scanDir(fullPath + "/", newRelPath);
|
|
} else {
|
|
// Copy file and register in asset map
|
|
var targetPath = targetDir + relAssetPath;
|
|
try {
|
|
File.copy(fullPath, targetPath);
|
|
|
|
// Register asset
|
|
if (!assetMap.exists(libraryName)) {
|
|
assetMap.set(libraryName, []);
|
|
}
|
|
|
|
assetMap.get(libraryName).push({
|
|
id: relAssetPath,
|
|
path: relAssetPath,
|
|
size: FileSystem.stat(fullPath).size,
|
|
type: getAssetType(fullPath)
|
|
});
|
|
} catch (e) {
|
|
Context.warning('Failed to copy asset: ${fullPath} -> ${targetPath}. Error: ${e}', Context.currentPos());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
scanDir(sourcePath);
|
|
}
|
|
|
|
/**
|
|
* Process a single file asset
|
|
*/
|
|
private static function processSingleFile(sourcePath:String, libraryName:String, targetPath:String, assetsOutputDir:String) {
|
|
var targetDir = assetsOutputDir + "/" + libraryName + "/";
|
|
createDirectory(targetDir);
|
|
|
|
var fileName = targetPath != null ? targetPath : sourcePath.split("/").pop();
|
|
var destPath = targetDir + fileName;
|
|
|
|
try {
|
|
File.copy(sourcePath, destPath);
|
|
|
|
// Register asset
|
|
if (!assetMap.exists(libraryName)) {
|
|
assetMap.set(libraryName, []);
|
|
}
|
|
|
|
assetMap.get(libraryName).push({
|
|
id: fileName,
|
|
path: fileName,
|
|
size: FileSystem.stat(sourcePath).size,
|
|
type: getAssetType(sourcePath)
|
|
});
|
|
} catch (e) {
|
|
Context.warning('Failed to copy asset: ${sourcePath} -> ${destPath}. Error: ${e}', Context.currentPos());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build manifest JSON files for each library
|
|
*/
|
|
private static function buildManifests(manifestDir:String) {
|
|
// Create an array to hold all assets for the default manifest
|
|
var allAssets = [];
|
|
|
|
// First, process individual library manifests
|
|
for (library in assetMap.keys()) {
|
|
var assets = assetMap.get(library);
|
|
var manifest = {
|
|
name: library,
|
|
assets: assets,
|
|
rootPath: "../assets/" + library + "/"
|
|
};
|
|
|
|
// Add assets to combined collection with library prefix
|
|
for (asset in assets) {
|
|
allAssets.push({
|
|
id: library + "/" + asset.id,
|
|
path: library + "/" + asset.path,
|
|
size: asset.size,
|
|
type: asset.type
|
|
});
|
|
}
|
|
|
|
var manifestPath = manifestDir + "/" + library + ".json";
|
|
try {
|
|
var content = Json.stringify(manifest, null, " ");
|
|
File.saveContent(manifestPath, content);
|
|
Context.info('Built manifest: ${manifestPath} with ${assets.length} assets', Context.currentPos());
|
|
} catch (e) {
|
|
Context.warning('Failed to write manifest: ${manifestPath}. Error: ${e}', Context.currentPos());
|
|
}
|
|
}
|
|
|
|
// Create the default manifest with all assets
|
|
var defaultManifest = {
|
|
name: "default",
|
|
assets: allAssets,
|
|
rootPath: "../assets/"
|
|
};
|
|
|
|
// Save the default manifest
|
|
var defaultManifestPath = manifestDir + "/default.json";
|
|
try {
|
|
var content = Json.stringify(defaultManifest, null, " ");
|
|
File.saveContent(defaultManifestPath, content);
|
|
Context.info('Built default manifest: ${defaultManifestPath} with ${allAssets.length} assets', Context.currentPos());
|
|
} catch (e) {
|
|
Context.warning('Failed to write default manifest: ${defaultManifestPath}. Error: ${e}', Context.currentPos());
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
private static function getOutputDir():String {
|
|
// Try to parse the --cpp argument from the build.hxml
|
|
var args = Sys.args();
|
|
for (i in 0...args.length) {
|
|
if (args[i] == "--cpp" && i < args.length - 1) {
|
|
return args[i + 1];
|
|
}
|
|
}
|
|
return "bin/cpp"; // Default output directory
|
|
}
|
|
|
|
private static function createDirectory(path:String) {
|
|
if (!FileSystem.exists(path)) {
|
|
try {
|
|
FileSystem.createDirectory(path);
|
|
} catch (e) {
|
|
Context.warning('Failed to create directory: ${path}. Error: ${e}', Context.currentPos());
|
|
}
|
|
}
|
|
}
|
|
|
|
private static function removeDirectory(path:String):Void {
|
|
if (!FileSystem.exists(path)) return;
|
|
|
|
for (entry in FileSystem.readDirectory(path)) {
|
|
var entryPath = path + "/" + entry;
|
|
if (FileSystem.isDirectory(entryPath)) {
|
|
removeDirectory(entryPath);
|
|
} else {
|
|
FileSystem.deleteFile(entryPath);
|
|
}
|
|
}
|
|
FileSystem.deleteDirectory(path);
|
|
}
|
|
|
|
/** Stage the Lime CFFI module that matches the selected haxelib version. */
|
|
private static function copyLimeNativeLibrary(outputDir:String):Void {
|
|
var platformDirectory = if (Context.defined("linux")) {
|
|
"Linux64";
|
|
} else if (Context.defined("windows")) {
|
|
"Windows64";
|
|
} else if (Context.defined("mac")) {
|
|
Context.defined("arm64") ? "MacArm64" : "Mac64";
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
var limeRoot:String = null;
|
|
for (classPath in Context.getClassPath()) {
|
|
var normalized = classPath.split("\\").join("/");
|
|
if (normalized.indexOf("/lime/") != -1 && normalized.endsWith("/src/")) {
|
|
limeRoot = normalized.substr(0, normalized.length - 4);
|
|
break;
|
|
}
|
|
}
|
|
if (limeRoot == null) {
|
|
Context.warning("Could not locate Lime native library from the compiler class path", Context.currentPos());
|
|
return;
|
|
}
|
|
|
|
var source = limeRoot + "ndll/" + platformDirectory + "/lime.ndll";
|
|
if (!FileSystem.exists(source) && platformDirectory.endsWith("64")) {
|
|
platformDirectory = platformDirectory.substr(0, platformDirectory.length - 2);
|
|
source = limeRoot + "ndll/" + platformDirectory + "/lime.ndll";
|
|
}
|
|
if (!FileSystem.exists(source)) {
|
|
Context.warning("Lime native library not found: " + source, Context.currentPos());
|
|
return;
|
|
}
|
|
|
|
try {
|
|
File.copy(source, outputDir + "/lime.ndll");
|
|
} catch (e:Dynamic) {
|
|
Context.error("Failed to copy Lime native library: " + e, Context.currentPos());
|
|
}
|
|
}
|
|
|
|
private static function findAssetsMeta(metaAccess:Array<MetadataEntry>):MetadataEntry {
|
|
if (metaAccess == null) return null;
|
|
|
|
for (meta in metaAccess) {
|
|
if (meta.name == ":Assets" || meta.name == "Assets") {
|
|
return meta;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static function extractString(expr:Expr):String {
|
|
switch (expr.expr) {
|
|
case EConst(CString(s)): return s;
|
|
default: Context.error("Expected string literal", expr.pos);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static function getAssetType(path:String):String {
|
|
var ext = path.split(".").pop().toLowerCase();
|
|
return (switch (ext) {
|
|
case "jpg", "jpeg", "png", "gif", "bmp": "image";
|
|
case "mp3", "ogg", "wav": "sound";
|
|
case "ttf", "otf": "font";
|
|
case "json", "xml", "txt": "text";
|
|
default: "binary";
|
|
}).toUpperCase();
|
|
}
|
|
} |