#!/usr/bin/env python3 # Bundles the root *.lua (library + imgui_demo.lua), backends/ and fonts/ into ONE ModuleScript: build/ImGui.luau # --no-demo: library only (imgui_demo.lua left out, like not compiling imgui_demo.cpp) # --target love: build/imgui_love.lua for LÖVE/LuaJIT instead (LÖVE backend, shims from the LÖVE backend's COMPAT section, @native stripped). # The default (roblox) build is unaffected by it. # Why: ModuleScripts don't share globals, and the setfenv() alternative turns off Luau's fast paths / native codegen. # Each source file becomes a function in __FILES; IM_INCLUDE(name) runs it once and caches the result. # The module returns a table with every global the library defines (ImGui, ImVec2, ImGuiConfigFlags, ImGui_ImplRoblox, ...). # Roblox target also writes ImGuiTyped.luau (types + require(script.Impl), see tools/gen_types.py). import os, re ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) import sys NO_DEMO = "--no-demo" in sys.argv LOVE = False if "--target" in sys.argv: i = sys.argv.index("--target"); LOVE = sys.argv[i + 1] == "love"; del sys.argv[i:i + 2] sys.argv = [a for a in sys.argv if a != "--no-demo"] BACKEND = "imgui_impl_love.lua" if LOVE else "imgui_impl_roblox.lua" SKIP = {"imgui_impl_roblox.lua", "imgui_impl_roblox_fonts.lua"} if LOVE else {"imgui_impl_love.lua"} FILES = {} for d in (ROOT, os.path.join(ROOT, "backends"), os.path.join(ROOT, "fonts")): for f in sorted(os.listdir(d)): if f.endswith(".lua") and not (NO_DEMO and f == "imgui_demo.lua") and f not in SKIP: src = open(os.path.join(d, f)).read() if LOVE: src = re.sub(r"^@native$", "", src, flags=re.M) # keep line numbers FILES[f] = src ENTRY = ["imgui.lua", BACKEND] + ([] if NO_DEMO else ["imgui_demo.lua"]) # run order names = set() for src in FILES.values(): names.update(re.findall(r"^function ([A-Za-z_]\w*)\s*\(", src, re.M)) names.update(re.findall(r"^([A-Za-z_]\w*)\s*=[^=]", src, re.M)) out = ["--!optimize 2", "-- GENERATED by tools/bundle.py from *.lua, backends/, fonts/ - do not edit", ""] if LOVE: out = ["-- GENERATED by tools/bundle.py --target love - do not edit", re.search(r"--@compat-begin\n(.*?)--@compat-end", open(os.path.join(ROOT, "backends", "imgui_impl_love.lua")).read(), re.S).group(1), ""] out += [ "local __FILES, __LOADED = {}, {}", "function IM_INCLUDE(name)", " local r = __LOADED[name]", " if r == nil then r = __FILES[name](); if r == nil then r = true end; __LOADED[name] = r end", " return r", "end", ""] linemap = [] # (first bundle line, file) for error -> source mapping (tools/test/run.py) for name, src in sorted(FILES.items()): out.append("__FILES[%r] = function()" % name) linemap.append((sum(x.count("\n") + 1 for x in out) + 1, name)) out.append(src.rstrip("\n")) out.append("end") out.append("") for name in ENTRY: out.append("IM_INCLUDE(%r)" % name) out.append("") out.append("return {") for n in sorted(names): out.append(" %s = %s," % (n, n)) out.append("}") import sys OUT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(ROOT, "build") os.makedirs(OUT, exist_ok=True) NAME = "imgui_love.lua" if LOVE else "ImGui.luau" open(os.path.join(OUT, NAME), "w").write("\n".join(out) + "\n") import json json.dump(linemap, open(os.path.join(OUT, NAME.rsplit(".", 1)[0] + ".linemap.json"), "w")) if not LOVE: sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from gen_types import wrapper open(os.path.join(OUT, "ImGuiTyped.luau"), "w").write(wrapper(FILES)) print("wrote build/%s (%d files, %d exports)" % (NAME, len(FILES), len(names)))