From e14d8e7db0a9e5353e5c6ec2343da19e3e5fd669 Mon Sep 17 00:00:00 2001 From: Joshua Granick Date: Fri, 21 Oct 2016 15:24:37 -0700 Subject: [PATCH] Initial support for modular JS output --- include.xml | 72 ++++++++++ lime/project/HXProject.hx | 25 +++- lime/project/ModuleData.hx | 57 ++++++++ lime/project/ProjectXMLParser.hx | 193 ++++++++++++++++++++++++++ lime/tools/helpers/ArrayHelper.hx | 49 +++++++ lime/tools/helpers/ModuleHelper.hx | 179 ++++++++++++++++++++++++ lime/tools/platforms/HTML5Platform.hx | 5 + tools/CommandLineTools.hx | 20 +++ 8 files changed, 599 insertions(+), 1 deletion(-) create mode 100644 lime/project/ModuleData.hx create mode 100644 lime/tools/helpers/ModuleHelper.hx diff --git a/include.xml b/include.xml index 557ade113..005dea3f6 100644 --- a/include.xml +++ b/include.xml @@ -68,6 +68,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lime/project/HXProject.hx b/lime/project/HXProject.hx index fef6a4b88..3d1172013 100644 --- a/lime/project/HXProject.hx +++ b/lime/project/HXProject.hx @@ -49,6 +49,7 @@ class HXProject { public var libraries:Array ; public var libraryHandlers:Map ; public var meta:MetaData; + public var modules:Map; public var ndlls:Array ; public var platformType:PlatformType; public var postBuildCallbacks:Array ; @@ -234,6 +235,7 @@ class HXProject { javaPaths = new Array (); libraries = new Array (); libraryHandlers = new Map (); + modules = new Map (); ndlls = new Array (); postBuildCallbacks = new Array (); preBuildCallbacks = new Array (); @@ -325,6 +327,12 @@ class HXProject { ObjectHelper.copyFields (meta, project.meta); + for (key in modules.keys ()) { + + project.modules.set (key, modules.get (key).clone ()); + + } + for (ndll in ndlls) { project.ndlls.push (ndll.clone ()); @@ -370,7 +378,7 @@ class HXProject { } - private function filter (text:String, include:Array = null, exclude:Array = null):Bool { + private function filter (text:String, include:Array = null, exclude:Array = null):Bool { if (include == null) { @@ -744,6 +752,21 @@ class HXProject { icons = ArrayHelper.concatUnique (icons, project.icons); javaPaths = ArrayHelper.concatUnique (javaPaths, project.javaPaths, true); libraries = ArrayHelper.concatUnique (libraries, project.libraries, true); + + for (key in project.modules.keys ()) { + + if (modules.exists (key)) { + + modules.get (key).merge (project.modules.get (key)); + + } else { + + modules.set (key, project.modules.get (key)); + + } + + } + ndlls = ArrayHelper.concatUnique (ndlls, project.ndlls); postBuildCallbacks = postBuildCallbacks.concat (project.postBuildCallbacks); preBuildCallbacks = preBuildCallbacks.concat (project.preBuildCallbacks); diff --git a/lime/project/ModuleData.hx b/lime/project/ModuleData.hx new file mode 100644 index 000000000..2802efdd7 --- /dev/null +++ b/lime/project/ModuleData.hx @@ -0,0 +1,57 @@ +package lime.project; + + +import lime.tools.helpers.ArrayHelper; + + +class ModuleData { + + + public var classNames:Array; + public var excludeTypes:Array; + public var haxeflags:Array; + public var includeTypes:Array; + public var name:String; + + + public function new (name:String) { + + this.name = name; + classNames = []; + excludeTypes = []; + haxeflags = []; + includeTypes = []; + + } + + + public function clone ():ModuleData { + + var copy = new ModuleData (name); + copy.classNames = classNames.copy (); + copy.excludeTypes = excludeTypes.copy (); + copy.haxeflags = haxeflags.copy (); + copy.includeTypes = includeTypes.copy (); + return copy; + + } + + + public function merge (other:ModuleData):Bool { + + if (other.name == name) { + + classNames = ArrayHelper.concatUnique (classNames, other.classNames); + excludeTypes = ArrayHelper.concatUnique (excludeTypes, other.excludeTypes); + haxeflags = ArrayHelper.concatUnique (haxeflags, other.haxeflags); + includeTypes = ArrayHelper.concatUnique (includeTypes, other.includeTypes); + return true; + + } + + return false; + + } + + +} \ No newline at end of file diff --git a/lime/project/ProjectXMLParser.hx b/lime/project/ProjectXMLParser.hx index dd1f2360f..44fe35e35 100644 --- a/lime/project/ProjectXMLParser.hx +++ b/lime/project/ProjectXMLParser.hx @@ -133,6 +133,10 @@ class ProjectXMLParser extends HXProject { defines.set ("debug", "1"); + } else if (targetFlags.exists ("final")) { + + defines.set ("final", "1"); + } else { defines.set ("release", "1"); @@ -754,6 +758,191 @@ class ProjectXMLParser extends HXProject { } + private function parseModuleElement (element:Fast, basePath:String = "", moduleData:ModuleData = null):Void { + + var topLevel = (moduleData == null); + + var exclude = ""; + var include = "*"; + + if (element.has.include) { + + include = substitute (element.att.include); + + } + + if (element.has.exclude) { + + exclude = substitute (element.att.exclude); + + } + + if (moduleData == null) { + + var name = substitute (element.att.name); + + if (modules.exists (name)) { + + moduleData = modules.get (name); + + } else { + + moduleData = new ModuleData (name); + modules.set (name, moduleData); + + } + + } + + switch (element.name) { + + case "module": + + if (element.has.source) { + + var source = PathHelper.combine (basePath, substitute (element.att.source)); + + if (!FileSystem.exists (source)) { + + LogHelper.error ("Could not find module source \"" + source + "\""); + return; + + } + + moduleData.haxeflags.push ("-cp " + source); + + var path = source; + + if (element.has.resolve ("package")) { + + path = PathHelper.combine (source, StringTools.replace (substitute (element.att.resolve ("package")), ".", "/")); + + } + + parseModuleElementSource (source, moduleData, include.split ("|"), exclude.split ("|"), path); + + } + + case "source": + + if (element.has.path) { + + var source = PathHelper.combine (basePath, substitute (element.att.path)); + + if (!FileSystem.exists (source)) { + + LogHelper.error ("Could not find module source \"" + source + "\""); + return; + + } + + moduleData.haxeflags.push ("-cp " + source); + + var path = source; + + if (element.has.resolve ("package")) { + + path = PathHelper.combine (source, StringTools.replace (substitute (element.att.resolve ("package")), ".", "/")); + + } + + parseModuleElementSource (source, moduleData, include.split ("|"), exclude.split ("|"), path); + + } + + case "class": + + moduleData.classNames.push (substitute (element.att.name)); + + case "haxedef": + + var value = substitute (element.att.name); + + if (element.has.value) { + + value += "=" + substitute (element.att.value); + + } + + moduleData.haxeflags.push ("-D " + value); + + case "haxeflag": + + var flag = substitute (element.att.name); + + if (element.has.value) { + + flag += " " + substitute (element.att.value); + + } + + moduleData.haxeflags.push (substitute (flag)); + + case "include": + + moduleData.includeTypes.push (substitute (element.att.type)); + + case "exclude": + + moduleData.excludeTypes.push (substitute (element.att.type)); + + } + + if (topLevel) { + + for (childElement in element.elements) { + + if (isValidElement (childElement, "")) { + + parseModuleElement (childElement, basePath, moduleData); + + } + + } + + } + + } + + + private function parseModuleElementSource (source:String, moduleData:ModuleData, include:Array, exclude:Array, currentPath:String):Void { + + var files = FileSystem.readDirectory (currentPath); + var filePath, className; + + for (file in files) { + + filePath = PathHelper.combine (currentPath, file); + + if (FileSystem.isDirectory (filePath)) { + + parseModuleElementSource (source, moduleData, include, exclude, filePath); + + } else { + + if (Path.extension (file) != "hx") continue; + + className = StringTools.replace (filePath, source, ""); + className = StringTools.replace (className, "\\", "/"); + + while (StringTools.startsWith (className, "/")) className = className.substr (1); + + className = StringTools.replace (className, "/", "."); + className = StringTools.replace (className, ".hx", ""); + + if (filter (className, include, exclude)) { + + moduleData.classNames.push (className); + + } + + } + + } + + } + + private function parseOutputElement (element:Fast):Void { if (element.has.name) { @@ -1367,6 +1556,10 @@ class ProjectXMLParser extends HXProject { } + case "module": + + parseModuleElement (element, extensionPath); + case "ssl": //if (wantSslCertificate()) diff --git a/lime/tools/helpers/ArrayHelper.hx b/lime/tools/helpers/ArrayHelper.hx index 9d4077589..35470d516 100644 --- a/lime/tools/helpers/ArrayHelper.hx +++ b/lime/tools/helpers/ArrayHelper.hx @@ -111,4 +111,53 @@ class ArrayHelper { } + public static function getUnique (a:Array, b:Array, key:String = null):Array { + + if (a == null && b == null) { + + return new Array (); + + } else if (a == null && b != null) { + + return b; + + } + + var concat = []; + + for (bValue in b) { + + var hasValue = false; + + for (aValue in a) { + + if (key != null) { + + if (Reflect.field (aValue, key) == Reflect.field (bValue, key)) { + + hasValue = true; + + } + + } else if (aValue == bValue) { + + hasValue = true; + + } + + } + + if (!hasValue) { + + concat.push (bValue); + + } + + } + + return concat; + + } + + } diff --git a/lime/tools/helpers/ModuleHelper.hx b/lime/tools/helpers/ModuleHelper.hx new file mode 100644 index 000000000..2261b3ba1 --- /dev/null +++ b/lime/tools/helpers/ModuleHelper.hx @@ -0,0 +1,179 @@ +package lime.tools.helpers; #if !macro + + +import lime.project.Dependency; +import lime.project.Haxelib; +import lime.project.HXProject; +import sys.io.File; + + +class ModuleHelper { + + + public static function buildModules (project:HXProject, tempDirectory:String, outputDirectory:String):Void { + + tempDirectory = PathHelper.combine (tempDirectory, "lib"); + outputDirectory = PathHelper.combine (outputDirectory, "lib"); + + PathHelper.mkdir (tempDirectory); + PathHelper.mkdir (outputDirectory); + + var importName, hxmlPath, importPath, outputPath, moduleImport, hxml; + + for (module in project.modules) { + + if (module.classNames.length > 0) { + + importName = "Module" + module.name.charAt (0).toUpperCase () + module.name.substr (1); + + hxmlPath = PathHelper.combine (tempDirectory, module.name + ".hxml"); + importPath = PathHelper.combine (tempDirectory, importName + ".hx"); + + if (project.targetFlags.exists ("final")) { + + outputPath = PathHelper.combine (outputDirectory, module.name + ".min.js"); + + } else { + + outputPath = PathHelper.combine (outputDirectory, module.name + ".js"); + + } + + moduleImport = "package;\n\nimport " + module.classNames.join (";\nimport ") + ";"; + + hxml = "-cp " + tempDirectory; + + hxml += "\n" + module.haxeflags.join ("\n"); + hxml += "\n-cp " + PathHelper.getHaxelib (new Haxelib ("lime")); + + for (key in project.haxedefs.keys ()) { + + if (key != "no-compilation") { + + var value = project.haxedefs.get (key); + + if (value == null || value == "") { + + hxml += "\n-D " + key; + + } else { + + hxml += "\n-D " + key + "=" + value; + + } + + } + + } + + hxml += "\n-D html5"; + hxml += "\n-D html"; + hxml += "\n--no-inline"; + hxml += "\n-dce no"; + hxml += "\n-js " + outputPath; + + var includeTypes = module.classNames.concat (module.includeTypes); + var excludeTypes = module.excludeTypes; + + for (otherModule in project.modules) { + + if (otherModule != module) { + + excludeTypes = excludeTypes.concat (ArrayHelper.getUnique (includeTypes, otherModule.classNames)); + excludeTypes = excludeTypes.concat (ArrayHelper.getUnique (includeTypes, otherModule.includeTypes)); + + } + + } + + if (excludeTypes.length > 0) { + + hxml += "\n--macro lime.tools.helpers.ModuleHelper.exclude(['" + excludeTypes.join ("','") + "'])"; + + } + + hxml += "\n--macro lime.tools.helpers.ModuleHelper.expose(['" + includeTypes.join ("','") + "'])"; + + hxml += "\n" + importName; + + File.saveContent (importPath, moduleImport); + File.saveContent (hxmlPath, hxml); + + ProcessHelper.runCommand ("", "haxe", [ hxmlPath ]); + + if (project.targetFlags.exists ("final")) { + + HTML5Helper.minify (project, outputPath); + + } + + } + + } + + } + + + public static function updateProject (project:HXProject):Void { + + var excludeTypes = []; + var suffix = (project.targetFlags.exists ("final") ? ".min" : "") + ".js"; + + for (module in project.modules) { + + project.dependencies.push (new Dependency ("./lib/" + module.name + suffix, null)); + + excludeTypes = ArrayHelper.concatUnique (excludeTypes, module.classNames); + excludeTypes = ArrayHelper.concatUnique (excludeTypes, module.excludeTypes); + excludeTypes = ArrayHelper.concatUnique (excludeTypes, module.includeTypes); + + } + + if (excludeTypes.length > 0) { + + project.haxeflags.push ("--macro lime.tools.helpers.ModuleHelper.exclude(['" + excludeTypes.join ("','") + "'])"); + + } + + } + + +} + + +#else + + +import haxe.macro.Compiler; + + +class ModuleHelper { + + + public static function exclude (types:Array):Void { + + for (type in types) { + + Compiler.exclude (type); + Compiler.addMetadata ("@:native(\"$hx_exports." + type + "\")", type); + + } + + } + + + public static function expose (classNames:Array):Void { + + for (className in classNames) { + + Compiler.addMetadata ("@:expose('" + className + "')", className); + + } + + } + + +} + + +#end \ No newline at end of file diff --git a/lime/tools/platforms/HTML5Platform.hx b/lime/tools/platforms/HTML5Platform.hx index 22647d68f..94229fd0f 100644 --- a/lime/tools/platforms/HTML5Platform.hx +++ b/lime/tools/platforms/HTML5Platform.hx @@ -9,6 +9,7 @@ import lime.tools.helpers.FileHelper; import lime.tools.helpers.HTML5Helper; import lime.tools.helpers.IconHelper; import lime.tools.helpers.LogHelper; +import lime.tools.helpers.ModuleHelper; import lime.tools.helpers.PathHelper; import lime.tools.helpers.ProcessHelper; import lime.project.AssetType; @@ -36,6 +37,8 @@ class HTML5Platform extends PlatformTarget { public override function build ():Void { + ModuleHelper.buildModules (project, targetDirectory + "/obj", targetDirectory + "/bin"); + if (project.app.main != null) { var type = "release"; @@ -169,6 +172,8 @@ class HTML5Platform extends PlatformTarget { } + ModuleHelper.updateProject (project); + var context = project.templateContext; context.WIN_FLASHBACKGROUND = project.window.background != null ? StringTools.hex (project.window.background, 6) : ""; diff --git a/tools/CommandLineTools.hx b/tools/CommandLineTools.hx index 10a7e09ec..04ce4aad2 100644 --- a/tools/CommandLineTools.hx +++ b/tools/CommandLineTools.hx @@ -1395,6 +1395,26 @@ class CommandLineTools { } + if (Sys.getEnv ("HAXEPATH") == null) { + + if (PlatformHelper.hostPlatform == Platform.WINDOWS) { + + Sys.putEnv ("HAXEPATH", "C:\\HaxeToolkit\\haxe\\"); + + } else { + + Sys.putEnv ("HAXEPATH", "/usr/lib/haxe"); + + } + + } + + if (Sys.getEnv ("HAXE_STD_PATH") == null) { + + Sys.putEnv ("HAXE_STD_PATH", PathHelper.combine (Sys.getEnv ("HAXEPATH"), "std")); + + } + if (project == null) { HXProject._command = command;