diff --git a/ogl_editor/bin/macosx/tundra/scripts/strict.lua b/ogl_editor/bin/macosx/tundra/scripts/strict.lua new file mode 100644 index 0000000..604619d --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/strict.lua @@ -0,0 +1,41 @@ +-- +-- strict.lua +-- checks uses of undeclared global variables +-- All global variables must be 'declared' through a regular assignment +-- (even assigning nil will do) in a main chunk before being used +-- anywhere or assigned to inside a function. +-- + +local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget + +local mt = getmetatable(_G) +if mt == nil then + mt = {} + setmetatable(_G, mt) +end + +mt.__declared = {} + +local function what () + local d = getinfo(3, "S") + return d and d.what or "C" +end + +mt.__newindex = function (t, n, v) + if not mt.__declared[n] then + local w = what() + if w ~= "main" and w ~= "C" then + error("assign to undeclared variable '"..n.."'", 2) + end + mt.__declared[n] = true + end + rawset(t, n, v) +end + +mt.__index = function (t, n) + if not mt.__declared[n] and what() ~= "C" then + error("variable '"..n.."' is not declared", 2) + end + return rawget(t, n) +end + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra.lua new file mode 100644 index 0000000..cede76e --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra.lua @@ -0,0 +1,43 @@ +require "strict" + +local boot = require "tundra.boot" + +local actions = { + ['generate-dag'] = function(build_script) + assert(build_script, "need a build script name") + boot.generate_dag_data(build_script) + end, + + ['generate-ide-files'] = function(build_script, ide_script) + assert(build_script, "need a build script name") + assert(ide_script, "need a generator name") + boot.generate_ide_files(build_script, ide_script) + end, + + ['selftest'] = function() + require "tundra.selftest" + end +} + +local function main(action_name, ...) + assert(action_name, "need an action") + + local action = actions[action_name] + assert(action, "unknown action '" .. action_name .. "'") + + -- check if debugger was requested + for i, v in ipairs(arg) do + if v == "--lua-debugger" then + table.remove(arg, i) + require "tundra.debugger" + pause() + break + end + end + + action(unpack(arg)) +end + +return { + main = main +} diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/boot.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/boot.lua new file mode 100644 index 0000000..4c0dbf9 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/boot.lua @@ -0,0 +1,142 @@ +module(..., package.seeall) + +-- Use "strict" when developing to flag accesses to nil global variables +-- This has very low perf impact (<0.1%), so always leave it on. +require "strict" + +local os = require "os" +local platform = require "tundra.platform" +local util = require "tundra.util" +local depgraph = require "tundra.depgraph" +local unitgen = require "tundra.unitgen" +local buildfile = require "tundra.buildfile" +local native = require "tundra.native" + +-- This trio is so useful we want them everywhere without imports. +function _G.printf(msg, ...) + local str = string.format(msg, ...) + print(str) +end + +function _G.errorf(msg, ...) + local str = string.format(msg, ...) + error(str) +end + +function _G.croak(msg, ...) + local str = string.format(msg, ...) + io.stderr:write(str, "\n") + os.exit(1) +end + +-- Expose benchmarking function for those times everything sucks +-- +-- Wrap a function so that it prints execution times. +-- +-- Usage: +-- foo = bench("foo", foo) -- benchmark function foo +function _G.bench(name, fn) + return function (...) + local t1 = native.get_timer() + local result = { fn(...) } + local t2 = native.get_timer() + printf("%s: %ss", name, native.timerdiff(t1, t2)) + return unpack(result) + end +end + +local environment = require "tundra.environment" +local nodegen = require "tundra.nodegen" +local decl = require "tundra.decl" +local path = require "tundra.path" +local depgraph = require "tundra.depgraph" +local dagsave = require "tundra.dagsave" + +_G.SEP = platform.host_platform() == "windows" and "\\" or "/" + +_G.Options = { + FullPaths = 1 +} + +local function make_default_env(build_data, add_unfiltered_vars) + local default_env = environment.create() + + default_env:set_many { + ["OBJECTROOT"] = "t2-output", + ["SEP"] = SEP, + } + + local host_platform = platform.host_platform() + do + local mod_name = "tundra.host." .. host_platform + local mod = require(mod_name) + mod.apply_host(default_env) + end + + -- Add any unfiltered entries from the build data's Env and ReplaceEnv to the + -- default environment. For config environments, this will be false, because we + -- want to wait until the config's tools have run before adding any user + -- customizations. + if add_unfiltered_vars then + if build_data.Env then + nodegen.append_filtered_env_vars(default_env, build_data.Env, nil, true) + end + if build_data.ReplaceEnv then + nodegen.replace_filtered_env_vars(default_env, build_data.ReplaceEnv, nil, true) + end + end + + return default_env +end + +function generate_dag_data(build_script_fn) + local build_data = buildfile.run(build_script_fn) + local env = make_default_env(build_data.BuildData, false) + local raw_nodes, node_bindings = unitgen.generate_dag( + build_data.BuildTuples, + build_data.BuildData, + build_data.Passes, + build_data.Configs, + env) + + dagsave.save_dag_data( + node_bindings, + build_data.DefaultVariant, + build_data.DefaultSubVariant, + build_data.ContentDigestExtensions, + build_data.Options) +end + +function generate_ide_files(build_script_fn, ide_script) + -- We are generating IDE integration files. Load the specified + -- integration module rather than DAG builders. + -- + -- Also, default to using full paths for all commands to aid with locating + -- sources better. + Options.FullPaths = 1 + + local build_data = buildfile.run(build_script_fn) + local build_tuples = assert(build_data.BuildTuples) + local raw_data = assert(build_data.BuildData) + local passes = assert(build_data.Passes) + + local env = make_default_env(raw_data, true) + + if not ide_script:find('.', 1, true) then + ide_script = 'tundra.ide.' .. ide_script + end + + require(ide_script) + + -- Generate dag + local raw_nodes, node_bindings = unitgen.generate_dag( + build_data.BuildTuples, + build_data.BuildData, + build_data.Passes, + build_data.Configs, + env) + + -- Pass the build tuples directly to the generator and let it write + -- files. + nodegen.generate_ide_files(build_tuples, build_data.DefaultNodes, raw_nodes, env, raw_data.IdeGenerationHints, ide_script) +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/buildfile.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/buildfile.lua new file mode 100644 index 0000000..cf24d1f --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/buildfile.lua @@ -0,0 +1,253 @@ +module(..., package.seeall) + +local util = require "tundra.util" +local native = require "tundra.native" + +local function mk_defvariant(name) + return { Name = name; Options = {} } +end + +local default_variants = { + mk_defvariant "debug", + mk_defvariant "production", + mk_defvariant "release" +} + +local default_subvariants = { + "default" +} + +local _config_class = {} + +-- Table constructor to make tundra.lua syntax a bit nicer in the Configs array +function _G.Config(args) + local name = args.Name + if not name then + error("no `Name' specified for configuration") + end + if not name:match("^[%w_]+-[%w_]+$") then + errorf("configuration name %s doesn't follow - pattern", name) + end + + if args.SubConfigs then + if not args.DefaultSubConfig then + errorf("configuration %s has `SubConfigs' but no `DefaultSubConfig'", name) + end + end + + return setmetatable(args, _config_class) +end + +local function analyze_targets(configs, variants, subvariants) + local build_tuples = {} + + local build_configs = {} + local build_variants = {} + local build_subvariants = {} + + for _, cfg in pairs(configs) do + + if not cfg.Virtual then -- skip virtual configs + + if not cfg.SupportedHosts then + if cfg.DefaultOnHost then + if type(cfg.DefaultOnHost) == "table" then + cfg.SupportedHosts = cfg.DefaultOnHost + else + cfg.SupportedHosts = { cfg.DefaultOnHost } + end + else + printf("1.0-compat: config %s doesn't specify SupportedHosts -- will never be built", cfg.Name); + cfg.SupportedHosts = { } + end + end + + local lut = util.make_lookup_table(cfg.SupportedHosts) + if lut[native.host_platform] then + build_configs[#build_configs + 1] = cfg + end + end + end + for _, var in pairs(variants) do build_variants[#build_variants + 1] = var end + for var, _ in pairs(subvariants) do build_subvariants[#build_subvariants + 1] = var end + + for _, config in ipairs(build_configs) do + if config.Virtual then + croak("can't build configuration %s directly; it is a support configuration only", config.Name) + end + for _, variant in ipairs(build_variants) do + for _, subvariant in ipairs(build_subvariants) do + build_tuples[#build_tuples + 1] = { Config = config, Variant = variant, SubVariant = subvariant } + end + end + end + + if #build_tuples == 0 then + errorf("no build tuples available\n") + end + + return build_tuples +end + +-- Custom pcall error handler to scan for syntax errors (thrown as tables) and +-- report them without a backtrace, trying to get the filename and line number +-- right so the user can fix their build file. +function syntax_error_catcher(err_obj) + if type(err_obj) == "table" and err_obj.Class and err_obj.Message then + local i = 1 + -- Walk down the stack until we find a function that isn't sourced from + -- a file. These have 'source' names that don't start with an @ sign. + -- Because we read all files into memory before executing them, this + -- will give us the source filename of the user script. + while true do + local info = debug.getinfo(i, 'Sl') + --print(util.tostring(info)) + if not info then + break + end + if info.what == "C" or (info.source:sub(1, 1) == "@" and info.source ~= "@units.lua") then + i = i + 1 + else + local fn = info.source + if info.source:sub(1, 1) == "@" then + fn = info.source:sub(2) + end + if info.currentline == -1 then + return string.format("%s: %s", err_obj.Class, err_obj.Message) + else + return string.format("%s(%d): %s: %s", fn, info.currentline, err_obj.Class, err_obj.Message) + end + end + end + return string.format("%s: %s", err_obj.Class, err_obj.Message) + else + return debug.traceback(err_obj, 2) + end +end + + + +-- A place to store the result of the user's build script calling Build() +local build_result = nil + +-- The Build function is the main entry point for "tundra.lua" when invoked. +function _G.Build(args) + if type(args.Configs) ~= "table" or #args.Configs == 0 then + croak("Need at least one config; got %s", util.tostring(args.Configs or "none at all")) + end + + local configs, variants, subvariants = {}, {}, {} + + -- Legacy support: run "Config" constructor automatically on naked tables + -- passed in Configs array. + for idx = 1, #args.Configs do + local cfg = args.Configs[idx] + if getmetatable(cfg) ~= _config_class then + cfg = Config(cfg) + args.Configs[idx] = cfg + end + configs[cfg.Name] = cfg + end + + for _, dir in util.nil_ipairs(args.ScriptDirs) do + -- Make sure dir is sane and ends with a slash + dir = dir:gsub("[/\\]", SEP):gsub("[/\\]$", "") + local expr = dir .. SEP .. "?.lua" + + -- Add user toolset dir first so they can override builtin scripts. + package.path = expr .. ";" .. package.path + end + + if args.Variants then + for i, x in ipairs(args.Variants) do + if type(x) == "string" then + args.Variants[i] = mk_defvariant(x) + else + assert(x.Name) + if not x.Options then + x.Options = {} + end + end + end + end + + local variant_array = args.Variants or default_variants + for _, variant in ipairs(variant_array) do variants[variant.Name] = variant end + + local subvariant_array = args.SubVariants or default_subvariants + for _, subvariant in ipairs(subvariant_array) do subvariants[subvariant] = true end + + local default_variant = variant_array[1] + if args.DefaultVariant then + for _, x in ipairs(variant_array) do + if x.Name == args.DefaultVariant then + default_variant = x + end + end + end + + local default_subvariant = args.DefaultSubVariant or subvariant_array[1] + local build_tuples = analyze_targets(configs, variants, subvariants) + local passes = args.Passes or { Default = { Name = "Default", BuildOrder = 1 } } + + printf("%d valid build tuples", #build_tuples) + + -- Validate pass data + for id, data in pairs(passes) do + if not data.Name then + croak("Pass %s has no Name attribute", id) + elseif not data.BuildOrder then + croak("Pass %s has no BuildOrder attribute", id) + end + end + + -- Assume syntax for C and DotNet is always needed + -- for now. Could possible make an option for which generator sets to load + -- in the future. + require "tundra.syntax.native" + require "tundra.syntax.dotnet" + + build_result = { + BuildTuples = build_tuples, + BuildData = args, + Passes = passes, + Configs = configs, + DefaultVariant = default_variant, + DefaultSubVariant = default_subvariant, + ContentDigestExtensions = args.ContentDigestExtensions, + Options = args.Options, + } +end + +function run(build_script_fn) + local f, err = io.open(build_script_fn, 'r') + + if not f then + croak("%s", err) + end + + local text = f:read("*all") + f:close() + + local script_globals, script_globals_mt = {}, {} + script_globals_mt.__index = _G + setmetatable(script_globals, script_globals_mt) + + local chunk, error_msg = loadstring(text, build_script_fn) + if not chunk then + croak("%s", error_msg) + end + setfenv(chunk, script_globals) + + local success, result = xpcall(chunk, syntax_error_catcher) + + if not success then + print("Build script execution failed") + croak("%s", result or "") + end + + local result = build_result + build_result = nil + return result +end + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/dagsave.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/dagsave.lua new file mode 100644 index 0000000..37dba90 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/dagsave.lua @@ -0,0 +1,409 @@ +module(..., package.seeall) + +local depgraph = require "tundra.depgraph" +local util = require "tundra.util" +local scanner = require "tundra.scanner" +local dirwalk = require "tundra.dirwalk" +local platform = require "tundra.platform" +local native = require "tundra.native" +local njson = require "tundra.native.json" +local path = require "tundra.path" + +local dag_dag_magic = 0x15890105 + +local function get_passes(nodes) + local result = {} + local seen_passes = {} + + for _, node in ipairs(nodes) do + local p = node.pass + if not seen_passes[p] then + assert(type(p) == "table", "Passes must be tables, have " .. util.tostring(p)) + assert(type(p.BuildOrder) == "number", "Pass BuildOrder must be a number") + result[#result + 1] = p + seen_passes[p] = true + end + end + + table.sort(result, function (a, b) return a.BuildOrder < b.BuildOrder end) + + local pass_lookup = {} + + for index, pass in ipairs(result) do + pass_lookup[pass] = index - 1 + end + + return result, pass_lookup +end + +local function setup_input_deps(nodes) + local producers = {} + + local cwd = native.getcwd() .. SEP + local filter + if native.host_platform == 'windows' or native.host_platform == 'macosx' then + filter = function (str) return str:lower() end + else + filter = function (str) return str end + end + + local node_deps = {} + + -- Record producing node for all output files + for _, n in ipairs(nodes) do + for _, output in util.nil_ipairs(n.outputs) do + if not path.is_absolute(output) then + output = cwd .. output + end + output = filter(output) + if producers[output] then + errorf("file %s set to be written by more than one target:\n%s\n%s\n", + output, n.annotation, producers[output].annotation) + end + producers[output] = n + end + if n.deps then + node_deps[n] = util.make_lookup_table(n.deps) + end + end + + -- Map input files to dependencies + for _, n in ipairs(nodes) do + for _, inputf in util.nil_ipairs(n.inputs) do + if not path.is_absolute(inputf) then + inputf = cwd .. inputf + end + inputf = filter(inputf) + local producer = producers[inputf] + local deps_lut = node_deps[n] + if producer and (not deps_lut or not deps_lut[producer]) then + n.deps[#n.deps + 1] = producer + if not deps_lut then + deps_lut = {} + node_deps[n] = deps_lut + end + deps_lut[producer] = true + end + end + end + +end + +local function get_scanners(nodes) + local scanners = {} + local scanner_to_index = {} + for _, node in ipairs(nodes) do + local scanner = node.scanner + if scanner and not scanner_to_index[scanner] then + scanner_to_index[scanner] = #scanners + scanners[#scanners + 1] = scanner + end + end + return scanners, scanner_to_index +end + +local function save_passes(w, passes) + w:begin_array("Passes") + for _, s in ipairs(passes) do + w:write_string(s.Name) + end + w:end_array() +end + +local function save_scanners(w, scanners) + w:begin_array("Scanners") + for _, s in ipairs(scanners) do + w:begin_object() + w:write_string(s.Kind, 'Kind') + w:begin_array("IncludePaths") + for _, path in util.nil_ipairs(s.Paths) do + w:write_string(path) + end + w:end_array() + -- Serialize specialized state for generic scanners + if s.Kind == 'generic' then + w:write_bool(s.RequireWhitespace, 'RequireWhitespace') + w:write_bool(s.UseSeparators, 'UseSeparators') + w:write_bool(s.BareMeansSystem, 'BareMeansSystem') + w:begin_array('Keywords') + for _, kw in util.nil_ipairs(s.Keywords) do + w:write_string(kw) + end + w:end_array() + w:begin_array('KeywordsNoFollow') + for _, kw in util.nil_ipairs(s.KeywordsNoFollow) do + w:write_string(kw) + end + w:end_array() + end + w:end_object() + end + w:end_array() +end + +local function save_nodes(w, nodes, pass_to_index, scanner_to_index) + w:begin_array("Nodes") + for idx, node in ipairs(nodes) do + w:begin_object() + assert(idx - 1 == node.index) + if node.action then + w:write_string(node.action, "Action") + end + if node.preaction then + w:write_string(node.preaction, "PreAction") + end + w:write_string(node.annotation, "Annotation") + + w:write_number(pass_to_index[node.pass], "PassIndex") + + if #node.deps > 0 then + w:begin_array("Deps") + for _, dep in ipairs(node.deps) do + w:write_number(dep.index) + end + w:end_array() + end + + local function dump_file_list(list, name) + if list and #list > 0 then + w:begin_array(name) + for _, fn in ipairs(list) do + w:write_string(fn) + end + w:end_array(name) + end + end + + dump_file_list(node.inputs, "Inputs") + dump_file_list(node.outputs, "Outputs") + dump_file_list(node.aux_outputs, "AuxOutputs") + + -- Save environment strings + local env_count = 0 + for k, v in util.nil_pairs(node.env) do + env_count = env_count + 1 + end + + if env_count > 0 then + w:begin_array("Env") + for k, v in pairs(node.env) do + w:begin_object() + w:write_string(k, "Key") + w:write_string(v, "Value") + w:end_object() + end + w:end_array() + end + + if node.scanner then + w:write_number(scanner_to_index[node.scanner], "ScannerIndex") + end + + if node.overwrite_outputs then + w:write_bool(true, "OverwriteOutputs") + end + + if node.is_precious then + w:write_bool(true, "PreciousOutputs") + end + + if node.expensive then + w:write_bool(true, "Expensive") + end + + w:end_object() + end + w:end_array() +end + +local function save_configs(w, bindings, default_variant, default_subvariant) + local configs = {} + local variants = {} + local subvariants = {} + local config_index = {} + local variant_index = {} + local subvariant_index = {} + local default_config = nil + local host_platform = platform.host_platform() + + for _, b in ipairs(bindings) do + if not configs[b.Config.Name] then + configs[b.Config.Name] = #config_index + config_index[#config_index+1] = b.Config.Name + end + if not variants[b.Variant.Name] then + variants[b.Variant.Name] = #variant_index + variant_index[#variant_index+1] = b.Variant.Name + end + if not subvariants[b.SubVariant] then + subvariants[b.SubVariant] = #subvariant_index + subvariant_index[#subvariant_index+1] = b.SubVariant + end + + if b.Config.DefaultOnHost == host_platform then + default_config = b.Config + end + end + + assert(#config_index > 0) + assert(#variant_index > 0) + assert(#subvariant_index > 0) + + local function dump_str_array(array, name) + if array and #array > 0 then + w:begin_array(name) + for _, name in ipairs(array) do + w:write_string(name) + end + w:end_array() + end + end + + w:begin_object("Setup") + dump_str_array(config_index, "Configs") + dump_str_array(variant_index, "Variants") + dump_str_array(subvariant_index, "SubVariants") + + w:begin_array("BuildTuples") + for index, binding in ipairs(bindings) do + w:begin_object() + w:write_number(configs[binding.Config.Name], "ConfigIndex") + w:write_number(variants[binding.Variant.Name], "VariantIndex") + w:write_number(subvariants[binding.SubVariant], "SubVariantIndex") + local function store_node_index_array(nodes, name) + w:begin_array(name) + for _, node in util.nil_ipairs(nodes) do + w:write_number(node.index) + end + w:end_array() + end + store_node_index_array(binding.AlwaysNodes, "AlwaysNodes") + store_node_index_array(binding.DefaultNodes, "DefaultNodes") + + w:begin_object("NamedNodes") + for name, node in pairs(binding.NamedNodes) do + w:write_number(node.index, name) + end + w:end_object() + + w:end_object() + end + w:end_array() + + -- m_DefaultBuildTuple + w:begin_object("DefaultBuildTuple") + if default_config then + w:write_number(configs[default_config.Name], "ConfigIndex") + else + w:write_number(-1, "ConfigIndex") + end + + if default_variant then + w:write_number(variants[default_variant.Name], "VariantIndex") + else + w:write_number(-1, "VariantIndex") + end + + if default_subvariant then + w:write_number(subvariants[default_subvariant], "SubVariantIndex") + else + w:write_number(-1, "SubVariantIndex") + end + w:end_object() + + w:end_object() + +end + +local function save_signatures(w, accessed_lua_files) + w:begin_array("FileSignatures") + for _, fn in ipairs(accessed_lua_files) do + w:begin_object() + local stat = native.stat_file(fn) + if not stat.exists then + errorf("accessed file %s is gone: %s", fn, err) + end + w:write_string(fn, "File") + w:write_number(stat.timestamp, "Timestamp") + w:end_object() + end + w:end_array() + + w:begin_array("GlobSignatures") + local globs = dirwalk.all_queries() + for _, glob in ipairs(globs) do + w:begin_object() + w:write_string(glob.Path, "Path") + w:begin_array("Files") + for _, fn in ipairs(glob.Files) do w:write_string(fn) end + w:end_array() + w:begin_array("SubDirs") + for _, fn in ipairs(glob.SubDirs) do w:write_string(fn) end + w:end_array() + w:end_object() + end + w:end_array() +end + +local function check_deps(nodes) + for _, node in ipairs(nodes) do + for _ , dep in ipairs(node.deps) do + if dep.pass.BuildOrder > node.pass.BuildOrder then + errorf("%s (pass: %s) depends on %s in later pass (%s)", node.annotation, node.pass.Name, dep.annotation, dep.pass.Name) + end + end + end +end + +function save_dag_data(bindings, default_variant, default_subvariant, content_digest_exts, misc_options) + + -- Call builtin function to get at accessed file table + local accessed_lua_files = util.table_keys(get_accessed_files()) + + misc_options = misc_options or {} + local max_expensive_jobs = misc_options.MaxExpensiveJobs or -1 + + printf("save_dag_data: %d bindings, %d accessed files", #bindings, #accessed_lua_files) + + local nodes = depgraph.get_all_nodes() + + -- Set node indices + for idx, node in ipairs(nodes) do + node.index = idx - 1 + end + + -- Set up array of passes + local passes, pass_to_index = get_passes(nodes) + + -- Hook up dependencies due to input files + setup_input_deps(nodes) + + check_deps(nodes) + + -- Find scanners + local scanners, scanner_to_index = get_scanners(nodes) + + local w = njson.new('.tundra2.dag.json') + + w:begin_object() + save_configs(w, bindings, default_variant, default_subvariant) + save_passes(w, passes) + save_scanners(w, scanners) + save_nodes(w, nodes, pass_to_index, scanner_to_index) + save_signatures(w, accessed_lua_files) + + if content_digest_exts and #content_digest_exts > 0 then + w:begin_array("ContentDigestExtensions") + for _, ext in ipairs(content_digest_exts) do + w:write_string(ext) + end + w:end_array() + end + + w:write_number(max_expensive_jobs, "MaxExpensiveCount") + + w:end_object() + + w:close() +end + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/debugger.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/debugger.lua new file mode 100644 index 0000000..73a79ff --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/debugger.lua @@ -0,0 +1,1343 @@ + +--{{{ history + +--15/03/06 DCN Created based on RemDebug +--28/04/06 DCN Update for Lua 5.1 +--01/06/06 DCN Fix command argument parsing +-- Add step/over N facility +-- Add trace lines facility +--05/06/06 DCN Add trace call/return facility +--06/06/06 DCN Make it behave when stepping through the creation of a coroutine +--06/06/06 DCN Integrate the simple debugger into the main one +--07/06/06 DCN Provide facility to step into coroutines +--13/06/06 DCN Fix bug that caused the function environment to get corrupted with the global one +--14/06/06 DCN Allow 'sloppy' file names when setting breakpoints +--04/08/06 DCN Allow for no space after command name +--11/08/06 DCN Use io.write not print +--30/08/06 DCN Allow access to array elements in 'dump' +--10/10/06 DCN Default to breakfile for all commands that require a filename and give '-' +--06/12/06 DCN Allow for punctuation characters in DUMP variable names +--03/01/07 DCN Add pause on/off facility +--19/06/07 DCN Allow for duff commands being typed in the debugger (thanks to Michael.Bringmann@lsi.com) +-- Allow for case sensitive file systems (thanks to Michael.Bringmann@lsi.com) + +--}}} +--{{{ description + +--A simple command line debug system for Lua written by Dave Nichols of +--Match-IT Limited. Its public domain software. Do with it as you wish. + +--This debugger was inspired by: +-- RemDebug 1.0 Beta +-- Copyright Kepler Project 2005 (http://www.keplerproject.org/remdebug) + +--Usage: +-- require('debugger') --load the debug library +-- pause(message) --start/resume a debug session + +--An assert() failure will also invoke the debugger. + +--}}} + +local IsWindows = string.find(string.lower(os.getenv('OS') or ''),'^windows') + +local coro_debugger +local events = { BREAK = 1, WATCH = 2, STEP = 3, SET = 4 } +local breakpoints = {} +local watches = {} +local step_into = false +local step_over = false +local step_lines = 0 +local step_level = {main=0} +local stack_level = {main=0} +local trace_level = {main=0} +local trace_calls = false +local trace_returns = false +local trace_lines = false +local ret_file, ret_line, ret_name +local current_thread = 'main' +local started = false +local pause_off = false +local _g = _G +local cocreate, cowrap = coroutine.create, coroutine.wrap +local pausemsg = 'pause' + +--{{{ local hints -- command help +--The format in here is name=summary|description +local hints = { + +pause = [[ +pause(msg) -- start/resume a debugger session| + +This can only be used in your code or from the console as a means to +start/resume a debug session. +If msg is given that is shown when the session starts/resumes. Useful to +give a context if you've instrumented your code with pause() statements. +]], + +poff = [[ +poff -- turn off pause() command| + +This causes all pause() commands to be ignored. This is useful if you have +instrumented your code in a busy loop and want to continue normal execution +with no further interruption. +]], + +pon = [[ +pon -- turn on pause() command| + +This re-instates honouring the pause() commands you may have instrumented +your code with. +]], + +setb = [[ +setb [line file] -- set a breakpoint to line/file| + +If file is omitted or is "-" the breakpoint is set at the file for the +currently set level (see "set"). Execution pauses when this line is about +to be executed and the debugger session is re-activated. + +The file can be given as the fully qualified name, partially qualified or +just the file name. E.g. if file is set as "myfile.lua", then whenever +execution reaches any file that ends with "myfile.lua" it will pause. +]], + +delb = [[ +delb [line file] -- removes a breakpoint| + +If file is omitted or is "-" the breakpoint is removed for the file of the +currently set level (see "set"). +]], + +delallb = [[ +delallb -- removes all breakpoints| +]], + +setw = [[ +setw -- adds a new watch expression| + +The expression is evaluated before each line is executed. If the expression +yields true then execution is paused and the debugger session re-activated. +The expression is executed in the context of the line about to be executed. +]], + +delw = [[ +delw -- removes the watch expression at index| + +The index is that returned when the watch expression was set by setw. +]], + +delallw = [[ +delallw -- removes all watch expressions| +]], + +run = [[ +run -- run until next breakpoint or watch expression| +]], + +step = [[ +step [N] -- run next N lines, stepping into function calls| + +If N is omitted, use 1. +]], + +over = [[ +over [N] -- run next N lines, stepping over function calls| + +If N is omitted, use 1. +]], + +out = [[ +out [N] -- run lines until stepped out of N functions| + +If N is omitted, use 1. +If you are inside a function, using "out 1" will run until you return +from that function to the caller. +]], + +goto = [[ +goto -- step to line number in the current file| + +The line and current file are those in the currently set context level. +]], + +listb = [[ +listb -- lists breakpoints| +]], + +listw = [[ +listw -- lists watch expressions| +]], + +set = [[ +set [level] -- set context to stack level, omitted=show| + +If level is omitted it just prints the current level set. +This sets the current context to the level given. This affects the +context used for several other functions (e.g. vars). The possible +levels are those shown by trace. +]], + +vars = [[ +vars [depth] -- list context locals to depth, omitted=1| + +If depth is omitted then uses 1. +Use a depth of 0 for the maximum. +Lists all non-nil local variables and all non-nil upvalues in the +currently set context. For variables that are tables, lists all fields +to the given depth. +]], + +fenv = [[ +fenv [depth] -- list context function env to depth, omitted=1| + +If depth is omitted then uses 1. +Use a depth of 0 for the maximum. +Lists all function environment variables in the currently set context. +For variables that are tables, lists all fields to the given depth. +]], + +glob = [[ +glob [depth] -- list globals to depth, omitted=1| + +If depth is omitted then uses 1. +Use a depth of 0 for the maximum. +Lists all global variables. +For variables that are tables, lists all fields to the given depth. +]], + +ups = [[ +ups -- list all the upvalue names| + +These names will also be in the "vars" list unless their value is nil. +This provides a means to identify which vars are upvalues and which are +locals. If a name is both an upvalue and a local, the local value takes +precedance. +]], + +locs = [[ +locs -- list all the locals names| + +These names will also be in the "vars" list unless their value is nil. +This provides a means to identify which vars are upvalues and which are +locals. If a name is both an upvalue and a local, the local value takes +precedance. +]], + +dump = [[ +dump [depth] -- dump all fields of variable to depth| + +If depth is omitted then uses 1. +Use a depth of 0 for the maximum. +Prints the value of in the currently set context level. If +is a table, lists all fields to the given depth. can be just a +name, or name.field or name.# to any depth, e.g. t.1.f accesses field +'f' in array element 1 in table 't'. + +Can also be called from a script as dump(var,depth). +]], + +tron = [[ +tron [crl] -- turn trace on for (c)alls, (r)etuns, (l)lines| + +If no parameter is given then tracing is turned off. +When tracing is turned on a line is printed to the console for each +debug 'event' selected. c=function calls, r=function returns, l=lines. +]], + +trace = [[ +trace -- dumps a stack trace| + +Format is [level] = file,line,name +The level is a candidate for use by the 'set' command. +]], + +info = [[ +info -- dumps the complete debug info captured| + +Only useful as a diagnostic aid for the debugger itself. This information +can be HUGE as it dumps all variables to the maximum depth, so be careful. +]], + +show = [[ +show line file X Y -- show X lines before and Y after line in file| + +If line is omitted or is '-' then the current set context line is used. +If file is omitted or is '-' then the current set context file is used. +If file is not fully qualified and cannot be opened as specified, then +a search for the file in the package[path] is performed using the usual +"require" searching rules. If no file extension is given, .lua is used. +Prints the lines from the source file around the given line. +]], + +exit = [[ +exit -- exits debugger, re-start it using pause()| +]], + +help = [[ +help [command] -- show this list or help for command| +]], + +[""] = [[ + -- execute a statement in the current context| + +The statement can be anything that is legal in the context, including +assignments. Such assignments affect the context and will be in force +immediately. Any results returned are printed. Use '=' as a short-hand +for 'return', e.g. "=func(arg)" will call 'func' with 'arg' and print +the results, and "=var" will just print the value of 'var'. +]], + +what = [[ +what -- show where is defined (if known)| +]], + +} +--}}} + +--{{{ local function getinfo(level,field) + +--like debug.getinfo but copes with no activation record at the given level +--and knows how to get 'field'. 'field' can be the name of any of the +--activation record fields or any of the 'what' names or nil for everything. +--only valid when using the stack level to get info, not a function name. + +local function getinfo(level,field) + level = level + 1 --to get to the same relative level as the caller + if not field then return debug.getinfo(level) end + local what + if field == 'name' or field == 'namewhat' then + what = 'n' + elseif field == 'what' or field == 'source' or field == 'linedefined' or field == 'lastlinedefined' or field == 'short_src' then + what = 'S' + elseif field == 'currentline' then + what = 'l' + elseif field == 'nups' then + what = 'u' + elseif field == 'func' then + what = 'f' + else + return debug.getinfo(level,field) + end + local ar = debug.getinfo(level,what) + if ar then return ar[field] else return nil end +end + +--}}} +--{{{ local function indented( level, ... ) + +local function indented( level, ... ) + io.write( string.rep(' ',level), table.concat({...}), '\n' ) +end + +--}}} +--{{{ local function dumpval( level, name, value, limit ) + +local dumpvisited + +local function dumpval( level, name, value, limit ) + local index + if type(name) == 'number' then + index = string.format('[%d] = ',name) + elseif type(name) == 'string' + and (name == '__VARSLEVEL__' or name == '__ENVIRONMENT__' or name == '__GLOBALS__' or name == '__UPVALUES__' or name == '__LOCALS__') then + --ignore these, they are debugger generated + return + elseif type(name) == 'string' and string.find(name,'^[_%a][_.%w]*$') then + index = name ..' = ' + else + index = string.format('[%q] = ',tostring(name)) + end + if type(value) == 'table' then + if dumpvisited[value] then + indented( level, index, string.format('ref%q;',dumpvisited[value]) ) + else + dumpvisited[value] = tostring(value) + if (limit or 0) > 0 and level+1 >= limit then + indented( level, index, dumpvisited[value] ) + else + indented( level, index, '{ -- ', dumpvisited[value] ) + for n,v in pairs(value) do + dumpval( level+1, n, v, limit ) + end + indented( level, '};' ) + end + end + else + if type(value) == 'string' then + if string.len(value) > 40 then + indented( level, index, '[[', value, ']];' ) + else + indented( level, index, string.format('%q',value), ';' ) + end + else + indented( level, index, tostring(value), ';' ) + end + end +end + +--}}} +--{{{ local function dumpvar( value, limit, name ) + +local function dumpvar( value, limit, name ) + dumpvisited = {} + dumpval( 0, name or tostring(value), value, limit ) +end + +--}}} +--{{{ local function show(file,line,before,after) + +--show +/-N lines of a file around line M + +local function show(file,line,before,after) + + line = tonumber(line or 1) + before = tonumber(before or 10) + after = tonumber(after or before) + + if not string.find(file,'%.') then file = file..'.lua' end + + local f = io.open(file,'r') + if not f then + --{{{ try to find the file in the path + + -- + -- looks for a file in the package path + -- + local path = package.path or LUA_PATH or '' + for c in string.gmatch (path, "[^;]+") do + local c = string.gsub (c, "%?%.lua", file) + f = io.open (c,'r') + if f then + break + end + end + + --}}} + if not f then + io.write('Cannot find '..file..'\n') + return + end + end + + local i = 0 + for l in f:lines() do + i = i + 1 + if i >= (line-before) then + if i > (line+after) then break end + if i == line then + io.write(i..'***\t'..l..'\n') + else + io.write(i..'\t'..l..'\n') + end + end + end + + f:close() + +end + +--}}} +--{{{ local function tracestack(l) + +local function gi( i ) + return function() i=i+1 return debug.getinfo(i),i end +end + +local function gl( level, j ) + return function() j=j+1 return debug.getlocal( level, j ) end +end + +local function gu( func, k ) + return function() k=k+1 return debug.getupvalue( func, k ) end +end + +local traceinfo + +local function tracestack(l) + local l = l + 1 --NB: +1 to get level relative to caller + traceinfo = {} + traceinfo.pausemsg = pausemsg + for ar,i in gi(l) do + table.insert( traceinfo, ar ) + local names = {} + local values = {} + for n,v in gl(i,0) do + if string.sub(n,1,1) ~= '(' then --ignore internal control variables + table.insert( names, n ) + table.insert( values, v ) + end + end + if #names > 0 then + ar.lnames = names + ar.lvalues = values + end + if ar.func then + local names = {} + local values = {} + for n,v in gu(ar.func,0) do + if string.sub(n,1,1) ~= '(' then --ignore internal control variables + table.insert( names, n ) + table.insert( values, v ) + end + end + if #names > 0 then + ar.unames = names + ar.uvalues = values + end + end + end +end + +--}}} +--{{{ local function trace() + +local function trace(set) + local mark + for level,ar in ipairs(traceinfo) do + if level == set then + mark = '***' + else + mark = '' + end + io.write('['..level..']'..mark..'\t'..(ar.name or ar.what)..' in '..ar.short_src..':'..ar.currentline..'\n') + end +end + +--}}} +--{{{ local function info() + +local function info() dumpvar( traceinfo, 0, 'traceinfo' ) end + +--}}} + +--{{{ local function set_breakpoint(file, line) + +local function set_breakpoint(file, line) + if not breakpoints[line] then + breakpoints[line] = {} + end + breakpoints[line][file] = true +end + +--}}} +--{{{ local function remove_breakpoint(file, line) + +local function remove_breakpoint(file, line) + if breakpoints[line] then + breakpoints[line][file] = nil + end +end + +--}}} +--{{{ local function has_breakpoint(file, line) + +--allow for 'sloppy' file names +--search for file and all variations walking up its directory hierachy +--ditto for the file with no extension + +local function has_breakpoint(file, line) + if not breakpoints[line] then return false end + local noext = string.gsub(file,"(%..-)$",'',1) + if noext == file then noext = nil end + while file do + if breakpoints[line][file] then return true end + file = string.match(file,"[:/\](.+)$") + end + while noext do + if breakpoints[line][noext] then return true end + noext = string.match(noext,"[:/\](.+)$") + end + return false +end + +--}}} +--{{{ local function capture_vars(ref,level,line) + +local function capture_vars(ref,level,line) + --get vars, file and line for the given level relative to debug_hook offset by ref + + local lvl = ref + level --NB: This includes an offset of +1 for the call to here + + --{{{ capture variables + + local ar = debug.getinfo(lvl, "f") + if not ar then return {},'?',0 end + + local vars = {__UPVALUES__={}, __LOCALS__={}} + local i + + local func = ar.func + if func then + i = 1 + while true do + local name, value = debug.getupvalue(func, i) + if not name then break end + if string.sub(name,1,1) ~= '(' then --NB: ignoring internal control variables + vars[name] = value + vars.__UPVALUES__[i] = name + end + i = i + 1 + end + vars.__ENVIRONMENT__ = getfenv(func) + end + + vars.__GLOBALS__ = getfenv(0) + + i = 1 + while true do + local name, value = debug.getlocal(lvl, i) + if not name then break end + if string.sub(name,1,1) ~= '(' then --NB: ignoring internal control variables + vars[name] = value + vars.__LOCALS__[i] = name + end + i = i + 1 + end + + vars.__VARSLEVEL__ = level + + if func then + --NB: Do not do this until finished filling the vars table + setmetatable(vars, { __index = getfenv(func), __newindex = getfenv(func) }) + end + + --NB: Do not read or write the vars table anymore else the metatable functions will get invoked! + + --}}} + + local file = getinfo(lvl, "source") + if string.find(file, "@") == 1 then + file = string.sub(file, 2) + end + if IsWindows then file = string.lower(file) end + + if not line then + line = getinfo(lvl, "currentline") + end + + return vars,file,line + +end + +--}}} +--{{{ local function restore_vars(ref,vars) + +local function restore_vars(ref,vars) + + if type(vars) ~= 'table' then return end + + local level = vars.__VARSLEVEL__ --NB: This level is relative to debug_hook offset by ref + if not level then return end + + level = level + ref --NB: This includes an offset of +1 for the call to here + + local i + local written_vars = {} + + i = 1 + while true do + local name, value = debug.getlocal(level, i) + if not name then break end + if vars[name] and string.sub(name,1,1) ~= '(' then --NB: ignoring internal control variables + debug.setlocal(level, i, vars[name]) + written_vars[name] = true + end + i = i + 1 + end + + local ar = debug.getinfo(level, "f") + if not ar then return end + + local func = ar.func + if func then + + i = 1 + while true do + local name, value = debug.getupvalue(func, i) + if not name then break end + if vars[name] and string.sub(name,1,1) ~= '(' then --NB: ignoring internal control variables + if not written_vars[name] then + debug.setupvalue(func, i, vars[name]) + end + written_vars[name] = true + end + i = i + 1 + end + + end + +end + +--}}} +--{{{ local function trace_event(event, line, level) + +local function print_trace(level,depth,event,file,line,name) + + --NB: level here is relative to the caller of trace_event, so offset by 2 to get to there + level = level + 2 + + local file = file or getinfo(level,'short_src') + local line = line or getinfo(level,'currentline') + local name = name or getinfo(level,'name') + + local prefix = '' + if current_thread ~= 'main' then prefix = '['..tostring(current_thread)..'] ' end + + io.write(prefix.. + string.format('%08.2f:%02i.',os.clock(),depth).. + string.rep('.',depth%32).. + (file or '')..' ('..(line or '')..') '.. + (name or '').. + ' ('..event..')\n') + +end + +local function trace_event(event, line, level) + + if event == 'return' and trace_returns then + --note the line info for later + ret_file = getinfo(level+1,'short_src') + ret_line = getinfo(level+1,'currentline') + ret_name = getinfo(level+1,'name') + end + + if event ~= 'line' then return end + + local slevel = stack_level[current_thread] + local tlevel = trace_level[current_thread] + + if trace_calls and slevel > tlevel then + --we are now in the function called, so look back 1 level further to find the calling file and line + print_trace(level+1,slevel-1,'c',nil,nil,getinfo(level+1,'name')) + end + + if trace_returns and slevel < tlevel then + print_trace(level,slevel,'r',ret_file,ret_line,ret_name) + end + + if trace_lines then + print_trace(level,slevel,'l') + end + + trace_level[current_thread] = stack_level[current_thread] + +end + +--}}} +--{{{ local function debug_hook(event, line, level, thread) + +local function debug_hook(event, line, level, thread) + if not started then debug.sethook() return end + current_thread = thread or 'main' + local level = level or 2 + trace_event(event,line,level) + if event == "call" then + stack_level[current_thread] = stack_level[current_thread] + 1 + elseif event == "return" then + stack_level[current_thread] = stack_level[current_thread] - 1 + if stack_level[current_thread] < 0 then stack_level[current_thread] = 0 end + else + local vars,file,line = capture_vars(level,1,line) + local stop, ev, idx = false, events.STEP, 0 + while true do + for index, value in pairs(watches) do + setfenv(value.func, vars) + local status, res = pcall(value.func) + if status and res then + ev, idx = events.WATCH, index + stop = true + break + end + end + if stop then break end + if (step_into) + or (step_over and (stack_level[current_thread] <= step_level[current_thread] or stack_level[current_thread] == 0)) then + step_lines = step_lines - 1 + if step_lines < 1 then + ev, idx = events.STEP, 0 + break + end + end + if has_breakpoint(file, line) then + ev, idx = events.BREAK, 0 + break + end + return + end + tracestack(level) + local last_next = 1 + local err, next = assert(coroutine.resume(coro_debugger, ev, vars, file, line, idx)) + while true do + if next == 'cont' then + return + elseif next == 'stop' then + started = false + debug.sethook() + return + elseif tonumber(next) then --get vars for given level or last level + next = tonumber(next) + if next == 0 then next = last_next end + last_next = next + restore_vars(level,vars) + vars, file, line = capture_vars(level,next) + err, next = assert(coroutine.resume(coro_debugger, events.SET, vars, file, line, idx)) + else + io.write('Unknown command from debugger_loop: '..tostring(next)..'\n') + io.write('Stopping debugger\n') + next = 'stop' + end + end + end +end + +--}}} +--{{{ local function report(ev, vars, file, line, idx_watch) + +local function report(ev, vars, file, line, idx_watch) + local vars = vars or {} + local file = file or '?' + local line = line or 0 + local prefix = '' + if current_thread ~= 'main' then prefix = '['..tostring(current_thread)..'] ' end + if ev == events.STEP then + io.write(prefix.."Paused at file "..file.." line "..line..' ('..stack_level[current_thread]..')\n') + elseif ev == events.BREAK then + io.write(prefix.."Paused at file "..file.." line "..line..' ('..stack_level[current_thread]..') (breakpoint)\n') + elseif ev == events.WATCH then + io.write(prefix.."Paused at file "..file.." line "..line..' ('..stack_level[current_thread]..')'.." (watch expression "..idx_watch.. ": ["..watches[idx_watch].exp.."])\n") + elseif ev == events.SET then + --do nothing + else + io.write(prefix.."Error in application: "..file.." line "..line.."\n") + end + if ev ~= events.SET then + if pausemsg and pausemsg ~= '' then io.write('Message: '..pausemsg..'\n') end + pausemsg = '' + end + return vars, file, line +end + +--}}} + +--{{{ local function debugger_loop(server) + +local function debugger_loop(ev, vars, file, line, idx_watch) + + io.write("Lua Debugger\n") + local eval_env, breakfile, breakline = report(ev, vars, file, line, idx_watch) + io.write("Type 'help' for commands\n") + + local command, args + + --{{{ local function getargs(spec) + + --get command arguments according to the given spec from the args string + --the spec has a single character for each argument, arguments are separated + --by white space, the spec characters can be one of: + -- F for a filename (defaults to breakfile if - given in args) + -- L for a line number (defaults to breakline if - given in args) + -- N for a number + -- V for a variable name + -- S for a string + + local function getargs(spec) + local res={} + local char,arg + local ptr=1 + local _ + for i=1,string.len(spec) do + char = string.sub(spec,i,i) + if char == 'F' then + _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr) + if not arg or arg == '' then arg = '-' end + if arg == '-' then arg = breakfile end + elseif char == 'L' then + _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr) + if not arg or arg == '' then arg = '-' end + if arg == '-' then arg = breakline end + arg = tonumber(arg) or 0 + elseif char == 'N' then + _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr) + if not arg or arg == '' then arg = '0' end + arg = tonumber(arg) or 0 + elseif char == 'V' then + _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr) + if not arg or arg == '' then arg = '' end + elseif char == 'S' then + _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr) + if not arg or arg == '' then arg = '' end + else + arg = '' + end + table.insert(res,arg or '') + end + return unpack(res) + end + + --}}} + + while true do + io.write("[DEBUG]> ") + local line = io.read("*line") + if line == nil then io.write('\n'); line = 'exit' end + + if string.find(line, "^[a-z]+") then + command = string.sub(line, string.find(line, "^[a-z]+")) + args = string.gsub(line,"^[a-z]+%s*",'',1) --strip command off line + else + command = '' + end + + if command == "setb" then + --{{{ set breakpoint + + local line, filename = getargs('LF') + if filename ~= '' and line ~= '' then + set_breakpoint(filename,line) + io.write("Breakpoint set in file "..filename..' line '..line..'\n') + else + io.write("Bad request\n") + end + + --}}} + + elseif command == "delb" then + --{{{ delete breakpoint + + local line, filename = getargs('LF') + if filename ~= '' and line ~= '' then + remove_breakpoint(filename, line) + io.write("Breakpoint deleted from file "..filename..' line '..line.."\n") + else + io.write("Bad request\n") + end + + --}}} + + elseif command == "delallb" then + --{{{ delete all breakpoints + breakpoints = {} + io.write('All breakpoints deleted\n') + --}}} + + elseif command == "listb" then + --{{{ list breakpoints + for i, v in pairs(breakpoints) do + for ii, vv in pairs(v) do + io.write("Break at: "..i..' in '..ii..'\n') + end + end + --}}} + + elseif command == "setw" then + --{{{ set watch expression + + if args and args ~= '' then + local func = loadstring("return(" .. args .. ")") + local newidx = #watches + 1 + watches[newidx] = {func = func, exp = args} + io.write("Set watch exp no. " .. newidx..'\n') + else + io.write("Bad request\n") + end + + --}}} + + elseif command == "delw" then + --{{{ delete watch expression + + local index = tonumber(args) + if index then + watches[index] = nil + io.write("Watch expression deleted\n") + else + io.write("Bad request\n") + end + + --}}} + + elseif command == "delallw" then + --{{{ delete all watch expressions + watches = {} + io.write('All watch expressions deleted\n') + --}}} + + elseif command == "listw" then + --{{{ list watch expressions + for i, v in pairs(watches) do + io.write("Watch exp. " .. i .. ": " .. v.exp..'\n') + end + --}}} + + elseif command == "run" then + --{{{ run until breakpoint + step_into = false + step_over = false + eval_env, breakfile, breakline = report(coroutine.yield('cont')) + --}}} + + elseif command == "step" then + --{{{ step N lines (into functions) + local N = tonumber(args) or 1 + step_over = false + step_into = true + step_lines = tonumber(N or 1) + eval_env, breakfile, breakline = report(coroutine.yield('cont')) + --}}} + + elseif command == "over" then + --{{{ step N lines (over functions) + local N = tonumber(args) or 1 + step_into = false + step_over = true + step_lines = tonumber(N or 1) + step_level[current_thread] = stack_level[current_thread] + eval_env, breakfile, breakline = report(coroutine.yield('cont')) + --}}} + + elseif command == "out" then + --{{{ step N lines (out of functions) + local N = tonumber(args) or 1 + step_into = false + step_over = true + step_lines = 1 + step_level[current_thread] = stack_level[current_thread] - tonumber(N or 1) + eval_env, breakfile, breakline = report(coroutine.yield('cont')) + --}}} + + elseif command == "goto" then + --{{{ step until reach line + local N = tonumber(args) + if N then + step_over = false + step_into = false + if has_breakpoint(breakfile,N) then + eval_env, breakfile, breakline = report(coroutine.yield('cont')) + else + local bf = breakfile + set_breakpoint(breakfile,N) + eval_env, breakfile, breakline = report(coroutine.yield('cont')) + if breakfile == bf and breakline == N then remove_breakpoint(breakfile,N) end + end + else + io.write("Bad request\n") + end + --}}} + + elseif command == "set" then + --{{{ set/show context level + local level = args + if level and level == '' then level = nil end + if level then + eval_env, breakfile, breakline = report(coroutine.yield(level)) + end + if eval_env.__VARSLEVEL__ then + io.write('Level: '..eval_env.__VARSLEVEL__..'\n') + else + io.write('No level set\n') + end + --}}} + + elseif command == "vars" then + --{{{ list context variables + local depth = args + if depth and depth == '' then depth = nil end + depth = tonumber(depth) or 1 + dumpvar(eval_env, depth+1, 'variables') + --}}} + + elseif command == "glob" then + --{{{ list global variables + local depth = args + if depth and depth == '' then depth = nil end + depth = tonumber(depth) or 1 + dumpvar(eval_env.__GLOBALS__,depth+1,'globals') + --}}} + + elseif command == "fenv" then + --{{{ list function environment variables + local depth = args + if depth and depth == '' then depth = nil end + depth = tonumber(depth) or 1 + dumpvar(eval_env.__ENVIRONMENT__,depth+1,'environment') + --}}} + + elseif command == "ups" then + --{{{ list upvalue names + dumpvar(eval_env.__UPVALUES__,2,'upvalues') + --}}} + + elseif command == "locs" then + --{{{ list locals names + dumpvar(eval_env.__LOCALS__,2,'upvalues') + --}}} + + elseif command == "what" then + --{{{ show where a function is defined + if args and args ~= '' then + local v = eval_env + local n = nil + for w in string.gmatch(args,"[%w_]+") do + v = v[w] + if n then n = n..'.'..w else n = w end + if not v then break end + end + if type(v) == 'function' then + local def = debug.getinfo(v,'S') + if def then + io.write(def.what..' in '..def.short_src..' '..def.linedefined..'..'..def.lastlinedefined..'\n') + else + io.write('Cannot get info for '..v..'\n') + end + else + io.write(v..' is not a function\n') + end + else + io.write("Bad request\n") + end + --}}} + + elseif command == "dump" then + --{{{ dump a variable + local name, depth = getargs('VN') + if name ~= '' then + if depth == '' or depth == 0 then depth = nil end + depth = tonumber(depth or 1) + local v = eval_env + local n = nil + for w in string.gmatch(name,"[^%.]+") do --get everything between dots + if tonumber(w) then + v = v[tonumber(w)] + else + v = v[w] + end + if n then n = n..'.'..w else n = w end + if not v then break end + end + dumpvar(v,depth+1,n) + else + io.write("Bad request\n") + end + --}}} + + elseif command == "show" then + --{{{ show file around a line or the current breakpoint + + local line, file, before, after = getargs('LFNN') + if before == 0 then before = 10 end + if after == 0 then after = before end + + if file ~= '' and file ~= "=stdin" then + show(file,line,before,after) + else + io.write('Nothing to show\n') + end + + --}}} + + elseif command == "poff" then + --{{{ turn pause command off + pause_off = true + --}}} + + elseif command == "pon" then + --{{{ turn pause command on + pause_off = false + --}}} + + elseif command == "tron" then + --{{{ turn tracing on/off + local option = getargs('S') + trace_calls = false + trace_returns = false + trace_lines = false + if string.find(option,'c') then trace_calls = true end + if string.find(option,'r') then trace_returns = true end + if string.find(option,'l') then trace_lines = true end + --}}} + + elseif command == "trace" then + --{{{ dump a stack trace + trace(eval_env.__VARSLEVEL__) + --}}} + + elseif command == "info" then + --{{{ dump all debug info captured + info() + --}}} + + elseif command == "pause" then + --{{{ not allowed in here + io.write('pause() should only be used in the script you are debugging\n') + --}}} + + elseif command == "help" then + --{{{ help + local command = getargs('S') + if command ~= '' and hints[command] then + io.write(hints[command]..'\n') + else + for _,v in pairs(hints) do + local _,_,h = string.find(v,"(.+)|") + io.write(h..'\n') + end + end + --}}} + + elseif command == "exit" then + --{{{ exit debugger + return 'stop' + --}}} + + elseif line ~= '' then + --{{{ just execute whatever it is in the current context + + --map line starting with "=..." to "return ..." + if string.sub(line,1,1) == '=' then line = string.gsub(line,'=','return ',1) end + + local ok, func = pcall(loadstring,line) + if func == nil then --Michael.Bringmann@lsi.com + io.write("Compile error: "..line..'\n') + elseif not ok then + io.write("Compile error: "..func..'\n') + else + setfenv(func, eval_env) + local res = {pcall(func)} + if res[1] then + if res[2] then + table.remove(res,1) + for _,v in ipairs(res) do + io.write(tostring(v)) + io.write('\t') + end + io.write('\n') + end + --update in the context + eval_env, breakfile, breakline = report(coroutine.yield(0)) + else + io.write("Run error: "..res[2]..'\n') + end + end + + --}}} + end + end + +end + +--}}} + +--{{{ coroutine.create + +--This function overrides the built-in for the purposes of propagating +--the debug hook settings from the creator into the created coroutine. + +_G.coroutine.create = function(f) + local thread + local hook, mask, count = debug.gethook() + if hook then + local function thread_hook(event,line) + hook(event,line,3,thread) + end + thread = cocreate(function(...) + stack_level[thread] = 0 + trace_level[thread] = 0 + step_level [thread] = 0 + debug.sethook(thread_hook,mask,count) + return f(...) + end) + return thread + else + return cocreate(f) + end +end + +--}}} +--{{{ coroutine.wrap + +--This function overrides the built-in for the purposes of propagating +--the debug hook settings from the creator into the created coroutine. + +_G.coroutine.wrap = function(f) + local thread + local hook, mask, count = debug.gethook() + if hook then + local function thread_hook(event,line) + hook(event,line,3,thread) + end + thread = cowrap(function(...) + stack_level[thread] = 0 + trace_level[thread] = 0 + step_level [thread] = 0 + debug.sethook(thread_hook,mask,count) + return f(...) + end) + return thread + else + return cowrap(f) + end +end + +--}}} + +--{{{ function pause() + +-- +-- Starts/resumes a debug session +-- + +function pause(x) + if pause_off then return end --being told to ignore pauses + pausemsg = x or 'pause' + local lines + local src = getinfo(2,'short_src') + if src == "stdin" then + lines = 1 --if in a console session, stop now + else + lines = 2 --if in a script, stop when get out of pause() + end + if started then + --we'll stop now 'cos the existing debug hook will grab us + step_lines = lines + step_into = true + else + coro_debugger = cocreate(debugger_loop) --NB: Use original coroutune.create + --set to stop when get out of pause() + trace_level[current_thread] = 0 + step_level [current_thread] = 0 + stack_level[current_thread] = 1 + step_lines = lines + step_into = true + started = true + debug.sethook(debug_hook, "crl") --NB: this will cause an immediate entry to the debugger_loop + end +end + +--}}} +--{{{ function dump() + +--shows the value of the given variable, only really useful +--when the variable is a table +--see dump debug command hints for full semantics + +function dump(v,depth) + dumpvar(v,(depth or 1)+1,tostring(v)) +end + +--}}} +--{{{ function debug.traceback(x) + +local _traceback = debug.traceback --note original function + +--override standard function +debug.traceback = function(x) + local assertmsg = _traceback(x) --do original function + pause(x) --let user have a look at stuff + return assertmsg --carry on +end + +_TRACEBACK = debug.traceback --Lua 5.0 function + +--}}} + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/decl.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/decl.lua new file mode 100644 index 0000000..9ede9c4 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/decl.lua @@ -0,0 +1,91 @@ +module(..., package.seeall) + +local nodegen = require "tundra.nodegen" + +local functions = {} +local _decl_meta = {} +_decl_meta.__index = _decl_meta + +local current = nil + +local function new_parser() + local obj = { + Functions = {}, + Results = {}, + DefaultTargets = {}, + AlwaysTargets = {}, + } + + local outer_env = _G + local iseval = nodegen.is_evaluator + local function indexfunc(tab, var) + if iseval(var) then + -- Return an anonymous function such that + -- the code "Foo { ... }" will result in a call to + -- "nodegen.evaluate('Foo', { ... })" + return function (data) + local result = nodegen.evaluate(var, data) + obj.Results[#obj.Results + 1] = result + return result + end + end + local p = obj.Functions[var] + if p then return p end + return outer_env[var] + end + + obj.FunctionMeta = { __index = indexfunc, __newindex = error } + obj.FunctionEnv = setmetatable({}, obj.FunctionMeta) + + for name, fn in pairs(functions) do + obj.Functions[name] = setfenv(fn, obj.FunctionEnv) + end + + obj.Functions["Default"] = function(default_obj) + obj.DefaultTargets[#obj.DefaultTargets + 1] = default_obj + end + + obj.Functions["Always"] = function(always_obj) + obj.AlwaysTargets[#obj.AlwaysTargets + 1] = always_obj + end + + current = setmetatable(obj, _decl_meta) + return current +end + +function add_function(name, fn) + assert(name and fn) + functions[name] = fn + + if current then + -- require called from within unit script + current.Functions[name] = setfenv(fn, current.FunctionEnv) + end +end + +function _decl_meta:parse_rec(data) + local chunk + if type(data) == "table" then + for _, gen in ipairs(data) do + self:parse_rec(gen) + end + return + elseif type(data) == "function" then + chunk = data + elseif type(data) == "string" then + chunk = assert(loadfile(data)) + else + croak("unknown type %s for unit_generator %q", type(data), tostring(data)) + end + + setfenv(chunk, self.FunctionEnv) + chunk() +end + +function parse(data) + p = new_parser() + current = p + p:parse_rec(data) + current = nil + return p.Results, p.DefaultTargets, p.AlwaysTargets +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/depgraph.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/depgraph.lua new file mode 100644 index 0000000..d7a0bdf --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/depgraph.lua @@ -0,0 +1,155 @@ +module(..., package.seeall) + +local boot = require "tundra.boot" +local util = require "tundra.util" +local path = require "tundra.path" +local native = require "tundra.native" +local environment = require "tundra.environment" + +local default_pass = { Name = "Default", BuildOrder = 100000 } + +local all_nodes = {} + +local _node_mt = {} +_node_mt.__index = _node_mt + +function make_node(data_) + local env_ = data_.Env + assert(environment.is_environment(env_), "Env must be provided") + + local root_path = native.getcwd() .. env_:get('SEP') + + local function path_for_cmdline(p) + local full_path + if path.is_absolute(p) then + full_path = p + else + full_path = root_path .. p + end + if full_path:find(' ', 1, true) then + return '"' .. full_path .. '"' + else + return full_path + end + end + + local function normalize_paths(paths) + return util.mapnil(paths, function (x) + if type(x) == "string" then + local v = env_:interpolate(x) + v = path.normalize(v) + return v + else + return x + end + end) + end + + -- these are the inputs that $(<) expand to + local regular_inputs = normalize_paths(data_.InputFiles) + + -- these are other, auxillary input files that shouldn't appear on the command line + -- useful to e.g. add an input dependency on a tool + local implicit_inputs = normalize_paths(data_.ImplicitInputs) + + local inputs = util.merge_arrays_2(regular_inputs, implicit_inputs) + local outputs = normalize_paths(data_.OutputFiles) + + local inputs_sorted = inputs and util.clone_array(inputs) or {} + local outputs_sorted = outputs and util.clone_array(outputs) or {} + + local cmdline_inputs = util.merge_arrays(regular_inputs, data_.InputFilesUntracked) + + table.sort(inputs_sorted) + table.sort(outputs_sorted) + + -- Quote the paths before interpolation into the command line + local expand_env = { + ['<'] = util.mapnil(cmdline_inputs, path_for_cmdline), + ['@'] = util.mapnil(outputs, path_for_cmdline), + } + + local expand_env_pretty = { + ['<'] = cmdline_inputs, + ['@'] = outputs, + } + + local overwrite = true + if type(data_.OverwriteOutputs) ~= "nil" then + overwrite = data_.OverwriteOutputs + end + + if data_.Scanner and not data_.Scanner.Kind then + errorf("Missing scanner kind") + end + + -- make sure dependencies are unique + local unique_deps = util.uniq(data_.Dependencies or {}) + + local params = { + pass = data_.Pass or default_pass, + scanner = data_.Scanner, + deps = unique_deps, + inputs = inputs_sorted, + outputs = outputs_sorted, + is_precious = data_.Precious, + expensive = data_.Expensive, + overwrite_outputs = overwrite, + src_env = env_, + env = env_.external_vars, + aux_outputs = util.mapnil(data_.AuxOutputFiles, function (x) + local result = env_:interpolate(x, expand_env) + return path.normalize(result) + end), + } + + if data_.Action then + params.action = env_:interpolate(data_.Action, expand_env) + else + assert(0 == #params.outputs, "can't have output files without an action") + params.action = "" + end + + if data_.PreAction then + params.preaction = env_:interpolate(data_.PreAction, expand_env) + end + + params.annotation = env_:interpolate(data_.Label or "?", expand_env_pretty) + + local result = setmetatable(params, _node_mt) + + -- Stash node + all_nodes[#all_nodes + 1] = result + + return result +end + +function is_node(obj) + return getmetatable(obj) == _node_mt +end + +function _node_mt:insert_output_files(tab, exts) + if exts then + local lut = util.make_lookup_table(exts) + for _, fn in ipairs(self.outputs) do + local ext = path.get_extension(fn) + if lut[ext] then + tab[#tab + 1] = fn + end + end + else + for _, fn in ipairs(self.outputs) do + tab[#tab + 1] = fn + end + end +end + +function _node_mt:insert_deps(tab) + for _, dep in util.nil_ipairs(self.deps) do + tab[#tab + 1] = dep + end +end + +function get_all_nodes() + return all_nodes +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/dirwalk.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/dirwalk.lua new file mode 100644 index 0000000..cfa067d --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/dirwalk.lua @@ -0,0 +1,41 @@ +module(..., package.seeall) + +local native = require "tundra.native" + +-- Stash of all dir walks performed for signature generation. +local query_records = {} + +function walk(path, filter_callback) + + local dir_stack = { path } + local paths_out = {} + + while #dir_stack > 0 do + local dir = dir_stack[#dir_stack] + table.remove(dir_stack) + + local subdirs, files = native.list_directory(dir) + query_records[dir] = { Files = files, SubDirs = subdirs } + + for _, subdir in ipairs(subdirs) do + full_dir_path = dir .. SEP .. subdir + if not filter_callback or filter_callback(subdir) then + table.insert(dir_stack, full_dir_path) + end + end + + for _, file in ipairs(files) do + table.insert(paths_out, dir .. SEP .. file) + end + end + + return paths_out +end + +function all_queries() + local result = {} + for k, v in pairs(query_records) do + result[#result + 1] = { Path = k, Files = v.Files, SubDirs = v.SubDirs } + end + return result +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/environment.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/environment.lua new file mode 100644 index 0000000..7934620 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/environment.lua @@ -0,0 +1,316 @@ +module(..., package.seeall) + +local util = require 'tundra.util' +local path = require 'tundra.path' +local depgraph = require 'tundra.depgraph' +local nenv = require 'tundra.environment.native' +local os = require 'os' + +local global_setup = {} + +--[==[ + +The environment is a holder for variables and their associated values. Values +are always kept as tables, even if there is only a single value. + +FOO = { a b c } + +e:interpolate("$(FOO)") -> "a b c" +e:interpolate("$(FOO:j, )") -> "a, b, c" +e:interpolate("$(FOO:p-I)") -> "-Ia -Ib -Ic" + +Missing keys trigger errors unless a default value is specified. + +]==]-- + +local envclass = {} + +function envclass:create(parent, assignments, obj) + obj = obj or {} + setmetatable(obj, self) + self.__index = self + + obj.cached_interpolation = {} + obj.vars = {} + obj.parent = parent + obj.lookup = { obj.vars } + obj.memos = {} + obj.memo_keys = {} + obj.external_vars = parent and util.clone_table(parent.external_vars) or nil + + -- assign initial bindings + if assignments then + obj:set_many(assignments) + end + + return obj +end + +function envclass:clone(assignments) + return envclass:create(self, assignments) +end + +function envclass:register_implicit_make_fn(ext, fn, docstring) + if type(ext) ~= "string" then + errorf("extension must be a string") + end + if type(fn) ~= "function" then + errorf("fn must be a function") + end + + if not ext:match("^%.") then + ext = "." .. ext -- we want the dot in the extension + end + + if not self._implicit_exts then + self._implicit_exts = {} + end + + self._implicit_exts[ext] = { + Function = fn, + Doc = docstring or "", + } +end + +function envclass:get_implicit_make_fn(filename) + local ext = path.get_extension(filename) + local chain = self + while chain do + local t = chain._implicit_exts + if t then + local v = t[ext] + if v then return v.Function end + end + chain = chain.parent + end + return nil +end + +function envclass:has_key(key) + local chain = self + while chain do + if chain.vars[key] then + return true + end + chain = chain.parent + end + + return false +end + +function envclass:get_vars() + return self.vars +end + +function envclass:set_many(table) + for k, v in pairs(table) do + self:set(k, v) + end +end + +function envclass:append(key, value) + if type(value) ~= "string" then + error("environment append: " .. util.tostring(value) .. " is not a string", 2) + end + self:invalidate_memos(key) + local t = self:get_list(key, 1) + local result + if type(t) == "table" then + result = util.clone_array(t) + table.insert(result, value) + else + result = { value } + end + self.vars[key] = result +end + +function envclass:append_many(data) + for k, v in pairs(data) do + self:append(k, v) + end +end + +function envclass:replace(key, value) + if type(value) == "string" then + value = { value } + end + assert(type(value) == "table") + + self:invalidate_memos(key) + self.vars[key] = value +end + +function envclass:invalidate_memos(key) + self.cached_interpolation = {} + local name_tab = self.memo_keys[key] + if name_tab then + for name, _ in pairs(name_tab) do + self.memos[name] = nil + end + end +end + +function envclass:set_default(key, value) + if not self:has_key(key) then + self:set(key, value) + end +end + +function envclass:set_default_many(table) + for key, value in pairs(table) do + self:set_default(key, value) + end +end + +function envclass:set(key, value) + self:invalidate_memos(key) + assert(key:len() > 0, "key must not be empty") + assert(type(key) == "string", "key must be a string") + if type(value) == "string" then + if value:len() > 0 then + self.vars[key] = { value } + else + -- let empty strings make empty tables + self.vars[key] = {} + end + elseif type(value) == "table" then + -- FIXME: should filter out empty values + for _, v in ipairs(value) do + if not type(v) == "string" then + error("key " .. key .. "'s table value contains non-string value " .. tostring(v)) + end + end + self.vars[key] = util.clone_array(value) + else + error("key " .. key .. "'s value is neither table nor string: " .. tostring(value)) + end +end + +function envclass:get_id() + return self.id +end + +function envclass:get(key, default) + local v = self.vars[key] + if v then + return table.concat(v, " ") + elseif self.parent then + return self.parent:get(key, default) + elseif default then + return default + else + error(string.format("key '%s' not present in environment", key)) + end +end + +function envclass:get_list(key, default) + local v = self.vars[key] + if v then + return v -- FIXME: this should be immutable from the outside + elseif self.parent then + return self.parent:get_list(key, default) + elseif default then + return default + elseif not key then + error("nil key is not allowed") + else + error(string.format("key '%s' not present in environment", key)) + end +end + +function envclass:get_parent() + return self.parent +end + +function envclass:interpolate(str, vars) + local cached = self.cached_interpolation[str] + + if not cached then + cached = nenv.interpolate(str, self) + self.cached_interpolation[str] = cached + end + + if vars then + return nenv.interpolate(cached, self, vars) + else + return cached + end +end + +function create(parent, assignments, obj) + return envclass:create(parent, assignments, obj) +end + +function envclass:record_memo_var(key, name) + local tab = self.memo_keys[key] + if not tab then + tab = {} + self.memo_keys[key] = tab + end + tab[name] = true +end + +function envclass:memoize(key, name, fn) + local memo = self.memos[name] + if not memo then + self:record_memo_var(key, name) + memo = fn() + self.memos[name] = memo + end + return memo +end + +function envclass:get_external_env_var(key) + local chain = self + while chain do + local t = self.external_vars + if t then + local v = t[key] + if v then return v end + end + + chain = chain.parent + end + + return os.getenv(key) +end + +function envclass:set_external_env_var(key, value) + local t = self.external_vars + if not t then + t = {} + self.external_vars = t + end + t[key] = value +end + +function envclass:add_setup_function(fn) + local t = self.setup_funcs + if not t then + t = {} + self.setup_funcs = t + end + t[#t + 1] = fn +end + +function envclass:run_setup_functions() + for _, func in ipairs(global_setup) do + func(self) + end + t = self.setup_funcs + local chain = self + while chain do + for _, func in util.nil_ipairs(chain.setup_funcs) do + func(self) + end + chain = chain.parent + end +end + +function add_global_setup(fn) + global_setup[#global_setup + 1] = fn +end + +function is_environment(datum) + return getmetatable(datum) == envclass +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/host/freebsd.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/host/freebsd.lua new file mode 100644 index 0000000..87cade2 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/host/freebsd.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply_host(env) + env:set_many { + ["DOTNETRUN"] = "mono ", + ["HOSTPROGSUFFIX"] = "", + ["HOSTSHLIBSUFFIX"] = ".so", + ["_COPY_FILE"] = "cp -f $(<) $(@)", + ["_HARDLINK_FILE"] = "ln -f $(<) $(@)", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/host/linux.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/host/linux.lua new file mode 100644 index 0000000..87cade2 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/host/linux.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply_host(env) + env:set_many { + ["DOTNETRUN"] = "mono ", + ["HOSTPROGSUFFIX"] = "", + ["HOSTSHLIBSUFFIX"] = ".so", + ["_COPY_FILE"] = "cp -f $(<) $(@)", + ["_HARDLINK_FILE"] = "ln -f $(<) $(@)", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/host/macosx.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/host/macosx.lua new file mode 100644 index 0000000..81d2c59 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/host/macosx.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply_host(env) + env:set_many { + ["DOTNETRUN"] = "mono ", + ["HOSTPROGSUFFIX"] = "", + ["HOSTSHLIBSUFFIX"] = ".dylib", + ["_COPY_FILE"] = "cp -f $(<) $(@)", + ["_HARDLINK_FILE"] = "ln -f $(<) $(@)", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/host/openbsd.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/host/openbsd.lua new file mode 100644 index 0000000..87cade2 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/host/openbsd.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply_host(env) + env:set_many { + ["DOTNETRUN"] = "mono ", + ["HOSTPROGSUFFIX"] = "", + ["HOSTSHLIBSUFFIX"] = ".so", + ["_COPY_FILE"] = "cp -f $(<) $(@)", + ["_HARDLINK_FILE"] = "ln -f $(<) $(@)", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/host/windows.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/host/windows.lua new file mode 100644 index 0000000..dd5d2de --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/host/windows.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply_host(env) + env:set_many { + ["DOTNETRUN"] = "", + ["HOSTPROGSUFFIX"] = ".exe", + ["HOSTSHLIBSUFFIX"] = ".dll", + ["_COPY_FILE"] = "copy $(<) $(@)", + ["_HARDLINK_FILE"] = "copy /f $(<) $(@)", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc-common.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc-common.lua new file mode 100644 index 0000000..4f78bfe --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc-common.lua @@ -0,0 +1,864 @@ +module(..., package.seeall) + +local native = require "tundra.native" +local nodegen = require "tundra.nodegen" +local path = require "tundra.path" +local util = require "tundra.util" + +LF = '\r\n' +local UTF_HEADER = '\239\187\191' -- byte mark EF BB BF + +local VERSION_NUMBER = "12.00" +local VERSION_YEAR = "2012" +local HOOKS = {} + +local msvc_generator = {} +msvc_generator.__index = msvc_generator + +local project_types = util.make_lookup_table { + "Program", "SharedLibrary", "StaticLibrary", "CSharpExe", "CSharpLib", "ObjGroup", +} + +local toplevel_stuff = util.make_lookup_table { + ".exe", ".lib", ".dll", +} + +local binary_extension = util.make_lookup_table { + ".exe", ".lib", ".dll", ".pdb", ".res", ".obj", ".o", ".a", +} + +local header_exts = util.make_lookup_table { + ".h", ".hpp", ".hh", ".inl", +} + +-- Scan for sources, following dependencies until those dependencies seem to be +-- a different top-level unit +local function get_sources(dag, sources, generated, level, dag_lut) + for _, output in util.nil_ipairs(dag.outputs) do + local ext = path.get_extension(output) + if not binary_extension[ext] then + generated[output] = true + sources[output] = true -- pick up generated headers + end + end + + for _, input in util.nil_ipairs(dag.inputs) do + local ext = path.get_extension(input) + if not binary_extension[ext] then + sources[input] = true + end + end + + for _, dep in util.nil_ipairs(dag.deps) do + if not dag_lut[dep] then -- don't go into other top-level DAGs + get_sources(dep, sources, generated, level + 1, dag_lut) + end + end +end + +function get_guid_string(data) + local sha1 = native.digest_guid(data) + local guid = sha1:sub(1, 8) .. '-' .. sha1:sub(9,12) .. '-' .. sha1:sub(13,16) .. '-' .. sha1:sub(17,20) .. '-' .. sha1:sub(21, 32) + assert(#guid == 36) + return guid:upper() +end + +local function get_headers(unit, source_lut, dag_lut, name_to_dags) + local src_dir = '' + + if not unit.Decl then + -- Ignore ExternalLibrary and similar that have no data. + return + end + + if unit.Decl.SourceDir then + src_dir = unit.Decl.SourceDir .. '/' + end + for _, src in util.nil_ipairs(nodegen.flatten_list('*-*-*-*', unit.Decl.Sources)) do + if type(src) == "string" then + local ext = path.get_extension(src) + if header_exts[ext] then + local full_path = path.normalize(src_dir .. src) + source_lut[full_path] = true + end + end + end + + local function toplevel(u) + if type(u) == "string" then + return type(name_to_dags[u]) ~= "nil" + end + + for _, dag in pairs(u.Decl.__DagNodes) do + if dag_lut[dag] then + return true + end + end + return false + end + + -- Repeat for dependencies ObjGroups + for _, dep in util.nil_ipairs(nodegen.flatten_list('*-*-*-*', unit.Decl.Depends)) do + + if not toplevel(dep) then + get_headers(dep, source_lut, dag_lut) + end + end +end + +local function make_meta_project(base_dir, data) + data.Guid = get_guid_string(data.Name) + data.IdeGenerationHints = { Msvc = { SolutionFolder = "Build System Meta" } } + data.IsMeta = true + data.RelativeFilename = data.Name .. ".vcxproj" + data.Filename = base_dir .. data.RelativeFilename + data.Type = "meta" + if not data.Sources then + data.Sources = {} + end + return data +end + +local function tundra_cmdline(args) + local root_dir = native.getcwd() + return "\"" .. TundraExePath .. "\" -C \"" .. root_dir .. "\" " .. args +end + +local function project_regen_commandline(ide_script) + return tundra_cmdline("-g " .. ide_script) +end + +local function make_project_data(units_raw, env, proj_extension, hints, ide_script) + + -- Filter out stuff we don't care about. + local units = util.filter(units_raw, function (u) + return u.Decl.Name and project_types[u.Keyword] + end) + + local base_dir = hints.MsvcSolutionDir and (hints.MsvcSolutionDir .. '\\') or env:interpolate('$(OBJECTROOT)$(SEP)') + native.mkdir(base_dir) + + local project_by_name = {} + local all_sources = {} + local dag_node_lut = {} -- lookup table of all named, top-level DAG nodes + local name_to_dags = {} -- table mapping unit name to array of dag nodes (for configs) + + -- Map out all top-level DAG nodes + for _, unit in ipairs(units) do + local decl = unit.Decl + + local dag_nodes = assert(decl.__DagNodes, "no dag nodes for " .. decl.Name) + for build_id, dag_node in pairs(dag_nodes) do + dag_node_lut[dag_node] = unit + local array = name_to_dags[decl.Name] + if not array then + array = {} + name_to_dags[decl.Name] = array + end + array[#array + 1] = dag_node + end + end + + local function get_output_project(name) + if not project_by_name[name] then + local relative_fn = name .. proj_extension + project_by_name[name] = { + Name = name, + Sources = {}, + RelativeFilename = relative_fn, + Filename = base_dir .. relative_fn, + Guid = get_guid_string(name), + BuildByDefault = hints.BuildAllByDefault, + } + end + return project_by_name[name] + end + + -- Sort units based on dependency complexity. We want to visit the leaf nodes + -- first so that any source file references are picked up as close to the + -- bottom of the dependency chain as possible. + local unit_weights = {} + for _, unit in ipairs(units) do + local decl = unit.Decl + local stack = { } + for _, dag in pairs(decl.__DagNodes) do + stack[#stack + 1] = dag + end + local weight = 0 + while #stack > 0 do + local node = table.remove(stack) + if dag_node_lut[node] then + weight = weight + 1 + end + for _, dep in util.nil_ipairs(node.deps) do + stack[#stack + 1] = dep + end + end + unit_weights[unit] = weight + end + + table.sort(units, function (a, b) + return unit_weights[a] < unit_weights[b] + end) + + -- Keep track of what source files have already been grabbed by other projects. + local grabbed_sources = {} + + for _, unit in ipairs(units) do + local decl = unit.Decl + local name = decl.Name + + local source_lut = {} + local generated_lut = {} + for build_id, dag_node in pairs(decl.__DagNodes) do + get_sources(dag_node, source_lut, generated_lut, 0, dag_node_lut) + end + + -- Explicitly add all header files too as they are not picked up from the DAG + -- Also pick up headers from non-toplevel DAGs we're depending on + get_headers(unit, source_lut, dag_node_lut, name_to_dags) + + -- Figure out which project should get this data. + local output_name = name + local ide_hints = unit.Decl.IdeGenerationHints + if ide_hints then + if ide_hints.OutputProject then + output_name = ide_hints.OutputProject + end + end + + local proj = get_output_project(output_name) + + if output_name == name then + -- This unit is the real thing for this project, not something that's + -- just being merged into it (like an ObjGroup). Set some more attributes. + proj.IdeGenerationHints = ide_hints + proj.DagNodes = decl.__DagNodes + proj.Unit = unit + end + + for src, _ in pairs(source_lut) do + local norm_src = path.normalize(src) + if not grabbed_sources[norm_src] then + grabbed_sources[norm_src] = unit + local is_generated = generated_lut[src] + proj.Sources[#proj.Sources+1] = { + Path = norm_src, + Generated = is_generated, + } + end + end + end + + -- Get all accessed Lua files + local accessed_lua_files = util.table_keys(get_accessed_files()) + + -- Filter out the ones that belong to this build (exclude ones coming from Tundra) + local function is_non_tundra_lua_file(p) + return not path.is_absolute(p) + end + local function make_src_node(p) + return { Path = path.normalize(p) } + end + local source_list = util.map(util.filter(accessed_lua_files, is_non_tundra_lua_file), make_src_node) + + local solution_hints = hints.MsvcSolutions + if not solution_hints then + print("No IdeGenerationHints.MsvcSolutions specified - using defaults") + solution_hints = { + ['tundra-generated.sln'] = {} + } + end + + local projects = util.table_values(project_by_name) + local vanilla_projects = util.clone_array(projects) + + local solutions = {} + + -- Create meta project to regenerate solutions/projects. Added to every solution. + local regen_meta_proj = make_meta_project(base_dir, { + Name = "00-Regenerate-Projects", + FriendlyName = "Regenerate Solutions and Projects", + BuildCommand = project_regen_commandline(ide_script), + }) + + projects[#projects + 1] = regen_meta_proj + + for name, data in pairs(solution_hints) do + local sln_projects + local ext_projects = {} + if data.Projects then + sln_projects = {} + for _, pname in ipairs(data.Projects) do + local pp = project_by_name[pname] + if not pp then + errorf("can't find project %s for inclusion in %s -- check your MsvcSolutions data", pname, name) + end + sln_projects[#sln_projects + 1] = pp + end + else + -- All the projects (that are not meta) + sln_projects = util.clone_array(vanilla_projects) + end + + for _, ext in util.nil_ipairs(data.ExternalProjects) do + ext_projects[#ext_projects + 1] = ext + end + + local meta_proj = make_meta_project(base_dir, { + Name = "00-tundra-" .. path.drop_suffix(name), + FriendlyName = "Build This Solution", + BuildByDefault = true, + Sources = source_list, + BuildProjects = util.clone_array(sln_projects), + }) + + sln_projects[#sln_projects + 1] = regen_meta_proj + sln_projects[#sln_projects + 1] = meta_proj + projects[#projects + 1] = meta_proj + + solutions[#solutions + 1] = { + Filename = base_dir .. name, + Projects = sln_projects, + ExternalProjects = ext_projects, + BuildSolutionProject = meta_proj, + } + end + + return solutions, projects +end + +local cl_tags = { + ['.h'] = 'ClInclude', + ['.hh'] = 'ClInclude', + ['.hpp'] = 'ClInclude', + ['.inl'] = 'ClInclude', +} + +local function slurp_file(fn) + local fh, err = io.open(fn, 'rb') + if fh then + local data = fh:read("*all") + fh:close() + return data + end + return '' +end + +local function replace_if_changed(new_fn, old_fn) + local old_data = slurp_file(old_fn) + local new_data = slurp_file(new_fn) + if old_data == new_data then + os.remove(new_fn) + return + end + printf("Updating %s", old_fn) + os.remove(old_fn) + os.rename(new_fn, old_fn) +end + +function msvc_generator:generate_solution(fn, projects, ext_projects, solution) + local sln = io.open(fn .. '.tmp', 'wb') + sln:write(UTF_HEADER, LF, "Microsoft Visual Studio Solution File, Format Version ", VERSION_NUMBER, LF, "# Visual Studio ", VERSION_YEAR, LF) + + -- Map folder names to array of projects under that folder + local sln_folders = {} + for _, proj in ipairs(projects) do + local hints = proj.IdeGenerationHints + local msvc_hints = hints and hints.Msvc or nil + local folder = msvc_hints and msvc_hints.SolutionFolder or nil + if folder then + local projects = sln_folders[folder] or {} + projects[#projects + 1] = proj + sln_folders[folder] = projects + end + end + + for _, proj in ipairs(projects) do + local name = proj.Name + local fname = proj.RelativeFilename + local guid = proj.Guid + sln:write(string.format('Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "%s", "%s", "{%s}"', name, fname, guid), LF) + sln:write('EndProject', LF) + end + + -- Dump external projects. Make them depend on everything in this solution being built by Tundra. + for _, data in util.nil_ipairs(ext_projects) do + local guid = data.Guid + local fname = path.normalize(path.join(native.getcwd(), data.Filename)) + local name = path.get_filename_base(fname) + sln:write(string.format('Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "%s", "%s", "{%s}"', name, fname, guid), LF) + local build_sln_proj = solution.BuildSolutionProject + if build_sln_proj then + local meta_guid = build_sln_proj.Guid + sln:write('\tProjectSection(ProjectDependencies) = postProject', LF) + sln:write('\t\t{', meta_guid,'} = {', meta_guid,'}', LF) + sln:write('\tEndProjectSection', LF) + end + sln:write('EndProject', LF) + end + + for folder_name, _ in pairs(sln_folders) do + local folder_guid = get_guid_string("folder/" .. folder_name) + sln:write(string.format('Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "%s", "%s", "{%s}"', folder_name, folder_name, folder_guid), LF) + sln:write('EndProject', LF) + end + + sln:write("Global", LF) + sln:write("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution", LF) + for _, tuple in ipairs(self.config_tuples) do + sln:write(string.format('\t\t%s = %s', tuple.MsvcName, tuple.MsvcName), LF) + end + sln:write("\tEndGlobalSection", LF) + + sln:write("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution", LF) + for _, proj in ipairs(projects) do + for _, tuple in ipairs(self.config_tuples) do + local leader = string.format('\t\t{%s}.%s.', proj.Guid, tuple.MsvcName) + sln:write(leader, "ActiveCfg = ", tuple.MsvcName, LF) + if proj.BuildByDefault then + sln:write(leader, "Build.0 = ", tuple.MsvcName, LF) + end + end + end + + -- External projects build by default, and after Tundra is done (depends on "Build this solution"). + for _, proj in util.nil_ipairs(ext_projects) do + for _, tuple in ipairs(self.config_tuples) do + local leader = string.format('\t\t{%s}.%s.', proj.Guid, tuple.MsvcName) + sln:write(leader, "ActiveCfg = ", tuple.MsvcName, LF) + if not proj.Platform or proj.Platform == tuple.MsvcPlatform then + sln:write(leader, "Build.0 = ", tuple.MsvcName, LF) + end + end + end + sln:write("\tEndGlobalSection", LF) + + sln:write("\tGlobalSection(SolutionProperties) = preSolution", LF) + sln:write("\t\tHideSolutionNode = FALSE", LF) + sln:write("\tEndGlobalSection", LF) + + sln:write("\tGlobalSection(NestedProjects) = preSolution", LF) + for folder_name, projects in pairs(sln_folders) do + local folder_guid = get_guid_string("folder/" .. folder_name) + for _, project in ipairs(projects) do + sln:write(string.format('\t\t{%s} = {%s}', project.Guid, folder_guid), LF) + end + end + sln:write("\tEndGlobalSection", LF) + + sln:write("EndGlobal", LF) + sln:close() + + replace_if_changed(fn .. ".tmp", fn) +end + +local function find_dag_node_for_config(project, tuple) + local build_id = string.format("%s-%s-%s", tuple.Config.Name, tuple.Variant.Name, tuple.SubVariant) + local nodes = project.DagNodes + if not nodes then + return nil + end + + if nodes[build_id] then + return nodes[build_id] + end + errorf("couldn't find config %s for project %s (%d dag nodes) - available: %s", + build_id, project.Name, #nodes, table.concat(util.table_keys(nodes), ", ")) +end + +function msvc_generator:generate_project(project, all_projects) + local fn = project.Filename + local p = assert(io.open(fn .. ".tmp", 'wb')) + p:write('', LF) + p:write('', LF) + + -- List all project configurations + p:write('\t', LF) + for _, tuple in ipairs(self.config_tuples) do + p:write('\t\t', LF) + p:write('\t\t\t', tuple.MsvcConfiguration, '', LF) + p:write('\t\t\t', tuple.MsvcPlatform, '', LF) + p:write('\t\t', LF) + end + p:write('\t', LF) + + p:write('\t', LF) + p:write('\t\t{', project.Guid, '}', LF) + p:write('\t\tMakeFileProj', LF) + if project.FriendlyName then + p:write('\t\t', project.FriendlyName, '', LF) + end + + if HOOKS.global_properties then + HOOKS.global_properties(p, project) + end + + p:write('\t', LF) + p:write('\t', LF) + if VERSION_YEAR == '2012' then + p:write('\t\t<_ProjectFileVersion>10.0.30319.1', LF) + end + p:write('\t', LF) + + p:write('\t', LF) + + -- Mark all project configurations as makefile-type projects + for _, tuple in ipairs(self.config_tuples) do + p:write('\t', LF) + p:write('\t\tMakefile', LF) + p:write('\t\ttrue', LF) -- I have no idea what this setting affects + if VERSION_YEAR == '2012' then + p:write('\t\tv110', LF) -- I have no idea what this setting affects + elseif VERSION_YEAR == '2013' then + p:write('\t\tv120', LF) -- I have no idea what this setting affects + end + p:write('\t', LF) + end + + p:write('\t', LF) + + for _, tuple in ipairs(self.config_tuples) do + p:write('\t', LF) + + local dag_node = find_dag_node_for_config(project, tuple) + local include_paths, defines + if dag_node then + local env = dag_node.src_env + local paths = util.map(env:get_list("CPPPATH"), function (p) + local ip = path.normalize(env:interpolate(p)) + if not path.is_absolute(ip) then + ip = native.getcwd() .. '\\' .. ip + end + return ip + end) + include_paths = table.concat(paths, ';') + local ext_paths = env:get_external_env_var('INCLUDE') + if ext_paths then + include_paths = include_paths .. ';' .. ext_paths + end + defines = env:interpolate("$(CPPDEFS:j;)") + else + include_paths = '' + defines = '' + end + + local root_dir = native.getcwd() + local build_id = string.format("%s-%s-%s", tuple.Config.Name, tuple.Variant.Name, tuple.SubVariant) + local base = "\"" .. TundraExePath .. "\" -C \"" .. root_dir .. "\" " + local build_cmd = base .. build_id + local clean_cmd = base .. "--clean " .. build_id + local rebuild_cmd = base .. "--rebuild " .. build_id + + if project.BuildCommand then + build_cmd = project.BuildCommand + clean_cmd = "" + rebuild_cmd = "" + elseif not project.IsMeta then + build_cmd = build_cmd .. " " .. project.Name + clean_cmd = clean_cmd .. " " .. project.Name + rebuild_cmd = rebuild_cmd .. " " .. project.Name + else + local all_projs_str = table.concat( + util.map(assert(project.BuildProjects), function (p) return p.Name end), ' ') + build_cmd = build_cmd .. " " .. all_projs_str + clean_cmd = clean_cmd .. " " .. all_projs_str + rebuild_cmd = rebuild_cmd .. " " .. all_projs_str + end + + p:write('\t\t', build_cmd, '', LF) + p:write('\t\t', LF) + p:write('\t\t', clean_cmd, '', LF) + p:write('\t\t', rebuild_cmd, '', LF) + p:write('\t\t', defines, ';$(NMakePreprocessorDefinitions)', LF) + p:write('\t\t', include_paths, ';$(NMakeIncludeSearchPath)', LF) + p:write('\t\t$(NMakeForcedIncludes)', LF) + p:write('\t', LF) + end + + if HOOKS.pre_sources then + HOOKS.pre_sources(p, project) + end + + -- Emit list of source files + p:write('\t', LF) + for _, record in ipairs(project.Sources) do + local path_str = assert(record.Path) + if not path.is_absolute(path_str) then + path_str = native.getcwd() .. '\\' .. path_str + end + local ext = path.get_extension(path_str) + local cl_tag = cl_tags[ext] or 'ClCompile' + p:write('\t\t<', cl_tag,' Include="', path_str, '" />', LF) + end + p:write('\t', LF) + + local post_src_hook = HOOKS.post_sources + if post_src_hook then + post_src_hook(p, project) + end + + p:write('\t', LF) + + if VERSION_YEAR == "2012" then + -- Import helper msbuild stuff to make build aborting work propertly in VS2012 + local xml = path.normalize(TundraScriptDir .. '/tundra/ide/msvc-rules.xml') + p:write('\t', LF) + end + + p:write('', LF) + p:close() + + replace_if_changed(fn .. ".tmp", fn) +end + +local function get_common_dir(sources) + local dir_tokens = {} + for _, src in ipairs(sources) do + local path = assert(src.Path) + if not tundra.path.is_absolute(path) then + local subdirs = {} + for subdir in path:gmatch("([^\\\]+)\\") do + subdirs[#subdirs + 1] = subdir + end + + if #dir_tokens == 0 then + dir_tokens = subdirs + else + for i = 1, #dir_tokens do + if dir_tokens[i] ~= subdirs[i] then + while #dir_tokens >= i do + table.remove(dir_tokens) + end + break + end + end + end + end + end + + local result = table.concat(dir_tokens, '\\') + if #result > 0 then + result = result .. '\\' + end + return result +end + +function msvc_generator:generate_project_filters(project) + local fn = project.Filename .. ".filters" + local p = assert(io.open(fn .. ".tmp", 'wb')) + p:write('', LF) + p:write('', LF) + + local common_dir = get_common_dir(util.filter(project.Sources, function (s) return not s.Generated end)) + local common_dir_gen = get_common_dir(util.filter(project.Sources, function (s) return s.Generated end)) + + local filters = {} + local sources = {} + + -- Mangle source filenames, and find which filters need to be created + for _, record in ipairs(project.Sources) do + local fn = record.Path + local common_start = record.Generated and common_dir_gen or common_dir + if fn:find(common_start, 1, true) then + fn = fn:sub(#common_start+1) + end + + local dir, filename = path.split(fn) + + if dir == '.' then + dir = nil + end + + local abs_path = record.Path + if not path.is_absolute(abs_path) then + abs_path = native.getcwd() .. '\\' .. abs_path + end + + if record.Generated then + dir = 'Generated Files' + end + + sources[#sources + 1] = { + FullPath = abs_path, + Directory = dir, + } + + -- Register filter and all its parents + while dir and dir ~= '.' do + filters[dir] = true + dir, _ = path.split(dir) + end + end + + -- Emit list of filters + p:write('\t', LF) + for filter_name, _ in pairs(filters) do + if filter_name ~= "" then + filter_guid = get_guid_string(filter_name) + p:write('\t\t', LF) + p:write('\t\t\t{', filter_guid, '}', LF) + p:write('\t\t', LF) + end + end + p:write('\t', LF) + + -- Emit list of source files + p:write('\t', LF) + for _, source in ipairs(sources) do + local ext = path.get_extension(source.FullPath) + local cl_tag = cl_tags[ext] or 'ClCompile' + if not source.Directory then + p:write('\t\t<', cl_tag, ' Include="', source.FullPath, '" />', LF) + else + p:write('\t\t<', cl_tag, ' Include="', source.FullPath, '">', LF) + p:write('\t\t\t', source.Directory, '', LF) + p:write('\t\t', LF) + end + end + p:write('\t', LF) + + p:write('', LF) + + p:close() + + replace_if_changed(fn .. ".tmp", fn) +end + +function msvc_generator:generate_project_user(project) + local fn = project.Filename .. ".user" + -- Don't overwrite user settings + do + local p, err = io.open(fn, 'rb') + if p then + p:close() + return + end + end + + local p = assert(io.open(fn, 'wb')) + p:write('', LF) + p:write('', LF) + + for _, tuple in ipairs(self.config_tuples) do + local dag_node = find_dag_node_for_config(project, tuple) + if dag_node then + local exe = nil + for _, output in util.nil_ipairs(dag_node.outputs) do + if output:match("%.exe") then + exe = output + break + end + end + if exe then + p:write('\t', LF) + p:write('\t\t', native.getcwd() .. '\\' .. exe, '', LF) + p:write('\t\tWindowsLocalDebugger', LF) + p:write('\t\t', native.getcwd(), '', LF) + p:write('\t', LF) + end + end + end + + p:write('', LF) + + p:close() +end + +function msvc_generator:generate_files(ngen, config_tuples, raw_nodes, env, default_names, hints, ide_script) + assert(config_tuples and #config_tuples > 0) + + if not hints then + hints = {} + end + + local complained_mappings = {} + + self.msvc_platforms = {} + local msvc_hints = hints.Msvc or {} + local variant_mappings = msvc_hints.VariantMappings or {} + local platform_mappings = msvc_hints.PlatformMappings or {} + local full_mappings = msvc_hints.FullMappings or {} + + for _, tuple in ipairs(config_tuples) do + local build_id = string.format("%s-%s-%s", tuple.Config.Name, tuple.Variant.Name, tuple.SubVariant) + if full_mappings[build_id] then + local m = full_mappings[build_id] + tuple.MsvcConfiguration = assert(m.Config) + tuple.MsvcPlatform = assert(m.Platform) + elseif variant_mappings[tuple.Variant.Name] then + tuple.MsvcConfiguration = variant_mappings[tuple.Variant.Name] + elseif variant_mappings[tuple.Variant.Name .. "-" .. tuple.SubVariant] then + tuple.MsvcConfiguration = variant_mappings[tuple.Variant.Name .. "-" .. tuple.SubVariant] + else + tuple.MsvcConfiguration = tuple.Variant.Name + end + + -- Use IdeGenerationHints.Msvc.PlatformMappings table to map tundra + -- configurations to MSVC platform names. Note that this isn't a huge deal + -- for building stuff as Tundra doesn't care about this setting. But it + -- might influence the choice of debugger and affect include paths for + -- things like Intellisense that certain users may care about. + if not tuple.MsvcPlatform then + tuple.MsvcPlatform = platform_mappings[tuple.Config.Name] + end + + -- If we didn't find anything, warn and then default to Win32, which VS + -- will always accept (or so one would assume) + if not tuple.MsvcPlatform then + tuple.MsvcPlatform = "Win32" + if not complained_mappings[tuple.Config.Name] then + printf("warning: No VS platform mapping for %s, mapping to Win32", tuple.Config.Name) + print("(Add one to IdeGenerationHints.Msvc.PlatformMappings to override)") + complained_mappings[tuple.Config.Name] = true + end + end + tuple.MsvcName = tuple.MsvcConfiguration .. "|" .. tuple.MsvcPlatform + self.msvc_platforms[tuple.MsvcPlatform] = true + end + + self.config_tuples = config_tuples + + printf("Generating Visual Studio projects for %d configurations/variants", #config_tuples) + + -- Figure out where we're going to store the projects + local solutions, projects = make_project_data(raw_nodes, env, ".vcxproj", hints, ide_script) + + local proj_lut = {} + for _, p in ipairs(projects) do + proj_lut[p.Name] = p + end + + for _, sln in pairs(solutions) do + self:generate_solution(sln.Filename, sln.Projects, sln.ExternalProjects, sln) + end + + for _, proj in ipairs(projects) do + self:generate_project(proj, projects) + self:generate_project_filters(proj) + self:generate_project_user(proj) + end +end + +function setup(version_short, version_year, hooks) + VERSION_NUMBER = version_short + VERSION_YEAR = version_year + if hooks then + HOOKS = hooks + end + nodegen.set_ide_backend(function(...) + local state = setmetatable({}, msvc_generator) + state:generate_files(...) + end) +end + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc-rules.xml b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc-rules.xml new file mode 100644 index 0000000..b62155d --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc-rules.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + { + if (line.Data != null) + Log.LogMessageFromText(line.Data, MessageImportance.High); + }; + p.ErrorDataReceived += (object sender, DataReceivedEventArgs line) => { + if (line.Data != null) + Log.LogMessageFromText(line.Data, MessageImportance.High); + }; + p.Start(); + p.BeginOutputReadLine(); + p.BeginErrorReadLine(); + + while (!p.WaitForExit(100)) + { + if (m_Cancel) + { + // Keep sending CTRL+C events - sometimes it takes more than one.. + GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, 0); + } + } + + p.WaitForExit(); + return m_Cancel ? false : p.ExitCode == 0; + } + } + catch(Exception e) + { + Console.WriteLine(e); + return false; + } + } + } + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GetNativeTargetPath;%(Targets) + + + + + + + + + + + obj + lib + dll + xdc + + + + + + + $(TargetPath) + + + + + + + + + + + + + + Project + + + + + PropertySheet + + + + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc100.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc100.lua new file mode 100644 index 0000000..f2381b5 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc100.lua @@ -0,0 +1,7 @@ +-- Microsoft Visual Studio 2010 Solution/Project file generation + +module(..., package.seeall) + +local msvc_common = require "tundra.ide.msvc-common" +msvc_common.setup("11.00", "2010") + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc110.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc110.lua new file mode 100644 index 0000000..1410de9 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc110.lua @@ -0,0 +1,7 @@ +-- Microsoft Visual Studio 2012 Solution/Project file generation + +module(..., package.seeall) + +local msvc_common = require "tundra.ide.msvc-common" + +msvc_common.setup("12.00", "2012") diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc120.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc120.lua new file mode 100644 index 0000000..14f55f2 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/msvc120.lua @@ -0,0 +1,7 @@ +-- Microsoft Visual Studio 2013 Solution/Project file generation + +module(..., package.seeall) + +local msvc_common = require "tundra.ide.msvc-common" + +msvc_common.setup("12.00", "2013") diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/xcode3.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/xcode3.lua new file mode 100644 index 0000000..643fe72 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/xcode3.lua @@ -0,0 +1,735 @@ +-- Xcode 3 (works in 4 as well) Workspace/Project file generation + +module(..., package.seeall) + +local path = require "tundra.path" +local nodegen = require "tundra.nodegen" +local util = require "tundra.util" +local native = require "tundra.native" + +local xcode_generator = {} +local xcode_generator = {} +xcode_generator.__index = xcode_generator + +function xcode_generator:generate_workspace(fn, projects) + local sln = io.open(fn, 'wb') + + sln:write('\n') + sln:write('\n') + + for _, proj in ipairs(projects) do + local name = proj.Decl.Name + local fname = proj.RelativeFilename + if fname == '.' then fname = '' + else fname = fname ..'/' + end + sln:write('\t\n') + sln:write('\t\n') + end + + sln:write('\n') +end + +local project_types = util.make_lookup_table { + "Program", "SharedLibrary", "StaticLibrary", +} + +local function get_absolute_output_path(env) + local base_dir = env:interpolate('$(OBJECTROOT)$(SEP)') + local cwd = native.getcwd() + return cwd .. "/" .. base_dir +end + +local function newid(data) + local string = native.digest_guid(data) + -- a bit ugly but is to match the xcode style of UIds + return string.sub(string.gsub(string, '-', ''), 1, 24) +end + +local function getfiletype(name) + local types = { + [".c"] = "sourcecode.c.c", + [".cc"] = "sourcecode.cpp.cpp", + [".cpp"] = "sourcecode.cpp.cpp", + [".css"] = "text.css", + [".cxx"] = "sourcecode.cpp.cpp", + [".framework"] = "wrapper.framework", + [".gif"] = "image.gif", + [".h"] = "sourcecode.c.h", + [".html"] = "text.html", + [".lua"] = "sourcecode.lua", + [".m"] = "sourcecode.c.objc", + [".mm"] = "sourcecode.cpp.objc", + [".nib"] = "wrapper.nib", + [".pch"] = "sourcecode.c.h", + [".plist"] = "text.plist.xml", + [".strings"] = "text.plist.strings", + [".xib"] = "file.xib", + [".icns"] = "image.icns", + [""] = "compiled.mach-o.executable", + } + return types[path.get_extension(name)] or "text" +end + + +local function get_project_data(unit, env) + local decl = unit.Decl + + if decl.Name and project_types[unit.Keyword] then + + local relative_fn = decl.Name + local sources = util.flatten(decl.Sources) or {} + sources = util.filter(sources, function (x) return type(x) == "string" end) + + if decl.SourceDir then + sources = util.map(sources, function (x) return decl.SourceDir .. x end) + end + + local source_list = {} + + -- Rebuild source list with ids that is needed by the xcode project layout + for _, fn in ipairs(sources) do + source_list[newid(fn)] = fn + end + + return { + Type = unit.Keyword, + Decl = decl, + Sources = source_list, + RelativeFilename = relative_fn, + Guid = newid(decl.Name .. "ProjectId"), + } + + elseif unit.Keyword == "OsxBundle" then + + decl.Name = "OsxBundle" + + local source_list = {} + + source_list[newid(decl.InfoPList)] = decl.InfoPList + + for _, resource in ipairs(decl.Resources) do + if resource.Decl then + source_list[newid(resource.Decl.Source)] = resource.Decl.Source + end + end + + return { + Type = unit.Keyword, + Decl = decl, + Sources = source_list, + RelativeFilename = "$(OBJECTDIR)/MyApp.app", + Guid = newid("OsxBundle"), + } + else + return nil + end +end + +local function sort_filelist(source_list) + local dest = {} + for k, v in pairs(source_list) do table.insert(dest, { Key = k, Value = v }) end + table.sort(dest, function(a, b) return a.Value < b.Value end) + return dest +end + +local function write_file_refs(p, projects) + p:write('/* Begin FBXFileReference section */\n') + local cwd = native.getcwd(); + + -- build the source list + + local full_source_list = {} + + for _, project in pairs(projects) do + local sources = project.Sources + for key, fn in pairs(sources) do + full_source_list[key] = fn + end + + -- include executable names in the source list as well + + if project.Type == "Program" then + full_source_list[newid(project.Decl.Name .. "Program")] = project.Decl.Name + end + end + + local source_list = {} + + -- As we can't sort hashtables we need to move this over to a regular table + + source_list = sort_filelist(full_source_list) + + for _, entry in pairs(source_list) do + local key = entry.Key + local fn = entry.Value + local name = path.get_filename(fn) + local file_type = getfiletype(fn) + local str = "" + if file_type == "compiled.mach-o.executable" then + str = string.format('\t\t%s /* %s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = %s; name = "%s"; includeInIndex = 0; path = "%s"; sourceTree = BUILT_PRODUCTS_DIR; };', + key, fn, file_type, name, fn) + else + str = string.format('\t\t%s /* %s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = %s; name = "%s"; path = "%s"; sourceTree = ""; };', + key, fn, file_type, name, path.join(cwd, fn)) + end + p:write(str, '\n') + end + + p:write('/* End FBXFileReference section */\n\n') +end + +local function write_legacy_targets(p, projects, env) + p:write('/* Begin PBXLegacyTarget section */\n') + + local script_path = get_absolute_output_path(env) + + for _, project in pairs(projects) do + local decl = project.Decl + + if project.IsMeta then + --[[ + isa = PBXLegacyTarget; + buildArgumentsString = ""; + buildConfigurationList = D7D12762170E4CF98A79B5EF /* Build configuration list for PBXLegacyTarget "!UpdateWorkspace" */; + buildPhases = ( + ); + buildToolPath = /Users/danielcollin/unity_ps3/ps3/Projects/JamGenerated/_workspace.xcode_/updateworkspace; + dependencies = ( + ); + name = "!UpdateWorkspace"; + passBuildSettingsInEnvironment = 1; + productName = "!UpdateWorkspace"; + --]] + + p:write('\t\t', newid(decl.Name .. "Target"), ' /* ', decl.Name, ' */ = {\n') + p:write('\t\t\tisa = PBXLegacyTarget;\n') + p:write('\t\t\tbuildArgumentsString = "', project.MetaData.BuildArgs, '";\n') + p:write('\t\t\tbuildConfigurationList = ', newid(decl.Name .. 'Config'), ' /* Build configuration list for PBXLegacyTarget "',decl.Name, '" */;\n') + p:write('\t\t\tbuildPhases = (\n') + p:write('\t\t\t);\n'); + p:write('\t\t\tbuildToolPath = ', script_path .. project.MetaData.BuildTool, ';\n') + p:write('\t\t\tdependencies = (\n\t\t\t);\n') + p:write('\t\t\tname = "', decl.Name, '";\n') + p:write('\t\t\tpassBuildSettingsInEnvironment = 1;\n') + p:write('\t\t\tproductName = "', decl.Name or "", '";\n') + p:write('\t\t};\n') + end + end + + p:write('/* End PBXLegacyTarget section */\n') +end + +local function write_native_targes(p, projects) + p:write('/* Begin PBXNativeTarget section */\n') + + local categories = { + ["Program"] = "com.apple.product-type.tool", + ["StaticLibrary"] = "com.apple.product-type.library.static", + ["SharedLibrary"] = "com.apple.product-type.library.dynamic", + } + + for _, project in pairs(projects) do + local decl = project.Decl + + if not project.IsMeta then + p:write('\t\t', newid(decl.Name .. "Target"), ' /* ', decl.Name, ' */ = {\n') + p:write('\t\t\tisa = PBXNativeTarget;\n') + p:write('\t\t\tbuildConfigurationList = ', newid(decl.Name .. 'Config'), ' /* Build configuration list for PBXNativeTarget "',decl.Name, '" */;\n') + p:write('\t\t\tbuildPhases = (\n') + p:write('\t\t\t\t', newid(decl.Name .. "ShellScript"), ' /* ShellScript */,\n') + p:write('\t\t\t);\n'); + p:write('\t\t\tbuildRules = (\n\t\t\t);\n') + p:write('\t\t\tdependencies = (\n\t\t\t);\n') + p:write('\t\t\tname = "', decl.Name, '";\n') + p:write('\t\t\tProductName = "', decl.Name, '";\n') + p:write('\t\t\tproductReference = ', newid(decl.Name .. "Program"), ' /* ', decl.Name, ' */;\n ') + p:write('\t\t\tproductType = "', categories[project.Type] or "", '";\n') + p:write('\t\t};\n') + end + end + + p:write('/* End PBXNativeTarget section */\n') +end + + +local function write_header(p) + p:write('// !$*UTF8*$!\n') + p:write('{\n') + p:write('\tarchiveVersion = 1;\n') + p:write('\tclasses = {\n') + p:write('\t};\n') + p:write('\tobjectVersion = 45;\n') + p:write('\tobjects = {\n') + p:write('\n') +end + +local function get_projects(raw_nodes, env) + local projects = {} + + local source_list = {} + source_list[newid("tundra.lua")] = "tundra.lua" + local units = io.open("units.lua") + if units then + source_list[newid("units.lua")] = "units.lua" + io.close(units) + end + + local meta_name = "!BuildWorkspace" + + projects[#projects + 1] = { + Decl = { Name = meta_name, }, + Type = "LegacyTarget", + RelativeFilename = "", + Sources = source_list, + Guid = newid(meta_name .. 'ProjectId'), + IsMeta = true, + MetaData = { BuildArgs = "'' $(CONFIG) $(VARIANT) $(SUBVARIANT) $(ACTION)", + BuildTool = "xcodetundra" }, + } + + local meta_name = "!UpdateWorkspace" + + projects[#projects + 1] = { + Decl = { Name = "!UpdateWorkspace", }, + Type = "LegacyTarget", + RelativeFilename = "", + Sources = source_list, + Guid = newid(meta_name .. 'ProjectId'), + IsMeta = true, + MetaData = { BuildArgs = "", + BuildTool = "xcodeupdateproj" }, + } + + for _, unit in ipairs(raw_nodes) do + local data = get_project_data(unit, env) + if data then projects[#projects + 1] = data; end + end + + return projects +end + +local function split(fn) + local dir, file = fn:match("^(.*)[/\\]([^\\/]*)$") + if not dir then + return ".", fn + else + return dir, file + end +end + +local function split_str(str, pat, name) + local t = {} -- NOTE: use {n = 0} in Lua-5.0 + local fpat = "(.-)" .. pat + local last_end = 1 + local s, e, cap = str:find(fpat, 1) + table.insert(t,name) + while s do + if s ~= 1 or cap ~= "" then + table.insert(t,cap) + end + last_end = e+1 + s, e, cap = str:find(fpat, last_end) + end + if last_end <= #str then + cap = str:sub(last_end) + table.insert(t, cap) + end + return t +end + +local function build_name_id(entry, offset, end_count) + local entryname = "" + for p = offset, end_count, 1 do + if entry[p] ~= nil then + entryname = entryname .. entry[p] + end + end + return newid(entryname) +end + + +local function make_indent(level) + local indent = '\t'; + for i=1, level, 1 do + indent = indent .. '\t' + end + return indent +end + +local function make_full_path( grp ) + + local full_path_string = grp.Name + local gparent = grp.Parent + while gparent ~= nil do + full_path_string = gparent.Name ..'/'..full_path_string + gparent = gparent.Parent + end + return full_path_string .. ' : ' .. grp.Key +end + +local function write_group_ref(p, g, full_path) + + p:write('\t\t', g.Key, ' /* ', full_path .. '/' .. g.Name, ' */ = {\n') + p:write('\t\t\tisa = PBXGroup;\n') + p:write('\t\t\tchildren = (\n') + + local dirs = {} + local files = {} + + for _, ref in pairs(g.Children) do + if ref.IsDir then + local key = ref.Key + dirs[#dirs + 1] = { Key = key, Name = ref.Name } + else + local key = ref.Key + files[#files + 1] = { Key = key, Name = ref.Name } + end + end + + table.sort(dirs, function(a, b) return a.Name < b.Name end) + table.sort(files, function(a, b) return a.Name < b.Name end) + + for _, ref in pairs(dirs) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', ref.Key, full_path .. '/' .. ref.Name)) + end + + for _, ref in pairs(files) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', ref.Key, full_path .. '/' .. ref.Name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t\tname = "', g.Name, '"; \n'); + p:write('\t\t\tsourceTree = "";\n'); + p:write('\t\t};\n') +end + +local function print_children_2(p, children, path, level) + if children == nil then + return path + end + + local c + local local_path = ''--path + for _, c in pairs(children) do + local indent = make_indent(level) + local_path = print_children_2( p, c.Children, path .. '/' .. c.Name, level + 1 ) + if #c.Children ~= 0 then + write_group_ref(p, c, path) + end + + end + + return path +end + +local function find_group(groups, group, parent) + if groups == nil then return nil end + for _, g in pairs(groups) do + if g.Name == group and g.Parent == parent then + return g + end + local r = find_group( g.Children, group, parent ) + if r ~= nil then return r end + end + return nil +end + +local function write_sources(p, children, name, parent) + + local filelist = sort_filelist(children) + local groups = {}; + + table.insert(groups, {Name = name, Parent = nil, Key = parent, Children = {} }) + + for _, entry in pairs(filelist) do + local parent_group = nil + local path, filename = split(entry.Value) + local split_path = split_str(path, "/", name) + for i=1 , #split_path, 1 do + if split_path[i] ~= '.' then + local grp = find_group(groups, split_path[i], parent_group) + if grp == nil then + grp = { IsDir = true, Name=split_path[i], Parent=parent_group, Key=newid(util.tostring(parent_group)..split_path[i]), Children={} } + if parent_group == nil then + table.insert(groups, grp) + else + parent_group = grp.Parent + table.insert(parent_group.Children, grp) + end + end + parent_group = grp + end + end + if parent_group ~= nil then + table.insert(parent_group.Children, { IsDir = false, Name=filename, Parent=parent_group, Key = entry.Key, Children = {}} ) + end + end + + print_children_2(p, groups, '.', 0); +end + + +local function write_groups(p, projects) + p:write('/* Begin PBXGroup section */\n') + + local all_targets_name = "AllTargets.workspace" + local all_targets_id = newid(all_targets_name) + + for _, project in pairs(projects) do + write_sources(p, project.Sources, project.Decl.Name, project.Guid) + end + + -- write last group that links the projects names above + + p:write('\t\t', all_targets_id, ' /* ', all_targets_name, ' */ = {\n') + p:write('\t\t\tisa = PBXGroup;\n') + p:write('\t\t\tchildren = (\n') + + for _, project in pairs(projects) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', project.Guid, project.Decl.Name)) + end + p:write('\t\t\t);\n') + p:write('\t\t\tname = "', all_targets_name, '"; \n'); + p:write('\t\t\tsourceTree = "";\n'); + p:write('\t\t};\n') + + p:write('/* End PBXGroup section */\n\n') +end + +local function write_project(p, projects) + + local all_targets_name = "AllTargets.workspace" + local all_targets_id = newid(all_targets_name) + + local project_id = newid("ProjectObject") + local project_config_list_id = newid("ProjectObjectConfigList") + + p:write('/* Begin PBXProject section */\n') + p:write('\t\t', project_id, ' /* Project object */ = {\n') + p:write('\t\t\tisa = PBXProject;\n') + p:write('\t\t\tbuildConfigurationList = ', project_config_list_id, ' /* Build configuration list for PBXProject "', "Project Object", '" */;\n') + p:write('\t\t\tcompatibilityVersion = "Xcode 3.1";\n') + p:write('\t\t\thasScannedForEncodings = 1;\n') + p:write('\t\t\tmainGroup = ', all_targets_id, ' /* ', all_targets_name, ' */;\n') + p:write('\t\t\tprojectDirPath = "";\n') + p:write('\t\t\tprojectRoot = "";\n') + p:write('\t\t\ttargets = (\n') + + for _, project in pairs(projects) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', newid(project.Decl.Name .. "Target"), project.Decl.Name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t};\n') + p:write('/* End PBXProject section */\n') +end + +local function write_shellscripts(p, projects, env) + p:write('/* Begin PBXShellScriptBuildPhase section */\n') + + -- TODO: Do we really need to repead this for all projects? seems a bit wasteful + + local xcodetundra_filename = get_absolute_output_path(env) .. "xcodetundra" + + for _, project in pairs(projects) do + local name = project.Decl.Name + if not project.IsMeta then + p:write('\t\t', newid(name .. "ShellScript"), ' /* ShellScript */ = {\n') + p:write('\t\t\tisa = PBXShellScriptBuildPhase;\n') + p:write('\t\t\tbuildActionMask = 2147483647;\n') + p:write('\t\t\tfiles = (\n') + p:write('\t\t\t);\n') + p:write('\t\t\tinputPaths = (\n') + p:write('\t\t\t);\n') + p:write('\t\t\toutputPaths = (\n') + p:write('\t\t\t);\n') + p:write('\t\t\trunOnlyForDeploymentPostprocessing = 0;\n') + p:write('\t\t\tshellPath = /bin/sh;\n') + p:write('\t\t\tshellScript = "', xcodetundra_filename, ' $TARGET_NAME $CONFIG $VARIANT $SUBVARIANT $ACTION -v";\n') + p:write('\t\t};\n') + end + end + + p:write('/* Begin PBXShellScriptBuildPhase section */\n') +end + +local function get_full_config_name(config) + return config.Config.Name .. '-' .. config.Variant.Name .. '-' .. config.SubVariant +end + +local function write_configs(p, projects, config_tuples, env) + + p:write('/* Begin XCConfigurationList section */\n') + + -- I wonder if we really need to do it this way for all configs? + + for __, project in ipairs(projects) do + for _, tuple in ipairs(config_tuples) do + local full_config_name = get_full_config_name(tuple) + + local is_macosx_native = false + + for _, host in util.nil_ipairs(tuple.Config.SupportedHosts) do + if host == "macosx" then + is_macosx_native = true + end + end + + if "macosx" == tuple.Config.DefaultOnHost then + is_macosx_native = true + end + + local config_id = newid(project.Decl.Name .. full_config_name) + + p:write('\t\t', config_id, ' = {\n') + p:write('\t\t\tisa = XCBuildConfiguration;\n') + + -- Don't add any think extra if subvariant is default + + p:write('\t\t\tbuildSettings = {\n') + + if is_macosx_native then + p:write('\t\t\t\tARCHS = "$(NATIVE_ARCH_ACTUAL)";\n') + end + + p:write('\t\t\t\tVARIANT = "', tuple.Variant.Name, '";\n') + p:write('\t\t\t\tCONFIG = "', tuple.Config.Name, '";\n') + p:write('\t\t\t\tSUBVARIANT = "', tuple.SubVariant, '";\n') + + if is_macosx_native and not project.IsMeta then + p:write('\t\t\t\tCONFIGURATION_BUILD_DIR = "', full_config_name, '";\n') + end + + -- this is a little hack to get xcode to clean the whole output folder when using "FullBuild" + p:write('\t\t\t\tPRODUCT_NAME = "',project.Decl.Name , '";\n') + p:write('\t\t\t\tTARGET_NAME = "',project.Decl.Name , '";\n') + + p:write('\t\t\t};\n') + p:write('\t\t\tname = "',full_config_name , '";\n') + p:write('\t\t};\n') + end + end + + p:write('/* End XCConfigurationList section */\n') + +end + +local function write_config_list(p, projects, config_tuples) + + p:write('/* Begin XCConfigurationList section */\n') + + local default_config = ""; + + -- find the default config + + for _, tuple in ipairs(config_tuples) do + local is_macosx_native = tuple.Config.Name:match('^(%macosx)%-') + + if is_macosx_native and tuple.Variant.Name == "debug" then + default_config = get_full_config_name(tuple) + break + end + end + + -- if we did't find a default config just grab the first one + + if default_config == "" then + default_config = get_full_config_name(config_tuples[0]) + end + + for __, project in ipairs(projects) do + + local config_id = newid(project.Decl.Name .. 'Config') + + p:write('\t\t', config_id, ' /* Build config list for "', project.Decl.Name, '" */ = {\n') + p:write('\t\t\tisa = XCConfigurationList;\n') + + -- Don't add any think extra if subvariant is default + + p:write('\t\t\tbuildConfigurations = (\n') + + for _, tuple in ipairs(config_tuples) do + local full_config_name = get_full_config_name(tuple) + p:write(string.format('\t\t\t\t%s /* %s */,\n', newid(project.Decl.Name .. full_config_name), full_config_name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t\tdefaultConfigurationIsVisible = 1;\n') + p:write('\t\t\tdefaultConfigurationName = "', default_config, '";\n') + + p:write('\t\t};\n') + end + + p:write('/* End XCConfigurationList section */\n') + +end + +local function write_footer(p) + p:write('\t};\n') + p:write('\trootObject = ', newid("ProjectObject"), ' /* Project object */;\n') + p:write('}\n') +end + +local function generate_shellscript(env) + local filename = path.join(get_absolute_output_path(env), "xcodetundra") + local p = assert(io.open(filename, 'wb')) + p:write("#/bin/sh\n") + p:write("TARGET_NAME=$1\n") + p:write("CONFIG=$2\n") + p:write("VARIANT=$3\n") + p:write("SUBVARIANT=$4\n") + p:write("ACTION=$5\n") + p:write('if [ "$5" = "clean" ]; then\n') + p:write(' ACTION="-c"\n') + p:write("fi\n\n") + p:write('if [ "$5" = "build" ]; then\n') + p:write(' ACTION=""\n') + p:write("fi\n\n") + p:write(TundraExePath .. " --full-paths $TARGET_NAME $CONFIG-$VARIANT-$SUBVARIANT $ACTION -v\n") + p:close() + os.execute("chmod +x " .. filename) + local filename = path.join(get_absolute_output_path(env), "xcodeupdateproj") + local p = io.open(filename, 'wb') + p:write("#/bin/sh\n") + p:write(TundraExePath .. " --ide-gen xcode3 -a\n") + p:close() + os.execute("chmod +x " .. filename) +end + +function xcode_generator:generate_files(ngen, config_tuples, raw_nodes, env, default_names) + assert(config_tuples and #config_tuples > 0) + + -- TODO: Set the first default config as default + + local base_dir = env:interpolate('$(OBJECTROOT)$(SEP)') + local xcodeproj_dir = base_dir .. "tundra-generated.xcodeproj/" + + native.mkdir(base_dir) + native.mkdir(xcodeproj_dir) + + generate_shellscript(env) + + local p = io.open(path.join(xcodeproj_dir, "project.pbxproj"), 'wb') + + local projects = get_projects(raw_nodes, env) + + write_header(p) + write_file_refs(p, projects) + write_groups(p, projects) + write_legacy_targets(p, projects, env) + write_native_targes(p, projects) + write_project(p, projects) + write_shellscripts(p, projects, env) + write_configs(p, projects, config_tuples, env) + write_config_list(p, projects, config_tuples) + write_footer(p) +end + +nodegen.set_ide_backend(function(...) + local state = setmetatable({}, xcode_generator) + state:generate_files(...) +end) + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/xcode5.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/xcode5.lua new file mode 100644 index 0000000..6fb5353 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/ide/xcode5.lua @@ -0,0 +1,924 @@ +-- Xcode 5 Workspace/Project file generation + +module(..., package.seeall) + +local path = require "tundra.path" +local nodegen = require "tundra.nodegen" +local util = require "tundra.util" +local native = require "tundra.native" + +local xcode_generator = {} +xcode_generator.__index = xcode_generator + +function xcode_generator:generate_workspace(fn, projects) + local sln = io.open(fn, 'wb') + + sln:write('\n') + sln:write('\n') + + for _, proj in ipairs(projects) do + local name = proj.Decl.Name + local fname = proj.RelativeFilename + if fname == '.' then fname = '' + else fname = fname ..'/' + end + sln:write('\t\n') + sln:write('\t\n') + end + + sln:write('\n') +end + +local project_types = util.make_lookup_table { + "Program", "SharedLibrary", "StaticLibrary", +} +local toplevel_stuff = util.make_lookup_table { + ".exe", ".lib", ".dll", +} + +local binary_extension = util.make_lookup_table { + "", ".obj", ".o", ".a", +} + +local header_exts = util.make_lookup_table { + ".h", ".hpp", ".hh", ".inl", +} + +local function newid(data) + local string = native.digest_guid(data) + -- a bit ugly but is to match the xcode style of UIds + return string.sub(string.gsub(string, '-', ''), 1, 24) +end + +local file_types = { + [".c"] = "sourcecode.c.c", + [".cc"] = "sourcecode.cpp.cpp", + [".cpp"] = "sourcecode.cpp.cpp", + [".css"] = "text.css", + [".cxx"] = "sourcecode.cpp.cpp", + [".framework"] = "wrapper.framework", + [".gif"] = "image.gif", + [".h"] = "sourcecode.c.h", + [".html"] = "text.html", + [".lua"] = "sourcecode.lua", + [".m"] = "sourcecode.c.objc", + [".mm"] = "sourcecode.cpp.objc", + [".nib"] = "wrapper.nib", + [".pch"] = "sourcecode.c.h", + [".plist"] = "text.plist.xml", + [".strings"] = "text.plist.strings", + [".xib"] = "file.xib", + [".icns"] = "image.icns", + [""] = "compiled.mach-o.executable", +} + +local function getfiletype(name) + return file_types[path.get_extension(name)] or "text" +end + +-- Scan for sources, following dependencies until those dependencies seem to be a different top-level unit +local function get_sources(dag, sources, generated, dag_lut) + for _, output in ipairs(dag.outputs) do + local ext = path.get_extension(output) + if not binary_extension[ext] then + generated[output] = true + sources[output] = true -- pick up generated headers + end + end + + for _, input in ipairs(dag.inputs) do + local ext = path.get_extension(input) + if not binary_extension[ext] then + sources[input] = true + end + end + + for _, dep in util.nil_ipairs(dag.deps) do + if not dag_lut[dep] then -- don't go into other top-level DAGs + get_sources(dep, sources, generated, dag_lut) + end + end +end + +local function get_headers(unit, sources, dag_lut, name_to_dags) + local src_dir = '' + + if not unit.Decl then + -- Ignore ExternalLibrary and similar that have no data. + return + end + + if unit.Decl.SourceDir then + src_dir = unit.Decl.SourceDir .. '/' + end + for _, src in util.nil_ipairs(nodegen.flatten_list('*-*-*-*', unit.Decl.Sources)) do + if type(src) == "string" then + local ext = path.get_extension(src) + if header_exts[ext] then + local full_path = path.normalize(src_dir .. src) + sources[full_path] = true + end + end + end + + local function toplevel(u) + if type(u) == "string" then + return type(name_to_dags[u]) ~= "nil" + end + + for _, dag in pairs(u.Decl.__DagNodes) do + if dag_lut[dag] then + return true + end + end + return false + end + + -- Repeat for dependencies ObjGroups + for _, dep in util.nil_ipairs(nodegen.flatten_list('*-*-*-*', unit.Decl.Depends)) do + + if not toplevel(dep) then + get_headers(dep, sources, dag_lut) + end + end +end + +local function sort_filelist(source_list) + local dest = {} + for k, v in pairs(source_list) do table.insert(dest, { Key = k, Value = v }) end + table.sort(dest, function(a, b) return a.Value < b.Value end) + return dest +end + +local function write_file_refs(p, projects) + p:write('/* Begin FBXFileReference section */\n') + local cwd = native.getcwd(); + + -- build the source list + + local full_source_list = {} + + for _, project in ipairs(projects) do + local sources = project.Sources + for key, fn in pairs(sources) do + full_source_list[key] = fn + end + + -- include executable names in the source list as well + + if project.Type == "Program" then + full_source_list[newid(project.Decl.Name .. "Program")] = project.Decl.Name + end + end + + local source_list = {} + + -- As we can't sort hashtables we need to move this over to a regular table + + source_list = sort_filelist(full_source_list) + + for _, entry in pairs(source_list) do + local key = entry.Key + local fn = entry.Value + local name = path.get_filename(fn) + local file_type = getfiletype(fn) + local str = "" + if file_type == "compiled.mach-o.executable" then + str = string.format('\t\t%s /* %s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = %s; name = "%s"; includeInIndex = 0; path = "%s"; sourceTree = BUILT_PRODUCTS_DIR; };', + key, fn, file_type, name, fn) + else + str = string.format('\t\t%s /* %s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = %s; name = "%s"; path = "%s"; sourceTree = ""; };', + key, fn, file_type, name, path.join(cwd, fn)) + end + p:write(str, '\n') + end + + p:write('/* End FBXFileReference section */\n\n') +end + +local function write_legacy_targets(p, projects, env) + p:write('/* Begin PBXLegacyTarget section */\n') + + for _, project in ipairs(projects) do + local decl = project.Decl + + if project.IsMeta then + --[[ + isa = PBXLegacyTarget; + buildArgumentsString = ""; + buildConfigurationList = D7D12762170E4CF98A79B5EF /* Build configuration list for PBXLegacyTarget "!UpdateWorkspace" */; + buildPhases = ( + ); + buildToolPath = /Users/danielcollin/unity_ps3/ps3/Projects/JamGenerated/_workspace.xcode_/updateworkspace; + dependencies = ( + ); + name = "!UpdateWorkspace"; + passBuildSettingsInEnvironment = 1; + productName = "!UpdateWorkspace"; + --]] + + p:write('\t\t', newid(decl.Name .. "Target"), ' /* ', decl.Name, ' */ = {\n') + p:write('\t\t\tisa = PBXLegacyTarget;\n') + p:write('\t\t\tbuildArgumentsString = "', project.MetaData.BuildArgs, '";\n') + p:write('\t\t\tbuildConfigurationList = ', newid(decl.Name .. 'Config'), ' /* Build configuration list for PBXLegacyTarget "',decl.Name, '" */;\n') + p:write('\t\t\tbuildPhases = (\n') + p:write('\t\t\t);\n'); + p:write('\t\t\tbuildToolPath = ', project.MetaData.BuildTool, ';\n') + p:write('\t\t\tbuildWorkingDirectory = ', '..', ';\n') + + p:write('\t\t\tdependencies = (\n\t\t\t);\n') + p:write('\t\t\tname = "', decl.Name, '";\n') + p:write('\t\t\tpassBuildSettingsInEnvironment = 1;\n') + p:write('\t\t\tproductName = "', decl.Name or "", '";\n') + p:write('\t\t};\n') + end + end + + p:write('/* End PBXLegacyTarget section */\n') +end + +local function write_native_targes(p, projects) + p:write('/* Begin PBXNativeTarget section */\n') + + local categories = { + ["Program"] = "com.apple.product-type.tool", + ["StaticLibrary"] = "com.apple.product-type.library.static", + ["SharedLibrary"] = "com.apple.product-type.library.dynamic", + } + + for _, project in ipairs(projects) do + local decl = project.Decl + + if not project.IsMeta then + p:write('\t\t', newid(decl.Name .. "Target"), ' /* ', decl.Name, ' */ = {\n') + p:write('\t\t\tisa = PBXNativeTarget;\n') + p:write('\t\t\tbuildConfigurationList = ', newid(decl.Name .. 'Config'), ' /* Build configuration list for PBXNativeTarget "',decl.Name, '" */;\n') + p:write('\t\t\tbuildPhases = (\n') + p:write('\t\t\t\t', newid(decl.Name .. "ShellScript"), ' /* ShellScript */,\n') + p:write('\t\t\t);\n'); + p:write('\t\t\tbuildRules = (\n\t\t\t);\n') + p:write('\t\t\tdependencies = (\n\t\t\t);\n') + p:write('\t\t\tname = "', decl.Name, '";\n') + p:write('\t\t\tProductName = "', decl.Name, '";\n') + p:write('\t\t\tproductReference = ', newid(decl.Name .. "Program"), ' /* ', decl.Name, ' */;\n ') + p:write('\t\t\tproductType = "', categories[project.Type] or "", '";\n') + p:write('\t\t};\n') + end + end + + p:write('/* End PBXNativeTarget section */\n') +end + + +local function write_header(p) + p:write('// !$*UTF8*$!\n') + p:write('{\n') + p:write('\tarchiveVersion = 1;\n') + p:write('\tclasses = {\n') + p:write('\t};\n') + p:write('\tobjectVersion = 45;\n') + p:write('\tobjects = {\n') + p:write('\n') +end + +local function get_projects(raw_nodes, env, hints, ide_script) + local projects = {} + + -- Filter out stuff we don't care about. + local units = util.filter(raw_nodes, function (u) + return u.Decl.Name and project_types[u.Keyword] + end) + + local dag_node_lut = {} -- lookup table of all named, top-level DAG nodes + local name_to_dags = {} -- table mapping unit name to array of dag nodes (for configs) + + -- Map out all top-level DAG nodes + for _, unit in ipairs(units) do + local decl = unit.Decl + + local dag_nodes = assert(decl.__DagNodes, "no dag nodes for " .. decl.Name) + for build_id, dag_node in pairs(dag_nodes) do + dag_node_lut[dag_node] = unit + local array = name_to_dags[decl.Name] + if not array then + array = {} + name_to_dags[decl.Name] = array + end + array[#array + 1] = dag_node + end + end + + -- Sort units based on dependency complexity. We want to visit the leaf nodes + -- first so that any source file references are picked up as close to the + -- bottom of the dependency chain as possible. + local unit_weights = {} + for _, unit in ipairs(units) do + local decl = unit.Decl + local stack = { } + for _, dag in pairs(decl.__DagNodes) do + stack[#stack + 1] = dag + end + local weight = 0 + while #stack > 0 do + local node = table.remove(stack) + if dag_node_lut[node] then + weight = weight + 1 + end + for _, dep in util.nil_ipairs(node.deps) do + stack[#stack + 1] = dep + end + end + unit_weights[unit] = weight + end + + table.sort(units, function (a, b) + return unit_weights[a] < unit_weights[b] + end) + + -- Keep track of what source files have already been grabbed by other projects. + local grabbed_sources = {} + + for _, unit in ipairs(units) do + local decl = unit.Decl + local name = decl.Name + + local sources = {} + local generated = {} + for build_id, dag_node in pairs(decl.__DagNodes) do + get_sources(dag_node, sources, generated, dag_node_lut) + end + + -- Explicitly add all header files too as they are not picked up from the DAG + -- Also pick up headers from non-toplevel DAGs we're depending on + get_headers(unit, sources, dag_node_lut, name_to_dags) + + -- Figure out which project should get this data. + local output_name = name + local ide_hints = unit.Decl.IdeGenerationHints + if ide_hints then + if ide_hints.OutputProject then + output_name = ide_hints.OutputProject + end + end + + -- Rebuild source list with ids that are needed by the xcode project layout + local source_list = {} + for src, _ in pairs(sources) do + local norm_src = path.normalize(src) + -- if not grabbed_sources[norm_src] then + grabbed_sources[norm_src] = unit + source_list[newid(norm_src)] = norm_src + -- end + end + + projects[name] = { + Type = unit.Keyword, + Decl = decl, + Sources = source_list, + RelativeFilename = name, + Guid = newid(name .. "ProjectId"), + IdeGenerationHints = unit.Decl.IdeGenerationHints + } + end + + for _, unit in ipairs(raw_nodes) do + if unit.Keyword == "OsxBundle" then + local decl = unit.Decl + decl.Name = "OsxBundle" + + local source_list = {[newid(decl.InfoPList)] = decl.InfoPList} + for _, resource in util.nil_ipairs(decl.Resources) do + if resource.Decl then + source_list[newid(resource.Decl.Source)] = resource.Decl.Source + end + end + + projects["OsxBundle"] = { + Type = unit.Keyword, + Decl = decl, + Sources = source_list, + RelativeFilename = "$(OBJECTDIR)/MyApp.app", + Guid = newid("OsxBundle"), + } + end + end + + return projects +end + +local function split(fn) + local dir, file = fn:match("^(.*)[/\\]([^\\/]*)$") + if not dir then + return ".", fn + else + return dir, file + end +end + +local function split_str(str, pat) + local t = {} -- NOTE: use {n = 0} in Lua-5.0 + local fpat = "(.-)" .. pat + local last_end = 1 + local s, e, cap = str:find(fpat, 1) + while s do + if s ~= 1 or cap ~= "" then + table.insert(t,cap) + end + last_end = e+1 + s, e, cap = str:find(fpat, last_end) + end + if last_end <= #str then + cap = str:sub(last_end) + table.insert(t, cap) + end + return t +end + +local function print_children_2(p, groupname, key, children, path) + + for name, c in pairs(children) do + if c.Type > 0 then + print_children_2(p, name, c.Key, c.Children, c.Type == 1 and path..'/'..name or path) + end + end + + p:write('\t\t', key, ' /* ', path, ' */ = {\n') + p:write('\t\t\tisa = PBXGroup;\n') + p:write('\t\t\tchildren = (\n') + + local dirs = {} + local files = {} + + for name, ref in pairs(children) do + if ref.Type > 0 then + dirs[#dirs + 1] = { Key = ref.Key, Name = name } + else + files[#files + 1] = { Key = ref.Key, Name = name } + end + end + + table.sort(dirs, function(a, b) return a.Name < b.Name end) + table.sort(files, function(a, b) return a.Name < b.Name end) + + for i, ref in pairs(dirs) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', ref.Key, path .. '/' .. ref.Name)) + end + + for i, ref in pairs(files) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', ref.Key, path .. '/' .. ref.Name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t\tname = "', groupname, '"; \n'); + p:write('\t\t\tsourceTree = "";\n'); + p:write('\t\t};\n') +end + +local function prune_groups(group) + local i = 0 + local first_name + local first_child + + for name, child in pairs(group.Children) do + first_name = name + first_child = child + i = i + 1 + end + + if i == 1 and first_child.Type > 0 then + local new_name = prune_groups(first_child) + group.Children = first_child.Children; + if not new_name then + new_name = first_name + end + return new_name + + else + local children = {} + for name, child in pairs(group.Children) do + if child.Type > 0 then + local new_name = prune_groups(child) + if new_name then + name = new_name + end + end + children[name] = child + end + group.children = children + return nil + end + +end + + +local function make_groups(p, files, key) + local filelist = sort_filelist(files) + local group = { Type = 2, Key = key, Children = {} } + + for _, entry in pairs(filelist) do + local parent_group = group + local path, filename = split(entry.Value) + for i, part in ipairs(split_str(path, "/")) do + if part ~= '.' then + local grp = parent_group.Children[part] + if grp == nil then + grp = { Type = 1, Key=newid(util.tostring(parent_group)..part), Children={} } + parent_group.Children[part] = grp + end + parent_group = grp + end + end + parent_group.Children[filename] = { Type = 0, Key = entry.Key } + end + + -- prune single-entry groups + prune_groups(group) + + return group +end + + +local function write_groups(p, projects) + p:write('/* Begin PBXGroup section */\n') + + -- Map folder names to array of projects under that folder + local folders = {} + for _, project in ipairs(projects) do + local hints = project.IdeGenerationHints + local msvc_hints = hints and hints.Msvc + local fname = msvc_hints and msvc_hints.SolutionFolder + if fname == nil then + fname = "" + end + local folder = folders[fname] + if folder == nil then + folder = { Type = 2, Key = newid("Folder"..fname), Children = {} } + folders[fname] = folder + end + folder.Children[project.Decl.Name] = make_groups(p, project.Sources, project.Guid) + end + + local root = folders[""]; + for name, folder in pairs(folders) do + if folder ~= root then + root.Children[name] = folder + end + end + + local all_targets_name = "AllTargets.workspace" + local all_targets_id = newid(all_targets_name) + print_children_2(p, all_targets_name, all_targets_id, root.Children, '.'); + + -- write last group that links the projects names above + + -- local all_targets_name = "AllTargets.workspace" + -- local all_targets_id = newid(all_targets_name) + -- p:write('\t\t', all_targets_id, ' /* ', all_targets_name, ' */ = {\n') + -- p:write('\t\t\tisa = PBXGroup;\n') + -- p:write('\t\t\tchildren = (\n') + + -- for _, project in pairs(projects) do + -- p:write(string.format('\t\t\t\t%s /* %s */,\n', project.Guid, project.Decl.Name)) + -- end + -- p:write('\t\t\t);\n') + -- p:write('\t\t\tname = "', all_targets_name, '"; \n'); + -- p:write('\t\t\tsourceTree = "";\n'); + -- p:write('\t\t};\n') + + p:write('/* End PBXGroup section */\n\n') +end + +local function write_project(p, projects) + + local all_targets_name = "AllTargets.workspace" + local all_targets_id = newid(all_targets_name) + + local project_id = newid("ProjectObject") + local project_config_list_id = newid("ProjectObjectConfigList") + + p:write('/* Begin PBXProject section */\n') + p:write('\t\t', project_id, ' /* Project object */ = {\n') + p:write('\t\t\tisa = PBXProject;\n') + p:write('\t\t\tbuildConfigurationList = ', project_config_list_id, ' /* Build configuration list for PBXProject "', "Project Object", '" */;\n') + p:write('\t\t\tcompatibilityVersion = "Xcode 3.1";\n') + p:write('\t\t\thasScannedForEncodings = 1;\n') + p:write('\t\t\tmainGroup = ', all_targets_id, ' /* ', all_targets_name, ' */;\n') + p:write('\t\t\tprojectDirPath = "";\n') + p:write('\t\t\tprojectRoot = "";\n') + p:write('\t\t\ttargets = (\n') + + for _, project in ipairs(projects) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', newid(project.Decl.Name .. "Target"), project.Decl.Name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t};\n') + p:write('/* End PBXProject section */\n') +end + +local function write_shellscripts(p, projects, env) + p:write('/* Begin PBXShellScriptBuildPhase section */\n') + + -- TODO: Do we really need to repeat this for all projects? seems a bit wasteful + + for _, project in ipairs(projects) do + local name = project.Decl.Name + if not project.IsMeta then + p:write('\t\t', newid(name .. "ShellScript"), ' /* ShellScript */ = {\n') + p:write('\t\t\tisa = PBXShellScriptBuildPhase;\n') + p:write('\t\t\tbuildActionMask = 2147483647;\n') + p:write('\t\t\tfiles = (\n') + p:write('\t\t\t);\n') + p:write('\t\t\tinputPaths = (\n') + p:write('\t\t\t);\n') + p:write('\t\t\toutputPaths = (\n') + p:write('\t\t\t);\n') + p:write('\t\t\trunOnlyForDeploymentPostprocessing = 0;\n') + p:write('\t\t\tshellPath = /bin/sh;\n') + p:write('\t\t\tshellScript = "cd ..\\n', TundraExePath, ' $(CONFIG)-$(VARIANT)-$(SUBVARIANT)";\n') + p:write('\t\t};\n') + end + end + + p:write('/* End PBXShellScriptBuildPhase section */\n') +end + +local function get_full_config_name(config) + return config.Config.Name .. '-' .. config.Variant.Name .. '-' .. config.SubVariant +end + +local function write_configs(p, projects, config_tuples, env, set_env) + + p:write('/* Begin XCBuildConfiguration section */\n') + + -- I wonder if we really need to do it this way for all configs? + + for _, project in ipairs(projects) do + for _, tuple in ipairs(config_tuples) do + local full_config_name = get_full_config_name(tuple) + local is_macosx_native = false + + for _, host in util.nil_ipairs(tuple.Config.SupportedHosts) do + if host == "macosx" then + is_macosx_native = true + end + end + + if "macosx" == tuple.Config.DefaultOnHost then + is_macosx_native = true + end + + local config_id = newid(project.Decl.Name .. full_config_name) + + p:write('\t\t', config_id, ' = {\n') + p:write('\t\t\tisa = XCBuildConfiguration;\n') + + -- Don't add any think extra if subvariant is default + + p:write('\t\t\tbuildSettings = {\n') + + if is_macosx_native then + p:write('\t\t\t\tARCHS = "$(NATIVE_ARCH_ACTUAL)";\n') + end + + p:write('\t\t\t\tVARIANT = "', tuple.Variant.Name, '";\n') + p:write('\t\t\t\tCONFIG = "', tuple.Config.Name, '";\n') + p:write('\t\t\t\tSUBVARIANT = "', tuple.SubVariant, '";\n') + + if is_macosx_native and not project.IsMeta then + p:write('\t\t\t\tCONFIGURATION_BUILD_DIR = "', full_config_name, '";\n') + end + + -- this is a little hack to get xcode to clean the whole output folder when using "FullBuild" + p:write('\t\t\t\tPRODUCT_NAME = "',project.Decl.Name , '";\n') + p:write('\t\t\t\tTARGET_NAME = "',project.Decl.Name , '";\n') + + for i, var in ipairs(set_env) do + p:write('\t\t\t\t', var, ' = "', os.getenv(var), '";\n') + end + + p:write('\t\t\t};\n') + p:write('\t\t\tname = "',full_config_name , '";\n') + p:write('\t\t};\n') + end + end + + -- PBXProject configurations + + for _, tuple in ipairs(config_tuples) do + local full_config_name = get_full_config_name(tuple) + local config_id = newid("ProjectObject" .. full_config_name) + + p:write('\t\t', config_id, ' = {\n') + p:write('\t\t\tisa = XCBuildConfiguration;\n') + + p:write('\t\t\tbuildSettings = {\n') + p:write('\t\t\t};\n') + p:write('\t\t\tname = "',full_config_name , '";\n') + p:write('\t\t};\n') + end + + p:write('/* End XCBuildConfiguration section */\n') + +end + +local function write_config_list(p, projects, config_tuples) + p:write('/* Begin XCConfigurationList section */\n') + + local default_config = ""; + + -- find the default config + + for _, tuple in ipairs(config_tuples) do + local is_macosx_native = tuple.Config.Name:match('^(%macosx)%-') + + if is_macosx_native and tuple.Variant.Name == "debug" then + default_config = get_full_config_name(tuple) + break + end + end + + -- if we did't find a default config just grab the first one + + if default_config == "" then + default_config = get_full_config_name(config_tuples[1]) + end + + for __, project in ipairs(projects) do + local config_id = newid(project.Decl.Name .. 'Config') + + p:write('\t\t', config_id, ' /* Build config list for "', project.Decl.Name, '" */ = {\n') + p:write('\t\t\tisa = XCConfigurationList;\n') + + -- Don't add any think extra if subvariant is default + + p:write('\t\t\tbuildConfigurations = (\n') + + for _, tuple in ipairs(config_tuples) do + local full_config_name = get_full_config_name(tuple) + p:write(string.format('\t\t\t\t%s /* %s */,\n', newid(project.Decl.Name .. full_config_name), full_config_name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t\tdefaultConfigurationIsVisible = 1;\n') + p:write('\t\t\tdefaultConfigurationName = "', default_config, '";\n') + + p:write('\t\t};\n') + end + + -- PBXProject configuration list + + local config_id = newid("ProjectObjectConfigList") + + p:write('\t\t', config_id, ' /* Build config list for PBXProject */ = {\n') + p:write('\t\t\tisa = XCConfigurationList;\n') + + -- Don't add any think extra if subvariant is default + + p:write('\t\t\tbuildConfigurations = (\n') + + for _, tuple in ipairs(config_tuples) do + local full_config_name = get_full_config_name(tuple) + p:write(string.format('\t\t\t\t%s /* %s */,\n', newid("ProjectObject" .. full_config_name), full_config_name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t\tdefaultConfigurationIsVisible = 1;\n') + p:write('\t\t\tdefaultConfigurationName = "', default_config, '";\n') + p:write('\t\t};\n') + + p:write('/* End XCConfigurationList section */\n') + +end + +local function write_footer(p) + p:write('\t};\n') + p:write('\trootObject = ', newid("ProjectObject"), ' /* Project object */;\n') + p:write('}\n') +end + +local function make_meta_projects(ide_script) + local source_list = { + [newid("tundra.lua")] = "tundra.lua" + } + local units = io.open("units.lua") + if units then + source_list[newid("units.lua")] = "units.lua" + io.close(units) + end + + local meta_name1 = "!BuildWorkspace" + local meta_name2 = "!UpdateWorkspace" + return { + { + Decl = { Name = meta_name1, }, + Type = "LegacyTarget", + RelativeFilename = "", + Sources = source_list, + Guid = newid(meta_name1 .. 'ProjectId'), + IsMeta = true, + MetaData = { BuildArgs = "-v $(CONFIG)-$(VARIANT)-$(SUBVARIANT)", BuildTool = TundraExePath }, + }, + { + Decl = { Name = meta_name2, }, + Type = "LegacyTarget", + RelativeFilename = "", + Sources = source_list, + Guid = newid(meta_name2 .. 'ProjectId'), + IsMeta = true, + MetaData = { BuildArgs = "--g " .. ide_script, BuildTool = TundraExePath }, + } + } +end + +function xcode_generator:generate_files(ngen, config_tuples, raw_nodes, env, default_names, hints, ide_script) + assert(config_tuples and #config_tuples > 0) + + hints = hints or {} + hints = hints.Xcode or {} + local base_dir = hints.BaseDir and (hints.BaseDir .. '/') or env:interpolate('$(OBJECTROOT)$(SEP)') + native.mkdir(base_dir) + + local projects = get_projects(raw_nodes, env, hints, ide_script) + + local source_list = { + [newid("tundra.lua")] = "tundra.lua" + } + local units = io.open("units.lua") + if units then + source_list[newid("units.lua")] = "units.lua" + io.close(units) + end + + local meta_name = "!BuildWorkspace" + local build_project = { + Decl = { Name = meta_name, }, + Type = "LegacyTarget", + RelativeFilename = "", + Sources = source_list, + Guid = newid(meta_name .. 'ProjectId'), + IsMeta = true, + MetaData = { BuildArgs = "$(CONFIG)-$(VARIANT)-$(SUBVARIANT)", BuildTool = TundraExePath }, + } + local meta_name = "!UpdateWorkspace" + local generate_project = { + Decl = { Name = meta_name, }, + Type = "LegacyTarget", + RelativeFilename = "", + Sources = source_list, + Guid = newid(meta_name .. 'ProjectId'), + IsMeta = true, + MetaData = { BuildArgs = "--g " .. ide_script, BuildTool = TundraExePath }, + } + + local solution_hints = hints.Projects + if not solution_hints then + print("No IdeGenerationHints.Xcode.Projects specified - using defaults") + solution_hints = { + ['tundra-generated.sln'] = { } + } + end + + for name, data in pairs(solution_hints) do + local sln_projects = { build_project, generate_project } + + if data.Projects then + for _, pname in ipairs(data.Projects) do + local pp = projects[pname] + if not pp then + errorf("can't find project %s for inclusion in %s -- check your Projects data", pname, name) + end + sln_projects[#sln_projects + 1] = pp + end + else + -- All the projects (that are not meta) + for pname, pp in pairs(projects) do + sln_projects[#sln_projects + 1] = pp + end + end + + local proj_dir = base_dir .. path.drop_suffix(name) .. ".xcodeproj/" + native.mkdir(proj_dir) + + local p = io.open(path.join(proj_dir, "project.pbxproj"), 'wb') + + write_header(p) + write_file_refs(p, sln_projects) + write_groups(p, sln_projects) + write_legacy_targets(p, sln_projects, env) + write_native_targes(p, sln_projects) + write_project(p, sln_projects) + write_shellscripts(p, sln_projects, env) + write_configs(p, sln_projects, config_tuples, env, hints.EnvVars or {}) + write_config_list(p, sln_projects, config_tuples) + write_footer(p) + end +end + +nodegen.set_ide_backend(function(...) + local state = setmetatable({}, xcode_generator) + state:generate_files(...) +end) + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/init.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/init.lua new file mode 100644 index 0000000..5f22ca3 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/init.lua @@ -0,0 +1,34 @@ +module(..., package.seeall) + +init_tundra_lua = [====[ +local CFiles = { ".c", ".h" } +Build { + Configs = { + Config { + Name = "generic-gcc", + DefaultOnHost = "linux", + Tools = { "gcc" }, + }, + Config { + Name = "macosx-gcc", + DefaultOnHost = "macosx", + Tools = { "gcc-osx" }, + }, + Config { + Name = "win64-msvc", + DefaultOnHost = "windows", + Tools = { "msvc-vs2008"; TargetPlatform = "x64" }, + }, + }, + Units = function() + require "tundra.syntax.glob" + Program { + Name = "a.out", + Sources = { Glob { Dir = ".", Extensions = CFiles } }, + } + + Default "a.out" + end, +} +]====] + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/nodegen.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/nodegen.lua new file mode 100644 index 0000000..e560aa1 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/nodegen.lua @@ -0,0 +1,914 @@ +module(..., package.seeall) + +local unitgen = require "tundra.unitgen" +local util = require "tundra.util" +local path = require "tundra.path" +local depgraph = require "tundra.depgraph" +local buildfile = require "tundra.buildfile" +local native = require "tundra.native" + +local ide_backend = nil + +local current = nil + +local _nodegen = { } +_nodegen.__index = _nodegen + +local function syntax_error(msg, ...) + error { Class = 'syntax error', Message = string.format(msg, ...) } +end + +local function validate_boolean(name, value) + if type(value) == "boolean" then + return value + end + syntax_error("%s: expected boolean value, got %q", name, type(value)) +end + +local function validate_string(name, value) + if type(value) == "string" then + return value + end + syntax_error("%s: expected string value, got %q", name, type(value)) +end + +local function validate_pass(name, value) + if type(value) == "string" then + return value + else + syntax_error("%s: expected pass name, got %q", name, type(value)) + end +end + +local function validate_table(name, value) + -- A single string can be converted into a table value very easily + local t = type(value) + if t == "table" then + return value + elseif t == "string" then + return { value } + else + syntax_error("%s: expected table value, got %q", name, t) + end +end + +local function validate_config(name, value) + if type(value) == "table" or type(value) == "string" then + return value + end + syntax_error("%s: expected config, got %q", name, type(value)) +end + +local validators = { + ["string"] = validate_string, + ["pass"] = validate_pass, + ["table"] = validate_table, + ["filter_table"] = validate_table, + ["source_list"] = validate_table, + ["boolean"] = validate_boolean, + ["config"] = validate_config, +} + +function _nodegen:validate() + local decl = self.Decl + for name, detail in pairs(assert(self.Blueprint)) do + local val = decl[name] + if not val then + if detail.Required then + syntax_error("%s: missing argument: '%s'", self.Keyword, name) + end + -- ok, optional value + else + local validator = validators[detail.Type] + decl[name] = validator(name, val) + end + end + for name, detail in pairs(decl) do + if not self.Blueprint[name] then + syntax_error("%s: unsupported argument: '%s'", self.Keyword, name) + end + end +end + +function _nodegen:customize_env(env, raw_data) + -- available for subclasses +end + +function _nodegen:configure_env(env, deps) + local build_id = env:get('BUILD_ID') + local propagate_blocks = {} + local decl = self.Decl + + for _, dep_obj in util.nil_ipairs(deps) do + local data = dep_obj.Decl.Propagate + if data then + propagate_blocks[#propagate_blocks + 1] = data + end + end + + local function push_bindings(env_key, data) + if data then + for _, item in util.nil_ipairs(flatten_list(build_id, data)) do + env:append(env_key, item) + end + end + end + + local function replace_bindings(env_key, data) + if data then + local first = true + for _, item in util.nil_ipairs(flatten_list(build_id, data)) do + if first then + env:replace(env_key, item) + first = false + else + env:append(env_key, item) + end + end + end + end + + -- Push Libs, Defines and so in into the environment of this unit. + -- These are named for convenience but are aliases for syntax niceness. + for decl_key, env_key in util.nil_pairs(self.DeclToEnvMappings) do + -- First pick settings from our own unit. + push_bindings(env_key, decl[decl_key]) + + for _, data in ipairs(propagate_blocks) do + push_bindings(env_key, data[decl_key]) + end + end + + -- Push Env blocks as is + for k, v in util.nil_pairs(decl.Env) do + push_bindings(k, v) + end + + for k, v in util.nil_pairs(decl.ReplaceEnv) do + replace_bindings(k, v) + end + + for _, block in util.nil_ipairs(propagate_blocks) do + for k, v in util.nil_pairs(block.Env) do + push_bindings(k, v) + end + + for k, v in util.nil_pairs(block.ReplaceEnv) do + replace_bindings(k, v) + end + end +end + +local function resolve_sources(env, items, accum, base_dir) + local ignored_exts = util.make_lookup_table(env:get_list("IGNORED_AUTOEXTS", {})) + for _, item in util.nil_ipairs(items) do + local type_name = type(item) + + assert(type_name ~= "function") + + if type_name == "userdata" then + accum[#accum + 1] = item + elseif type_name == "table" then + if depgraph.is_node(item) then + accum[#accum + 1] = item + elseif getmetatable(item) then + accum[#accum + 1] = item:get_dag(env) + else + resolve_sources(env, item, accum, item.SourceDir or base_dir) + end + else + assert(type_name == "string") + local ext = path.get_extension(item) + if not ignored_exts[ext] then + if not base_dir or path.is_absolute(item) then + accum[#accum + 1] = item + else + local p = path.join(base_dir, item) + accum[#accum + 1] = p + end + end + end + end + return accum +end + +-- Analyze source list, returning list of input files and list of dependencies. +-- +-- This is so you can pass a mix of actions producing files and regular +-- filenames as inputs to the next step in the chain and the output files of +-- such nodes will be used automatically. +-- +-- list - list of source files and nodes that produce source files +-- suffixes - acceptable source suffixes to pick up from nodes in source list +local function analyze_sources(env, pass, list, suffixes) + if not list then + return nil + end + + list = util.flatten(list) + local deps = {} + + local function implicit_make(source_file) + local t = type(source_file) + if t == "table" then + return source_file + end + assert(t == "string") + + local make = env:get_implicit_make_fn(source_file) + if make then + return make(env, pass, source_file) + else + return nil + end + end + + local function transform(output, fn) + if type(fn) ~= "string" then + error(util.tostring(fn) .. " is not a string", 2) + end + + local t = implicit_make(fn) + if t then + deps[#deps + 1] = t + t:insert_output_files(output, suffixes) + else + output[#output + 1] = fn + end + end + + local files = {} + for _, src in ipairs(list) do + if depgraph.is_node(src) then + deps[#deps + 1] = src + src:insert_output_files(files, suffixes) + elseif type(src) == "table" then + error("non-DAG node in source list at this point") + else + files[#files + 1] = src + end + end + + while true do + local result = {} + local old_dep_count = #deps + for _, src in ipairs(files) do + transform(result, src) + end + files = result + if #deps == old_dep_count then + --print("scan", util.tostring(list), util.tostring(suffixes), util.tostring(result)) + return result, deps + end + end +end + +local function x_identity(self, name, info, value, env, out_deps) + return value +end + +local function x_source_list(self, name, info, value, env, out_deps) + local build_id = env:get('BUILD_ID') + local source_files + if build_id then + source_files = filter_structure(build_id, value) + else + source_files = value + end + local sources = resolve_sources(env, source_files, {}, self.Decl.SourceDir) + local source_exts = env:get_list(info.ExtensionKey) + local inputs, ideps = analyze_sources(env, resolve_pass(self.Decl.Pass), sources, source_exts) + if ideps then + util.append_table(out_deps, ideps) + end + return inputs +end + +local function x_filter_table(self, name, info, value, env, out_deps) + local build_id = env:get('BUILD_ID') + return flatten_list(build_id, value) +end + +local function find_named_node(name_or_dag) + if type(name_or_dag) == "table" then + return name_or_dag:get_dag(current.default_env) + elseif type(name_or_dag) == "string" then + local generator = current.units[name_or_dag] + if not generator then + errorf("unknown node specified: %q", tostring(name_or_dag)) + end + return generator:get_dag(current.default_env) + else + errorf("illegal node specified: %q", tostring(name_or_dag)) + end +end + +-- Special resolver for dependencies in a nested (config-filtered) list. +local function resolve_dependencies(decl, raw_deps, env) + if not raw_deps then + return {} + end + + local build_id = env:get('BUILD_ID') + local deps = flatten_list(build_id, raw_deps) + return util.map_in_place(deps, function (i) + if type(i) == "string" then + local n = current.units[i] + if not n then + errorf("%s: Unknown 'Depends' target %q", decl.Name, i) + end + return n + elseif type(i) == "table" and getmetatable(i) and i.Decl then + return i + else + errorf("bad 'Depends' value of type %q", type(i)) + end + end) +end + +local function x_pass(self, name, info, value, env, out_deps) + return resolve_pass(value) +end + +local decl_transformers = { + -- the x_identity data types have already been checked at script time through validate_xxx + ["string"] = x_identity, + ["table"] = x_identity, + ["config"] = x_identity, + ["boolean"] = x_identity, + ["pass"] = x_pass, + ["source_list"] = x_source_list, + ["filter_table"] = x_filter_table, +} + +-- Create input data for the generator's DAG creation function based on the +-- blueprint passed in when the generator was registered. This is done here +-- centrally rather than in all the different node generators to reduce code +-- duplication and keep the generators miminal. If you need to do something +-- special, you can override create_input_data() in your subclass. +function _nodegen:create_input_data(env) + local decl = self.Decl + local data = {} + local deps = {} + + for name, detail in pairs(assert(self.Blueprint)) do + local val = decl[name] + if val then + local xform = decl_transformers[detail.Type] + data[name] = xform(self, name, detail, val, env, deps) + end + end + + return data, deps +end + +function get_pass(self, name) + if not name then + return nil + end + +end + +local pattern_cache = {} +local function get_cached_pattern(p) + local v = pattern_cache[p] + if not v then + local comp = '[%w_]+' + local sub_pattern = p:gsub('*', '[%%w_]+') + local platform, tool, variant, subvariant = unitgen.match_build_id(sub_pattern, comp) + v = string.format('^%s%%-%s%%-%s%%-%s$', platform, tool, variant, subvariant) + pattern_cache[p] = v + end + return v +end + +local function config_matches(pattern, build_id) + local ptype = type(pattern) + if ptype == "nil" then + return true + elseif ptype == "string" then + local fpattern = get_cached_pattern(pattern) + return build_id:match(fpattern) + elseif ptype == "table" then + for _, pattern_item in ipairs(pattern) do + if config_matches(pattern_item, build_id) then + return true + end + end + return false + else + error("bad 'Config' pattern type: " .. ptype) + end +end + +local function make_unit_env(unit) + -- Select an environment for this unit based on its SubConfig tag + -- to support cross compilation. + local env + local subconfig = unit.Decl.SubConfig or current.default_subconfig + if subconfig and current.base_envs then + env = current.base_envs[subconfig] + if Options.VeryVerbose then + if env then + printf("%s: using subconfig %s (%s)", unit.Decl.Name, subconfig, env:get('BUILD_ID')) + else + if current.default_subconfig then + errorf("%s: couldn't find a subconfig env", unit.Decl.Name) + else + printf("%s: no subconfig %s found; using default env", unit.Decl.Name, subconfig) + end + end + end + end + + if not env then + env = current.default_env + end + + return env:clone() +end + +local anon_count = 1 +function _nodegen:get_dag(parent_env) + local build_id = parent_env:get('BUILD_ID') + local dag = self.DagCache[build_id] + + if not dag then + if build_id:len() > 0 and not config_matches(self.Decl.Config, build_id) then + -- Unit has been filtered out via Config attribute. + -- Create a fresh dummy node for it. + local name + if not self.Decl.Name then + name = string.format("Dummy node %d", anon_count) + else + name = string.format("Dummy node %d for %s", anon_count, self.Decl.Name) + end + anon_count = anon_count + 1 + + dag = depgraph.make_node { + Env = parent_env, + Pass = resolve_pass(self.Decl.Pass), + Label = name, + } + else + local unit_env = make_unit_env(self) + + if self.Decl.Name then + unit_env:set('UNIT_PREFIX', '__' .. self.Decl.Name) + end + + -- Before accessing the unit's dependencies, resolve them via filtering. + local deps = resolve_dependencies(self.Decl, self.Decl.Depends, unit_env) + + self:configure_env(unit_env, deps) + self:customize_env(unit_env, self.Decl, deps) + + local input_data, input_deps = self:create_input_data(unit_env, parent_env) + -- Copy over dependencies which have been pre-resolved + input_data.Depends = deps + + for _, dep in util.nil_ipairs(deps) do + input_deps[#input_deps + 1] = dep:get_dag(parent_env) + end + + dag = self:create_dag(unit_env, input_data, input_deps, parent_env) + + if not dag then + error("create_dag didn't generate a result node") + end + end + self.DagCache[build_id] = dag + end + + return dag +end + +local _generator = { + Evaluators = {}, +} +_generator.__index = _generator + +local function new_generator(s) + s = s or {} + s.units = {} + return setmetatable(s, _generator) +end + +local function create_unit_map(state, raw_nodes) + -- Build name=>decl mapping + for _, unit in ipairs(raw_nodes) do + assert(unit.Decl) + local name = unit.Decl.Name + if name and type(name) == "string" then + if state.units[name] then + errorf("duplicate unit name: %s", name) + end + state.units[name] = unit + end + end +end + +function _generate_dag(args) + local envs = assert(args.Envs) + local raw_nodes = assert(args.Declarations) + + local state = new_generator { + base_envs = envs, + root_env = envs["__default"], -- the outmost config's env in a cross-compilation scenario + config = assert(args.Config), + variant = assert(args.Variant), + passes = assert(args.Passes), + } + + current = state + + create_unit_map(state, raw_nodes) + + local subconfigs = state.config.SubConfigs + + -- Pick a default environment which is used for + -- 1. Nodes without a SubConfig declaration + -- 2. Nodes with a missing SubConfig declaration + -- 3. All nodes if there are no SubConfigs set for the current config + if subconfigs then + state.default_subconfig = assert(state.config.DefaultSubConfig) + state.default_env = assert(envs[state.default_subconfig], "unknown DefaultSubConfig specified") + else + state.default_env = assert(envs["__default"]) + end + + + local always_lut = util.make_lookup_table(args.AlwaysNodes) + local default_lut = util.make_lookup_table(args.DefaultNodes) + + local always_nodes = util.map(args.AlwaysNodes, find_named_node) + local default_nodes = util.map(args.DefaultNodes, find_named_node) + + local named_nodes = {} + for name, _ in pairs(state.units) do + named_nodes[name] = find_named_node(name) + end + + current = nil + + return { always_nodes, default_nodes, named_nodes } +end + +function generate_dag(args) + local success, result = xpcall(function () return _generate_dag(args) end, buildfile.syntax_error_catcher) + + if success then + return result[1], result[2], result[3] + else + croak("%s", result) + end + +end + +function resolve_pass(name) + assert(current) + if name then + local p = current.passes[name] + if not p then + syntax_error("%q is not a valid pass name", name) + end + return p + else + return nil + end +end + +function get_target(data, suffix, prefix) + local target = data.Target + if not target then + assert(data.Name) + target = "$(OBJECTDIR)/" .. (prefix or "") .. data.Name .. (suffix or "") + end + return target +end + +function get_evaluator(name) + return _generator.Evaluators[name] +end + +function is_evaluator(name) + if _generator.Evaluators[name] then return true else return false end +end + +local common_blueprint = { + Propagate = { + Help = "Declarations to propagate to dependent units", + Type = "filter_table", + }, + Depends = { + Help = "Dependencies for this node", + Type = "table", -- handled specially + }, + Env = { + Help = "Data to append to the environment for the unit", + Type = "filter_table", + }, + ReplaceEnv = { + Help = "Data to replace in the environment for the unit", + Type = "filter_table", + }, + Pass = { + Help = "Specify build pass", + Type = "pass", + }, + SourceDir = { + Help = "Specify base directory for source files", + Type = "string", + }, + Config = { + Help = "Specify configuration this unit will build in", + Type = "config", + }, + SubConfig = { + Help = "Specify sub-configuration this unit will build in", + Type = "config", + }, + __DagNodes = { + Help = "Internal node to keep track of DAG nodes generated so far", + Type = "table", + } +} + +function create_eval_subclass(meta_tbl, base) + base = base or _nodegen + setmetatable(meta_tbl, base) + meta_tbl.__index = meta_tbl + return meta_tbl +end + +function add_evaluator(name, meta_tbl, blueprint) + assert(type(name) == "string") + assert(type(meta_tbl) == "table") + assert(type(blueprint) == "table") + + -- Set up this metatable as a subclass of _nodegen unless it is already + -- configured. + if not getmetatable(meta_tbl) then + setmetatable(meta_tbl, _nodegen) + meta_tbl.__index = meta_tbl + end + + -- Install common blueprint items. + for name, val in pairs(common_blueprint) do + if not blueprint[name] then + blueprint[name] = val + end + end + + -- Expand environment shortcuts into options. + for decl_key, env_key in util.nil_pairs(meta_tbl.DeclToEnvMappings) do + blueprint[decl_key] = { + Type = "filter_table", + Help = "Shortcut for environment key " .. env_key, + } + end + + for name, val in pairs(blueprint) do + local type_ = assert(val.Type) + if not validators[type_] then + errorf("unsupported blueprint type %q", type_) + end + + if val.Type == "source_list" and not val.ExtensionKey then + errorf("%s: source_list must provide ExtensionKey", name) + end + end + + -- Record blueprint for use when validating user constructs. + meta_tbl.Keyword = name + meta_tbl.Blueprint = blueprint + + -- Store this evaluator under the keyword that will trigger it. + _generator.Evaluators[name] = meta_tbl +end + +-- Called when processing build scripts, keywords is something previously +-- registered as an evaluator here. +function evaluate(eval_keyword, data) + local meta_tbl = assert(_generator.Evaluators[eval_keyword]) + + -- Give the evaluator change to fix up the data before we validate it. + data = meta_tbl:preprocess_data(data) + + local object = setmetatable({ + DagCache = {}, -- maps BUILD_ID -> dag node + Decl = data + }, meta_tbl) + + -- Expose the dag cache to the raw input data so the IDE generator can find it later + data.__DagNodes = object.DagCache + object.__index = object + + -- Validate data according to Blueprint settings + object:validate() + return object +end + +-- Given a list of strings or nested lists, flatten the structure to a single +-- list of strings while applying configuration filters. Configuration filters +-- match against the current build identifier like this: +-- +-- { "a", "b", { "nixfile1", "nixfile2"; Config = "unix-*-*" }, "bar", { "debugfile"; Config = "*-*-debug" }, } +-- +-- If 'exclusive' is set, then: +-- If 'build_id' is set, only values _with_ a 'Config' filter are included. +-- If 'build_id' is nil, only values _without_ a 'Config' filter are included. +function flatten_list(build_id, list, exclusive) + if not list then return nil end + local filter_defined = build_id ~= nil + + -- Helper function to apply filtering recursively and append results to an + -- accumulator table. + local function iter(node, accum, filtered) + local node_type = type(node) + if node_type == "table" and not getmetatable(node) then + if node.Config then filtered = true end + if not filter_defined or config_matches(node.Config, build_id) then + for _, item in ipairs(node) do + iter(item, accum, filtered) + end + end + elseif not exclusive or (filtered == filter_defined) then + accum[#accum + 1] = node + end + end + + local results = {} + iter(list, results, false) + return results +end + +-- Conceptually similar to flatten_list(), but retains table structure. +-- Use to keep source tables as they are passed in, to retain nested SourceDir attributes. +local empty_leaf = {} -- constant +function filter_structure(build_id, data, exclusive) + if type(data) == "table" then + if getmetatable(data) then + return data -- it's already a DAG node; use as-is + end + + local filtered = data.Config and true or false + + if not data.Config or config_matches(data.Config, build_id) then + local result = {} + for k, item in pairs(data) do + if type(k) == "number" then + -- Filter array elements. + result[#result + 1] = filter_structure(build_id, item, filtered) + elseif k ~= "Config" then + -- Copy key-value data through. + result[k] = item + end + end + return result + else + return empty_leaf + end + else + return data + end +end + +-- Processes an "Env" table. For each value, the corresponding variable in +-- 'env' is appended to if its "Config" filter matches 'build_id'. If +-- 'build_id' is nil, filtered values are skipped. +function append_filtered_env_vars(env, values_to_append, build_id, exclusive) + for key, val in util.pairs(values_to_append) do + if type(val) == "table" then + local list = flatten_list(build_id, val, exclusive) + for _, subvalue in ipairs(list) do + env:append(key, subvalue) + end + elseif not (exclusive and build_id) then + env:append(key, val) + end + end +end + +-- Like append_filtered_env_vars(), but replaces existing variables instead +-- of appending to them. +function replace_filtered_env_vars(env, values_to_replace, build_id, exclusive) + for key, val in util.pairs(values_to_replace) do + if type(val) == "table" then + local list = flatten_list(build_id, val, exclusive) + if #list > 0 then + env:replace(key, list) + end + elseif not (exclusive and build_id) then + env:replace(key, val) + end + end +end + +function generate_ide_files(config_tuples, default_names, raw_nodes, env, hints, ide_script) + local state = new_generator { default_env = env } + assert(state.default_env) + create_unit_map(state, raw_nodes) + local backend_fn = assert(ide_backend) + backend_fn(state, config_tuples, raw_nodes, env, default_names, hints, ide_script) +end + +function set_ide_backend(backend_fn) + ide_backend = backend_fn +end + +-- Expose the DefRule helper which is used to register builder syntax in a +-- simplified way. + +function _G.DefRule(ruledef) + local name = assert(ruledef.Name, "Missing Name string in DefRule") + local setup_fn = assert(ruledef.Setup, "Missing Setup function in DefRule " .. name) + local cmd = assert(ruledef.Command, "Missing Command string in DefRule " .. name) + local blueprint = assert(ruledef.Blueprint, "Missing Blueprint in DefRule " .. name) + local mt = create_eval_subclass {} + local annot = ruledef.Annotation + + if not annot then + annot = name .. " $(<)" + end + + local preproc = ruledef.Preprocess + + local function verify_table(v, tag) + if not v then + errorf("No %s returned from DefRule %s", tag, name) + end + + if type(v) ~= "table" then + errorf("%s returned from DefRule %s is not a table", tag, name) + end + end + + local function make_node(input_files, output_files, env, data, deps, scanner) + return depgraph.make_node { + Env = env, + Label = annot, + Action = cmd, + Pass = data.Pass or resolve_pass(ruledef.Pass), + InputFiles = input_files, + OutputFiles = output_files, + ImplicitInputs = ruledef.ImplicitInputs, + Scanner = scanner, + Dependencies = deps, + } + end + + if ruledef.ConfigInvariant then + local cache = {} + + function mt:create_dag(env, data, deps) + local setup_data = setup_fn(env, data) + local input_files = setup_data.InputFiles + local output_files = setup_data.OutputFiles + verify_table(input_files, "InputFiles") + verify_table(output_files, "OutputFiles") + + local mashup = { } + for _, input in util.nil_ipairs(input_files) do + mashup[#mashup + 1] = input + end + mashup[#mashup + 1] = "@@" + for _, output in util.nil_ipairs(output_files) do + mashup[#mashup + 1] = output + end + mashup[#mashup + 1] = "@@" + for _, implicit_input in util.nil_ipairs(setup_data.ImplicitInputs) do + mashup[#mashup + 1] = implicit_input + end + local key = native.digest_guid(table.concat(mashup, ';')) + + local key = util.tostring(key) + if cache[key] then + return cache[key] + else + local node = make_node(input_files, output_files, env, data, deps, setup_data.Scanner) + cache[key] = node + return node + end + end + else + function mt:create_dag(env, data, deps) + local setup_data = setup_fn(env, data) + verify_table(setup_data.InputFiles, "InputFiles") + verify_table(setup_data.OutputFiles, "OutputFiles") + return make_node(setup_data.InputFiles, setup_data.OutputFiles, env, data, deps, setup_data.Scanner) + end + end + + if preproc then + function mt:preprocess_data(raw_data) + return preproc(raw_data) + end + end + + add_evaluator(name, mt, blueprint) +end + +function _nodegen:preprocess_data(data) + return data +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/path.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/path.lua new file mode 100644 index 0000000..f5d7068 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/path.lua @@ -0,0 +1,50 @@ +module(..., package.seeall) + +local npath = require "tundra.native.path" + +split = npath.split +normalize = npath.normalize +join = npath.join +get_filename_dir = npath.get_filename_dir +get_filename = npath.get_filename +get_extension = npath.get_extension +drop_suffix = npath.drop_suffix +get_filename_base = npath.get_filename_base +is_absolute = npath.is_absolute + +function remove_prefix(prefix, fn) + if fn:find(prefix, 1, true) == 1 then + return fn:sub(#prefix + 1) + else + return fn + end +end + +function make_object_filename(env, src_fn, suffix) + local object_fn + + local src_suffix = get_extension(src_fn):sub(2) + + -- Drop leading $(OBJECTDIR)[/\\] in the input filename. + do + local pname = src_fn:match("^%$%(OBJECTDIR%)[/\\](.*)$") + if pname then + object_fn = pname + else + object_fn = src_fn + end + end + + -- Compute path under OBJECTDIR we want for the resulting object file. + -- Replace ".." with "dotdot" to avoid creating files outside the + -- object directory. Also salt the generated object name with the source + -- suffix, so that multiple source files with the same base name don't end + -- up clobbering each other (Tundra emits an error for this when checking + -- the DAG) + do + local relative_name = drop_suffix(object_fn:gsub("%.%.", "dotdot")) + object_fn = "$(OBJECTDIR)/$(UNIT_PREFIX)/" .. relative_name .. "__" .. src_suffix .. suffix + end + + return object_fn +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/platform.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/platform.lua new file mode 100644 index 0000000..402b79d --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/platform.lua @@ -0,0 +1,7 @@ +module(..., package.seeall) + +local native = require "tundra.native" + +function host_platform() + return native.host_platform +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/scanner.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/scanner.lua new file mode 100644 index 0000000..8750627 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/scanner.lua @@ -0,0 +1,57 @@ +module(..., package.seeall) + +local util = require "tundra.util" +local native = require "tundra.native" + +local _scanner_mt = {} +setmetatable(_scanner_mt, { __index = _scanner_mt }) + +local cpp_scanner_cache = {} +local generic_scanner_cache = {} + +function make_cpp_scanner(paths) + local key = table.concat(paths, '\0') + + if not cpp_scanner_cache[key] then + local data = { Kind = 'cpp', Paths = paths, Index = #cpp_scanner_cache } + cpp_scanner_cache[key] = setmetatable(data, _scanner_mt) + end + + return cpp_scanner_cache[key] +end + +function make_generic_scanner(data) + data.Kind = 'generic' + local mashup = { } + local function add_all(l) + for _, value in util.nil_ipairs(l) do + mashup[#mashup + 1] = value + end + end + add_all(data.Paths) + add_all(data.Keywords) + add_all(data.KeywordsNoFollow) + mashup[#mashup + 1] = '!!' + mashup[#mashup + 1] = data.RequireWhitespace and 'y' or 'n' + mashup[#mashup + 1] = data.UseSeparators and 'y' or 'n' + mashup[#mashup + 1] = data.BareMeansSystem and 'y' or 'n' + local key_str = table.concat(mashup, '\001') + local key = native.digest_guid(key_str) + local value = generic_scanner_cache[key] + if not value then + value = data + generic_scanner_cache[key] = data + end + return value +end + +function all_scanners() + local scanners = {} + for k, v in pairs(cpp_scanner_cache) do + scanners[v.Index + 1] = v + end + for k, v in pairs(generic_scanner_cache) do + scanners[v.Index + 1] = v + end + return scanners +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/selftest.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/selftest.lua new file mode 100644 index 0000000..f506f92 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/selftest.lua @@ -0,0 +1,37 @@ +module(..., package.seeall) + +local error_count = 0 + +function _G.unit_test(label, fn) + local t_mt = { + check_equal = function (obj, a, b) + if a ~= b then + error { Message = "Equality test failed: " .. tostring(a) .. " != " .. tostring(b) } + end + end + } + t_mt.__index = t_mt + + local t = setmetatable({}, t_mt) + local function stack_dumper(err_obj) + if type(err_obj) == "table" then + return err_obj.Message + end + local debug = require 'debug' + return debug.traceback(err_obj, 2) + end + + io.stdout:write("Testing ", label, ": ") + io.stdout:flush() + local ok, err = xpcall(function () fn(t) end, stack_dumper) + if not ok then + io.stdout:write("failed\n") + io.stdout:write(tostring(err), "\n") + error_count = error_count + 1 + else + io.stderr:write("OK\n") + end +end + +require "tundra.test.t_env" +require "tundra.test.t_path" diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/alias.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/alias.lua new file mode 100644 index 0000000..361c07a --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/alias.lua @@ -0,0 +1,40 @@ +-- alias.lua -- support for named aliases in the DAG + +module(..., package.seeall) + +local nodegen = require "tundra.nodegen" +local depgraph = require "tundra.depgraph" +local util = require "tundra.util" + +local _alias_mt = nodegen.create_eval_subclass {} + +function _alias_mt:create_dag(env, data, input_deps) + + local deps = util.clone_table(input_deps) + + for _, dep in util.nil_ipairs(data.Depends) do + deps[#deps+1] = dep:get_dag(env:get_parent()) + end + + local dag = depgraph.make_node { + Env = env, + Label = "Named alias " .. data.Name .. " for " .. env:get('BUILD_ID'), + Pass = data.Pass, + Dependencies = deps, + } + + -- Remember this dag node for IDE file generation purposes + data.__DagNode = dag + + return dag +end + +local alias_blueprint = { + Name = { + Required = true, + Help = "Set alias name", + Type = "string", + }, +} + +nodegen.add_evaluator("Alias", _alias_mt, alias_blueprint) diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/bison.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/bison.lua new file mode 100644 index 0000000..a1bcd3c --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/bison.lua @@ -0,0 +1,42 @@ +module(..., package.seeall) + +local nodegen = require "tundra.nodegen" +local path = require "tundra.path" +local depgraph = require "tundra.depgraph" + +local _bison_mt = nodegen.create_eval_subclass {} + +local bison_blueprint = { + Source = { Required = true, Type = "string" }, + OutputFile = { Required = false, Type = "string" }, + TokenDefines = { Required = false, Type = "boolean" }, +} + +function _bison_mt:create_dag(env, data, deps) + local src = data.Source + local out_src + if data.OutputFile then + out_src = "$(OBJECTDIR)$(SEP)" .. data.OutputFile + else + local targetbase = "$(OBJECTDIR)$(SEP)bisongen_" .. path.get_filename_base(src) + out_src = targetbase .. ".c" + end + local defopt = "" + local outputs = { out_src } + if data.TokenDefines then + local out_hdr = path.drop_suffix(out_src) .. ".h" + defopt = "--defines=" .. out_hdr + outputs[#outputs + 1] = out_hdr + end + return depgraph.make_node { + Env = env, + Pass = data.Pass, + Label = "Bison $(@)", + Action = "$(BISON) $(BISONOPT) " .. defopt .. " --output-file=$(@:[1]) $(<)", + InputFiles = { src }, + OutputFiles = outputs, + Dependencies = deps, + } +end + +nodegen.add_evaluator("Bison", _bison_mt, bison_blueprint) diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/dotnet.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/dotnet.lua new file mode 100644 index 0000000..eef4722 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/dotnet.lua @@ -0,0 +1,117 @@ +module(..., package.seeall) + +local util = require "tundra.util" +local nodegen = require "tundra.nodegen" +local depgraph = require "tundra.depgraph" + +local _csbase_mt = nodegen.create_eval_subclass { + DeclToEnvMappings = { + References = "CSLIBS", + RefPaths = "CSLIBPATH", + }, +} + +local _csexe_mt = nodegen.create_eval_subclass({ + Label = "CSharpExe $(@)", + Suffix = "$(CSPROGSUFFIX)", + Action = "$(CSCEXECOM)" +}, _csbase_mt) + +local _cslib_mt = nodegen.create_eval_subclass({ + Label = "CSharpLib $(@)", + Suffix = "$(CSLIBSUFFIX)", + Action = "$(CSCLIBCOM)" +}, _csbase_mt) + + +local csSourceExts = { ".cs" } +local csResXExts = { ".resx" } + +local function setup_refs_from_dependencies(env, dep_nodes, deps) + local dll_exts = { env:interpolate("$(CSLIBSUFFIX)") } + local refs = {} + local parent_env = env:get_parent() + for _, x in util.nil_ipairs(dep_nodes) do + if x.Keyword == "CSharpLib" then + local outputs = {} + local dag = x:get_dag(parent_env) + deps[#deps + 1] = dag + dag:insert_output_files(refs, dll_exts) + end + end + for _, r in ipairs(refs) do + env:append("CSLIBS", r) + end +end + +local function setup_resources(generator, env, assembly_name, resx_files, pass) + local result_files = {} + local deps = {} + local i = 1 + for _, resx in util.nil_ipairs(resx_files) do + local basename = path.get_filename_base(resx) + local result_file = string.format("$(OBJECTDIR)/_rescompile/%s.%s.resources", assembly_name, basename) + result_files[i] = result_file + deps[i] = depgraph.make_node { + Env = env, + Pass = pass, + Label = "resgen $(@)", + Action = "$(CSRESGEN)", + InputFiles = { resx }, + OutputFiles = { result_file }, + } + env:append("CSRESOURCES", result_file) + i = i + 1 + end + return result_files, deps +end + +function _csbase_mt:create_dag(env, data, deps) + local sources = data.Sources + local resources = data.Resources or {} + for _, r in util.nil_ipairs(resources) do + env:append("CSRESOURCES", r) + end + + sources = util.merge_arrays_2(sources, resources) + + setup_refs_from_dependencies(env, data.Depends, deps) + + return depgraph.make_node { + Env = env, + Pass = data.Pass, + Label = self.Label, + Action = self.Action, + InputFiles = sources, + OutputFiles = { nodegen.get_target(data, self.Suffix, self.Prefix) }, + Dependencies = util.uniq(deps), + } +end + +do + local csblueprint = { + Name = { + Required = true, + Help = "Set output (base) filename", + Type = "string", + }, + Sources = { + Required = true, + Help = "List of source files", + Type = "source_list", + ExtensionKey = "DOTNET_SUFFIXES", + }, + Resources = { + Help = "List of resource files", + Type = "source_list", + ExtensionKey = "DOTNET_SUFFIXES_RESOURCE", + }, + Target = { + Help = "Override target location", + Type = "string", + }, + } + + nodegen.add_evaluator("CSharpExe", _csexe_mt, csblueprint) + nodegen.add_evaluator("CSharpLib", _cslib_mt, csblueprint) +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/embed_lua.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/embed_lua.lua new file mode 100644 index 0000000..9bc49c6 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/embed_lua.lua @@ -0,0 +1,65 @@ +module(..., package.seeall) + +local util = require "tundra.util" +local path = require "tundra.path" +local glob = require "tundra.syntax.glob" +local nodegen = require "tundra.nodegen" +local depgraph = require "tundra.depgraph" + +local lua_exts = { ".lua" } +local luac_mt_ = nodegen.create_eval_subclass {} + +local function luac(env, src, pass) + local target = "$(OBJECTDIR)/" .. path.drop_suffix(src) .. ".luac" + return target, depgraph.make_node { + Env = env, + Pass = pass, + Label = "LuaC $(@)", + Action = "$(LUAC) -o $(@) -- $(<)", + InputFiles = { src }, + OutputFiles = { target }, + ImplicitInputs = { "$(LUAC)" }, + } +end + +function luac_mt_:create_dag(env, data, deps) + local files = {} + local deps = {} + local inputs = {} + local action_fragments = {} + + for _, base_dir in ipairs(data.Dirs) do + local lua_files = glob.Glob { Dir = base_dir, Extensions = lua_exts } + local dir_len = base_dir:len() + for _, filename in pairs(lua_files) do + local rel_name = filename:sub(dir_len+2) + local pkg_name = rel_name:gsub("[/\\]", "."):gsub("%.lua$", "") + inputs[#inputs + 1] = filename + if env:get("LUA_EMBED_ASCII", "no") == "no" then + files[#files + 1], deps[#deps + 1] = luac(env, filename, data.Pass) + else + files[#files + 1] = filename + end + action_fragments[#action_fragments + 1] = pkg_name + action_fragments[#action_fragments + 1] = files[#files] + end + end + + return depgraph.make_node { + Env = env, + Label = "EmbedLuaSources $(@)", + Pass = data.Pass, + Action = "$(GEN_LUA_DATA) " .. table.concat(action_fragments, " ") .. " > $(@)", + InputFiles = inputs, + OutputFiles = { "$(OBJECTDIR)/" .. data.OutputFile }, + Dependencies = deps, + ImplicitInputs = { "$(GEN_LUA_DATA)" }, + } +end + +local blueprint = { + Dirs = { Type = "table", Required = "true" }, + OutputFile = { Type = "string", Required = "true" }, +} + +nodegen.add_evaluator("EmbedLuaSources", luac_mt_, blueprint) diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/files.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/files.lua new file mode 100644 index 0000000..05cdb40 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/files.lua @@ -0,0 +1,50 @@ +module(..., package.seeall) + +local decl = require "tundra.decl" +local depgraph = require "tundra.depgraph" + +local common_blueprint = { + Source = { + Required = true, + Help = "Source filename", + Type = "string", + }, + Target = { + Required = true, + Help = "Target filename", + Type = "string", + }, +} + +local function def_copy_rule(name, command, cfg_invariant) + DefRule { + Name = name, + ConfigInvariant = cfg_invariant, + Blueprint = common_blueprint, + Command = command, + Setup = function (env, data) + return { + InputFiles = { data.Source }, + OutputFiles = { data.Target }, + } + end, + } +end + +def_copy_rule('CopyFile', '$(_COPY_FILE)') +def_copy_rule('CopyFileInvariant', '$(_COPY_FILE)', true) +def_copy_rule('HardLinkFile', '$(_HARDLINK_FILE)') +def_copy_rule('HardLinkFileInvariant', '$(_HARDLINK_FILE)', true) + + +function hardlink_file(env, src, dst, pass, deps) + return depgraph.make_node { + Env = env, + Annotation = "HardLink $(<)", + Action = "$(_HARDLINK_FILE)", + InputFiles = { src }, + OutputFiles = { dst }, + Dependencies = deps, + Pass = pass, + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/flex.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/flex.lua new file mode 100644 index 0000000..6e796a9 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/flex.lua @@ -0,0 +1,28 @@ +-- flex.lua - Support for FLEX + +module(..., package.seeall) + +local path = require "tundra.path" + +DefRule { + Name = "Flex", + Command = "flex --outfile=$(@:[1]) --header-file=$(@:[2]) $(<)", + ConfigInvariant = true, + + Blueprint = { + Source = { Required = true, Type = "string" }, + OutputCFile = { Required = false, Type = "string" }, + OutputHeaderFile = { Required = false, Type = "string" }, + }, + + Setup = function (env, data) + local src = data.Source + local base_name = path.drop_suffix(src) + local gen_c = data.OutputCFile or (base_name .. '.c') + local gen_h = data.OutputHeaderFileFile or (base_name .. '.h') + return { + InputFiles = { src }, + OutputFiles = { gen_c, gen_h }, + } + end, +} diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/glob.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/glob.lua new file mode 100644 index 0000000..02a623f --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/glob.lua @@ -0,0 +1,122 @@ +-- glob.lua - Glob syntax elements for declarative tundra.lua usage + +module(..., package.seeall) + +local util = require "tundra.util" +local path = require "tundra.path" +local decl = require "tundra.decl" +local dirwalk = require "tundra.dirwalk" + +local ignored_dirs = util.make_lookup_table { ".git", ".svn", "CVS" } + +local function glob(directory, recursive, filter_fn) + local result = {} + + local function dir_filter(dir_name) + if not recursive or ignored_dirs[dir_name] then + return false + end + return true + end + + for _, path in ipairs(dirwalk.walk(directory, dir_filter)) do + if filter_fn(path) then + result[#result + 1] = path + end + end + return result +end + +-- Glob syntax - Search for source files matching extension list +-- +-- Synopsis: +-- Glob { +-- Dir = "...", +-- Extensions = { ".ext", ... }, +-- [Recursive = false,] +-- } +-- +-- Options: +-- Dir = "directory" (required) +-- - Base directory to search in +-- +-- Extensions = { ".ext1", ".ext2" } (required) +-- - List of file extensions to include +-- +-- Recursive = boolean (optional, default: true) +-- - Specified whether to recurse into subdirectories +function Glob(args) + local recursive = args.Recursive + if type(recursive) == "nil" then + recursive = true + end + if not args.Extensions then + croak("no 'Extensions' specified in Glob (Dir is '%s')", args.Dir) + end + local extensions = assert(args.Extensions) + local ext_lookup = util.make_lookup_table(extensions) + return glob(args.Dir, recursive, function (fn) + local ext = path.get_extension(fn) + return ext_lookup[ext] + end) +end + +-- FGlob syntax - Search for source files matching extension list with +-- configuration filtering +-- +-- Usage: +-- FGlob { +-- Dir = "...", +-- Extensions = { ".ext", .... }, +-- Filters = { +-- { Pattern = "/[Ww]in32/", Config = "win32-*-*" }, +-- { Pattern = "/[Dd]ebug/", Config = "*-*-debug" }, +-- ... +-- }, +-- [Recursive = false], +-- } +local function FGlob(args) + -- Use the regular glob to fetch the file list. + local files = Glob(args) + local pats = {} + local result = {} + + -- Construct a mapping from { Pattern = ..., Config = ... } + -- to { Pattern = { Config = ... } } with new arrays per config that can be + -- embedded in the source result. + for _, fitem in ipairs(args.Filters) do + if not fitem.Config then + croak("no 'Config' specified in FGlob (Pattern is '%s')", fitem.Pattern) + end + local tab = { Config = assert(fitem.Config) } + pats[assert(fitem.Pattern)] = tab + result[#result + 1] = tab + end + + -- Traverse all files and see if they match any configuration filters. If + -- they do, stick them in matching list. Otherwise, just keep them in the + -- main list. This has the effect of returning an array such as this: + -- { + -- { "foo.c"; Config = "abc-*-*" }, + -- { "bar.c"; Config = "*-*-def" }, + -- "baz.c", "qux.m" + -- } + for _, f in ipairs(files) do + local filtered = false + for filter, list in pairs(pats) do + if f:match(filter) then + filtered = true + list[#list + 1] = f + break + end + end + if not filtered then + result[#result + 1] = f + end + end + return result +end + +decl.add_function("Glob", Glob) +decl.add_function("FGlob", FGlob) + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/install.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/install.lua new file mode 100644 index 0000000..c8dedcd --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/install.lua @@ -0,0 +1,40 @@ +-- install.lua - Express file copying in unit form. + +module(..., package.seeall) + +local nodegen = require "tundra.nodegen" +local files = require "tundra.syntax.files" +local path = require "tundra.path" +local util = require "tundra.util" +local depgraph = require "tundra.depgraph" + +local _mt = nodegen.create_eval_subclass {} + +local blueprint = { + Sources = { Type = "source_list", Required = true }, + TargetDir = { Type = "string", Required = true }, +} + +function _mt:create_dag(env, data, deps) + local my_pass = data.Pass + local sources = data.Sources + local target_dir = data.TargetDir + + local copies = {} + + -- all the copy operations will depend on all the incoming deps + for _, src in util.nil_ipairs(sources) do + local base_fn = select(2, path.split(src)) + local target = target_dir .. '/' .. base_fn + copies[#copies + 1] = files.copy_file(env, src, target, my_pass, deps) + end + + return depgraph.make_node { + Env = env, + Label = "Install group for " .. decl.Name, + Pass = my_pass, + Dependencies = copies + } +end + +nodegen.add_evaluator("Install", _mt, blueprint) diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/ispc.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/ispc.lua new file mode 100644 index 0000000..5c212b8 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/ispc.lua @@ -0,0 +1,25 @@ +-- ispc.lua - Support for Intel SPMD Program Compiler + +module(..., package.seeall) + +local path = require "tundra.path" + +DefRule { + Name = "ISPC", + Command = "$(ISPCCOM)", + + Blueprint = { + Source = { Required = true, Type = "string" }, + }, + + Setup = function (env, data) + local src = data.Source + local base_name = path.drop_suffix(src) + local objFile = "$(OBJECTDIR)$(SEP)" .. base_name .. "__" .. path.get_extension(src):sub(2) .. "$(OBJECTSUFFIX)" + local hFile = "$(OBJECTDIR)$(SEP)" .. base_name .. "_ispc.h" + return { + InputFiles = { src }, + OutputFiles = { objFile, hFile }, + } + end, +} diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/lemon.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/lemon.lua new file mode 100644 index 0000000..4cf173f --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/lemon.lua @@ -0,0 +1,27 @@ +-- lemon.lua - Support for the Lemon parser generator + +module(..., package.seeall) + +local path = require "tundra.path" + +DefRule { + Name = "Lemon", + Command = "lemon $(<)", + ConfigInvariant = true, + + Blueprint = { + Source = { Required = true, Type = "string" }, + }, + + Setup = function (env, data) + local src = data.Source + local base_name = path.drop_suffix(src) + local gen_c = base_name .. '.c' + local gen_h = base_name .. '.h' + local gen_out = base_name .. '.out' + return { + InputFiles = { src }, + OutputFiles = { gen_c, gen_h, gen_out }, + } + end, +} diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/native.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/native.lua new file mode 100644 index 0000000..e57c65c --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/native.lua @@ -0,0 +1,312 @@ +-- native.lua -- support for programs, static libraries and such + +module(..., package.seeall) + +local util = require "tundra.util" +local nodegen = require "tundra.nodegen" +local path = require "tundra.path" +local depgraph = require "tundra.depgraph" + +local _native_mt = nodegen.create_eval_subclass { + DeclToEnvMappings = { + Libs = "LIBS", + Defines = "CPPDEFS", + Includes = "CPPPATH", + Frameworks = "FRAMEWORKS", + LibPaths = "LIBPATH", + }, +} + +local _object_mt = nodegen.create_eval_subclass({ + Suffix = "$(OBJECTSUFFIX)", + Prefix = "", + Action = "$(OBJCOM)", + Label = "Object $(<)", + OverwriteOutputs = true, +}, _native_mt) + +local _program_mt = nodegen.create_eval_subclass({ + Suffix = "$(PROGSUFFIX)", + Prefix = "$(PROGPREFIX)", + Action = "$(PROGCOM)", + Label = "Program $(@)", + PreciousOutputs = true, + OverwriteOutputs = true, + Expensive = true, +}, _native_mt) + +local _staticlib_mt = nodegen.create_eval_subclass({ + Suffix = "$(LIBSUFFIX)", + Prefix = "$(LIBPREFIX)", + Action = "$(LIBCOM)", + Label = "StaticLibrary $(@)", + -- Play it safe and delete the output files of this node before re-running it. + -- Solves iterative issues with e.g. AR + OverwriteOutputs = false, + IsStaticLib = true, +}, _native_mt) + +local _objgroup_mt = nodegen.create_eval_subclass({ + Label = "ObjGroup $(<)", +}, _native_mt) + +local _shlib_mt = nodegen.create_eval_subclass({ + Suffix = "$(SHLIBSUFFIX)", + Prefix = "$(SHLIBPREFIX)", + Action = "$(SHLIBCOM)", + Label = "SharedLibrary $(@)", + PreciousOutputs = true, + OverwriteOutputs = true, + Expensive = true, +}, _native_mt) + +local _extlib_mt = nodegen.create_eval_subclass({ + Suffix = "", + Prefix = "", + Label = "", +}, _native_mt) + +local cpp_exts = util.make_lookup_table { ".cpp", ".cc", ".cxx", ".C" } + +local _is_native_mt = util.make_lookup_table { _object_mt, _program_mt, _staticlib_mt, _shlib_mt, _extlib_mt, _objgroup_mt } + +function _native_mt:customize_env(env, raw_data) + if env:get('GENERATE_PDB', '0') ~= '0' then + -- Figure out the final linked PDB (the one next to the dll or exe) + if raw_data.Target then + local target = env:interpolate(raw_data.Target) + local link_pdb = path.drop_suffix(target) .. '.pdb' + env:set('_PDB_LINK_FILE', link_pdb) + else + env:set('_PDB_LINK_FILE', "$(OBJECTDIR)/" .. raw_data.Name .. ".pdb") + end + -- Keep the compiler's idea of the PDB file separate + env:set('_PDB_CC_FILE', "$(OBJECTDIR)/" .. raw_data.Name .. "_ccpdb.pdb") + env:set('_USE_PDB_CC', '$(_USE_PDB_CC_OPT)') + env:set('_USE_PDB_LINK', '$(_USE_PDB_LINK_OPT)') + end + + local pch = raw_data.PrecompiledHeader + + if pch and env:get('_PCH_SUPPORTED', '0') ~= '0' then + assert(pch.Header) + if not nodegen.resolve_pass(pch.Pass) then + croak("%s: PrecompiledHeader requires a valid Pass", raw_data.Name) + end + env:set('_PCH_FILE', "$(OBJECTDIR)/" .. raw_data.Name .. ".pch") + env:set('_USE_PCH', '$(_USE_PCH_OPT)') + env:set('_PCH_SOURCE', path.normalize(pch.Source)) + env:set('_PCH_HEADER', pch.Header) + env:set('_PCH_PASS', pch.Pass) + if cpp_exts[path.get_extension(pch.Source)] then + env:set('PCHCOMPILE', '$(PCHCOMPILE_CXX)') + else + env:set('PCHCOMPILE', '$(PCHCOMPILE_CC)') + end + local pch_source = path.remove_prefix(raw_data.SourceDir or '', pch.Source) + if not util.array_contains(raw_data.Sources, pch_source) then + raw_data.Sources[#raw_data.Sources + 1] = pch_source + end + end + + if env:has_key('MODDEF') then + env:set('_USE_MODDEF', '$(_USE_MODDEF_OPT)') + end +end + +function _native_mt:create_dag(env, data, input_deps) + + local build_id = env:get("BUILD_ID") + local my_pass = data.Pass + local sources = data.Sources + local libsuffix = { env:get("LIBSUFFIX") } + local shlibsuffix = { env:get("SHLIBLINKSUFFIX") } + local my_extra_deps = {} + + -- Link with libraries in dependencies. + for _, dep in util.nil_ipairs(data.Depends) do + + if dep.Keyword == "SharedLibrary" then + -- On Win32 toolsets, we need foo.lib + -- On UNIX toolsets, we need -lfoo + -- + -- We only want to add this if the node actually produced any output (i.e + -- it's not completely filtered out.) + local node = dep:get_dag(env:get_parent()) + if #node.outputs > 0 then + my_extra_deps[#my_extra_deps + 1] = node + local target = dep.Decl.Target or dep.Decl.Name + target = target .. "$(SHLIBLINKSUFFIX)" + env:append('LIBS', target) + end + elseif dep.Keyword == "StaticLibrary" then + local node = dep:get_dag(env:get_parent()) + my_extra_deps[#my_extra_deps + 1] = node + if not self.IsStaticLib then + node:insert_output_files(sources, libsuffix) + end + elseif dep.Keyword == "ObjGroup" then + -- We want all .obj files + local objsuffix = { env:get("OBJECTSUFFIX") } + + -- And also .res files, if we know what that is + if env:has_key("W32RESSUFFIX") then + objsuffix[#objsuffix + 1] = env:get("W32RESSUFFIX") + end + + local node = dep:get_dag(env:get_parent()) + my_extra_deps[#my_extra_deps + 1] = node + if not sources then sources = {} end + for _, dep in util.nil_ipairs(node.deps) do + my_extra_deps[#my_extra_deps + 1] = dep + dep:insert_output_files(sources, objsuffix) + end + else + + --[[ + + A note about win32 import libraries: + + It is tempting to add an implicit input dependency on the import + library of the linked-to shared library here; but this would be + suboptimal: + + 1. Because there is a dependency between the nodes themselves, + the import library generation will always run before this link + step is run. Therefore, the import lib will always exist and be + updated before this link step runs. + + 2. Because the import library is regenerated whenever the DLL is + relinked we would have to custom-sign it (using a hash of the + DLLs export list) to avoid relinking the executable all the + time when only the DLL's internals change. + + 3. The DLL's export list should be available in headers anyway, + which is already covered in the compilation of the object files + that actually uses those APIs. + + Therefore the best way right now is to not tell Tundra about the + import lib at all and rely on header scanning to pick up API + changes. + + An implicit input dependency would be needed however if someone + is doing funky things with their import library (adding + non-linker-generated code for example). These cases are so rare + that we can safely put them off. + + ]]-- + end + end + + -- Make sure sources are not specified more than once. This can happen when + -- there are recursive dependencies on object groups. + if data.Sources and #data.Sources > 0 then + data.Sources = util.uniq(data.Sources) + end + + local aux_outputs = env:get_list("AUX_FILES_" .. self.Keyword:upper(), {}) + + if env:get('GENERATE_PDB', '0') ~= '0' then + aux_outputs[#aux_outputs + 1] = "$(_PDB_LINK_FILE)" + aux_outputs[#aux_outputs + 1] = "$(_PDB_CC_FILE)" + end + + local extra_inputs = {} + + if env:has_key('MODDEF') then + extra_inputs[#extra_inputs + 1] = "$(MODDEF)" + end + + local targets = nil + + if self.Action then + targets = { nodegen.get_target(data, self.Suffix, self.Prefix) } + end + + local deps = util.merge_arrays(input_deps, my_extra_deps) + + local dag = depgraph.make_node { + Env = env, + Label = self.Label, + Pass = data.Pass, + Action = self.Action, + PreAction = data.PreAction, + InputFiles = data.Sources, + InputFilesUntracked = data.UntrackedSources, + OutputFiles = targets, + AuxOutputFiles = aux_outputs, + ImplicitInputs = extra_inputs, + Dependencies = deps, + OverwriteOutputs = self.OverwriteOutputs, + PreciousOutputs = self.PreciousOutputs, + Expensive = self.Expensive, + } + + -- Remember this dag node for IDE file generation purposes + data.__DagNode = dag + + return dag +end + +local native_blueprint = { + Name = { + Required = true, + Help = "Set output (base) filename", + Type = "string", + }, + Sources = { + Required = true, + Help = "List of source files", + Type = "source_list", + ExtensionKey = "NATIVE_SUFFIXES", + }, + UntrackedSources = { + Help = "List of input files that are not tracked", + Type = "source_list", + ExtensionKey = "NATIVE_SUFFIXES", + }, + Target = { + Help = "Override target location", + Type = "string", + }, + PreAction = { + Help = "Optional action to run before main action.", + Type = "string", + }, + PrecompiledHeader = { + Help = "Enable precompiled header (if supported)", + Type = "table", + }, + IdeGenerationHints = { + Help = "Data to support control IDE file generation", + Type = "table", + }, +} + +local external_blueprint = { + Name = { + Required = true, + Help = "Set name of the external library", + Type = "string", + }, +} + +local external_counter = 1 + +function _extlib_mt:create_dag(env, data, input_deps) + local name = string.format("dummy node for %s (%d)", data.Name, external_counter) + external_counter = external_counter + 1 + return depgraph.make_node { + Env = env, + Label = name, + Pass = data.Pass, + Dependencies = input_deps, + } +end + +nodegen.add_evaluator("Object", _object_mt, native_blueprint) +nodegen.add_evaluator("Program", _program_mt, native_blueprint) +nodegen.add_evaluator("StaticLibrary", _staticlib_mt, native_blueprint) +nodegen.add_evaluator("SharedLibrary", _shlib_mt, native_blueprint) +nodegen.add_evaluator("ExternalLibrary", _extlib_mt, external_blueprint) +nodegen.add_evaluator("ObjGroup", _objgroup_mt, native_blueprint) diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/osx-bundle.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/osx-bundle.lua new file mode 100644 index 0000000..db1e052 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/osx-bundle.lua @@ -0,0 +1,93 @@ +-- osx-bundle.lua - Support for Max OS X bundles + +module(..., package.seeall) + +local nodegen = require "tundra.nodegen" +local files = require "tundra.syntax.files" +local path = require "tundra.path" +local util = require "tundra.util" +local depgraph = require "tundra.depgraph" + +_osx_bundle_mt = nodegen.create_eval_subclass { } +_compile_nib_mt = nodegen.create_eval_subclass { } + +function _osx_bundle_mt:create_dag(env, data, deps) + local bundle_dir = data.Target + local pass = data.Pass + local contents = bundle_dir .. "/Contents" + local copy_deps = {} + + local infoplist = data.InfoPList + copy_deps[#copy_deps+1] = files.hardlink_file(env, data.InfoPList, contents .. "/Info.plist", pass, deps) + + if data.PkgInfo then + copy_deps[#copy_deps+1] = files.hardlink_file(env, data.PkgInfo, contents .. "/PkgInfo", pass, deps) + end + + if data.Executable then + local basename = select(2, path.split(data.Executable)) + copy_deps[#copy_deps+1] = files.hardlink_file(env, data.Executable, contents .. "/MacOS/" .. basename, pass, deps) + end + + local dirs = { + { Tag = "Resources", Dir = contents .. "/Resources/" }, + { Tag = "MacOSFiles", Dir = contents .. "/MacOS/" }, + } + + for _, params in ipairs(dirs) do + local function do_copy(fn) + local basename = select(2, path.split(fn)) + copy_deps[#copy_deps+1] = files.hardlink_file(env, fn, params.Dir .. basename, pass, deps) + end + + local items = data[params.Tag] + for _, dep in util.nil_ipairs(nodegen.flatten_list(env:get('BUILD_ID'), items)) do + if type(dep) == "string" then + do_copy(dep) + else + local node = dep:get_dag(env) + print(node) + deps[#deps+1] = node + local files = {} + node:insert_output_files(files) + for _, fn in ipairs(files) do + do_copy(fn) + end + end + end + end + + return depgraph.make_node { + Env = env, + Pass = pass, + Label = "OsxBundle " .. data.Target, + Dependencies = util.merge_arrays_2(deps, copy_deps), + } +end + +function _compile_nib_mt:create_dag(env, data, deps) + return depgraph.make_node { + Env = env, + Pass = data.Pass, + Label = "CompileNib $(@)", + Action = "$(NIBCC)", + Dependencies = deps, + InputFiles = { data.Source }, + OutputFiles = { "$(OBJECTDIR)/" .. data.Target }, + } +end + +nodegen.add_evaluator("OsxBundle", _osx_bundle_mt, { + Target = { Type = "string", Required = true, Help = "Target .app directory name" }, + Executable = { Type = "string", Help = "Executable to embed" }, + InfoPList = { Type = "string", Required = true, Help = "Info.plist file" }, + PkgInfo = { Type = "string", Help = "PkgInfo file" }, + Resources = { Type = "filter_table", Help = "Files to copy to 'Resources'" }, + MacOSFiles = { Type = "filter_table", Help = "Files to copy to 'MacOS'" }, +}) + +nodegen.add_evaluator("CompileNib", _compile_nib_mt, { + Source = { Type = "string", Required = true }, + Target = { Type = "string", Required = true }, +}) + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/pkgconfig.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/pkgconfig.lua new file mode 100644 index 0000000..485291c --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/pkgconfig.lua @@ -0,0 +1,51 @@ +module(..., package.seeall) + +function ConfigureRaw(cmdline, name, constructor) + local fh = assert(io.popen(cmdline)) + local data = fh:read("*all") + fh:close() + + local cpppath = {} + local libpath = {} + local libs = {} + local defines = {} + local frameworks = {} + + for kind, value in data:gmatch("-([ILlD])([^ \n\r]+)") do + if kind == "I" then + cpppath[#cpppath + 1] = value + elseif kind == "D" then + defines[#defines + 1] = value + elseif kind == "L" then + libpath[#libpath + 1] = value + elseif kind == "l" then + libs[#libs + 1] = value + end + end + + for value in data:gmatch("-framework ([^ \n\r]+)") do + frameworks[#frameworks + 1] = value + end + + -- We don't have access to ExternalLibrary here - user has to pass it in. + return constructor({ + Name = name, + Propagate = { + Env = { + FRAMEWORKS = frameworks, + CPPDEFS = defines, + CPPPATH = cpppath, + LIBS = libs, + LIBPATH = libpath + } + } + }) +end + +function Configure(name, ctor) + return internal_cfg("pkg-config " .. name .. " --cflags --libs", name, ctor) +end + +function ConfigureWithTool(tool, name, ctor) + return internal_cfg(tool .. " --cflags --libs", name, ctor) +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/testsupport.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/testsupport.lua new file mode 100644 index 0000000..d79dd0f --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/syntax/testsupport.lua @@ -0,0 +1,29 @@ +-- testsupport.lua: A simple UpperCaseFile unit used for Tundra's test harness + +module(..., package.seeall) + +local util = require 'tundra.util' +local nodegen = require 'tundra.nodegen' +local depgraph = require 'tundra.depgraph' + +local mt = nodegen.create_eval_subclass {} + +function mt:create_dag(env, data, deps) + return depgraph.make_node { + Env = env, + Pass = data.Pass, + Label = "UpperCaseFile \$(@)", + Action = "tr a-z A-Z < \$(<) > \$(@)", + InputFiles = { data.InputFile }, + OutputFiles = { data.OutputFile }, + Dependencies = deps, + } +end + +nodegen.add_evaluator("UpperCaseFile", mt, { + Name = { Type = "string", Required = "true" }, + InputFile = { Type = "string", Required = "true" }, + OutputFile = { Type = "string", Required = "true" }, +}) + + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/test/t_env.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/test/t_env.lua new file mode 100644 index 0000000..1209e91 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/test/t_env.lua @@ -0,0 +1,62 @@ + +module(..., package.seeall) + +unit_test('scalar interpolation', function (t) + local e = require 'tundra.environment' + local e1, e2, e3 + e1 = e.create(nil, { Foo="Foo", Baz="Strut" }) + e2 = e1:clone({ Foo="Bar" }) + e3 = e1:clone({ Baz="c++" }) + + t:check_equal(e1:get("Foo"), "Foo") + t:check_equal(e1:get("Baz"), "Strut") + t:check_equal(e2:get("Foo"), "Bar") + t:check_equal(e2:get("Baz"), "Strut") + t:check_equal(e3:get("Fransos", "Ost"), "Ost") + + e1:set("Foo", "Foo") + t:check_equal(e1:interpolate("$(Foo)"), "Foo") + t:check_equal(e1:interpolate("$(Foo:u)"), "FOO") + t:check_equal(e1:interpolate("$(Foo:l)"), "foo") + + t:check_equal(e1:interpolate("$(Foo) $(Baz)"), "Foo Strut") + t:check_equal(e2:interpolate("$(Foo) $(Baz)"), "Bar Strut") + t:check_equal(e3:interpolate("$(Foo) $(Baz)"), "Foo c++") + t:check_equal(e1:interpolate("a $(<)", { ['<'] = "foo" }), "a foo") + + e1:set("FILE", "foo/bar.txt") + t:check_equal(e1:interpolate("$(FILE:B)"), "foo/bar") + t:check_equal(e1:interpolate("$(FILE:F)"), "bar.txt") + t:check_equal(e1:interpolate("$(FILE:D)"), "foo") +end) + +unit_test('list interpolation', function (t) + local e = require 'tundra.environment' + local e1 = e.create() + + e1:set("Foo", { "Foo" }) + t:check_equal(e1:interpolate("$(Foo)"), "Foo") + + e1:set("Foo", { "Foo", "Bar" } ) + t:check_equal(e1:interpolate("$(Foo)") , "Foo Bar") + t:check_equal(e1:interpolate("$(Foo:j,)"), "Foo,Bar") + t:check_equal(e1:interpolate("$(Foo:p!)") , "!Foo !Bar") + t:check_equal(e1:interpolate("$(Foo:a!)") , "Foo! Bar!") + t:check_equal(e1:interpolate("$(Foo:p-I:j__)") , "-IFoo__-IBar") + t:check_equal(e1:interpolate("$(Foo:j\\:)"), "Foo:Bar") + t:check_equal(e1:interpolate("$(Foo:u)"), "FOO BAR") + t:check_equal(e1:interpolate("$(Foo:[2])"), "Bar") + t:check_equal(e1:interpolate("$(Foo:Aoo)"), "Foo Baroo") + t:check_equal(e1:interpolate("$(Foo:PF)"), "Foo FBar") + + local lookaside = { + ['@'] = 'output', + ['<'] = { 'a', 'b' }, + } + + t:check_equal(e1:interpolate("$(Foo) $(<)=$(@)", lookaside), "Foo Bar a b=output") + + -- Verify interpolation caching is cleared when keys change. + e1:set("Foo", { "Baz" }) + t:check_equal(e1:interpolate("$(Foo) $(<)=$(@)", lookaside), "Baz a b=output") +end) diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/test/t_path.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/test/t_path.lua new file mode 100644 index 0000000..087bb99 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/test/t_path.lua @@ -0,0 +1,81 @@ +module(..., package.seeall) + +local path = require "tundra.path" +local native = require "tundra.native" + +local function check_path(t, p, expected) + p = p:gsub('\\', '/') + t:check_equal(p, expected) +end + +unit_test('path.normalize', function (t) + check_path(t, path.normalize("foo"), "foo") + check_path(t, path.normalize("foo/bar"), "foo/bar") + check_path(t, path.normalize("foo//bar"), "foo/bar") + check_path(t, path.normalize("foo/./bar"), "foo/bar") + check_path(t, path.normalize("foo/../bar"), "bar") + check_path(t, path.normalize("../bar"), "../bar") + check_path(t, path.normalize("foo/../../bar"), "../bar") +end) + +unit_test('path.join', function (t) + check_path(t, path.join("foo", "bar"), "foo/bar") + check_path(t, path.join("foo", "../bar"), "bar") + check_path(t, path.join("/foo", "bar"), "/foo/bar") +end) + +unit_test('path.split', function (t) + local function check_split(p, expected_dir, expected_fn) + local dir, fn = path.split(p) + dir = dir:gsub('\\', '/') + fn = fn:gsub('\\', '/') + t:check_equal(dir, expected_dir) + t:check_equal(fn, expected_fn) + end + + check_split("", ".", "") + check_split("foo", ".", "foo") + check_split("foo/bar", "foo", "bar") + check_split("/foo/bar", "/foo", "bar") + check_split("x:\\foo\\bar", "x:/foo", "bar") +end) + + +unit_test('path.get_filename_dir', function (t) + t:check_equal(path.get_filename_dir("foo/bar"), "foo") + t:check_equal(path.get_filename_dir("foo"), "") +end) + +unit_test('path.get_filename', function (t) + t:check_equal(path.get_filename("foo/bar"), "bar") + t:check_equal(path.get_filename("foo"), "foo") +end) + +unit_test('path.get_extension', function (t) + t:check_equal(path.get_extension("foo"), "") + t:check_equal(path.get_extension("foo."), ".") + t:check_equal(path.get_extension("foo.c"), ".c") + t:check_equal(path.get_extension("foo/bar/.c"), ".c") + t:check_equal(path.get_extension("foo/bar/baz.cpp"), ".cpp") +end) + +unit_test('path.drop_suffix', function (t) + t:check_equal(path.drop_suffix("foo.c"), "foo") + t:check_equal(path.drop_suffix("foo/bar.c"), "foo/bar") + t:check_equal(path.drop_suffix("/foo/bar.c"), "/foo/bar") +end) + +unit_test('path.get_filename_base', function (t) + t:check_equal(path.get_filename_base("foo1"), "foo1") + t:check_equal(path.get_filename_base("foo2.c"), "foo2") + t:check_equal(path.get_filename_base("/path/to/foo3"), "foo3") + t:check_equal(path.get_filename_base("/path/to/foo4.c"), "foo4") +end) + +unit_test('path.is_absolute', function (t) + t:check_equal(path.is_absolute("/foo") and "true" or "false", "true") + t:check_equal(path.is_absolute("foo") and "true" or "false", "false") + if native.host_platform == "windows" then + t:check_equal(path.is_absolute("x:\\foo") and "true" or "false", "true") + end +end) diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/clang-osx.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/clang-osx.lua new file mode 100644 index 0000000..bd5b66b --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/clang-osx.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply(env, options) + tundra.unitgen.load_toolset("gcc-osx", env) + + env:set_many { + ["CC"] = "clang", + ["CXX"] = "clang++", + ["LD"] = "clang", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/dotnet.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/dotnet.lua new file mode 100644 index 0000000..e669e25 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/dotnet.lua @@ -0,0 +1,28 @@ +module(..., package.seeall) + +local frameworkDir = "c:\\Windows\\Microsoft.NET\\Framework" +local defaultFrameworkVersion = "v3.5" + +function apply(env, options) + tundra.unitgen.load_toolset("generic-dotnet", env) + + local version = options and assert(options.Version) or defaultFrameworkVersion + env:set_external_env_var('FrameworkDir', frameworkDir) + env:set_external_env_var('FrameworkVersion', version) + + local binPath = frameworkDir .. "\\" .. version + env:set_external_env_var('PATH', binPath .. ";" .. env:get_external_env_var('PATH')) + + -- C# support + env:set_many { + ["DOTNET_SUFFIXES"] = { ".cs" }, + ["DOTNET_SUFFIXES_RESOURCE"] = { ".resource" }, + ["CSC"] = "csc.exe", + ["CSPROGSUFFIX"] = ".exe", + ["CSLIBSUFFIX"] = ".dll", + ["CSRESGEN"] = "resgen $(<) $(@)", + ["_CSC_COMMON"] = "-warn:$(CSC_WARNING_LEVEL) /nologo $(CSLIBPATH:b:p/lib\\:) $(CSRESOURCES:b:p/resource\\:) $(CSLIBS:p/reference\\::A.dll)", + ["CSCLIBCOM"] = "$(CSC) $(_CSC_COMMON) $(CSCOPTS) -target:library -out:$(@:b) $(<:b)", + ["CSCEXECOM"] = "$(CSC) $(_CSC_COMMON) $(CSCOPTS) -target:exe -out:$(@:b) $(<:b)", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/gas.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/gas.lua new file mode 100644 index 0000000..59a655c --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/gas.lua @@ -0,0 +1,12 @@ +module(..., package.seeall) + +function apply(env, options) + -- load the generic assembly toolset first + tundra.unitgen.load_toolset("generic-asm", env) + + env:set_many { + ["ASM"] = "as", + ["ASMCOM"] = "$(ASM) -o $(@) $(ASMDEFS:p-D) $(ASMOPTS) $(<)", + ["ASMINC_KEYWORDS"] = { ".include" }, + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/gcc-osx.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/gcc-osx.lua new file mode 100644 index 0000000..fb12ddd --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/gcc-osx.lua @@ -0,0 +1,19 @@ +module(..., package.seeall) + +function apply(env, options) + -- load the generic GCC toolset first + tundra.unitgen.load_toolset("gcc", env) + + env:set_many { + ["NATIVE_SUFFIXES"] = { ".c", ".cpp", ".cc", ".cxx", ".m", ".mm", ".a", ".o" }, + ["CXXEXTS"] = { "cpp", "cxx", "cc", "mm" }, + ["FRAMEWORKS"] = "", + ["SHLIBPREFIX"] = "lib", + ["SHLIBOPTS"] = "-shared", + ["_OS_CCOPTS"] = "$(FRAMEWORKS:p-F)", + ["SHLIBCOM"] = "$(LD) $(SHLIBOPTS) $(LIBPATH:p-L) $(LIBS:p-l) $(FRAMEWORKS:p-framework ) -o $(@) $(<)", + ["PROGCOM"] = "$(LD) $(PROGOPTS) $(LIBPATH:p-L) $(LIBS:p-l) $(FRAMEWORKS:p-framework ) -o $(@) $(<)", + ["OBJCCOM"] = "$(CCCOM)", -- objc uses same commandline + ["NIBCC"] = "ibtool --output-format binary1 --compile $(@) $(<)", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/gcc.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/gcc.lua new file mode 100644 index 0000000..7849750 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/gcc.lua @@ -0,0 +1,30 @@ +module(..., package.seeall) + +function apply(env, options) + -- load the generic C toolset first + tundra.unitgen.load_toolset("generic-cpp", env) + + env:set_many { + ["NATIVE_SUFFIXES"] = { ".c", ".cpp", ".cc", ".cxx", ".a", ".o" }, + ["OBJECTSUFFIX"] = ".o", + ["LIBPREFIX"] = "lib", + ["LIBSUFFIX"] = ".a", + ["_GCC_BINPREFIX"] = "", + ["CC"] = "$(_GCC_BINPREFIX)gcc", + ["CXX"] = "$(_GCC_BINPREFIX)g++", + ["LIB"] = "$(_GCC_BINPREFIX)ar", + ["LD"] = "$(_GCC_BINPREFIX)gcc", + ["_OS_CCOPTS"] = "", + ["_OS_CXXOPTS"] = "", + ["CCCOM"] = "$(CC) $(_OS_CCOPTS) -c $(CPPDEFS:p-D) $(CPPPATH:f:p-I) $(CCOPTS) $(CCOPTS_$(CURRENT_VARIANT:u)) -o $(@) $(<)", + ["CXXCOM"] = "$(CXX) $(_OS_CXXOPTS) -c $(CPPDEFS:p-D) $(CPPPATH:f:p-I) $(CXXOPTS) $(CXXOPTS_$(CURRENT_VARIANT:u)) -o $(@) $(<)", + ["PROGOPTS"] = "", + ["PROGCOM"] = "$(LD) $(PROGOPTS) $(LIBPATH:p-L) -o $(@) $(<) $(LIBS:p-l)", + ["PROGPREFIX"] = "", + ["LIBOPTS"] = "", + ["LIBCOM"] = "$(LIB) -rs $(LIBOPTS) $(@) $(<)", + ["SHLIBPREFIX"] = "lib", + ["SHLIBOPTS"] = "-shared", + ["SHLIBCOM"] = "$(LD) $(SHLIBOPTS) $(LIBPATH:p-L) -o $(@) $(<) $(LIBS:p-l)", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/generic-asm.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/generic-asm.lua new file mode 100644 index 0000000..e542eee --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/generic-asm.lua @@ -0,0 +1,78 @@ +module(..., package.seeall) + +local path = require "tundra.path" +local util = require "tundra.util" +local boot = require "tundra.boot" +local scanner = require "tundra.scanner" +local depgraph = require "tundra.depgraph" + +local default_keywords = { "include" } +local default_bin_keywords = { "incbin" } + +local function get_asm_scanner(env, fn) + local function test_bool(name, default) + val = env:get(name, default) + if val == "yes" or val == "true" or val == "1" then + return 1 + else + return 0 + end + end + local function new_scanner() + local paths = util.map(env:get_list("ASMINCPATH"), function (v) return env:interpolate(v) end) + local data = { + Paths = paths, + Keywords = env:get_list("ASMINC_KEYWORDS", default_keywords), + KeywordsNoFollow = env:get_list("ASMINC_BINARY_KEYWORDS", default_bin_keywords), + RequireWhitespace = test_bool("ASMINC_REQUIRE_WHITESPACE", "yes"), + UseSeparators = test_bool("ASMINC_USE_SEPARATORS", "yes"), + BareMeansSystem = test_bool("ASMINC_BARE_MEANS_SYSTEM", "no"), + } + return scanner.make_generic_scanner(data) + end + return env:memoize("ASMINCPATH", "_asm_scanner", new_scanner) +end + +-- Register implicit make functions for assembly files. +-- These functions are called to transform source files in unit lists into +-- object files. This function is registered as a setup function so it will be +-- run after user modifications to the environment, but before nodes are +-- processed. This way users can override the extension lists. +local function generic_asm_setup(env) + local _assemble = function(env, pass, fn) + local object_fn = path.make_object_filename(env, fn, '$(OBJECTSUFFIX)') + + return depgraph.make_node { + Env = env, + Label = 'Asm $(@)', + Pass = pass, + Action = "$(ASMCOM)", + InputFiles = { fn }, + OutputFiles = { object_fn }, + Scanner = get_asm_scanner(env, fn), + } + end + + for _, ext in ipairs(env:get_list("ASM_EXTS")) do + env:register_implicit_make_fn(ext, _assemble) + end +end + +function apply(_outer_env, options) + + _outer_env:add_setup_function(generic_asm_setup) + + _outer_env:set_many { + ["ASM_EXTS"] = { ".s", ".asm" }, + ["ASMINCPATH"] = {}, + ["ASMDEFS"] = "", + ["ASMDEFS_DEBUG"] = "", + ["ASMDEFS_PRODUCTION"] = "", + ["ASMDEFS_RELEASE"] = "", + ["ASMOPTS"] = "", + ["ASMOPTS_DEBUG"] = "", + ["ASMOPTS_PRODUCTION"] = "", + ["ASMOPTS_RELEASE"] = "", + } +end + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/generic-cpp.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/generic-cpp.lua new file mode 100644 index 0000000..548a61c --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/generic-cpp.lua @@ -0,0 +1,103 @@ +module(..., package.seeall) + +local nodegen = require "tundra.nodegen" +local boot = require "tundra.boot" +local util = require "tundra.util" +local path = require "tundra.path" +local scanner = require "tundra.scanner" +local depgraph = require "tundra.depgraph" + +local scanner_cache = {} + +function get_cpp_scanner(env, fn) + local paths = util.map(env:get_list("CPPPATH"), function (v) return env:interpolate(v) end) + return scanner.make_cpp_scanner(paths) +end + +-- Register implicit make functions for C, C++ and Objective-C files. +-- These functions are called to transform source files in unit lists into +-- object files. This function is registered as a setup function so it will be +-- run after user modifications to the environment, but before nodes are +-- processed. This way users can override the extension lists. +local function generic_cpp_setup(env) + local _anyc_compile = function(env, pass, fn, label, action) + local object_fn = path.make_object_filename(env, fn, '$(OBJECTSUFFIX)') + + local output_files = { object_fn } + + local pch_source = env:get('_PCH_SOURCE', '') + local implicit_inputs = nil + + if fn == pch_source then + + label = 'Precompiled header' + pass = nodegen.resolve_pass(env:get('_PCH_PASS', '')) + action = "$(PCHCOMPILE)" + output_files = { "$(_PCH_FILE)", object_fn } + + elseif pch_source ~= '' and fn ~= pch_source then + + -- It would be good to make all non-pch source files dependent upon the .pch node. + -- That would require that we generate the .pch node before generating these nodes. + -- As it stands presently, when .pch compilation fails, the remaining sources + -- fail to compile, but if the dependencies were correctly setup, then they wouldn't + -- even try to compile. + + end + + return depgraph.make_node { + Env = env, + Label = label .. ' $(<)', + Pass = pass, + Action = action, + InputFiles = { fn }, + OutputFiles = output_files, + ImplicitInputs = implicit_inputs, + Scanner = get_cpp_scanner(env, fn), + } + end + + local mappings = { + ["CCEXTS"] = { Label="Cc", Action="$(CCCOM)" }, + ["CXXEXTS"] = { Label="C++", Action="$(CXXCOM)" }, + ["OBJCEXTS"] = { Label="ObjC", Action="$(OBJCCOM)" }, + } + + for key, setup in pairs(mappings) do + for _, ext in ipairs(env:get_list(key)) do + env:register_implicit_make_fn(ext, function(env, pass, fn) + return _anyc_compile(env, pass, fn, setup.Label, setup.Action) + end) + end + end +end + +function apply(_outer_env, options) + + _outer_env:add_setup_function(generic_cpp_setup) + + _outer_env:set_many { + ["IGNORED_AUTOEXTS"] = { ".h", ".hpp", ".hh", ".hxx", ".inl" }, + ["CCEXTS"] = { "c" }, + ["CXXEXTS"] = { "cpp", "cxx", "cc" }, + ["OBJCEXTS"] = { "m" }, + ["PROGSUFFIX"] = "$(HOSTPROGSUFFIX)", + ["SHLIBSUFFIX"] = "$(HOSTSHLIBSUFFIX)", + ["CPPPATH"] = "", + ["CPPDEFS"] = "", + ["LIBS"] = "", + ["LIBPATH"] = "$(OBJECTDIR)", + ["CCOPTS"] = "", + ["CXXOPTS"] = "", + ["CPPDEFS_DEBUG"] = "", + ["CPPDEFS_PRODUCTION"] = "", + ["CPPDEFS_RELEASE"] = "", + ["CCOPTS_DEBUG"] = "", + ["CCOPTS_PRODUCTION"] = "", + ["CCOPTS_RELEASE"] = "", + ["CXXOPTS_DEBUG"] = "", + ["CXXOPTS_PRODUCTION"] = "", + ["CXXOPTS_RELEASE"] = "", + ["SHLIBLINKSUFFIX"] = "", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/generic-dotnet.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/generic-dotnet.lua new file mode 100644 index 0000000..2385440 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/generic-dotnet.lua @@ -0,0 +1,17 @@ +module(..., package.seeall) + +local function generic_dotnet_setup(env) + +end + +function apply(env, options) + env:add_setup_function(generic_dotnet_setup) + + env:set_many { + ["CSLIBS"] = "", -- assembly references + ["CSLIBPATH"] = {}, -- assembly directories + ["CSCOPTS"] = "-optimize", + ["CSRESOURCES"] = "", + ["CSC_WARNING_LEVEL"] = "4", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/ispc.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/ispc.lua new file mode 100644 index 0000000..3f0b1ea --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/ispc.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply(env, options) + env:set_many { + ["ISPC_SUFFIXES"] = { ".ispc", }, + ["ISPC"] = "ispc", + ["ISPCOPTS"] = "", + ["ISPCCOM"] = "$(ISPC) $(ISPCOPTS) -o $(@:[1]) -h $(@:[2]) $(<)", + } +end + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/mingw.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/mingw.lua new file mode 100644 index 0000000..4aeff36 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/mingw.lua @@ -0,0 +1,24 @@ +module(..., package.seeall) + +function apply(env, options) + + -- load the generic GCC toolset first + tundra.unitgen.load_toolset("gcc", env) + + -- load support for win32 resource compilation + tundra.unitgen.load_toolset("win32-rc", env) + + env:set_many { + ["NATIVE_SUFFIXES"] = { ".c", ".cpp", ".cc", ".cxx", ".a", ".o", ".rc" }, + ["OBJECTSUFFIX"] = ".o", + ["LIBPREFIX"] = "", + ["LIBSUFFIX"] = ".a", + ["W32RESSUFFIX"] = ".o", + ["CPPDEFS"] = "_WIN32", + ["_CPPDEFS"] = "$(CPPDEFS:p/D) $(CPPDEFS_$(CURRENT_VARIANT:u):p/D)", + ["RC"] = "windres", + ["RCOPTS"] = "", + ["RCCOM"] = "$(RC) $(RCOPTS) --output=$(@:b) $(CPPPATH:b:p-I) --input=$(<:b)", + ["SHLIBLINKSUFFIX"] = ".a", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/mono.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/mono.lua new file mode 100644 index 0000000..553a1cd --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/mono.lua @@ -0,0 +1,17 @@ +module(..., package.seeall) + +function apply(env, options) + tundra.unitgen.load_toolset("generic-dotnet", env) + + env:set_many { + ["DOTNET_SUFFIXES"] = { ".cs" }, + ["DOTNET_SUFFIXES_RESOURCE"] = { ".resource" }, + ["CSC"] = "gmcs", + ["CSPROGSUFFIX"] = ".exe", + ["CSLIBSUFFIX"] = ".dll", + ["CSRESGEN"] = "resgen2 $(<) $(@)", + ["_CSC_COMMON"] = "-warn:$(CSC_WARNING_LEVEL) /nologo $(CSLIBPATH:p-lib\\:) $(CSRESOURCES:p-resource\\:) $(CSLIBS:p-reference\\::A.dll)", + ["CSCLIBCOM"] = "$(CSC) $(_CSC_COMMON) $(CSCOPTS) -target:library -out:$(@) $(<)", + ["CSCEXECOM"] = "$(CSC) $(_CSC_COMMON) $(CSCOPTS) -target:exe -out:$(@) $(<)", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vs2008.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vs2008.lua new file mode 100644 index 0000000..99edf0f --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vs2008.lua @@ -0,0 +1,8 @@ + +module(..., package.seeall) + +local vscommon = require "tundra.tools.msvc-vscommon" + +function apply(env, options) + vscommon.apply_msvc_visual_studio("9.0", env, options) +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vs2010.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vs2010.lua new file mode 100644 index 0000000..5c1ef88 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vs2010.lua @@ -0,0 +1,8 @@ + +module(..., package.seeall) + +local vscommon = require "tundra.tools.msvc-vscommon" + +function apply(env, options) + vscommon.apply_msvc_visual_studio("10.0", env, options) +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vs2012.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vs2012.lua new file mode 100644 index 0000000..6c9d60a --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vs2012.lua @@ -0,0 +1,8 @@ + +module(..., package.seeall) + +local vscommon = require "tundra.tools.msvc-vscommon" + +function apply(env, options) + vscommon.apply_msvc_visual_studio("11.0", env, options) +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vs2013.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vs2013.lua new file mode 100644 index 0000000..6abddda --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vs2013.lua @@ -0,0 +1,8 @@ + +module(..., package.seeall) + +local vscommon = require "tundra.tools.msvc-vscommon" + +function apply(env, options) + vscommon.apply_msvc_visual_studio("12.0", env, options) +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vscommon.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vscommon.lua new file mode 100644 index 0000000..5fad59c --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vscommon.lua @@ -0,0 +1,267 @@ +-- msvc-vscommon.lua - utility code for all versions of Visual Studio + +module(..., package.seeall) + +local native = require "tundra.native" +local os = require "os" + +-- Visual Studio tooling layout + +local vc_bin_map = { + ["x86"] = { + ["x86"] = "", + ["x64"] = "x86_amd64", + ["arm"] = "x86_arm", + }, + ["x64"] = { + ["x86"] = "", + ["x64"] = "amd64", + ["arm"] = "x86_arm", -- is this really legal? + }, +} + +local vc_lib_map = { + ["x86"] = { + ["x86"] = "", + ["x64"] = "amd64", + ["arm"] = "arm", + }, + ["x64"] = { + ["x86"] = "", + ["x64"] = "amd64", + ["arm"] = "arm", + }, +} + +-- Windows SDK layout + +local pre_win8_sdk_dir = { + ["bin"] = "bin", + ["include"] = "include", + ["lib"] = "lib", +} + +local win8_sdk_dir = { + ["bin"] = "bin", + ["include"] = "include", + ["lib"] = "lib\\win8\\um", +} + +local win81_sdk_dir = { + ["bin"] = "bin", + ["include"] = "include", + ["lib"] = "lib\\winv6.3\\um", +} + +local pre_win8_sdk = { + ["x86"] = { + ["bin"] = "", + ["include"] = "", + ["lib"] = "", + }, + ["x64"] = { + ["bin"] = "x64", + ["include"] = "", + ["lib"] = "x64", + }, +} + +local post_win8_sdk = { + ["x86"] = { + ["bin"] = "x86", + ["include"] = { "shared", "um" }, + ["lib"] = "x86", + }, + ["x64"] = { + ["bin"] = "x64", + ["include"] = { "shared", "um" }, + ["lib"] = "x64", + }, + ["arm"] = { + ["bin"] = "arm", + ["include"] = { "shared", "um" }, + ["lib"] = "arm", + }, +} + +-- Each quadruplet specifies a registry key value that gets us the SDK location, +-- followed by a folder structure (for each supported target architecture) +-- and finally the corresponding bin, include and lib folder's relative location + +local sdk_map = { + ["9.0"] = { "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v6.0A", "InstallationFolder", pre_win8_sdk_dir, pre_win8_sdk }, + ["10.0"] = { "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v7.0A", "InstallationFolder", pre_win8_sdk_dir, pre_win8_sdk }, + ["11.0"] = { "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot", win8_sdk_dir, post_win8_sdk }, + ["12.0"] = { "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot81", win81_sdk_dir, post_win8_sdk }, +} + +local function get_host_arch() + local snative = native.getenv("PROCESSOR_ARCHITECTURE") + local swow = native.getenv("PROCESSOR_ARCHITEW6432", "") + if snative == "AMD64" or swow == "AMD64" then + return "x64" + elseif snative == "IA64" or swow == "IA64" then + return "itanium"; + else + return "x86" + end +end + +function path_combine(path, path_to_append) + if path == nil then + return path_to_append + end + if path:find("\\$") then + return path .. path_to_append + end + return path .. "\\" .. path_to_append +end + +function path_it(maybe_list) + if type(maybe_list) == "table" then + return ipairs(maybe_list) + end + return ipairs({maybe_list}) +end + +function apply_msvc_visual_studio(version, env, options) + + -- NOTE: don't make changes to `env` until you've asserted + -- that the requested version is in fact installed, + -- the `vs-wild` toolset will call this function + -- repeatedly with a the next version but the same `env`, + -- if a version fails (assert/error) + + if native.host_platform ~= "windows" then + error("the msvc toolset only works on windows hosts") + end + + -- Load basic MSVC environment setup first. + -- We're going to replace the paths to some tools. + tundra.unitgen.load_toolset('msvc', env) + + options = options or {} + + local target_arch = options.TargetArch or "x86" + local host_arch = options.HostArch or get_host_arch() + local sdk_version = options.SdkVersion or version -- we identify SDKs by VS version and fallback to current version + + -- We'll find any edition of VS (including Express) here + local vs_root = native.reg_query("HKLM", "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", version) + assert(vs_root, "The requested version of Visual Studio isn't installed") + vs_root = string.gsub(vs_root, "\\+$", "\\") + + local vc_lib + local vc_bin + + vc_bin = vc_bin_map[host_arch][target_arch] + if not vc_bin then + errorf("can't build target arch %s on host arch %s", target_arch, host_arch) + end + vc_bin = vs_root .. "vc\\bin\\" .. vc_bin + + vc_lib = vs_root .. "vc\\lib\\" .. vc_lib_map[host_arch][target_arch] + + -- + -- Now fix up the SDK + -- + + local sdk_root + local sdk_bin + local sdk_include = {} + local sdk_lib + + local sdk = sdk_map[sdk_version] + assert(sdk, "The requested version of Visual Studio isn't supported") + + sdk_root = native.reg_query("HKLM", sdk[1], sdk[2]) + assert(sdk_root, "The requested version of the SDK isn't installed") + sdk_root = string.gsub(sdk_root, "\\+$", "\\") + + local sdk_dir_base = sdk[3] + + local sdk_dir = sdk[4][target_arch] + assert(sdk_dir, "The target platform architecture isn't supported by the SDK") + + sdk_bin = sdk_root .. sdk_dir_base["bin"] .. "\\" .. sdk_dir["bin"] + + local sdk_dir_base_include = sdk_dir_base["include"] + for _, v in path_it(sdk_dir["include"]) do + sdk_include[#sdk_include + 1] = sdk_root .. sdk_dir_base_include .. "\\" .. v + end + + sdk_lib = sdk_root .. sdk_dir_base["lib"] .. "\\" .. sdk_dir["lib"] + + -- + -- Tools + -- + + local cl_exe = '"' .. path_combine(vc_bin, "cl.exe") .. '"' + local lib_exe = '"' .. path_combine(vc_bin, "lib.exe") .. '"' + local link_exe = '"' .. path_combine(vc_bin, "link.exe") .. '"' + local rc_exe = '"' .. path_combine(sdk_bin, "rc.exe") .. '"' -- pickup the Resource Compiler from the SDK + + env:set('CC', cl_exe) + env:set('CXX', cl_exe) + env:set('LIB', lib_exe) + env:set('LD', link_exe) + env:set('RC', rc_exe) + + if sdk_version == "9.0" then + env:set("RCOPTS", "") -- clear the "/nologo" option (it was first added in VS2010) + end + + if version == "12.0" then + -- Force MSPDBSRV.EXE + env:set("CCOPTS", "/FS") + env:set("CXXOPTS", "/FS") + end + + -- Wire-up the external environment + + env:set_external_env_var('VSINSTALLDIR', vs_root) + env:set_external_env_var('VCINSTALLDIR', vs_root .. "\\vc") + env:set_external_env_var('DevEnvDir', vs_root .. "Common7\\IDE") + + local include = {} + + for _, v in ipairs(sdk_include) do + include[#include + 1] = v + end + + include[#include + 1] = vs_root .. "VC\\ATLMFC\\INCLUDE" + include[#include + 1] = vs_root .. "VC\\INCLUDE" + + env:set_external_env_var("WindowsSdkDir", sdk_root) + env:set_external_env_var("INCLUDE", table.concat(include, ';')) + + -- if MFC isn't installed with VS + -- the linker will throw an error when looking for libs + -- Lua does not have a "does directory exist function" + -- we could use one here + local lib_str = sdk_lib .. ";" .. vs_root .. "\\VC\\ATLMFC\\lib\\" .. vc_lib_map[host_arch][target_arch] .. ";" .. vc_lib + env:set_external_env_var("LIB", lib_str) + env:set_external_env_var("LIBPATH", lib_str) + + -- Modify %PATH% + + local path = {} + + path[#path + 1] = sdk_root + path[#path + 1] = vs_root .. "Common7\\IDE" + + if "x86" == host_arch then + path[#path + 1] = vs_root .. "\\VC\\Bin" + elseif "x64" == host_arch then + path[#path + 1] = vs_root .. "\\VC\\Bin\\amd64" + elseif "arm" == host_arch then + path[#path + 1] = vs_root .. "\\VC\\Bin\\arm" + end + + path[#path + 1] = vs_root .. "\\Common7\\IDE" + + path[#path + 1] = env:get_external_env_var('PATH') + + env:set_external_env_var("PATH", table.concat(path, ';')) + +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vswild.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vswild.lua new file mode 100644 index 0000000..1804445 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-vswild.lua @@ -0,0 +1,23 @@ + +module(..., package.seeall) + +local vscommon = require "tundra.tools.msvc-vscommon" + +function apply(env, options) + + local vsvs = options.VsVersions or { "12.0", "11.0", "10.0", "9.0" } + + for _, v in ipairs(vsvs) do + local v1 = v + local success, result = xpcall(function() vscommon.apply_msvc_visual_studio(v1, env, options) end, function(err) return err end) + if success then + print("Visual Studio version " .. v1 .. " found ") + return + else + print("Visual Studio version " .. v1 .. " does not appear to be installed (" .. result .. ")") + end + end + + error("Unable to find suitable version of Visual Studio (please install either version " .. table.concat(vsvs, ", ") .. " of Visual Studio to continue)") + +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-winsdk.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-winsdk.lua new file mode 100644 index 0000000..5d7f7b1 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc-winsdk.lua @@ -0,0 +1,150 @@ +-- msvc-winsdk.lua - Use Microsoft Windows SDK 7.1 or later to build. + +module(..., package.seeall) + +local native = require "tundra.native" +local os = require "os" + +if native.host_platform ~= "windows" then + error("the msvc toolset only works on windows hosts") +end + +local function get_host_arch() + local snative = native.getenv("PROCESSOR_ARCHITECTURE") + local swow = native.getenv("PROCESSOR_ARCHITEW6432", "") + if snative == "AMD64" or swow == "AMD64" then + return "x64" + elseif snative == "IA64" or swow == "IA64" then + return "itanium"; + else + return "x86" + end +end + +local compiler_dirs = { + ["x86"] = { + ["x86"] = "bin\\", + ["x64"] = "bin\\x86_amd64\\", + ["itanium"] = "bin\\x86_ia64\\", + }, + ["x64"] = { + ["x86"] = "bin\\", + ["x64"] = { + ["11.0"] = "bin\\x86_amd64\\", + "bin\\amd64\\" + }, + ["itanium"] = "bin\\x86_ia64\\", + }, + ["itanium"] = { + ["x86"] = "bin\\x86_ia64\\", + ["itanium"] = "bin\\ia64\\", + }, +} + +local function setup(env, options) + options = options or {} + local target_arch = options.TargetArch or "x86" + local host_arch = options.HostArch or get_host_arch() + local vcversion = options.VcVersion or "10.0" + + local binDir = + compiler_dirs[host_arch][target_arch][vcversion] + or compiler_dirs[host_arch][target_arch][1] + or compiler_dirs[host_arch][target_arch] + + if not binDir then + errorf("can't build target arch %s on host arch %s", target_arch, host_arch) + end + + local sdkDir; + local sdkDirIncludes; + local sdkLibDir; + local vcLibDir; + + if vcversion == "11.0" then + local sdk_key = "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v8.0" + sdkDir = assert(native.reg_query("HKLM", sdk_key, "InstallationFolder")) + sdkDirIncludes = { sdkDir .. "\\INCLUDE\\UM", sdkDir .. "\\INCLUDE\\SHARED" } + + sdkLibDir = "LIB\\win8\\um\\" + vcLibDir = "LIB" + + if "x86" == target_arch then + sdkLibDir = sdkLibDir .. "x86" + elseif "x64" == target_arch then + sdkLibDir = sdkLibDir .. "x64" + vcLibDir = "LIB\\amd64" + elseif "arm" == target_arch then + sdkLibDir = sdkLibDir .. "arm" + end + else + local sdk_key = "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows" + sdkDir = assert(native.reg_query("HKLM", sdk_key, "CurrentInstallFolder")) + sdkDirIncludes = { sdkDir .. "\\INCLUDE" }; + + sdkLibDir = "LIB" + vcLibDir = "LIB" + + if "x64" == target_arch then + sdkLibDir = "LIB\\x64" + vcLibDir = "LIB\\amd64" + elseif "itanium" == target_arch then + sdkLibDir = "LIB\\IA64" + vcLibDir = "LIB\\IA64" + end + end + + + local vc_key = "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VC7" + local vc_dir = assert(native.reg_query("HKLM", vc_key, vcversion)) + if vc_dir:sub(-1) ~= '\\' then + vc_dir = vc_dir .. '\\' + end + + local cl_exe = '"' .. vc_dir .. binDir .. "cl.exe" ..'"' + local lib_exe = '"' .. vc_dir .. binDir .. "lib.exe" ..'"' + local link_exe = '"' .. vc_dir .. binDir .. "link.exe" ..'"' + + env:set('CC', cl_exe) + env:set('CXX', cl_exe) + env:set('LIB', lib_exe) + env:set('LD', link_exe) + + -- Set up the MS SDK associated with visual studio + + env:set_external_env_var("WindowsSdkDir", sdkDir) + env:set_external_env_var("INCLUDE", table.concat(sdkDirIncludes, ";") .. ";" .. vc_dir .. "\\INCLUDE") + + local rc_exe + print("vcversion", vcversion) + if vcversion == "11.0" then + rc_exe = '"' .. sdkDir .. "\\bin\\x86\\rc.exe" ..'"' + else + rc_exe = '"' .. sdkDir .. "\\bin\\rc.exe" ..'"' + end + env:set('RC', rc_exe) + + local libString = sdkDir .. "\\" .. sdkLibDir .. ";" .. vc_dir .. "\\" .. vcLibDir + env:set_external_env_var("LIB", libString) + env:set_external_env_var("LIBPATH", libString) + + local path = { } + local vc_root = vc_dir:sub(1, -4) + if binDir ~= "\\bin\\" then + path[#path + 1] = vc_dir .. "\\bin" + end + path[#path + 1] = vc_root .. "Common7\\Tools" -- drop vc\ at end + path[#path + 1] = vc_root .. "Common7\\IDE" -- drop vc\ at end + path[#path + 1] = sdkDir + path[#path + 1] = vc_dir .. binDir + path[#path + 1] = env:get_external_env_var('PATH') + + env:set_external_env_var("PATH", table.concat(path, ';')) +end + +function apply(env, options) + -- Load basic MSVC environment setup first. We're going to replace the paths to + -- some tools. + tundra.unitgen.load_toolset('msvc', env) + setup(env, options) +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc.lua new file mode 100644 index 0000000..ac64254 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc.lua @@ -0,0 +1,53 @@ +-- msvc.lua - common definitions for all flavors of MSVC +module(..., package.seeall) + +function apply(env, options) + + -- load the generic C toolset first + tundra.unitgen.load_toolset("generic-cpp", env) + + -- load support for win32 resource compilation + tundra.unitgen.load_toolset("win32-rc", env) + + env:set_many { + ["NATIVE_SUFFIXES"] = { ".c", ".cpp", ".cc", ".cxx", ".lib", ".obj", ".res", ".rc" }, + ["OBJECTSUFFIX"] = ".obj", + ["LIBPREFIX"] = "", + ["LIBSUFFIX"] = ".lib", + ["CC"] = "cl", + ["CXX"] = "cl", + ["LIB"] = "lib", + ["LD"] = "link", + ["CPPDEFS"] = "_WIN32", + ["_CPPDEFS"] = "$(CPPDEFS:p/D) $(CPPDEFS_$(CURRENT_VARIANT:u):p/D)", + ["_PCH_SUPPORTED"] = "1", + ["_USE_PCH_OPT"] = "/Fp$(_PCH_FILE:b) /Yu$(_PCH_HEADER)", + ["_USE_PCH"] = "", + ["_USE_PDB_CC_OPT"] = "/Zi /Fd$(_PDB_CC_FILE:b)", + ["_USE_PDB_LINK_OPT"] = "/DEBUG /PDB:$(_PDB_LINK_FILE)", + ["_USE_PDB_CC"] = "", + ["_USE_PDB_LINK"] = "", + ["_USE_MODDEF_OPT"] = "/DEF:$(MODDEF)", + ["_USE_MODDEF"] = "", + ["RC"] = "rc", + ["RCOPTS"] = "/nologo", + ["W32RESSUFFIX"] = ".res", + ["RCCOM"] = "$(RC) $(RCOPTS) /fo$(@:b) $(_CPPDEFS) $(CPPPATH:b:p/i) $(<:b)", + ["CCCOM"] = "$(CC) /c @RESPONSE|@|$(_CPPDEFS) $(CPPPATH:b:p/I) /nologo $(CCOPTS) $(CCOPTS_$(CURRENT_VARIANT:u)) $(_USE_PCH) $(_USE_PDB_CC) /Fo$(@:b) $(<:b)", + ["CXXCOM"] = "$(CC) /c @RESPONSE|@|$(_CPPDEFS) $(CPPPATH:b:p/I) /nologo $(CXXOPTS) $(CXXOPTS_$(CURRENT_VARIANT:u)) $(_USE_PCH) $(_USE_PDB_CC) /Fo$(@:b) $(<:b)", + ["PCHCOMPILE_CC"] = "$(CC) /c $(_CPPDEFS) $(CPPPATH:b:p/I) /nologo $(CCOPTS) $(CCOPTS_$(CURRENT_VARIANT:u)) $(_USE_PDB_CC) /Yc$(_PCH_HEADER) /Fp$(@:[1]:b) /Fo$(@:[2]:b) $(<:[1]:b)", + ["PCHCOMPILE_CXX"] = "$(CXX) /c $(_CPPDEFS) $(CPPPATH:b:p/I) /nologo $(CXXOPTS) $(CXXOPTS_$(CURRENT_VARIANT:u)) $(_USE_PDB_CC) /Yc$(_PCH_HEADER) /Fp$(@:[1]:b) /Fo$(@:[2]:b) $(<:[1]:b)", + ["LIBS"] = "", + ["PROGOPTS"] = "", + ["PROGCOM"] = "$(LD) /nologo @RESPONSE|@|$(_USE_PDB_LINK) $(PROGOPTS) $(LIBPATH:b:p/LIBPATH\\:) $(_USE_MODDEF) $(LIBS) /out:$(@:b) $(<:b:p\n)", + ["LIBOPTS"] = "", + ["LIBCOM"] = "$(LIB) /nologo @RESPONSE|@|$(LIBOPTS) /out:$(@:b) $(<:b:p\n)", + ["PROGPREFIX"] = "", + ["SHLIBLINKSUFFIX"] = ".lib", + ["SHLIBPREFIX"] = "", + ["SHLIBOPTS"] = "", + ["SHLIBCOM"] = "$(LD) /DLL /nologo @RESPONSE|@|$(_USE_PDB_LINK) $(SHLIBOPTS) $(LIBPATH:b:p/LIBPATH\\:) $(_USE_MODDEF) $(LIBS) /out:$(@:b) $(<:b)", + ["AUX_FILES_PROGRAM"] = { "$(@:B:a.exe.manifest)", "$(@:B:a.pdb)", "$(@:B:a.exp)", "$(@:B:a.lib)", "$(@:B:a.ilk)", }, + ["AUX_FILES_SHAREDLIBRARY"] = { "$(@:B:a.dll.manifest)", "$(@:B:a.pdb)", "$(@:B:a.exp)", "$(@:B:a.lib)", "$(@:B:a.ilk)", }, + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc6.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc6.lua new file mode 100644 index 0000000..17c3019 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/msvc6.lua @@ -0,0 +1,101 @@ +-- msvc6.lua - Visual Studio 6 + +module(..., package.seeall) + +local native = require "tundra.native" +local os = require "os" + +function path_combine(path, path_to_append) + if path == nil then + return path_to_append + end + if path:find("\\$") then + return path .. path_to_append + end + return path .. "\\" .. path_to_append +end + +function path_it(maybe_list) + if type(maybe_list) == "table" then + return ipairs(maybe_list) + end + return ipairs({maybe_list}) +end + +function apply(env, options) + + if native.host_platform ~= "windows" then + error("the msvc6 toolset only works on windows hosts") + end + + -- Load basic MSVC environment setup first. + -- We're going to replace the paths to some tools. + tundra.unitgen.load_toolset('msvc', env) + + options = options or {} + + -- We'll find any edition of VS (including Express) here + local vs_root = native.reg_query("HKLM", "SOFTWARE\\Microsoft\\VisualStudio\\6.0\\Setup\\Microsoft Visual C++", "ProductDir") + assert(vs_root, "The requested version of Visual Studio isn't installed") + vs_root = string.gsub(vs_root, "\\+$", "\\") + + local common_root = native.reg_query("HKLM", "SOFTWARE\\Microsoft\\VisualStudio\\6.0\\Setup", "VsCommonDir") + assert(common_root, "The requested version of Visual Studio isn't installed") + common_root = string.gsub(common_root, "\\+$", "\\") + + local vc_lib + local vc_bin + + vc_bin = vs_root .. "\\bin" + vc_lib = vs_root .. "\\lib" + + -- Tools + + local cl_exe = '"' .. path_combine(vc_bin, "cl.exe") .. '"' + local lib_exe = '"' .. path_combine(vc_bin, "lib.exe") .. '"' + local link_exe = '"' .. path_combine(vc_bin, "link.exe") .. '"' + local rc_exe = '"' .. path_combine(common_root, "MSDev98\\Bin\\rc.exe") .. '"' + + env:set('CC', cl_exe) + env:set('CXX', cl_exe) + env:set('LIB', lib_exe) + env:set('LD', link_exe) + env:set('RC', rc_exe) + + env:set("RCOPTS", "") -- clear the "/nologo" option (it was first added in VS2010) + + -- Wire-up the external environment + env:set_external_env_var('VSINSTALLDIR', vs_root) + env:set_external_env_var('VCINSTALLDIR', vs_root .. "\\vc") + --env:set_external_env_var('DevEnvDir', vs_root .. "Common7\\IDE") + + do + local include = { + path_combine(vs_root, "ATL\\INCLUDE"), + path_combine(vs_root, "INCLUDE"), + path_combine(vs_root, "MFC\\INCLUDE"), + } + env:set_external_env_var("INCLUDE", table.concat(include, ';')) + end + + do + local lib = { + path_combine(vs_root, "LIB"), + path_combine(vs_root, "MFC\\LIB"), + } + local lib_str = table.concat(lib, ';') + env:set_external_env_var("LIB", lib_str) + env:set_external_env_var("LIBPATH", lib_str) + end + + -- Modify %PATH% + + do + local path = { + path_combine(vs_root, "BIN"), + path_combine(common_root, "MSDev98\\BIN"), + env:get_external_env_var('PATH'), + } + env:set_external_env_var("PATH", table.concat(path, ';')) + end +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/openwatcom.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/openwatcom.lua new file mode 100644 index 0000000..849024e --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/openwatcom.lua @@ -0,0 +1,61 @@ +-- openwatcom.lua - Support for the Open Watcom compiler C/C++ compiler + +module(..., package.seeall) + +local native = require "tundra.native" +local os = require "os" + +local function setup(env, options) + + if native.host_platform ~= "windows" then + error("the openwatcom toolset only works on windows hosts") + end + + assert(options, "No Options provided") + local dir = assert(options.InstallDir) + env:set_external_env_var("WATCOM", dir) + env:set_external_env_var("EDPATH", dir .. "\\EDDAT") + env:set_external_env_var("WIPFC", dir .. "\\WIPFC") + local p = native.getenv("PATH") .. ";" .. dir .. "\\BINNT\\;" .. dir .. "\\BINW\\" + print(p) + env:set_external_env_var("PATH", p) + local inc = native.getenv("INCLUDE", "") + if inc then + inc = inc .. ";" + end + env:set_external_env_var("INCLUDE", inc .. dir .. "\\H;" .. dir .. "\\H\\NT;" .. dir .. "\\H\\NT\\DIRECTX;" .. dir .. "\\H\\NT\\DDK") + +end + +function apply(env, options) + -- load the generic C toolset first + tundra.unitgen.load_toolset("generic-cpp", env) + + setup(env, options) + + env:set_many { + ["NATIVE_SUFFIXES"] = { ".c", ".cpp", ".cc", ".cxx", ".lib", ".obj" }, + ["OBJECTSUFFIX"] = ".obj", + ["LIBSUFFIX"] = ".lib", + ["CC"] = "wcl386.exe", + ["CXX"] = "wcl386.exe", + ["LIB"] = "wlib.exe", + ["LD"] = "wlink.exe", + ["CPPDEFS"] = "_WIN32", + ["CCOPTS"] = "-wx -we", + ["_CPPDEFS"] = "$(CPPDEFS:p-d) $(CPPDEFS_$(CURRENT_VARIANT:u):p-d)", + ["_USE_PCH_OPT"] = "", + ["_USE_PCH"] = "", + ["_CCCOM"] = "$(CC) /c @RESPONSE|@|$(_CPPDEFS) $(CPPPATH:b:p-i) $(CCOPTS) $(CCOPTS_$(CURRENT_VARIANT:u)) $(_USE_PCH) -fo=$(@:b) $(<:b)", + ["CCCOM"] = "$(_CCCOM)", + ["CXXCOM"] = "$(_CCCOM)", + ["PCHCOMPILE"] = "", + ["LIBS"] = "", + ["PROGOPTS"] = "", + ["PROGCOM"] = "", -- "$(LD) @RESPONSE|@|$(PROGOPTS) $(LIBS) /out:$(@:b) $(<:b)", + ["LIBOPTS"] = "", + ["LIBCOM"] = "", -- "$(LIB) @RESPONSE|@|$(LIBOPTS) /out:$(@:b) $(<:b)", + ["SHLIBOPTS"] = "", + ["SHLIBCOM"] = "", -- "$(LD) /nologo @RESPONSE|@|$(SHLIBOPTS) $(LIBPATH:b:p/LIBPATH\\:) $(LIBS) /out:$(@:b) $(<:b)", + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/vbcc.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/vbcc.lua new file mode 100644 index 0000000..e196312 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/vbcc.lua @@ -0,0 +1,36 @@ +module(..., package.seeall) + +local native = require "tundra.native" + +function apply(env, options) + -- load the generic C toolset first + tundra.unitgen.load_toolset("generic-cpp", env) + -- Also add assembly support. + tundra.unitgen.load_toolset("generic-asm", env) + + local vbcc_root = assert(native.getenv("VBCC"), "VBCC environment variable must be set") + + env:set_many { + ["NATIVE_SUFFIXES"] = { ".c", ".cpp", ".cc", ".cxx", ".s", ".asm", ".a", ".o" }, + ["OBJECTSUFFIX"] = ".o", + ["LIBPREFIX"] = "", + ["LIBSUFFIX"] = ".a", + ["VBCC_ROOT"] = vbcc_root, + ["CC"] = vbcc_root .. "$(SEP)bin$(SEP)vc$(HOSTPROGSUFFIX)", + ["LIB"] = vbcc_root .. "$(SEP)bin$(SEP)vlink$(HOSTPROGSUFFIX)", + ["LD"] = vbcc_root .. "$(SEP)bin$(SEP)vc$(HOSTPROGSUFFIX)", + ["ASM"] = vbcc_root .. "$(SEP)bin$(SEP)vasmm68k_mot$(HOSTPROGSUFFIX)", + ["VBCC_SDK_INC"] = vbcc_root .. "$(SEP)include$(SEP)sdk", + ["_OS_CCOPTS"] = "", + ["_OS_CXXOPTS"] = "", + ["CCCOM"] = "$(CC) $(_OS_CCOPTS) -c $(CPPDEFS:p-D) $(CPPPATH:f:p-I) $(CCOPTS) $(CCOPTS_$(CURRENT_VARIANT:u)) -o $(@) $(<)", + ["ASMCOM"] = "$(ASM) -quiet -Fhunk -phxass $(ASMOPTS) $(ASMOPTS_$(CURRENT_VARIANT:u)) $(ASMDEFS:p-D) $(ASMINCPATH:f:p-I) -I$(VBCC_SDK_INC) -o $(@) $(<)", + ["PROGOPTS"] = "", + ["PROGCOM"] = "$(LD) $(PROGOPTS) $(LIBPATH:p-L) $(LIBS:p-l) -o $(@) $(<)", + ["PROGPREFIX"] = "", + ["LIBOPTS"] = "", + ["LIBCOM"] = "$(LIB) -r $(LIBOPTS) -o $(@) $(<)", + ["ASMINC_KEYWORDS"] = { "INCLUDE", "include" }, + ["ASMINC_BINARY_KEYWORDS"] = { "INCBIN", "incbin" }, + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/win32-rc.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/win32-rc.lua new file mode 100644 index 0000000..f546ec2 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/win32-rc.lua @@ -0,0 +1,21 @@ +module(..., package.seeall) + +local path = require("tundra.path") +local depgraph = require("tundra.depgraph") +local gencpp = require("tundra.tools.generic-cpp") + +local function compile_resource_file(env, pass, fn) + return depgraph.make_node { + Env = env, + Label = 'Rc $(@)', + Pass = pass, + Action = "$(RCCOM)", + InputFiles = { fn }, + OutputFiles = { path.make_object_filename(env, fn, env:get('W32RESSUFFIX')) }, + Scanner = gencpp.get_cpp_scanner(env, fn), + } +end + +function apply(env, options) + env:register_implicit_make_fn("rc", compile_resource_file) +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/yasm.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/yasm.lua new file mode 100644 index 0000000..fe01c55 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/tools/yasm.lua @@ -0,0 +1,12 @@ +module(..., package.seeall) + +function apply(env, options) + -- load the generic assembly toolset first + tundra.unitgen.load_toolset("generic-asm", env) + + env:set_many { + ["YASM"] = "yasm", + ["ASMCOM"] = "$(YASM) -o $(@) $(ASMDEFS:p-D ) $(ASMOPTS) $(<)", + ["ASMINC_KEYWORDS"] = { "%include" }, + } +end diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/unitgen.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/unitgen.lua new file mode 100644 index 0000000..ba8b251 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/unitgen.lua @@ -0,0 +1,197 @@ +module(..., package.seeall) + +local util = require "tundra.util" +local buildfile = require "tundra.buildfile" +local decl = require "tundra.decl" +local nodegen = require "tundra.nodegen" + +function match_build_id(id, default) + assert(id) + local i = id:gmatch("[^-]+") + local platform_name, toolset, variant, subvariant = i() or default, i() or default, i() or default, i() or default + return platform_name, toolset, variant, subvariant +end + +local function iter_inherits(config, name) + local tab = config + return function() + while tab do + local my_tab = tab + if not my_tab then break end + tab = my_tab.Inherit + local v = my_tab[name] + if v then return v end + end + end +end + +function load_toolset(id, ...) + -- For non-qualified packages, use a default package + if not id:find("%.") then + id = "tundra.tools." .. id + end + + local pkg, err = require(id) + + if err then + errorf("couldn't load extension module %s: %s", id, err) + end + + pkg.apply(...) +end + +local function setup_env(env, build_data, tuple, build_id) + local config = tuple.Config + local variant_name = tuple.Variant.Name + + if not build_id then + build_id = config.Name .. "-" .. variant_name .. "-" .. tuple.SubVariant + end + + local naked_platform, naked_toolset = match_build_id(build_id) + + env:set("CURRENT_PLATFORM", naked_platform) -- e.g. linux or macosx + env:set("CURRENT_TOOLSET", naked_toolset) -- e.g. gcc or msvc + env:set("CURRENT_VARIANT", tuple.Variant.Name) -- e.g. debug or release + env:set("BUILD_ID", build_id) -- e.g. linux-gcc-debug + env:set("OBJECTDIR", "$(OBJECTROOT)" .. SEP .. "$(BUILD_ID)") + + for tools in iter_inherits(config, "Tools") do + for k, v in pairs(tools) do + if type(k) == "string" then + error("Tools must be a plain array - to include options keys wrap them in their own tables:\n " .. + "e.g. Tools = { { 'foo'; Option = ... }, ... }.\n Your Tools:\n" .. util.tostring(tools)) + end + end + for _, data in ipairs(tools) do + local id, options + + if type(data) == "table" then + id = assert(data[1]) + options = data + data = id + end + + if type(data) == "string" then + load_toolset(data, env, options) + elseif type(data) == "function" then + data(env, options) + else + error("bad parameters") + end + end + end + + -- Incorporate matching values from the build data's Env and ReplaceEnv. + if build_data.Env then + nodegen.append_filtered_env_vars(env, build_data.Env, build_id, false) + end + if build_data.ReplaceEnv then + nodegen.replace_filtered_env_vars(env, build_data.ReplaceEnv, build_id, false) + end + + -- Incorporate matching values from the config's Env and ReplaceEnv. + for env_tab in iter_inherits(config, "Env") do + nodegen.append_filtered_env_vars(env, env_tab, build_id, false) + end + for env_tab in iter_inherits(config, "ReplaceEnv") do + nodegen.replace_filtered_env_vars(env, env_tab, build_id, false) + end + + -- Run post-setup functions. This typically sets up implicit make functions. + env:run_setup_functions() + + return env +end + + + +local function setup_envs(tuple, configs, default_env, build_data) + local result = {} + + local top_env = setup_env(default_env:clone(), build_data, tuple) + result["__default"] = top_env + + -- Use the same build id for all subconfigurations + local build_id = top_env:get("BUILD_ID") + + local cfg = configs[tuple.Config.Name] + for moniker, x in util.nil_pairs(cfg.SubConfigs) do + if result[x] then + croak("duplicate subconfig name: %s", x) + end + local sub_tuple = { Config = configs[x], Variant = tuple.Variant, SubVariant = tuple.SubVariant } + if not sub_tuple.Config then + errorf("%s: no such config (in SubConfigs specification)", x) + end + local sub_env = setup_env(default_env:clone(), build_data, sub_tuple, build_id) + result[moniker] = sub_env + end + return result +end + + +function parse_units(build_tuples, args, passes) + if args.SyntaxExtensions then + print("*WARNING* SyntaxExtensions has been deprecated. Use require instead.") + end + for _, id in util.nil_ipairs(args.SyntaxExtensions) do + require(id) + end + + local function chunk () + local raw_nodes, default_nodes, always_nodes = decl.parse(args.Units or "units.lua") + assert(#default_nodes > 0 or #always_nodes > 0, "no default unit name to build was set") + return { raw_nodes, default_nodes, always_nodes } + end + + local success, result = xpcall(chunk, buildfile.syntax_error_catcher) + + if success then + return result[1], result[2], result[3] + else + print("Build script execution failed") + croak("%s", result or "") + end +end + + +-- Inputs +-- build_tuples - the config/variant/subvariant pairs to include in the DAG +-- args - Raw data from Build() call +-- passes - Passes specified in Build() call +-- configs - Configs specified in Build() call +function generate_dag(build_tuples, args, passes, configs, default_env) + local raw_nodes, default_nodes, always_nodes = parse_units(build_tuples, args, passes) + + local results = {} + + -- Let the nodegen code generate DAG nodes for all active + -- configurations/variants. + for _, tuple in pairs(build_tuples) do + printf("Generating DAG for %s-%s-%s", tuple.Config.Name, tuple.Variant.Name, tuple.SubVariant) + local envs = setup_envs(tuple, configs, default_env, args) + local always_nodes, default_nodes, named_nodes = nodegen.generate_dag { + Envs = envs, + Config = tuple.Config, + Variant = tuple.Variant, + Declarations = raw_nodes, + DefaultNodes = default_nodes, + AlwaysNodes = always_nodes, + Passes = passes, + } + + results[#results + 1] = { + Config = assert(tuple.Config), + Variant = assert(tuple.Variant), + SubVariant = assert(tuple.SubVariant), + AlwaysNodes = always_nodes, + DefaultNodes = default_nodes, + NamedNodes = named_nodes, + } + end + + return raw_nodes, results +end + + diff --git a/ogl_editor/bin/macosx/tundra/scripts/tundra/util.lua b/ogl_editor/bin/macosx/tundra/scripts/tundra/util.lua new file mode 100644 index 0000000..c6e7073 --- /dev/null +++ b/ogl_editor/bin/macosx/tundra/scripts/tundra/util.lua @@ -0,0 +1,360 @@ +local _tostring = tostring +module(..., package.seeall) + +function tostring(value, stack) + local str = '' + stack = stack or {} + + if type(value) ~= 'table' then + if type(value) == 'string' then + str = string.format("%q", value) + else + str = _tostring(value) + end + elseif stack[value] then + return '' + else + stack[value] = true + local auxTable = {} + for k, v in pairs(value) do + auxTable[#auxTable + 1] = k + end + table.sort(auxTable, function (a, b) return _tostring(a) < _tostring(b) end) + + str = str..'{' + local separator = "" + local entry = "" + for index, fieldName in ipairs(auxTable) do + if ((tonumber(fieldName)) and (tonumber(fieldName) > 0)) then + entry = tostring(value[tonumber(fieldName)], stack) + else + entry = tostring(fieldName) .. " = " .. tostring(rawget(value, fieldName), stack) + end + str = str..separator..entry + separator = ", " + end + str = str..'}' + + local mt = getmetatable(value) + if mt then + str = str .. ' @meta = ' .. tostring(mt, stack) + end + end + return str +end + +function map_in_place(t, fn) + for x = 1, #t do + t[x] = fn(t[x]) + end + return t +end + +function map(t, fn) + local result = {} + for idx = 1, #t do + result[idx] = fn(t[idx]) + end + return result +end + +function mapnil(table, fn) + if not table then + return nil + else + return map(table, fn) + end +end + +function get_named_arg(tab, name, context) + local v = tab[name] + if v then + return v + else + if context then + error(context .. ": argument " .. name .. " must be specified", 3) + else + error("argument " .. name .. " must be specified", 3) + end + end +end + +function parse_cmdline(args, blueprint) + local index, max = 2, #args + local options, targets = {}, {} + local lookup = {} + + for _, opt in ipairs(blueprint) do + if opt.Short then + lookup[opt.Short] = opt + end + if opt.Long then + lookup[opt.Long] = opt + end + end + + while index <= max do + local s = args[index] + local key, val + + if s:sub(1, 2) == '--' then + key, val = s:match("^%-%-([-a-zA-Z0-9]+)=(.*)$") + if not key then + key = s:sub(3) + end + elseif s:sub(1, 1) == '-' then + key = s:sub(2,2) + if s:len() > 2 then + val = s:sub(3) + end + else + table.insert(targets, s) + end + + if key then + local opt = lookup[key] + if not opt then + return nil, nil, "Unknown option " .. s + end + if opt.HasValue then + if not val then + index = index + 1 + val = args[index] + end + if val then + options[opt.Name] = val + else + return nil, nil, "Missing value for option "..s + end + else + local v = options[opt.Name] or 0 + options[opt.Name] = v + 1 + end + end + + index = index + 1 + end + + return options, targets +end + +function clone_table(t) + if t then + local r = {} + for k, v in pairs(t) do + r[k] = v + end + for k, v in ipairs(t) do + r[k] = v + end + return r + else + return nil + end +end + +function deep_clone_table(t) + local function clone_value(v) + if type(v) == "table" then + return deep_clone_table(v) + else + return v + end + end + if t then + local r = {} + for k, v in pairs(t) do + r[clone_value(k)] = clone_value(v) + end + for k, v in ipairs(t) do + r[k] = clone_value(v) + end + return r + else + return nil + end +end + +function clone_array(t) + local r = {} + for k, v in ipairs(t) do + r[k] = v + end + return r +end + +function merge_arrays(...) + local result = {} + local count = select('#', ...) + for i = 1, count do + local tab = select(i, ...) + if tab then + for _, v in ipairs(tab) do + result[#result + 1] = v + end + end + end + return result +end + +function merge_arrays_2(a, b) + if a and b then + return merge_arrays(a, b) + elseif a then + return a + elseif b then + return b + else + return {} + end +end + +function matches_any(str, patterns) + for _, pattern in ipairs(patterns) do + if str:match(pattern) then + return true + end + end + return false +end + +function return_nil() +end + +function nil_pairs(t) + if t then + return next, t + else + return return_nil + end +end + +function nil_ipairs(t) + if t then + return ipairs(t) + else + return return_nil + end +end + +function clear_table(tab) + local key, val = next(tab) + while key do + tab[key] = nil + key, val = next(tab, key) + end + return tab +end + +function filter(tab, predicate) + local result = {} + for _, x in ipairs(tab) do + if predicate(x) then + result[#result + 1] = x + end + end + return result +end + +function filter_nil(tab, predicate) + if not predicate then return nil end + local result = {} + for _, x in ipairs(tab) do + if predicate(x) then + result[#result + 1] = x + end + end + return result +end + +function filter_in_place(tab, predicate) + local i, limit = 1, #tab + while i <= limit do + if not predicate(tab[i]) then + table.remove(tab, i) + limit = limit - 1 + else + i = i + 1 + end + end + return tab +end + +function append_table(result, items) + local offset = #result + for i = 1, #items do + result[offset + i] = items[i] + end + return result +end + +function flatten(array) + local function iter(item, accum) + if type(item) == 'table' and not getmetatable(item) then + for _, sub_item in ipairs(item) do + iter(sub_item, accum) + end + else + accum[#accum + 1] = item + end + end + local accum = {} + iter(array, accum) + return accum +end + +function memoize(closure) + local result = nil + return function(...) + if not result then + result = assert(closure(...)) + end + return result + end +end + +function uniq(array) + local seen = {} + local result = {} + for _, val in nil_ipairs(array) do + if not seen[val] then + seen[val] = true + result[#result + 1] = val + end + end + return result +end + +function make_lookup_table(array) + local result = {} + for _, item in nil_ipairs(array) do + result[item] = true + end + return result +end + +function table_keys(array) + local result = {} + for k, _ in nil_pairs(array) do + result[#result + 1] = k + end + return result +end + +function table_values(array) + local result = {} + for _, v in nil_pairs(array) do + result[#result + 1] = v + end + return result +end + +function array_contains(array, find) + for _, val in ipairs(array) do + if val == find then + return true + end + end + return false +end + diff --git a/ogl_editor/bin/macosx/tundra/t2-inspect b/ogl_editor/bin/macosx/tundra/t2-inspect new file mode 100755 index 0000000..cb52d0e Binary files /dev/null and b/ogl_editor/bin/macosx/tundra/t2-inspect differ diff --git a/ogl_editor/bin/macosx/tundra/t2-lua b/ogl_editor/bin/macosx/tundra/t2-lua new file mode 100755 index 0000000..a0b0182 Binary files /dev/null and b/ogl_editor/bin/macosx/tundra/t2-lua differ diff --git a/ogl_editor/bin/macosx/tundra/tundra2 b/ogl_editor/bin/macosx/tundra/tundra2 new file mode 100755 index 0000000..7b7525a Binary files /dev/null and b/ogl_editor/bin/macosx/tundra/tundra2 differ diff --git a/ogl_editor/bin/win32/scripts/strict.lua b/ogl_editor/bin/win32/scripts/strict.lua new file mode 100644 index 0000000..604619d --- /dev/null +++ b/ogl_editor/bin/win32/scripts/strict.lua @@ -0,0 +1,41 @@ +-- +-- strict.lua +-- checks uses of undeclared global variables +-- All global variables must be 'declared' through a regular assignment +-- (even assigning nil will do) in a main chunk before being used +-- anywhere or assigned to inside a function. +-- + +local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget + +local mt = getmetatable(_G) +if mt == nil then + mt = {} + setmetatable(_G, mt) +end + +mt.__declared = {} + +local function what () + local d = getinfo(3, "S") + return d and d.what or "C" +end + +mt.__newindex = function (t, n, v) + if not mt.__declared[n] then + local w = what() + if w ~= "main" and w ~= "C" then + error("assign to undeclared variable '"..n.."'", 2) + end + mt.__declared[n] = true + end + rawset(t, n, v) +end + +mt.__index = function (t, n) + if not mt.__declared[n] and what() ~= "C" then + error("variable '"..n.."' is not declared", 2) + end + return rawget(t, n) +end + diff --git a/ogl_editor/bin/win32/scripts/tundra.lua b/ogl_editor/bin/win32/scripts/tundra.lua new file mode 100644 index 0000000..cede76e --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra.lua @@ -0,0 +1,43 @@ +require "strict" + +local boot = require "tundra.boot" + +local actions = { + ['generate-dag'] = function(build_script) + assert(build_script, "need a build script name") + boot.generate_dag_data(build_script) + end, + + ['generate-ide-files'] = function(build_script, ide_script) + assert(build_script, "need a build script name") + assert(ide_script, "need a generator name") + boot.generate_ide_files(build_script, ide_script) + end, + + ['selftest'] = function() + require "tundra.selftest" + end +} + +local function main(action_name, ...) + assert(action_name, "need an action") + + local action = actions[action_name] + assert(action, "unknown action '" .. action_name .. "'") + + -- check if debugger was requested + for i, v in ipairs(arg) do + if v == "--lua-debugger" then + table.remove(arg, i) + require "tundra.debugger" + pause() + break + end + end + + action(unpack(arg)) +end + +return { + main = main +} diff --git a/ogl_editor/bin/win32/scripts/tundra/boot.lua b/ogl_editor/bin/win32/scripts/tundra/boot.lua new file mode 100644 index 0000000..4c0dbf9 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/boot.lua @@ -0,0 +1,142 @@ +module(..., package.seeall) + +-- Use "strict" when developing to flag accesses to nil global variables +-- This has very low perf impact (<0.1%), so always leave it on. +require "strict" + +local os = require "os" +local platform = require "tundra.platform" +local util = require "tundra.util" +local depgraph = require "tundra.depgraph" +local unitgen = require "tundra.unitgen" +local buildfile = require "tundra.buildfile" +local native = require "tundra.native" + +-- This trio is so useful we want them everywhere without imports. +function _G.printf(msg, ...) + local str = string.format(msg, ...) + print(str) +end + +function _G.errorf(msg, ...) + local str = string.format(msg, ...) + error(str) +end + +function _G.croak(msg, ...) + local str = string.format(msg, ...) + io.stderr:write(str, "\n") + os.exit(1) +end + +-- Expose benchmarking function for those times everything sucks +-- +-- Wrap a function so that it prints execution times. +-- +-- Usage: +-- foo = bench("foo", foo) -- benchmark function foo +function _G.bench(name, fn) + return function (...) + local t1 = native.get_timer() + local result = { fn(...) } + local t2 = native.get_timer() + printf("%s: %ss", name, native.timerdiff(t1, t2)) + return unpack(result) + end +end + +local environment = require "tundra.environment" +local nodegen = require "tundra.nodegen" +local decl = require "tundra.decl" +local path = require "tundra.path" +local depgraph = require "tundra.depgraph" +local dagsave = require "tundra.dagsave" + +_G.SEP = platform.host_platform() == "windows" and "\\" or "/" + +_G.Options = { + FullPaths = 1 +} + +local function make_default_env(build_data, add_unfiltered_vars) + local default_env = environment.create() + + default_env:set_many { + ["OBJECTROOT"] = "t2-output", + ["SEP"] = SEP, + } + + local host_platform = platform.host_platform() + do + local mod_name = "tundra.host." .. host_platform + local mod = require(mod_name) + mod.apply_host(default_env) + end + + -- Add any unfiltered entries from the build data's Env and ReplaceEnv to the + -- default environment. For config environments, this will be false, because we + -- want to wait until the config's tools have run before adding any user + -- customizations. + if add_unfiltered_vars then + if build_data.Env then + nodegen.append_filtered_env_vars(default_env, build_data.Env, nil, true) + end + if build_data.ReplaceEnv then + nodegen.replace_filtered_env_vars(default_env, build_data.ReplaceEnv, nil, true) + end + end + + return default_env +end + +function generate_dag_data(build_script_fn) + local build_data = buildfile.run(build_script_fn) + local env = make_default_env(build_data.BuildData, false) + local raw_nodes, node_bindings = unitgen.generate_dag( + build_data.BuildTuples, + build_data.BuildData, + build_data.Passes, + build_data.Configs, + env) + + dagsave.save_dag_data( + node_bindings, + build_data.DefaultVariant, + build_data.DefaultSubVariant, + build_data.ContentDigestExtensions, + build_data.Options) +end + +function generate_ide_files(build_script_fn, ide_script) + -- We are generating IDE integration files. Load the specified + -- integration module rather than DAG builders. + -- + -- Also, default to using full paths for all commands to aid with locating + -- sources better. + Options.FullPaths = 1 + + local build_data = buildfile.run(build_script_fn) + local build_tuples = assert(build_data.BuildTuples) + local raw_data = assert(build_data.BuildData) + local passes = assert(build_data.Passes) + + local env = make_default_env(raw_data, true) + + if not ide_script:find('.', 1, true) then + ide_script = 'tundra.ide.' .. ide_script + end + + require(ide_script) + + -- Generate dag + local raw_nodes, node_bindings = unitgen.generate_dag( + build_data.BuildTuples, + build_data.BuildData, + build_data.Passes, + build_data.Configs, + env) + + -- Pass the build tuples directly to the generator and let it write + -- files. + nodegen.generate_ide_files(build_tuples, build_data.DefaultNodes, raw_nodes, env, raw_data.IdeGenerationHints, ide_script) +end diff --git a/ogl_editor/bin/win32/scripts/tundra/buildfile.lua b/ogl_editor/bin/win32/scripts/tundra/buildfile.lua new file mode 100644 index 0000000..cf24d1f --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/buildfile.lua @@ -0,0 +1,253 @@ +module(..., package.seeall) + +local util = require "tundra.util" +local native = require "tundra.native" + +local function mk_defvariant(name) + return { Name = name; Options = {} } +end + +local default_variants = { + mk_defvariant "debug", + mk_defvariant "production", + mk_defvariant "release" +} + +local default_subvariants = { + "default" +} + +local _config_class = {} + +-- Table constructor to make tundra.lua syntax a bit nicer in the Configs array +function _G.Config(args) + local name = args.Name + if not name then + error("no `Name' specified for configuration") + end + if not name:match("^[%w_]+-[%w_]+$") then + errorf("configuration name %s doesn't follow - pattern", name) + end + + if args.SubConfigs then + if not args.DefaultSubConfig then + errorf("configuration %s has `SubConfigs' but no `DefaultSubConfig'", name) + end + end + + return setmetatable(args, _config_class) +end + +local function analyze_targets(configs, variants, subvariants) + local build_tuples = {} + + local build_configs = {} + local build_variants = {} + local build_subvariants = {} + + for _, cfg in pairs(configs) do + + if not cfg.Virtual then -- skip virtual configs + + if not cfg.SupportedHosts then + if cfg.DefaultOnHost then + if type(cfg.DefaultOnHost) == "table" then + cfg.SupportedHosts = cfg.DefaultOnHost + else + cfg.SupportedHosts = { cfg.DefaultOnHost } + end + else + printf("1.0-compat: config %s doesn't specify SupportedHosts -- will never be built", cfg.Name); + cfg.SupportedHosts = { } + end + end + + local lut = util.make_lookup_table(cfg.SupportedHosts) + if lut[native.host_platform] then + build_configs[#build_configs + 1] = cfg + end + end + end + for _, var in pairs(variants) do build_variants[#build_variants + 1] = var end + for var, _ in pairs(subvariants) do build_subvariants[#build_subvariants + 1] = var end + + for _, config in ipairs(build_configs) do + if config.Virtual then + croak("can't build configuration %s directly; it is a support configuration only", config.Name) + end + for _, variant in ipairs(build_variants) do + for _, subvariant in ipairs(build_subvariants) do + build_tuples[#build_tuples + 1] = { Config = config, Variant = variant, SubVariant = subvariant } + end + end + end + + if #build_tuples == 0 then + errorf("no build tuples available\n") + end + + return build_tuples +end + +-- Custom pcall error handler to scan for syntax errors (thrown as tables) and +-- report them without a backtrace, trying to get the filename and line number +-- right so the user can fix their build file. +function syntax_error_catcher(err_obj) + if type(err_obj) == "table" and err_obj.Class and err_obj.Message then + local i = 1 + -- Walk down the stack until we find a function that isn't sourced from + -- a file. These have 'source' names that don't start with an @ sign. + -- Because we read all files into memory before executing them, this + -- will give us the source filename of the user script. + while true do + local info = debug.getinfo(i, 'Sl') + --print(util.tostring(info)) + if not info then + break + end + if info.what == "C" or (info.source:sub(1, 1) == "@" and info.source ~= "@units.lua") then + i = i + 1 + else + local fn = info.source + if info.source:sub(1, 1) == "@" then + fn = info.source:sub(2) + end + if info.currentline == -1 then + return string.format("%s: %s", err_obj.Class, err_obj.Message) + else + return string.format("%s(%d): %s: %s", fn, info.currentline, err_obj.Class, err_obj.Message) + end + end + end + return string.format("%s: %s", err_obj.Class, err_obj.Message) + else + return debug.traceback(err_obj, 2) + end +end + + + +-- A place to store the result of the user's build script calling Build() +local build_result = nil + +-- The Build function is the main entry point for "tundra.lua" when invoked. +function _G.Build(args) + if type(args.Configs) ~= "table" or #args.Configs == 0 then + croak("Need at least one config; got %s", util.tostring(args.Configs or "none at all")) + end + + local configs, variants, subvariants = {}, {}, {} + + -- Legacy support: run "Config" constructor automatically on naked tables + -- passed in Configs array. + for idx = 1, #args.Configs do + local cfg = args.Configs[idx] + if getmetatable(cfg) ~= _config_class then + cfg = Config(cfg) + args.Configs[idx] = cfg + end + configs[cfg.Name] = cfg + end + + for _, dir in util.nil_ipairs(args.ScriptDirs) do + -- Make sure dir is sane and ends with a slash + dir = dir:gsub("[/\\]", SEP):gsub("[/\\]$", "") + local expr = dir .. SEP .. "?.lua" + + -- Add user toolset dir first so they can override builtin scripts. + package.path = expr .. ";" .. package.path + end + + if args.Variants then + for i, x in ipairs(args.Variants) do + if type(x) == "string" then + args.Variants[i] = mk_defvariant(x) + else + assert(x.Name) + if not x.Options then + x.Options = {} + end + end + end + end + + local variant_array = args.Variants or default_variants + for _, variant in ipairs(variant_array) do variants[variant.Name] = variant end + + local subvariant_array = args.SubVariants or default_subvariants + for _, subvariant in ipairs(subvariant_array) do subvariants[subvariant] = true end + + local default_variant = variant_array[1] + if args.DefaultVariant then + for _, x in ipairs(variant_array) do + if x.Name == args.DefaultVariant then + default_variant = x + end + end + end + + local default_subvariant = args.DefaultSubVariant or subvariant_array[1] + local build_tuples = analyze_targets(configs, variants, subvariants) + local passes = args.Passes or { Default = { Name = "Default", BuildOrder = 1 } } + + printf("%d valid build tuples", #build_tuples) + + -- Validate pass data + for id, data in pairs(passes) do + if not data.Name then + croak("Pass %s has no Name attribute", id) + elseif not data.BuildOrder then + croak("Pass %s has no BuildOrder attribute", id) + end + end + + -- Assume syntax for C and DotNet is always needed + -- for now. Could possible make an option for which generator sets to load + -- in the future. + require "tundra.syntax.native" + require "tundra.syntax.dotnet" + + build_result = { + BuildTuples = build_tuples, + BuildData = args, + Passes = passes, + Configs = configs, + DefaultVariant = default_variant, + DefaultSubVariant = default_subvariant, + ContentDigestExtensions = args.ContentDigestExtensions, + Options = args.Options, + } +end + +function run(build_script_fn) + local f, err = io.open(build_script_fn, 'r') + + if not f then + croak("%s", err) + end + + local text = f:read("*all") + f:close() + + local script_globals, script_globals_mt = {}, {} + script_globals_mt.__index = _G + setmetatable(script_globals, script_globals_mt) + + local chunk, error_msg = loadstring(text, build_script_fn) + if not chunk then + croak("%s", error_msg) + end + setfenv(chunk, script_globals) + + local success, result = xpcall(chunk, syntax_error_catcher) + + if not success then + print("Build script execution failed") + croak("%s", result or "") + end + + local result = build_result + build_result = nil + return result +end + diff --git a/ogl_editor/bin/win32/scripts/tundra/dagsave.lua b/ogl_editor/bin/win32/scripts/tundra/dagsave.lua new file mode 100644 index 0000000..37dba90 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/dagsave.lua @@ -0,0 +1,409 @@ +module(..., package.seeall) + +local depgraph = require "tundra.depgraph" +local util = require "tundra.util" +local scanner = require "tundra.scanner" +local dirwalk = require "tundra.dirwalk" +local platform = require "tundra.platform" +local native = require "tundra.native" +local njson = require "tundra.native.json" +local path = require "tundra.path" + +local dag_dag_magic = 0x15890105 + +local function get_passes(nodes) + local result = {} + local seen_passes = {} + + for _, node in ipairs(nodes) do + local p = node.pass + if not seen_passes[p] then + assert(type(p) == "table", "Passes must be tables, have " .. util.tostring(p)) + assert(type(p.BuildOrder) == "number", "Pass BuildOrder must be a number") + result[#result + 1] = p + seen_passes[p] = true + end + end + + table.sort(result, function (a, b) return a.BuildOrder < b.BuildOrder end) + + local pass_lookup = {} + + for index, pass in ipairs(result) do + pass_lookup[pass] = index - 1 + end + + return result, pass_lookup +end + +local function setup_input_deps(nodes) + local producers = {} + + local cwd = native.getcwd() .. SEP + local filter + if native.host_platform == 'windows' or native.host_platform == 'macosx' then + filter = function (str) return str:lower() end + else + filter = function (str) return str end + end + + local node_deps = {} + + -- Record producing node for all output files + for _, n in ipairs(nodes) do + for _, output in util.nil_ipairs(n.outputs) do + if not path.is_absolute(output) then + output = cwd .. output + end + output = filter(output) + if producers[output] then + errorf("file %s set to be written by more than one target:\n%s\n%s\n", + output, n.annotation, producers[output].annotation) + end + producers[output] = n + end + if n.deps then + node_deps[n] = util.make_lookup_table(n.deps) + end + end + + -- Map input files to dependencies + for _, n in ipairs(nodes) do + for _, inputf in util.nil_ipairs(n.inputs) do + if not path.is_absolute(inputf) then + inputf = cwd .. inputf + end + inputf = filter(inputf) + local producer = producers[inputf] + local deps_lut = node_deps[n] + if producer and (not deps_lut or not deps_lut[producer]) then + n.deps[#n.deps + 1] = producer + if not deps_lut then + deps_lut = {} + node_deps[n] = deps_lut + end + deps_lut[producer] = true + end + end + end + +end + +local function get_scanners(nodes) + local scanners = {} + local scanner_to_index = {} + for _, node in ipairs(nodes) do + local scanner = node.scanner + if scanner and not scanner_to_index[scanner] then + scanner_to_index[scanner] = #scanners + scanners[#scanners + 1] = scanner + end + end + return scanners, scanner_to_index +end + +local function save_passes(w, passes) + w:begin_array("Passes") + for _, s in ipairs(passes) do + w:write_string(s.Name) + end + w:end_array() +end + +local function save_scanners(w, scanners) + w:begin_array("Scanners") + for _, s in ipairs(scanners) do + w:begin_object() + w:write_string(s.Kind, 'Kind') + w:begin_array("IncludePaths") + for _, path in util.nil_ipairs(s.Paths) do + w:write_string(path) + end + w:end_array() + -- Serialize specialized state for generic scanners + if s.Kind == 'generic' then + w:write_bool(s.RequireWhitespace, 'RequireWhitespace') + w:write_bool(s.UseSeparators, 'UseSeparators') + w:write_bool(s.BareMeansSystem, 'BareMeansSystem') + w:begin_array('Keywords') + for _, kw in util.nil_ipairs(s.Keywords) do + w:write_string(kw) + end + w:end_array() + w:begin_array('KeywordsNoFollow') + for _, kw in util.nil_ipairs(s.KeywordsNoFollow) do + w:write_string(kw) + end + w:end_array() + end + w:end_object() + end + w:end_array() +end + +local function save_nodes(w, nodes, pass_to_index, scanner_to_index) + w:begin_array("Nodes") + for idx, node in ipairs(nodes) do + w:begin_object() + assert(idx - 1 == node.index) + if node.action then + w:write_string(node.action, "Action") + end + if node.preaction then + w:write_string(node.preaction, "PreAction") + end + w:write_string(node.annotation, "Annotation") + + w:write_number(pass_to_index[node.pass], "PassIndex") + + if #node.deps > 0 then + w:begin_array("Deps") + for _, dep in ipairs(node.deps) do + w:write_number(dep.index) + end + w:end_array() + end + + local function dump_file_list(list, name) + if list and #list > 0 then + w:begin_array(name) + for _, fn in ipairs(list) do + w:write_string(fn) + end + w:end_array(name) + end + end + + dump_file_list(node.inputs, "Inputs") + dump_file_list(node.outputs, "Outputs") + dump_file_list(node.aux_outputs, "AuxOutputs") + + -- Save environment strings + local env_count = 0 + for k, v in util.nil_pairs(node.env) do + env_count = env_count + 1 + end + + if env_count > 0 then + w:begin_array("Env") + for k, v in pairs(node.env) do + w:begin_object() + w:write_string(k, "Key") + w:write_string(v, "Value") + w:end_object() + end + w:end_array() + end + + if node.scanner then + w:write_number(scanner_to_index[node.scanner], "ScannerIndex") + end + + if node.overwrite_outputs then + w:write_bool(true, "OverwriteOutputs") + end + + if node.is_precious then + w:write_bool(true, "PreciousOutputs") + end + + if node.expensive then + w:write_bool(true, "Expensive") + end + + w:end_object() + end + w:end_array() +end + +local function save_configs(w, bindings, default_variant, default_subvariant) + local configs = {} + local variants = {} + local subvariants = {} + local config_index = {} + local variant_index = {} + local subvariant_index = {} + local default_config = nil + local host_platform = platform.host_platform() + + for _, b in ipairs(bindings) do + if not configs[b.Config.Name] then + configs[b.Config.Name] = #config_index + config_index[#config_index+1] = b.Config.Name + end + if not variants[b.Variant.Name] then + variants[b.Variant.Name] = #variant_index + variant_index[#variant_index+1] = b.Variant.Name + end + if not subvariants[b.SubVariant] then + subvariants[b.SubVariant] = #subvariant_index + subvariant_index[#subvariant_index+1] = b.SubVariant + end + + if b.Config.DefaultOnHost == host_platform then + default_config = b.Config + end + end + + assert(#config_index > 0) + assert(#variant_index > 0) + assert(#subvariant_index > 0) + + local function dump_str_array(array, name) + if array and #array > 0 then + w:begin_array(name) + for _, name in ipairs(array) do + w:write_string(name) + end + w:end_array() + end + end + + w:begin_object("Setup") + dump_str_array(config_index, "Configs") + dump_str_array(variant_index, "Variants") + dump_str_array(subvariant_index, "SubVariants") + + w:begin_array("BuildTuples") + for index, binding in ipairs(bindings) do + w:begin_object() + w:write_number(configs[binding.Config.Name], "ConfigIndex") + w:write_number(variants[binding.Variant.Name], "VariantIndex") + w:write_number(subvariants[binding.SubVariant], "SubVariantIndex") + local function store_node_index_array(nodes, name) + w:begin_array(name) + for _, node in util.nil_ipairs(nodes) do + w:write_number(node.index) + end + w:end_array() + end + store_node_index_array(binding.AlwaysNodes, "AlwaysNodes") + store_node_index_array(binding.DefaultNodes, "DefaultNodes") + + w:begin_object("NamedNodes") + for name, node in pairs(binding.NamedNodes) do + w:write_number(node.index, name) + end + w:end_object() + + w:end_object() + end + w:end_array() + + -- m_DefaultBuildTuple + w:begin_object("DefaultBuildTuple") + if default_config then + w:write_number(configs[default_config.Name], "ConfigIndex") + else + w:write_number(-1, "ConfigIndex") + end + + if default_variant then + w:write_number(variants[default_variant.Name], "VariantIndex") + else + w:write_number(-1, "VariantIndex") + end + + if default_subvariant then + w:write_number(subvariants[default_subvariant], "SubVariantIndex") + else + w:write_number(-1, "SubVariantIndex") + end + w:end_object() + + w:end_object() + +end + +local function save_signatures(w, accessed_lua_files) + w:begin_array("FileSignatures") + for _, fn in ipairs(accessed_lua_files) do + w:begin_object() + local stat = native.stat_file(fn) + if not stat.exists then + errorf("accessed file %s is gone: %s", fn, err) + end + w:write_string(fn, "File") + w:write_number(stat.timestamp, "Timestamp") + w:end_object() + end + w:end_array() + + w:begin_array("GlobSignatures") + local globs = dirwalk.all_queries() + for _, glob in ipairs(globs) do + w:begin_object() + w:write_string(glob.Path, "Path") + w:begin_array("Files") + for _, fn in ipairs(glob.Files) do w:write_string(fn) end + w:end_array() + w:begin_array("SubDirs") + for _, fn in ipairs(glob.SubDirs) do w:write_string(fn) end + w:end_array() + w:end_object() + end + w:end_array() +end + +local function check_deps(nodes) + for _, node in ipairs(nodes) do + for _ , dep in ipairs(node.deps) do + if dep.pass.BuildOrder > node.pass.BuildOrder then + errorf("%s (pass: %s) depends on %s in later pass (%s)", node.annotation, node.pass.Name, dep.annotation, dep.pass.Name) + end + end + end +end + +function save_dag_data(bindings, default_variant, default_subvariant, content_digest_exts, misc_options) + + -- Call builtin function to get at accessed file table + local accessed_lua_files = util.table_keys(get_accessed_files()) + + misc_options = misc_options or {} + local max_expensive_jobs = misc_options.MaxExpensiveJobs or -1 + + printf("save_dag_data: %d bindings, %d accessed files", #bindings, #accessed_lua_files) + + local nodes = depgraph.get_all_nodes() + + -- Set node indices + for idx, node in ipairs(nodes) do + node.index = idx - 1 + end + + -- Set up array of passes + local passes, pass_to_index = get_passes(nodes) + + -- Hook up dependencies due to input files + setup_input_deps(nodes) + + check_deps(nodes) + + -- Find scanners + local scanners, scanner_to_index = get_scanners(nodes) + + local w = njson.new('.tundra2.dag.json') + + w:begin_object() + save_configs(w, bindings, default_variant, default_subvariant) + save_passes(w, passes) + save_scanners(w, scanners) + save_nodes(w, nodes, pass_to_index, scanner_to_index) + save_signatures(w, accessed_lua_files) + + if content_digest_exts and #content_digest_exts > 0 then + w:begin_array("ContentDigestExtensions") + for _, ext in ipairs(content_digest_exts) do + w:write_string(ext) + end + w:end_array() + end + + w:write_number(max_expensive_jobs, "MaxExpensiveCount") + + w:end_object() + + w:close() +end + diff --git a/ogl_editor/bin/win32/scripts/tundra/debugger.lua b/ogl_editor/bin/win32/scripts/tundra/debugger.lua new file mode 100644 index 0000000..73a79ff --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/debugger.lua @@ -0,0 +1,1343 @@ + +--{{{ history + +--15/03/06 DCN Created based on RemDebug +--28/04/06 DCN Update for Lua 5.1 +--01/06/06 DCN Fix command argument parsing +-- Add step/over N facility +-- Add trace lines facility +--05/06/06 DCN Add trace call/return facility +--06/06/06 DCN Make it behave when stepping through the creation of a coroutine +--06/06/06 DCN Integrate the simple debugger into the main one +--07/06/06 DCN Provide facility to step into coroutines +--13/06/06 DCN Fix bug that caused the function environment to get corrupted with the global one +--14/06/06 DCN Allow 'sloppy' file names when setting breakpoints +--04/08/06 DCN Allow for no space after command name +--11/08/06 DCN Use io.write not print +--30/08/06 DCN Allow access to array elements in 'dump' +--10/10/06 DCN Default to breakfile for all commands that require a filename and give '-' +--06/12/06 DCN Allow for punctuation characters in DUMP variable names +--03/01/07 DCN Add pause on/off facility +--19/06/07 DCN Allow for duff commands being typed in the debugger (thanks to Michael.Bringmann@lsi.com) +-- Allow for case sensitive file systems (thanks to Michael.Bringmann@lsi.com) + +--}}} +--{{{ description + +--A simple command line debug system for Lua written by Dave Nichols of +--Match-IT Limited. Its public domain software. Do with it as you wish. + +--This debugger was inspired by: +-- RemDebug 1.0 Beta +-- Copyright Kepler Project 2005 (http://www.keplerproject.org/remdebug) + +--Usage: +-- require('debugger') --load the debug library +-- pause(message) --start/resume a debug session + +--An assert() failure will also invoke the debugger. + +--}}} + +local IsWindows = string.find(string.lower(os.getenv('OS') or ''),'^windows') + +local coro_debugger +local events = { BREAK = 1, WATCH = 2, STEP = 3, SET = 4 } +local breakpoints = {} +local watches = {} +local step_into = false +local step_over = false +local step_lines = 0 +local step_level = {main=0} +local stack_level = {main=0} +local trace_level = {main=0} +local trace_calls = false +local trace_returns = false +local trace_lines = false +local ret_file, ret_line, ret_name +local current_thread = 'main' +local started = false +local pause_off = false +local _g = _G +local cocreate, cowrap = coroutine.create, coroutine.wrap +local pausemsg = 'pause' + +--{{{ local hints -- command help +--The format in here is name=summary|description +local hints = { + +pause = [[ +pause(msg) -- start/resume a debugger session| + +This can only be used in your code or from the console as a means to +start/resume a debug session. +If msg is given that is shown when the session starts/resumes. Useful to +give a context if you've instrumented your code with pause() statements. +]], + +poff = [[ +poff -- turn off pause() command| + +This causes all pause() commands to be ignored. This is useful if you have +instrumented your code in a busy loop and want to continue normal execution +with no further interruption. +]], + +pon = [[ +pon -- turn on pause() command| + +This re-instates honouring the pause() commands you may have instrumented +your code with. +]], + +setb = [[ +setb [line file] -- set a breakpoint to line/file| + +If file is omitted or is "-" the breakpoint is set at the file for the +currently set level (see "set"). Execution pauses when this line is about +to be executed and the debugger session is re-activated. + +The file can be given as the fully qualified name, partially qualified or +just the file name. E.g. if file is set as "myfile.lua", then whenever +execution reaches any file that ends with "myfile.lua" it will pause. +]], + +delb = [[ +delb [line file] -- removes a breakpoint| + +If file is omitted or is "-" the breakpoint is removed for the file of the +currently set level (see "set"). +]], + +delallb = [[ +delallb -- removes all breakpoints| +]], + +setw = [[ +setw -- adds a new watch expression| + +The expression is evaluated before each line is executed. If the expression +yields true then execution is paused and the debugger session re-activated. +The expression is executed in the context of the line about to be executed. +]], + +delw = [[ +delw -- removes the watch expression at index| + +The index is that returned when the watch expression was set by setw. +]], + +delallw = [[ +delallw -- removes all watch expressions| +]], + +run = [[ +run -- run until next breakpoint or watch expression| +]], + +step = [[ +step [N] -- run next N lines, stepping into function calls| + +If N is omitted, use 1. +]], + +over = [[ +over [N] -- run next N lines, stepping over function calls| + +If N is omitted, use 1. +]], + +out = [[ +out [N] -- run lines until stepped out of N functions| + +If N is omitted, use 1. +If you are inside a function, using "out 1" will run until you return +from that function to the caller. +]], + +goto = [[ +goto -- step to line number in the current file| + +The line and current file are those in the currently set context level. +]], + +listb = [[ +listb -- lists breakpoints| +]], + +listw = [[ +listw -- lists watch expressions| +]], + +set = [[ +set [level] -- set context to stack level, omitted=show| + +If level is omitted it just prints the current level set. +This sets the current context to the level given. This affects the +context used for several other functions (e.g. vars). The possible +levels are those shown by trace. +]], + +vars = [[ +vars [depth] -- list context locals to depth, omitted=1| + +If depth is omitted then uses 1. +Use a depth of 0 for the maximum. +Lists all non-nil local variables and all non-nil upvalues in the +currently set context. For variables that are tables, lists all fields +to the given depth. +]], + +fenv = [[ +fenv [depth] -- list context function env to depth, omitted=1| + +If depth is omitted then uses 1. +Use a depth of 0 for the maximum. +Lists all function environment variables in the currently set context. +For variables that are tables, lists all fields to the given depth. +]], + +glob = [[ +glob [depth] -- list globals to depth, omitted=1| + +If depth is omitted then uses 1. +Use a depth of 0 for the maximum. +Lists all global variables. +For variables that are tables, lists all fields to the given depth. +]], + +ups = [[ +ups -- list all the upvalue names| + +These names will also be in the "vars" list unless their value is nil. +This provides a means to identify which vars are upvalues and which are +locals. If a name is both an upvalue and a local, the local value takes +precedance. +]], + +locs = [[ +locs -- list all the locals names| + +These names will also be in the "vars" list unless their value is nil. +This provides a means to identify which vars are upvalues and which are +locals. If a name is both an upvalue and a local, the local value takes +precedance. +]], + +dump = [[ +dump [depth] -- dump all fields of variable to depth| + +If depth is omitted then uses 1. +Use a depth of 0 for the maximum. +Prints the value of in the currently set context level. If +is a table, lists all fields to the given depth. can be just a +name, or name.field or name.# to any depth, e.g. t.1.f accesses field +'f' in array element 1 in table 't'. + +Can also be called from a script as dump(var,depth). +]], + +tron = [[ +tron [crl] -- turn trace on for (c)alls, (r)etuns, (l)lines| + +If no parameter is given then tracing is turned off. +When tracing is turned on a line is printed to the console for each +debug 'event' selected. c=function calls, r=function returns, l=lines. +]], + +trace = [[ +trace -- dumps a stack trace| + +Format is [level] = file,line,name +The level is a candidate for use by the 'set' command. +]], + +info = [[ +info -- dumps the complete debug info captured| + +Only useful as a diagnostic aid for the debugger itself. This information +can be HUGE as it dumps all variables to the maximum depth, so be careful. +]], + +show = [[ +show line file X Y -- show X lines before and Y after line in file| + +If line is omitted or is '-' then the current set context line is used. +If file is omitted or is '-' then the current set context file is used. +If file is not fully qualified and cannot be opened as specified, then +a search for the file in the package[path] is performed using the usual +"require" searching rules. If no file extension is given, .lua is used. +Prints the lines from the source file around the given line. +]], + +exit = [[ +exit -- exits debugger, re-start it using pause()| +]], + +help = [[ +help [command] -- show this list or help for command| +]], + +[""] = [[ + -- execute a statement in the current context| + +The statement can be anything that is legal in the context, including +assignments. Such assignments affect the context and will be in force +immediately. Any results returned are printed. Use '=' as a short-hand +for 'return', e.g. "=func(arg)" will call 'func' with 'arg' and print +the results, and "=var" will just print the value of 'var'. +]], + +what = [[ +what -- show where is defined (if known)| +]], + +} +--}}} + +--{{{ local function getinfo(level,field) + +--like debug.getinfo but copes with no activation record at the given level +--and knows how to get 'field'. 'field' can be the name of any of the +--activation record fields or any of the 'what' names or nil for everything. +--only valid when using the stack level to get info, not a function name. + +local function getinfo(level,field) + level = level + 1 --to get to the same relative level as the caller + if not field then return debug.getinfo(level) end + local what + if field == 'name' or field == 'namewhat' then + what = 'n' + elseif field == 'what' or field == 'source' or field == 'linedefined' or field == 'lastlinedefined' or field == 'short_src' then + what = 'S' + elseif field == 'currentline' then + what = 'l' + elseif field == 'nups' then + what = 'u' + elseif field == 'func' then + what = 'f' + else + return debug.getinfo(level,field) + end + local ar = debug.getinfo(level,what) + if ar then return ar[field] else return nil end +end + +--}}} +--{{{ local function indented( level, ... ) + +local function indented( level, ... ) + io.write( string.rep(' ',level), table.concat({...}), '\n' ) +end + +--}}} +--{{{ local function dumpval( level, name, value, limit ) + +local dumpvisited + +local function dumpval( level, name, value, limit ) + local index + if type(name) == 'number' then + index = string.format('[%d] = ',name) + elseif type(name) == 'string' + and (name == '__VARSLEVEL__' or name == '__ENVIRONMENT__' or name == '__GLOBALS__' or name == '__UPVALUES__' or name == '__LOCALS__') then + --ignore these, they are debugger generated + return + elseif type(name) == 'string' and string.find(name,'^[_%a][_.%w]*$') then + index = name ..' = ' + else + index = string.format('[%q] = ',tostring(name)) + end + if type(value) == 'table' then + if dumpvisited[value] then + indented( level, index, string.format('ref%q;',dumpvisited[value]) ) + else + dumpvisited[value] = tostring(value) + if (limit or 0) > 0 and level+1 >= limit then + indented( level, index, dumpvisited[value] ) + else + indented( level, index, '{ -- ', dumpvisited[value] ) + for n,v in pairs(value) do + dumpval( level+1, n, v, limit ) + end + indented( level, '};' ) + end + end + else + if type(value) == 'string' then + if string.len(value) > 40 then + indented( level, index, '[[', value, ']];' ) + else + indented( level, index, string.format('%q',value), ';' ) + end + else + indented( level, index, tostring(value), ';' ) + end + end +end + +--}}} +--{{{ local function dumpvar( value, limit, name ) + +local function dumpvar( value, limit, name ) + dumpvisited = {} + dumpval( 0, name or tostring(value), value, limit ) +end + +--}}} +--{{{ local function show(file,line,before,after) + +--show +/-N lines of a file around line M + +local function show(file,line,before,after) + + line = tonumber(line or 1) + before = tonumber(before or 10) + after = tonumber(after or before) + + if not string.find(file,'%.') then file = file..'.lua' end + + local f = io.open(file,'r') + if not f then + --{{{ try to find the file in the path + + -- + -- looks for a file in the package path + -- + local path = package.path or LUA_PATH or '' + for c in string.gmatch (path, "[^;]+") do + local c = string.gsub (c, "%?%.lua", file) + f = io.open (c,'r') + if f then + break + end + end + + --}}} + if not f then + io.write('Cannot find '..file..'\n') + return + end + end + + local i = 0 + for l in f:lines() do + i = i + 1 + if i >= (line-before) then + if i > (line+after) then break end + if i == line then + io.write(i..'***\t'..l..'\n') + else + io.write(i..'\t'..l..'\n') + end + end + end + + f:close() + +end + +--}}} +--{{{ local function tracestack(l) + +local function gi( i ) + return function() i=i+1 return debug.getinfo(i),i end +end + +local function gl( level, j ) + return function() j=j+1 return debug.getlocal( level, j ) end +end + +local function gu( func, k ) + return function() k=k+1 return debug.getupvalue( func, k ) end +end + +local traceinfo + +local function tracestack(l) + local l = l + 1 --NB: +1 to get level relative to caller + traceinfo = {} + traceinfo.pausemsg = pausemsg + for ar,i in gi(l) do + table.insert( traceinfo, ar ) + local names = {} + local values = {} + for n,v in gl(i,0) do + if string.sub(n,1,1) ~= '(' then --ignore internal control variables + table.insert( names, n ) + table.insert( values, v ) + end + end + if #names > 0 then + ar.lnames = names + ar.lvalues = values + end + if ar.func then + local names = {} + local values = {} + for n,v in gu(ar.func,0) do + if string.sub(n,1,1) ~= '(' then --ignore internal control variables + table.insert( names, n ) + table.insert( values, v ) + end + end + if #names > 0 then + ar.unames = names + ar.uvalues = values + end + end + end +end + +--}}} +--{{{ local function trace() + +local function trace(set) + local mark + for level,ar in ipairs(traceinfo) do + if level == set then + mark = '***' + else + mark = '' + end + io.write('['..level..']'..mark..'\t'..(ar.name or ar.what)..' in '..ar.short_src..':'..ar.currentline..'\n') + end +end + +--}}} +--{{{ local function info() + +local function info() dumpvar( traceinfo, 0, 'traceinfo' ) end + +--}}} + +--{{{ local function set_breakpoint(file, line) + +local function set_breakpoint(file, line) + if not breakpoints[line] then + breakpoints[line] = {} + end + breakpoints[line][file] = true +end + +--}}} +--{{{ local function remove_breakpoint(file, line) + +local function remove_breakpoint(file, line) + if breakpoints[line] then + breakpoints[line][file] = nil + end +end + +--}}} +--{{{ local function has_breakpoint(file, line) + +--allow for 'sloppy' file names +--search for file and all variations walking up its directory hierachy +--ditto for the file with no extension + +local function has_breakpoint(file, line) + if not breakpoints[line] then return false end + local noext = string.gsub(file,"(%..-)$",'',1) + if noext == file then noext = nil end + while file do + if breakpoints[line][file] then return true end + file = string.match(file,"[:/\](.+)$") + end + while noext do + if breakpoints[line][noext] then return true end + noext = string.match(noext,"[:/\](.+)$") + end + return false +end + +--}}} +--{{{ local function capture_vars(ref,level,line) + +local function capture_vars(ref,level,line) + --get vars, file and line for the given level relative to debug_hook offset by ref + + local lvl = ref + level --NB: This includes an offset of +1 for the call to here + + --{{{ capture variables + + local ar = debug.getinfo(lvl, "f") + if not ar then return {},'?',0 end + + local vars = {__UPVALUES__={}, __LOCALS__={}} + local i + + local func = ar.func + if func then + i = 1 + while true do + local name, value = debug.getupvalue(func, i) + if not name then break end + if string.sub(name,1,1) ~= '(' then --NB: ignoring internal control variables + vars[name] = value + vars.__UPVALUES__[i] = name + end + i = i + 1 + end + vars.__ENVIRONMENT__ = getfenv(func) + end + + vars.__GLOBALS__ = getfenv(0) + + i = 1 + while true do + local name, value = debug.getlocal(lvl, i) + if not name then break end + if string.sub(name,1,1) ~= '(' then --NB: ignoring internal control variables + vars[name] = value + vars.__LOCALS__[i] = name + end + i = i + 1 + end + + vars.__VARSLEVEL__ = level + + if func then + --NB: Do not do this until finished filling the vars table + setmetatable(vars, { __index = getfenv(func), __newindex = getfenv(func) }) + end + + --NB: Do not read or write the vars table anymore else the metatable functions will get invoked! + + --}}} + + local file = getinfo(lvl, "source") + if string.find(file, "@") == 1 then + file = string.sub(file, 2) + end + if IsWindows then file = string.lower(file) end + + if not line then + line = getinfo(lvl, "currentline") + end + + return vars,file,line + +end + +--}}} +--{{{ local function restore_vars(ref,vars) + +local function restore_vars(ref,vars) + + if type(vars) ~= 'table' then return end + + local level = vars.__VARSLEVEL__ --NB: This level is relative to debug_hook offset by ref + if not level then return end + + level = level + ref --NB: This includes an offset of +1 for the call to here + + local i + local written_vars = {} + + i = 1 + while true do + local name, value = debug.getlocal(level, i) + if not name then break end + if vars[name] and string.sub(name,1,1) ~= '(' then --NB: ignoring internal control variables + debug.setlocal(level, i, vars[name]) + written_vars[name] = true + end + i = i + 1 + end + + local ar = debug.getinfo(level, "f") + if not ar then return end + + local func = ar.func + if func then + + i = 1 + while true do + local name, value = debug.getupvalue(func, i) + if not name then break end + if vars[name] and string.sub(name,1,1) ~= '(' then --NB: ignoring internal control variables + if not written_vars[name] then + debug.setupvalue(func, i, vars[name]) + end + written_vars[name] = true + end + i = i + 1 + end + + end + +end + +--}}} +--{{{ local function trace_event(event, line, level) + +local function print_trace(level,depth,event,file,line,name) + + --NB: level here is relative to the caller of trace_event, so offset by 2 to get to there + level = level + 2 + + local file = file or getinfo(level,'short_src') + local line = line or getinfo(level,'currentline') + local name = name or getinfo(level,'name') + + local prefix = '' + if current_thread ~= 'main' then prefix = '['..tostring(current_thread)..'] ' end + + io.write(prefix.. + string.format('%08.2f:%02i.',os.clock(),depth).. + string.rep('.',depth%32).. + (file or '')..' ('..(line or '')..') '.. + (name or '').. + ' ('..event..')\n') + +end + +local function trace_event(event, line, level) + + if event == 'return' and trace_returns then + --note the line info for later + ret_file = getinfo(level+1,'short_src') + ret_line = getinfo(level+1,'currentline') + ret_name = getinfo(level+1,'name') + end + + if event ~= 'line' then return end + + local slevel = stack_level[current_thread] + local tlevel = trace_level[current_thread] + + if trace_calls and slevel > tlevel then + --we are now in the function called, so look back 1 level further to find the calling file and line + print_trace(level+1,slevel-1,'c',nil,nil,getinfo(level+1,'name')) + end + + if trace_returns and slevel < tlevel then + print_trace(level,slevel,'r',ret_file,ret_line,ret_name) + end + + if trace_lines then + print_trace(level,slevel,'l') + end + + trace_level[current_thread] = stack_level[current_thread] + +end + +--}}} +--{{{ local function debug_hook(event, line, level, thread) + +local function debug_hook(event, line, level, thread) + if not started then debug.sethook() return end + current_thread = thread or 'main' + local level = level or 2 + trace_event(event,line,level) + if event == "call" then + stack_level[current_thread] = stack_level[current_thread] + 1 + elseif event == "return" then + stack_level[current_thread] = stack_level[current_thread] - 1 + if stack_level[current_thread] < 0 then stack_level[current_thread] = 0 end + else + local vars,file,line = capture_vars(level,1,line) + local stop, ev, idx = false, events.STEP, 0 + while true do + for index, value in pairs(watches) do + setfenv(value.func, vars) + local status, res = pcall(value.func) + if status and res then + ev, idx = events.WATCH, index + stop = true + break + end + end + if stop then break end + if (step_into) + or (step_over and (stack_level[current_thread] <= step_level[current_thread] or stack_level[current_thread] == 0)) then + step_lines = step_lines - 1 + if step_lines < 1 then + ev, idx = events.STEP, 0 + break + end + end + if has_breakpoint(file, line) then + ev, idx = events.BREAK, 0 + break + end + return + end + tracestack(level) + local last_next = 1 + local err, next = assert(coroutine.resume(coro_debugger, ev, vars, file, line, idx)) + while true do + if next == 'cont' then + return + elseif next == 'stop' then + started = false + debug.sethook() + return + elseif tonumber(next) then --get vars for given level or last level + next = tonumber(next) + if next == 0 then next = last_next end + last_next = next + restore_vars(level,vars) + vars, file, line = capture_vars(level,next) + err, next = assert(coroutine.resume(coro_debugger, events.SET, vars, file, line, idx)) + else + io.write('Unknown command from debugger_loop: '..tostring(next)..'\n') + io.write('Stopping debugger\n') + next = 'stop' + end + end + end +end + +--}}} +--{{{ local function report(ev, vars, file, line, idx_watch) + +local function report(ev, vars, file, line, idx_watch) + local vars = vars or {} + local file = file or '?' + local line = line or 0 + local prefix = '' + if current_thread ~= 'main' then prefix = '['..tostring(current_thread)..'] ' end + if ev == events.STEP then + io.write(prefix.."Paused at file "..file.." line "..line..' ('..stack_level[current_thread]..')\n') + elseif ev == events.BREAK then + io.write(prefix.."Paused at file "..file.." line "..line..' ('..stack_level[current_thread]..') (breakpoint)\n') + elseif ev == events.WATCH then + io.write(prefix.."Paused at file "..file.." line "..line..' ('..stack_level[current_thread]..')'.." (watch expression "..idx_watch.. ": ["..watches[idx_watch].exp.."])\n") + elseif ev == events.SET then + --do nothing + else + io.write(prefix.."Error in application: "..file.." line "..line.."\n") + end + if ev ~= events.SET then + if pausemsg and pausemsg ~= '' then io.write('Message: '..pausemsg..'\n') end + pausemsg = '' + end + return vars, file, line +end + +--}}} + +--{{{ local function debugger_loop(server) + +local function debugger_loop(ev, vars, file, line, idx_watch) + + io.write("Lua Debugger\n") + local eval_env, breakfile, breakline = report(ev, vars, file, line, idx_watch) + io.write("Type 'help' for commands\n") + + local command, args + + --{{{ local function getargs(spec) + + --get command arguments according to the given spec from the args string + --the spec has a single character for each argument, arguments are separated + --by white space, the spec characters can be one of: + -- F for a filename (defaults to breakfile if - given in args) + -- L for a line number (defaults to breakline if - given in args) + -- N for a number + -- V for a variable name + -- S for a string + + local function getargs(spec) + local res={} + local char,arg + local ptr=1 + local _ + for i=1,string.len(spec) do + char = string.sub(spec,i,i) + if char == 'F' then + _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr) + if not arg or arg == '' then arg = '-' end + if arg == '-' then arg = breakfile end + elseif char == 'L' then + _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr) + if not arg or arg == '' then arg = '-' end + if arg == '-' then arg = breakline end + arg = tonumber(arg) or 0 + elseif char == 'N' then + _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr) + if not arg or arg == '' then arg = '0' end + arg = tonumber(arg) or 0 + elseif char == 'V' then + _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr) + if not arg or arg == '' then arg = '' end + elseif char == 'S' then + _,ptr,arg = string.find(args..' ',"%s*([%w%p]*)%s*",ptr) + if not arg or arg == '' then arg = '' end + else + arg = '' + end + table.insert(res,arg or '') + end + return unpack(res) + end + + --}}} + + while true do + io.write("[DEBUG]> ") + local line = io.read("*line") + if line == nil then io.write('\n'); line = 'exit' end + + if string.find(line, "^[a-z]+") then + command = string.sub(line, string.find(line, "^[a-z]+")) + args = string.gsub(line,"^[a-z]+%s*",'',1) --strip command off line + else + command = '' + end + + if command == "setb" then + --{{{ set breakpoint + + local line, filename = getargs('LF') + if filename ~= '' and line ~= '' then + set_breakpoint(filename,line) + io.write("Breakpoint set in file "..filename..' line '..line..'\n') + else + io.write("Bad request\n") + end + + --}}} + + elseif command == "delb" then + --{{{ delete breakpoint + + local line, filename = getargs('LF') + if filename ~= '' and line ~= '' then + remove_breakpoint(filename, line) + io.write("Breakpoint deleted from file "..filename..' line '..line.."\n") + else + io.write("Bad request\n") + end + + --}}} + + elseif command == "delallb" then + --{{{ delete all breakpoints + breakpoints = {} + io.write('All breakpoints deleted\n') + --}}} + + elseif command == "listb" then + --{{{ list breakpoints + for i, v in pairs(breakpoints) do + for ii, vv in pairs(v) do + io.write("Break at: "..i..' in '..ii..'\n') + end + end + --}}} + + elseif command == "setw" then + --{{{ set watch expression + + if args and args ~= '' then + local func = loadstring("return(" .. args .. ")") + local newidx = #watches + 1 + watches[newidx] = {func = func, exp = args} + io.write("Set watch exp no. " .. newidx..'\n') + else + io.write("Bad request\n") + end + + --}}} + + elseif command == "delw" then + --{{{ delete watch expression + + local index = tonumber(args) + if index then + watches[index] = nil + io.write("Watch expression deleted\n") + else + io.write("Bad request\n") + end + + --}}} + + elseif command == "delallw" then + --{{{ delete all watch expressions + watches = {} + io.write('All watch expressions deleted\n') + --}}} + + elseif command == "listw" then + --{{{ list watch expressions + for i, v in pairs(watches) do + io.write("Watch exp. " .. i .. ": " .. v.exp..'\n') + end + --}}} + + elseif command == "run" then + --{{{ run until breakpoint + step_into = false + step_over = false + eval_env, breakfile, breakline = report(coroutine.yield('cont')) + --}}} + + elseif command == "step" then + --{{{ step N lines (into functions) + local N = tonumber(args) or 1 + step_over = false + step_into = true + step_lines = tonumber(N or 1) + eval_env, breakfile, breakline = report(coroutine.yield('cont')) + --}}} + + elseif command == "over" then + --{{{ step N lines (over functions) + local N = tonumber(args) or 1 + step_into = false + step_over = true + step_lines = tonumber(N or 1) + step_level[current_thread] = stack_level[current_thread] + eval_env, breakfile, breakline = report(coroutine.yield('cont')) + --}}} + + elseif command == "out" then + --{{{ step N lines (out of functions) + local N = tonumber(args) or 1 + step_into = false + step_over = true + step_lines = 1 + step_level[current_thread] = stack_level[current_thread] - tonumber(N or 1) + eval_env, breakfile, breakline = report(coroutine.yield('cont')) + --}}} + + elseif command == "goto" then + --{{{ step until reach line + local N = tonumber(args) + if N then + step_over = false + step_into = false + if has_breakpoint(breakfile,N) then + eval_env, breakfile, breakline = report(coroutine.yield('cont')) + else + local bf = breakfile + set_breakpoint(breakfile,N) + eval_env, breakfile, breakline = report(coroutine.yield('cont')) + if breakfile == bf and breakline == N then remove_breakpoint(breakfile,N) end + end + else + io.write("Bad request\n") + end + --}}} + + elseif command == "set" then + --{{{ set/show context level + local level = args + if level and level == '' then level = nil end + if level then + eval_env, breakfile, breakline = report(coroutine.yield(level)) + end + if eval_env.__VARSLEVEL__ then + io.write('Level: '..eval_env.__VARSLEVEL__..'\n') + else + io.write('No level set\n') + end + --}}} + + elseif command == "vars" then + --{{{ list context variables + local depth = args + if depth and depth == '' then depth = nil end + depth = tonumber(depth) or 1 + dumpvar(eval_env, depth+1, 'variables') + --}}} + + elseif command == "glob" then + --{{{ list global variables + local depth = args + if depth and depth == '' then depth = nil end + depth = tonumber(depth) or 1 + dumpvar(eval_env.__GLOBALS__,depth+1,'globals') + --}}} + + elseif command == "fenv" then + --{{{ list function environment variables + local depth = args + if depth and depth == '' then depth = nil end + depth = tonumber(depth) or 1 + dumpvar(eval_env.__ENVIRONMENT__,depth+1,'environment') + --}}} + + elseif command == "ups" then + --{{{ list upvalue names + dumpvar(eval_env.__UPVALUES__,2,'upvalues') + --}}} + + elseif command == "locs" then + --{{{ list locals names + dumpvar(eval_env.__LOCALS__,2,'upvalues') + --}}} + + elseif command == "what" then + --{{{ show where a function is defined + if args and args ~= '' then + local v = eval_env + local n = nil + for w in string.gmatch(args,"[%w_]+") do + v = v[w] + if n then n = n..'.'..w else n = w end + if not v then break end + end + if type(v) == 'function' then + local def = debug.getinfo(v,'S') + if def then + io.write(def.what..' in '..def.short_src..' '..def.linedefined..'..'..def.lastlinedefined..'\n') + else + io.write('Cannot get info for '..v..'\n') + end + else + io.write(v..' is not a function\n') + end + else + io.write("Bad request\n") + end + --}}} + + elseif command == "dump" then + --{{{ dump a variable + local name, depth = getargs('VN') + if name ~= '' then + if depth == '' or depth == 0 then depth = nil end + depth = tonumber(depth or 1) + local v = eval_env + local n = nil + for w in string.gmatch(name,"[^%.]+") do --get everything between dots + if tonumber(w) then + v = v[tonumber(w)] + else + v = v[w] + end + if n then n = n..'.'..w else n = w end + if not v then break end + end + dumpvar(v,depth+1,n) + else + io.write("Bad request\n") + end + --}}} + + elseif command == "show" then + --{{{ show file around a line or the current breakpoint + + local line, file, before, after = getargs('LFNN') + if before == 0 then before = 10 end + if after == 0 then after = before end + + if file ~= '' and file ~= "=stdin" then + show(file,line,before,after) + else + io.write('Nothing to show\n') + end + + --}}} + + elseif command == "poff" then + --{{{ turn pause command off + pause_off = true + --}}} + + elseif command == "pon" then + --{{{ turn pause command on + pause_off = false + --}}} + + elseif command == "tron" then + --{{{ turn tracing on/off + local option = getargs('S') + trace_calls = false + trace_returns = false + trace_lines = false + if string.find(option,'c') then trace_calls = true end + if string.find(option,'r') then trace_returns = true end + if string.find(option,'l') then trace_lines = true end + --}}} + + elseif command == "trace" then + --{{{ dump a stack trace + trace(eval_env.__VARSLEVEL__) + --}}} + + elseif command == "info" then + --{{{ dump all debug info captured + info() + --}}} + + elseif command == "pause" then + --{{{ not allowed in here + io.write('pause() should only be used in the script you are debugging\n') + --}}} + + elseif command == "help" then + --{{{ help + local command = getargs('S') + if command ~= '' and hints[command] then + io.write(hints[command]..'\n') + else + for _,v in pairs(hints) do + local _,_,h = string.find(v,"(.+)|") + io.write(h..'\n') + end + end + --}}} + + elseif command == "exit" then + --{{{ exit debugger + return 'stop' + --}}} + + elseif line ~= '' then + --{{{ just execute whatever it is in the current context + + --map line starting with "=..." to "return ..." + if string.sub(line,1,1) == '=' then line = string.gsub(line,'=','return ',1) end + + local ok, func = pcall(loadstring,line) + if func == nil then --Michael.Bringmann@lsi.com + io.write("Compile error: "..line..'\n') + elseif not ok then + io.write("Compile error: "..func..'\n') + else + setfenv(func, eval_env) + local res = {pcall(func)} + if res[1] then + if res[2] then + table.remove(res,1) + for _,v in ipairs(res) do + io.write(tostring(v)) + io.write('\t') + end + io.write('\n') + end + --update in the context + eval_env, breakfile, breakline = report(coroutine.yield(0)) + else + io.write("Run error: "..res[2]..'\n') + end + end + + --}}} + end + end + +end + +--}}} + +--{{{ coroutine.create + +--This function overrides the built-in for the purposes of propagating +--the debug hook settings from the creator into the created coroutine. + +_G.coroutine.create = function(f) + local thread + local hook, mask, count = debug.gethook() + if hook then + local function thread_hook(event,line) + hook(event,line,3,thread) + end + thread = cocreate(function(...) + stack_level[thread] = 0 + trace_level[thread] = 0 + step_level [thread] = 0 + debug.sethook(thread_hook,mask,count) + return f(...) + end) + return thread + else + return cocreate(f) + end +end + +--}}} +--{{{ coroutine.wrap + +--This function overrides the built-in for the purposes of propagating +--the debug hook settings from the creator into the created coroutine. + +_G.coroutine.wrap = function(f) + local thread + local hook, mask, count = debug.gethook() + if hook then + local function thread_hook(event,line) + hook(event,line,3,thread) + end + thread = cowrap(function(...) + stack_level[thread] = 0 + trace_level[thread] = 0 + step_level [thread] = 0 + debug.sethook(thread_hook,mask,count) + return f(...) + end) + return thread + else + return cowrap(f) + end +end + +--}}} + +--{{{ function pause() + +-- +-- Starts/resumes a debug session +-- + +function pause(x) + if pause_off then return end --being told to ignore pauses + pausemsg = x or 'pause' + local lines + local src = getinfo(2,'short_src') + if src == "stdin" then + lines = 1 --if in a console session, stop now + else + lines = 2 --if in a script, stop when get out of pause() + end + if started then + --we'll stop now 'cos the existing debug hook will grab us + step_lines = lines + step_into = true + else + coro_debugger = cocreate(debugger_loop) --NB: Use original coroutune.create + --set to stop when get out of pause() + trace_level[current_thread] = 0 + step_level [current_thread] = 0 + stack_level[current_thread] = 1 + step_lines = lines + step_into = true + started = true + debug.sethook(debug_hook, "crl") --NB: this will cause an immediate entry to the debugger_loop + end +end + +--}}} +--{{{ function dump() + +--shows the value of the given variable, only really useful +--when the variable is a table +--see dump debug command hints for full semantics + +function dump(v,depth) + dumpvar(v,(depth or 1)+1,tostring(v)) +end + +--}}} +--{{{ function debug.traceback(x) + +local _traceback = debug.traceback --note original function + +--override standard function +debug.traceback = function(x) + local assertmsg = _traceback(x) --do original function + pause(x) --let user have a look at stuff + return assertmsg --carry on +end + +_TRACEBACK = debug.traceback --Lua 5.0 function + +--}}} + diff --git a/ogl_editor/bin/win32/scripts/tundra/decl.lua b/ogl_editor/bin/win32/scripts/tundra/decl.lua new file mode 100644 index 0000000..9ede9c4 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/decl.lua @@ -0,0 +1,91 @@ +module(..., package.seeall) + +local nodegen = require "tundra.nodegen" + +local functions = {} +local _decl_meta = {} +_decl_meta.__index = _decl_meta + +local current = nil + +local function new_parser() + local obj = { + Functions = {}, + Results = {}, + DefaultTargets = {}, + AlwaysTargets = {}, + } + + local outer_env = _G + local iseval = nodegen.is_evaluator + local function indexfunc(tab, var) + if iseval(var) then + -- Return an anonymous function such that + -- the code "Foo { ... }" will result in a call to + -- "nodegen.evaluate('Foo', { ... })" + return function (data) + local result = nodegen.evaluate(var, data) + obj.Results[#obj.Results + 1] = result + return result + end + end + local p = obj.Functions[var] + if p then return p end + return outer_env[var] + end + + obj.FunctionMeta = { __index = indexfunc, __newindex = error } + obj.FunctionEnv = setmetatable({}, obj.FunctionMeta) + + for name, fn in pairs(functions) do + obj.Functions[name] = setfenv(fn, obj.FunctionEnv) + end + + obj.Functions["Default"] = function(default_obj) + obj.DefaultTargets[#obj.DefaultTargets + 1] = default_obj + end + + obj.Functions["Always"] = function(always_obj) + obj.AlwaysTargets[#obj.AlwaysTargets + 1] = always_obj + end + + current = setmetatable(obj, _decl_meta) + return current +end + +function add_function(name, fn) + assert(name and fn) + functions[name] = fn + + if current then + -- require called from within unit script + current.Functions[name] = setfenv(fn, current.FunctionEnv) + end +end + +function _decl_meta:parse_rec(data) + local chunk + if type(data) == "table" then + for _, gen in ipairs(data) do + self:parse_rec(gen) + end + return + elseif type(data) == "function" then + chunk = data + elseif type(data) == "string" then + chunk = assert(loadfile(data)) + else + croak("unknown type %s for unit_generator %q", type(data), tostring(data)) + end + + setfenv(chunk, self.FunctionEnv) + chunk() +end + +function parse(data) + p = new_parser() + current = p + p:parse_rec(data) + current = nil + return p.Results, p.DefaultTargets, p.AlwaysTargets +end diff --git a/ogl_editor/bin/win32/scripts/tundra/depgraph.lua b/ogl_editor/bin/win32/scripts/tundra/depgraph.lua new file mode 100644 index 0000000..d7a0bdf --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/depgraph.lua @@ -0,0 +1,155 @@ +module(..., package.seeall) + +local boot = require "tundra.boot" +local util = require "tundra.util" +local path = require "tundra.path" +local native = require "tundra.native" +local environment = require "tundra.environment" + +local default_pass = { Name = "Default", BuildOrder = 100000 } + +local all_nodes = {} + +local _node_mt = {} +_node_mt.__index = _node_mt + +function make_node(data_) + local env_ = data_.Env + assert(environment.is_environment(env_), "Env must be provided") + + local root_path = native.getcwd() .. env_:get('SEP') + + local function path_for_cmdline(p) + local full_path + if path.is_absolute(p) then + full_path = p + else + full_path = root_path .. p + end + if full_path:find(' ', 1, true) then + return '"' .. full_path .. '"' + else + return full_path + end + end + + local function normalize_paths(paths) + return util.mapnil(paths, function (x) + if type(x) == "string" then + local v = env_:interpolate(x) + v = path.normalize(v) + return v + else + return x + end + end) + end + + -- these are the inputs that $(<) expand to + local regular_inputs = normalize_paths(data_.InputFiles) + + -- these are other, auxillary input files that shouldn't appear on the command line + -- useful to e.g. add an input dependency on a tool + local implicit_inputs = normalize_paths(data_.ImplicitInputs) + + local inputs = util.merge_arrays_2(regular_inputs, implicit_inputs) + local outputs = normalize_paths(data_.OutputFiles) + + local inputs_sorted = inputs and util.clone_array(inputs) or {} + local outputs_sorted = outputs and util.clone_array(outputs) or {} + + local cmdline_inputs = util.merge_arrays(regular_inputs, data_.InputFilesUntracked) + + table.sort(inputs_sorted) + table.sort(outputs_sorted) + + -- Quote the paths before interpolation into the command line + local expand_env = { + ['<'] = util.mapnil(cmdline_inputs, path_for_cmdline), + ['@'] = util.mapnil(outputs, path_for_cmdline), + } + + local expand_env_pretty = { + ['<'] = cmdline_inputs, + ['@'] = outputs, + } + + local overwrite = true + if type(data_.OverwriteOutputs) ~= "nil" then + overwrite = data_.OverwriteOutputs + end + + if data_.Scanner and not data_.Scanner.Kind then + errorf("Missing scanner kind") + end + + -- make sure dependencies are unique + local unique_deps = util.uniq(data_.Dependencies or {}) + + local params = { + pass = data_.Pass or default_pass, + scanner = data_.Scanner, + deps = unique_deps, + inputs = inputs_sorted, + outputs = outputs_sorted, + is_precious = data_.Precious, + expensive = data_.Expensive, + overwrite_outputs = overwrite, + src_env = env_, + env = env_.external_vars, + aux_outputs = util.mapnil(data_.AuxOutputFiles, function (x) + local result = env_:interpolate(x, expand_env) + return path.normalize(result) + end), + } + + if data_.Action then + params.action = env_:interpolate(data_.Action, expand_env) + else + assert(0 == #params.outputs, "can't have output files without an action") + params.action = "" + end + + if data_.PreAction then + params.preaction = env_:interpolate(data_.PreAction, expand_env) + end + + params.annotation = env_:interpolate(data_.Label or "?", expand_env_pretty) + + local result = setmetatable(params, _node_mt) + + -- Stash node + all_nodes[#all_nodes + 1] = result + + return result +end + +function is_node(obj) + return getmetatable(obj) == _node_mt +end + +function _node_mt:insert_output_files(tab, exts) + if exts then + local lut = util.make_lookup_table(exts) + for _, fn in ipairs(self.outputs) do + local ext = path.get_extension(fn) + if lut[ext] then + tab[#tab + 1] = fn + end + end + else + for _, fn in ipairs(self.outputs) do + tab[#tab + 1] = fn + end + end +end + +function _node_mt:insert_deps(tab) + for _, dep in util.nil_ipairs(self.deps) do + tab[#tab + 1] = dep + end +end + +function get_all_nodes() + return all_nodes +end diff --git a/ogl_editor/bin/win32/scripts/tundra/dirwalk.lua b/ogl_editor/bin/win32/scripts/tundra/dirwalk.lua new file mode 100644 index 0000000..cfa067d --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/dirwalk.lua @@ -0,0 +1,41 @@ +module(..., package.seeall) + +local native = require "tundra.native" + +-- Stash of all dir walks performed for signature generation. +local query_records = {} + +function walk(path, filter_callback) + + local dir_stack = { path } + local paths_out = {} + + while #dir_stack > 0 do + local dir = dir_stack[#dir_stack] + table.remove(dir_stack) + + local subdirs, files = native.list_directory(dir) + query_records[dir] = { Files = files, SubDirs = subdirs } + + for _, subdir in ipairs(subdirs) do + full_dir_path = dir .. SEP .. subdir + if not filter_callback or filter_callback(subdir) then + table.insert(dir_stack, full_dir_path) + end + end + + for _, file in ipairs(files) do + table.insert(paths_out, dir .. SEP .. file) + end + end + + return paths_out +end + +function all_queries() + local result = {} + for k, v in pairs(query_records) do + result[#result + 1] = { Path = k, Files = v.Files, SubDirs = v.SubDirs } + end + return result +end diff --git a/ogl_editor/bin/win32/scripts/tundra/environment.lua b/ogl_editor/bin/win32/scripts/tundra/environment.lua new file mode 100644 index 0000000..7934620 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/environment.lua @@ -0,0 +1,316 @@ +module(..., package.seeall) + +local util = require 'tundra.util' +local path = require 'tundra.path' +local depgraph = require 'tundra.depgraph' +local nenv = require 'tundra.environment.native' +local os = require 'os' + +local global_setup = {} + +--[==[ + +The environment is a holder for variables and their associated values. Values +are always kept as tables, even if there is only a single value. + +FOO = { a b c } + +e:interpolate("$(FOO)") -> "a b c" +e:interpolate("$(FOO:j, )") -> "a, b, c" +e:interpolate("$(FOO:p-I)") -> "-Ia -Ib -Ic" + +Missing keys trigger errors unless a default value is specified. + +]==]-- + +local envclass = {} + +function envclass:create(parent, assignments, obj) + obj = obj or {} + setmetatable(obj, self) + self.__index = self + + obj.cached_interpolation = {} + obj.vars = {} + obj.parent = parent + obj.lookup = { obj.vars } + obj.memos = {} + obj.memo_keys = {} + obj.external_vars = parent and util.clone_table(parent.external_vars) or nil + + -- assign initial bindings + if assignments then + obj:set_many(assignments) + end + + return obj +end + +function envclass:clone(assignments) + return envclass:create(self, assignments) +end + +function envclass:register_implicit_make_fn(ext, fn, docstring) + if type(ext) ~= "string" then + errorf("extension must be a string") + end + if type(fn) ~= "function" then + errorf("fn must be a function") + end + + if not ext:match("^%.") then + ext = "." .. ext -- we want the dot in the extension + end + + if not self._implicit_exts then + self._implicit_exts = {} + end + + self._implicit_exts[ext] = { + Function = fn, + Doc = docstring or "", + } +end + +function envclass:get_implicit_make_fn(filename) + local ext = path.get_extension(filename) + local chain = self + while chain do + local t = chain._implicit_exts + if t then + local v = t[ext] + if v then return v.Function end + end + chain = chain.parent + end + return nil +end + +function envclass:has_key(key) + local chain = self + while chain do + if chain.vars[key] then + return true + end + chain = chain.parent + end + + return false +end + +function envclass:get_vars() + return self.vars +end + +function envclass:set_many(table) + for k, v in pairs(table) do + self:set(k, v) + end +end + +function envclass:append(key, value) + if type(value) ~= "string" then + error("environment append: " .. util.tostring(value) .. " is not a string", 2) + end + self:invalidate_memos(key) + local t = self:get_list(key, 1) + local result + if type(t) == "table" then + result = util.clone_array(t) + table.insert(result, value) + else + result = { value } + end + self.vars[key] = result +end + +function envclass:append_many(data) + for k, v in pairs(data) do + self:append(k, v) + end +end + +function envclass:replace(key, value) + if type(value) == "string" then + value = { value } + end + assert(type(value) == "table") + + self:invalidate_memos(key) + self.vars[key] = value +end + +function envclass:invalidate_memos(key) + self.cached_interpolation = {} + local name_tab = self.memo_keys[key] + if name_tab then + for name, _ in pairs(name_tab) do + self.memos[name] = nil + end + end +end + +function envclass:set_default(key, value) + if not self:has_key(key) then + self:set(key, value) + end +end + +function envclass:set_default_many(table) + for key, value in pairs(table) do + self:set_default(key, value) + end +end + +function envclass:set(key, value) + self:invalidate_memos(key) + assert(key:len() > 0, "key must not be empty") + assert(type(key) == "string", "key must be a string") + if type(value) == "string" then + if value:len() > 0 then + self.vars[key] = { value } + else + -- let empty strings make empty tables + self.vars[key] = {} + end + elseif type(value) == "table" then + -- FIXME: should filter out empty values + for _, v in ipairs(value) do + if not type(v) == "string" then + error("key " .. key .. "'s table value contains non-string value " .. tostring(v)) + end + end + self.vars[key] = util.clone_array(value) + else + error("key " .. key .. "'s value is neither table nor string: " .. tostring(value)) + end +end + +function envclass:get_id() + return self.id +end + +function envclass:get(key, default) + local v = self.vars[key] + if v then + return table.concat(v, " ") + elseif self.parent then + return self.parent:get(key, default) + elseif default then + return default + else + error(string.format("key '%s' not present in environment", key)) + end +end + +function envclass:get_list(key, default) + local v = self.vars[key] + if v then + return v -- FIXME: this should be immutable from the outside + elseif self.parent then + return self.parent:get_list(key, default) + elseif default then + return default + elseif not key then + error("nil key is not allowed") + else + error(string.format("key '%s' not present in environment", key)) + end +end + +function envclass:get_parent() + return self.parent +end + +function envclass:interpolate(str, vars) + local cached = self.cached_interpolation[str] + + if not cached then + cached = nenv.interpolate(str, self) + self.cached_interpolation[str] = cached + end + + if vars then + return nenv.interpolate(cached, self, vars) + else + return cached + end +end + +function create(parent, assignments, obj) + return envclass:create(parent, assignments, obj) +end + +function envclass:record_memo_var(key, name) + local tab = self.memo_keys[key] + if not tab then + tab = {} + self.memo_keys[key] = tab + end + tab[name] = true +end + +function envclass:memoize(key, name, fn) + local memo = self.memos[name] + if not memo then + self:record_memo_var(key, name) + memo = fn() + self.memos[name] = memo + end + return memo +end + +function envclass:get_external_env_var(key) + local chain = self + while chain do + local t = self.external_vars + if t then + local v = t[key] + if v then return v end + end + + chain = chain.parent + end + + return os.getenv(key) +end + +function envclass:set_external_env_var(key, value) + local t = self.external_vars + if not t then + t = {} + self.external_vars = t + end + t[key] = value +end + +function envclass:add_setup_function(fn) + local t = self.setup_funcs + if not t then + t = {} + self.setup_funcs = t + end + t[#t + 1] = fn +end + +function envclass:run_setup_functions() + for _, func in ipairs(global_setup) do + func(self) + end + t = self.setup_funcs + local chain = self + while chain do + for _, func in util.nil_ipairs(chain.setup_funcs) do + func(self) + end + chain = chain.parent + end +end + +function add_global_setup(fn) + global_setup[#global_setup + 1] = fn +end + +function is_environment(datum) + return getmetatable(datum) == envclass +end diff --git a/ogl_editor/bin/win32/scripts/tundra/host/freebsd.lua b/ogl_editor/bin/win32/scripts/tundra/host/freebsd.lua new file mode 100644 index 0000000..87cade2 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/host/freebsd.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply_host(env) + env:set_many { + ["DOTNETRUN"] = "mono ", + ["HOSTPROGSUFFIX"] = "", + ["HOSTSHLIBSUFFIX"] = ".so", + ["_COPY_FILE"] = "cp -f $(<) $(@)", + ["_HARDLINK_FILE"] = "ln -f $(<) $(@)", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/host/linux.lua b/ogl_editor/bin/win32/scripts/tundra/host/linux.lua new file mode 100644 index 0000000..87cade2 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/host/linux.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply_host(env) + env:set_many { + ["DOTNETRUN"] = "mono ", + ["HOSTPROGSUFFIX"] = "", + ["HOSTSHLIBSUFFIX"] = ".so", + ["_COPY_FILE"] = "cp -f $(<) $(@)", + ["_HARDLINK_FILE"] = "ln -f $(<) $(@)", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/host/macosx.lua b/ogl_editor/bin/win32/scripts/tundra/host/macosx.lua new file mode 100644 index 0000000..81d2c59 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/host/macosx.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply_host(env) + env:set_many { + ["DOTNETRUN"] = "mono ", + ["HOSTPROGSUFFIX"] = "", + ["HOSTSHLIBSUFFIX"] = ".dylib", + ["_COPY_FILE"] = "cp -f $(<) $(@)", + ["_HARDLINK_FILE"] = "ln -f $(<) $(@)", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/host/openbsd.lua b/ogl_editor/bin/win32/scripts/tundra/host/openbsd.lua new file mode 100644 index 0000000..87cade2 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/host/openbsd.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply_host(env) + env:set_many { + ["DOTNETRUN"] = "mono ", + ["HOSTPROGSUFFIX"] = "", + ["HOSTSHLIBSUFFIX"] = ".so", + ["_COPY_FILE"] = "cp -f $(<) $(@)", + ["_HARDLINK_FILE"] = "ln -f $(<) $(@)", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/host/windows.lua b/ogl_editor/bin/win32/scripts/tundra/host/windows.lua new file mode 100644 index 0000000..dd5d2de --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/host/windows.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply_host(env) + env:set_many { + ["DOTNETRUN"] = "", + ["HOSTPROGSUFFIX"] = ".exe", + ["HOSTSHLIBSUFFIX"] = ".dll", + ["_COPY_FILE"] = "copy $(<) $(@)", + ["_HARDLINK_FILE"] = "copy /f $(<) $(@)", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/ide/msvc-common.lua b/ogl_editor/bin/win32/scripts/tundra/ide/msvc-common.lua new file mode 100644 index 0000000..4f78bfe --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/ide/msvc-common.lua @@ -0,0 +1,864 @@ +module(..., package.seeall) + +local native = require "tundra.native" +local nodegen = require "tundra.nodegen" +local path = require "tundra.path" +local util = require "tundra.util" + +LF = '\r\n' +local UTF_HEADER = '\239\187\191' -- byte mark EF BB BF + +local VERSION_NUMBER = "12.00" +local VERSION_YEAR = "2012" +local HOOKS = {} + +local msvc_generator = {} +msvc_generator.__index = msvc_generator + +local project_types = util.make_lookup_table { + "Program", "SharedLibrary", "StaticLibrary", "CSharpExe", "CSharpLib", "ObjGroup", +} + +local toplevel_stuff = util.make_lookup_table { + ".exe", ".lib", ".dll", +} + +local binary_extension = util.make_lookup_table { + ".exe", ".lib", ".dll", ".pdb", ".res", ".obj", ".o", ".a", +} + +local header_exts = util.make_lookup_table { + ".h", ".hpp", ".hh", ".inl", +} + +-- Scan for sources, following dependencies until those dependencies seem to be +-- a different top-level unit +local function get_sources(dag, sources, generated, level, dag_lut) + for _, output in util.nil_ipairs(dag.outputs) do + local ext = path.get_extension(output) + if not binary_extension[ext] then + generated[output] = true + sources[output] = true -- pick up generated headers + end + end + + for _, input in util.nil_ipairs(dag.inputs) do + local ext = path.get_extension(input) + if not binary_extension[ext] then + sources[input] = true + end + end + + for _, dep in util.nil_ipairs(dag.deps) do + if not dag_lut[dep] then -- don't go into other top-level DAGs + get_sources(dep, sources, generated, level + 1, dag_lut) + end + end +end + +function get_guid_string(data) + local sha1 = native.digest_guid(data) + local guid = sha1:sub(1, 8) .. '-' .. sha1:sub(9,12) .. '-' .. sha1:sub(13,16) .. '-' .. sha1:sub(17,20) .. '-' .. sha1:sub(21, 32) + assert(#guid == 36) + return guid:upper() +end + +local function get_headers(unit, source_lut, dag_lut, name_to_dags) + local src_dir = '' + + if not unit.Decl then + -- Ignore ExternalLibrary and similar that have no data. + return + end + + if unit.Decl.SourceDir then + src_dir = unit.Decl.SourceDir .. '/' + end + for _, src in util.nil_ipairs(nodegen.flatten_list('*-*-*-*', unit.Decl.Sources)) do + if type(src) == "string" then + local ext = path.get_extension(src) + if header_exts[ext] then + local full_path = path.normalize(src_dir .. src) + source_lut[full_path] = true + end + end + end + + local function toplevel(u) + if type(u) == "string" then + return type(name_to_dags[u]) ~= "nil" + end + + for _, dag in pairs(u.Decl.__DagNodes) do + if dag_lut[dag] then + return true + end + end + return false + end + + -- Repeat for dependencies ObjGroups + for _, dep in util.nil_ipairs(nodegen.flatten_list('*-*-*-*', unit.Decl.Depends)) do + + if not toplevel(dep) then + get_headers(dep, source_lut, dag_lut) + end + end +end + +local function make_meta_project(base_dir, data) + data.Guid = get_guid_string(data.Name) + data.IdeGenerationHints = { Msvc = { SolutionFolder = "Build System Meta" } } + data.IsMeta = true + data.RelativeFilename = data.Name .. ".vcxproj" + data.Filename = base_dir .. data.RelativeFilename + data.Type = "meta" + if not data.Sources then + data.Sources = {} + end + return data +end + +local function tundra_cmdline(args) + local root_dir = native.getcwd() + return "\"" .. TundraExePath .. "\" -C \"" .. root_dir .. "\" " .. args +end + +local function project_regen_commandline(ide_script) + return tundra_cmdline("-g " .. ide_script) +end + +local function make_project_data(units_raw, env, proj_extension, hints, ide_script) + + -- Filter out stuff we don't care about. + local units = util.filter(units_raw, function (u) + return u.Decl.Name and project_types[u.Keyword] + end) + + local base_dir = hints.MsvcSolutionDir and (hints.MsvcSolutionDir .. '\\') or env:interpolate('$(OBJECTROOT)$(SEP)') + native.mkdir(base_dir) + + local project_by_name = {} + local all_sources = {} + local dag_node_lut = {} -- lookup table of all named, top-level DAG nodes + local name_to_dags = {} -- table mapping unit name to array of dag nodes (for configs) + + -- Map out all top-level DAG nodes + for _, unit in ipairs(units) do + local decl = unit.Decl + + local dag_nodes = assert(decl.__DagNodes, "no dag nodes for " .. decl.Name) + for build_id, dag_node in pairs(dag_nodes) do + dag_node_lut[dag_node] = unit + local array = name_to_dags[decl.Name] + if not array then + array = {} + name_to_dags[decl.Name] = array + end + array[#array + 1] = dag_node + end + end + + local function get_output_project(name) + if not project_by_name[name] then + local relative_fn = name .. proj_extension + project_by_name[name] = { + Name = name, + Sources = {}, + RelativeFilename = relative_fn, + Filename = base_dir .. relative_fn, + Guid = get_guid_string(name), + BuildByDefault = hints.BuildAllByDefault, + } + end + return project_by_name[name] + end + + -- Sort units based on dependency complexity. We want to visit the leaf nodes + -- first so that any source file references are picked up as close to the + -- bottom of the dependency chain as possible. + local unit_weights = {} + for _, unit in ipairs(units) do + local decl = unit.Decl + local stack = { } + for _, dag in pairs(decl.__DagNodes) do + stack[#stack + 1] = dag + end + local weight = 0 + while #stack > 0 do + local node = table.remove(stack) + if dag_node_lut[node] then + weight = weight + 1 + end + for _, dep in util.nil_ipairs(node.deps) do + stack[#stack + 1] = dep + end + end + unit_weights[unit] = weight + end + + table.sort(units, function (a, b) + return unit_weights[a] < unit_weights[b] + end) + + -- Keep track of what source files have already been grabbed by other projects. + local grabbed_sources = {} + + for _, unit in ipairs(units) do + local decl = unit.Decl + local name = decl.Name + + local source_lut = {} + local generated_lut = {} + for build_id, dag_node in pairs(decl.__DagNodes) do + get_sources(dag_node, source_lut, generated_lut, 0, dag_node_lut) + end + + -- Explicitly add all header files too as they are not picked up from the DAG + -- Also pick up headers from non-toplevel DAGs we're depending on + get_headers(unit, source_lut, dag_node_lut, name_to_dags) + + -- Figure out which project should get this data. + local output_name = name + local ide_hints = unit.Decl.IdeGenerationHints + if ide_hints then + if ide_hints.OutputProject then + output_name = ide_hints.OutputProject + end + end + + local proj = get_output_project(output_name) + + if output_name == name then + -- This unit is the real thing for this project, not something that's + -- just being merged into it (like an ObjGroup). Set some more attributes. + proj.IdeGenerationHints = ide_hints + proj.DagNodes = decl.__DagNodes + proj.Unit = unit + end + + for src, _ in pairs(source_lut) do + local norm_src = path.normalize(src) + if not grabbed_sources[norm_src] then + grabbed_sources[norm_src] = unit + local is_generated = generated_lut[src] + proj.Sources[#proj.Sources+1] = { + Path = norm_src, + Generated = is_generated, + } + end + end + end + + -- Get all accessed Lua files + local accessed_lua_files = util.table_keys(get_accessed_files()) + + -- Filter out the ones that belong to this build (exclude ones coming from Tundra) + local function is_non_tundra_lua_file(p) + return not path.is_absolute(p) + end + local function make_src_node(p) + return { Path = path.normalize(p) } + end + local source_list = util.map(util.filter(accessed_lua_files, is_non_tundra_lua_file), make_src_node) + + local solution_hints = hints.MsvcSolutions + if not solution_hints then + print("No IdeGenerationHints.MsvcSolutions specified - using defaults") + solution_hints = { + ['tundra-generated.sln'] = {} + } + end + + local projects = util.table_values(project_by_name) + local vanilla_projects = util.clone_array(projects) + + local solutions = {} + + -- Create meta project to regenerate solutions/projects. Added to every solution. + local regen_meta_proj = make_meta_project(base_dir, { + Name = "00-Regenerate-Projects", + FriendlyName = "Regenerate Solutions and Projects", + BuildCommand = project_regen_commandline(ide_script), + }) + + projects[#projects + 1] = regen_meta_proj + + for name, data in pairs(solution_hints) do + local sln_projects + local ext_projects = {} + if data.Projects then + sln_projects = {} + for _, pname in ipairs(data.Projects) do + local pp = project_by_name[pname] + if not pp then + errorf("can't find project %s for inclusion in %s -- check your MsvcSolutions data", pname, name) + end + sln_projects[#sln_projects + 1] = pp + end + else + -- All the projects (that are not meta) + sln_projects = util.clone_array(vanilla_projects) + end + + for _, ext in util.nil_ipairs(data.ExternalProjects) do + ext_projects[#ext_projects + 1] = ext + end + + local meta_proj = make_meta_project(base_dir, { + Name = "00-tundra-" .. path.drop_suffix(name), + FriendlyName = "Build This Solution", + BuildByDefault = true, + Sources = source_list, + BuildProjects = util.clone_array(sln_projects), + }) + + sln_projects[#sln_projects + 1] = regen_meta_proj + sln_projects[#sln_projects + 1] = meta_proj + projects[#projects + 1] = meta_proj + + solutions[#solutions + 1] = { + Filename = base_dir .. name, + Projects = sln_projects, + ExternalProjects = ext_projects, + BuildSolutionProject = meta_proj, + } + end + + return solutions, projects +end + +local cl_tags = { + ['.h'] = 'ClInclude', + ['.hh'] = 'ClInclude', + ['.hpp'] = 'ClInclude', + ['.inl'] = 'ClInclude', +} + +local function slurp_file(fn) + local fh, err = io.open(fn, 'rb') + if fh then + local data = fh:read("*all") + fh:close() + return data + end + return '' +end + +local function replace_if_changed(new_fn, old_fn) + local old_data = slurp_file(old_fn) + local new_data = slurp_file(new_fn) + if old_data == new_data then + os.remove(new_fn) + return + end + printf("Updating %s", old_fn) + os.remove(old_fn) + os.rename(new_fn, old_fn) +end + +function msvc_generator:generate_solution(fn, projects, ext_projects, solution) + local sln = io.open(fn .. '.tmp', 'wb') + sln:write(UTF_HEADER, LF, "Microsoft Visual Studio Solution File, Format Version ", VERSION_NUMBER, LF, "# Visual Studio ", VERSION_YEAR, LF) + + -- Map folder names to array of projects under that folder + local sln_folders = {} + for _, proj in ipairs(projects) do + local hints = proj.IdeGenerationHints + local msvc_hints = hints and hints.Msvc or nil + local folder = msvc_hints and msvc_hints.SolutionFolder or nil + if folder then + local projects = sln_folders[folder] or {} + projects[#projects + 1] = proj + sln_folders[folder] = projects + end + end + + for _, proj in ipairs(projects) do + local name = proj.Name + local fname = proj.RelativeFilename + local guid = proj.Guid + sln:write(string.format('Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "%s", "%s", "{%s}"', name, fname, guid), LF) + sln:write('EndProject', LF) + end + + -- Dump external projects. Make them depend on everything in this solution being built by Tundra. + for _, data in util.nil_ipairs(ext_projects) do + local guid = data.Guid + local fname = path.normalize(path.join(native.getcwd(), data.Filename)) + local name = path.get_filename_base(fname) + sln:write(string.format('Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "%s", "%s", "{%s}"', name, fname, guid), LF) + local build_sln_proj = solution.BuildSolutionProject + if build_sln_proj then + local meta_guid = build_sln_proj.Guid + sln:write('\tProjectSection(ProjectDependencies) = postProject', LF) + sln:write('\t\t{', meta_guid,'} = {', meta_guid,'}', LF) + sln:write('\tEndProjectSection', LF) + end + sln:write('EndProject', LF) + end + + for folder_name, _ in pairs(sln_folders) do + local folder_guid = get_guid_string("folder/" .. folder_name) + sln:write(string.format('Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "%s", "%s", "{%s}"', folder_name, folder_name, folder_guid), LF) + sln:write('EndProject', LF) + end + + sln:write("Global", LF) + sln:write("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution", LF) + for _, tuple in ipairs(self.config_tuples) do + sln:write(string.format('\t\t%s = %s', tuple.MsvcName, tuple.MsvcName), LF) + end + sln:write("\tEndGlobalSection", LF) + + sln:write("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution", LF) + for _, proj in ipairs(projects) do + for _, tuple in ipairs(self.config_tuples) do + local leader = string.format('\t\t{%s}.%s.', proj.Guid, tuple.MsvcName) + sln:write(leader, "ActiveCfg = ", tuple.MsvcName, LF) + if proj.BuildByDefault then + sln:write(leader, "Build.0 = ", tuple.MsvcName, LF) + end + end + end + + -- External projects build by default, and after Tundra is done (depends on "Build this solution"). + for _, proj in util.nil_ipairs(ext_projects) do + for _, tuple in ipairs(self.config_tuples) do + local leader = string.format('\t\t{%s}.%s.', proj.Guid, tuple.MsvcName) + sln:write(leader, "ActiveCfg = ", tuple.MsvcName, LF) + if not proj.Platform or proj.Platform == tuple.MsvcPlatform then + sln:write(leader, "Build.0 = ", tuple.MsvcName, LF) + end + end + end + sln:write("\tEndGlobalSection", LF) + + sln:write("\tGlobalSection(SolutionProperties) = preSolution", LF) + sln:write("\t\tHideSolutionNode = FALSE", LF) + sln:write("\tEndGlobalSection", LF) + + sln:write("\tGlobalSection(NestedProjects) = preSolution", LF) + for folder_name, projects in pairs(sln_folders) do + local folder_guid = get_guid_string("folder/" .. folder_name) + for _, project in ipairs(projects) do + sln:write(string.format('\t\t{%s} = {%s}', project.Guid, folder_guid), LF) + end + end + sln:write("\tEndGlobalSection", LF) + + sln:write("EndGlobal", LF) + sln:close() + + replace_if_changed(fn .. ".tmp", fn) +end + +local function find_dag_node_for_config(project, tuple) + local build_id = string.format("%s-%s-%s", tuple.Config.Name, tuple.Variant.Name, tuple.SubVariant) + local nodes = project.DagNodes + if not nodes then + return nil + end + + if nodes[build_id] then + return nodes[build_id] + end + errorf("couldn't find config %s for project %s (%d dag nodes) - available: %s", + build_id, project.Name, #nodes, table.concat(util.table_keys(nodes), ", ")) +end + +function msvc_generator:generate_project(project, all_projects) + local fn = project.Filename + local p = assert(io.open(fn .. ".tmp", 'wb')) + p:write('', LF) + p:write('', LF) + + -- List all project configurations + p:write('\t', LF) + for _, tuple in ipairs(self.config_tuples) do + p:write('\t\t', LF) + p:write('\t\t\t', tuple.MsvcConfiguration, '', LF) + p:write('\t\t\t', tuple.MsvcPlatform, '', LF) + p:write('\t\t', LF) + end + p:write('\t', LF) + + p:write('\t', LF) + p:write('\t\t{', project.Guid, '}', LF) + p:write('\t\tMakeFileProj', LF) + if project.FriendlyName then + p:write('\t\t', project.FriendlyName, '', LF) + end + + if HOOKS.global_properties then + HOOKS.global_properties(p, project) + end + + p:write('\t', LF) + p:write('\t', LF) + if VERSION_YEAR == '2012' then + p:write('\t\t<_ProjectFileVersion>10.0.30319.1', LF) + end + p:write('\t', LF) + + p:write('\t', LF) + + -- Mark all project configurations as makefile-type projects + for _, tuple in ipairs(self.config_tuples) do + p:write('\t', LF) + p:write('\t\tMakefile', LF) + p:write('\t\ttrue', LF) -- I have no idea what this setting affects + if VERSION_YEAR == '2012' then + p:write('\t\tv110', LF) -- I have no idea what this setting affects + elseif VERSION_YEAR == '2013' then + p:write('\t\tv120', LF) -- I have no idea what this setting affects + end + p:write('\t', LF) + end + + p:write('\t', LF) + + for _, tuple in ipairs(self.config_tuples) do + p:write('\t', LF) + + local dag_node = find_dag_node_for_config(project, tuple) + local include_paths, defines + if dag_node then + local env = dag_node.src_env + local paths = util.map(env:get_list("CPPPATH"), function (p) + local ip = path.normalize(env:interpolate(p)) + if not path.is_absolute(ip) then + ip = native.getcwd() .. '\\' .. ip + end + return ip + end) + include_paths = table.concat(paths, ';') + local ext_paths = env:get_external_env_var('INCLUDE') + if ext_paths then + include_paths = include_paths .. ';' .. ext_paths + end + defines = env:interpolate("$(CPPDEFS:j;)") + else + include_paths = '' + defines = '' + end + + local root_dir = native.getcwd() + local build_id = string.format("%s-%s-%s", tuple.Config.Name, tuple.Variant.Name, tuple.SubVariant) + local base = "\"" .. TundraExePath .. "\" -C \"" .. root_dir .. "\" " + local build_cmd = base .. build_id + local clean_cmd = base .. "--clean " .. build_id + local rebuild_cmd = base .. "--rebuild " .. build_id + + if project.BuildCommand then + build_cmd = project.BuildCommand + clean_cmd = "" + rebuild_cmd = "" + elseif not project.IsMeta then + build_cmd = build_cmd .. " " .. project.Name + clean_cmd = clean_cmd .. " " .. project.Name + rebuild_cmd = rebuild_cmd .. " " .. project.Name + else + local all_projs_str = table.concat( + util.map(assert(project.BuildProjects), function (p) return p.Name end), ' ') + build_cmd = build_cmd .. " " .. all_projs_str + clean_cmd = clean_cmd .. " " .. all_projs_str + rebuild_cmd = rebuild_cmd .. " " .. all_projs_str + end + + p:write('\t\t', build_cmd, '', LF) + p:write('\t\t', LF) + p:write('\t\t', clean_cmd, '', LF) + p:write('\t\t', rebuild_cmd, '', LF) + p:write('\t\t', defines, ';$(NMakePreprocessorDefinitions)', LF) + p:write('\t\t', include_paths, ';$(NMakeIncludeSearchPath)', LF) + p:write('\t\t$(NMakeForcedIncludes)', LF) + p:write('\t', LF) + end + + if HOOKS.pre_sources then + HOOKS.pre_sources(p, project) + end + + -- Emit list of source files + p:write('\t', LF) + for _, record in ipairs(project.Sources) do + local path_str = assert(record.Path) + if not path.is_absolute(path_str) then + path_str = native.getcwd() .. '\\' .. path_str + end + local ext = path.get_extension(path_str) + local cl_tag = cl_tags[ext] or 'ClCompile' + p:write('\t\t<', cl_tag,' Include="', path_str, '" />', LF) + end + p:write('\t', LF) + + local post_src_hook = HOOKS.post_sources + if post_src_hook then + post_src_hook(p, project) + end + + p:write('\t', LF) + + if VERSION_YEAR == "2012" then + -- Import helper msbuild stuff to make build aborting work propertly in VS2012 + local xml = path.normalize(TundraScriptDir .. '/tundra/ide/msvc-rules.xml') + p:write('\t', LF) + end + + p:write('', LF) + p:close() + + replace_if_changed(fn .. ".tmp", fn) +end + +local function get_common_dir(sources) + local dir_tokens = {} + for _, src in ipairs(sources) do + local path = assert(src.Path) + if not tundra.path.is_absolute(path) then + local subdirs = {} + for subdir in path:gmatch("([^\\\]+)\\") do + subdirs[#subdirs + 1] = subdir + end + + if #dir_tokens == 0 then + dir_tokens = subdirs + else + for i = 1, #dir_tokens do + if dir_tokens[i] ~= subdirs[i] then + while #dir_tokens >= i do + table.remove(dir_tokens) + end + break + end + end + end + end + end + + local result = table.concat(dir_tokens, '\\') + if #result > 0 then + result = result .. '\\' + end + return result +end + +function msvc_generator:generate_project_filters(project) + local fn = project.Filename .. ".filters" + local p = assert(io.open(fn .. ".tmp", 'wb')) + p:write('', LF) + p:write('', LF) + + local common_dir = get_common_dir(util.filter(project.Sources, function (s) return not s.Generated end)) + local common_dir_gen = get_common_dir(util.filter(project.Sources, function (s) return s.Generated end)) + + local filters = {} + local sources = {} + + -- Mangle source filenames, and find which filters need to be created + for _, record in ipairs(project.Sources) do + local fn = record.Path + local common_start = record.Generated and common_dir_gen or common_dir + if fn:find(common_start, 1, true) then + fn = fn:sub(#common_start+1) + end + + local dir, filename = path.split(fn) + + if dir == '.' then + dir = nil + end + + local abs_path = record.Path + if not path.is_absolute(abs_path) then + abs_path = native.getcwd() .. '\\' .. abs_path + end + + if record.Generated then + dir = 'Generated Files' + end + + sources[#sources + 1] = { + FullPath = abs_path, + Directory = dir, + } + + -- Register filter and all its parents + while dir and dir ~= '.' do + filters[dir] = true + dir, _ = path.split(dir) + end + end + + -- Emit list of filters + p:write('\t', LF) + for filter_name, _ in pairs(filters) do + if filter_name ~= "" then + filter_guid = get_guid_string(filter_name) + p:write('\t\t', LF) + p:write('\t\t\t{', filter_guid, '}', LF) + p:write('\t\t', LF) + end + end + p:write('\t', LF) + + -- Emit list of source files + p:write('\t', LF) + for _, source in ipairs(sources) do + local ext = path.get_extension(source.FullPath) + local cl_tag = cl_tags[ext] or 'ClCompile' + if not source.Directory then + p:write('\t\t<', cl_tag, ' Include="', source.FullPath, '" />', LF) + else + p:write('\t\t<', cl_tag, ' Include="', source.FullPath, '">', LF) + p:write('\t\t\t', source.Directory, '', LF) + p:write('\t\t', LF) + end + end + p:write('\t', LF) + + p:write('', LF) + + p:close() + + replace_if_changed(fn .. ".tmp", fn) +end + +function msvc_generator:generate_project_user(project) + local fn = project.Filename .. ".user" + -- Don't overwrite user settings + do + local p, err = io.open(fn, 'rb') + if p then + p:close() + return + end + end + + local p = assert(io.open(fn, 'wb')) + p:write('', LF) + p:write('', LF) + + for _, tuple in ipairs(self.config_tuples) do + local dag_node = find_dag_node_for_config(project, tuple) + if dag_node then + local exe = nil + for _, output in util.nil_ipairs(dag_node.outputs) do + if output:match("%.exe") then + exe = output + break + end + end + if exe then + p:write('\t', LF) + p:write('\t\t', native.getcwd() .. '\\' .. exe, '', LF) + p:write('\t\tWindowsLocalDebugger', LF) + p:write('\t\t', native.getcwd(), '', LF) + p:write('\t', LF) + end + end + end + + p:write('', LF) + + p:close() +end + +function msvc_generator:generate_files(ngen, config_tuples, raw_nodes, env, default_names, hints, ide_script) + assert(config_tuples and #config_tuples > 0) + + if not hints then + hints = {} + end + + local complained_mappings = {} + + self.msvc_platforms = {} + local msvc_hints = hints.Msvc or {} + local variant_mappings = msvc_hints.VariantMappings or {} + local platform_mappings = msvc_hints.PlatformMappings or {} + local full_mappings = msvc_hints.FullMappings or {} + + for _, tuple in ipairs(config_tuples) do + local build_id = string.format("%s-%s-%s", tuple.Config.Name, tuple.Variant.Name, tuple.SubVariant) + if full_mappings[build_id] then + local m = full_mappings[build_id] + tuple.MsvcConfiguration = assert(m.Config) + tuple.MsvcPlatform = assert(m.Platform) + elseif variant_mappings[tuple.Variant.Name] then + tuple.MsvcConfiguration = variant_mappings[tuple.Variant.Name] + elseif variant_mappings[tuple.Variant.Name .. "-" .. tuple.SubVariant] then + tuple.MsvcConfiguration = variant_mappings[tuple.Variant.Name .. "-" .. tuple.SubVariant] + else + tuple.MsvcConfiguration = tuple.Variant.Name + end + + -- Use IdeGenerationHints.Msvc.PlatformMappings table to map tundra + -- configurations to MSVC platform names. Note that this isn't a huge deal + -- for building stuff as Tundra doesn't care about this setting. But it + -- might influence the choice of debugger and affect include paths for + -- things like Intellisense that certain users may care about. + if not tuple.MsvcPlatform then + tuple.MsvcPlatform = platform_mappings[tuple.Config.Name] + end + + -- If we didn't find anything, warn and then default to Win32, which VS + -- will always accept (or so one would assume) + if not tuple.MsvcPlatform then + tuple.MsvcPlatform = "Win32" + if not complained_mappings[tuple.Config.Name] then + printf("warning: No VS platform mapping for %s, mapping to Win32", tuple.Config.Name) + print("(Add one to IdeGenerationHints.Msvc.PlatformMappings to override)") + complained_mappings[tuple.Config.Name] = true + end + end + tuple.MsvcName = tuple.MsvcConfiguration .. "|" .. tuple.MsvcPlatform + self.msvc_platforms[tuple.MsvcPlatform] = true + end + + self.config_tuples = config_tuples + + printf("Generating Visual Studio projects for %d configurations/variants", #config_tuples) + + -- Figure out where we're going to store the projects + local solutions, projects = make_project_data(raw_nodes, env, ".vcxproj", hints, ide_script) + + local proj_lut = {} + for _, p in ipairs(projects) do + proj_lut[p.Name] = p + end + + for _, sln in pairs(solutions) do + self:generate_solution(sln.Filename, sln.Projects, sln.ExternalProjects, sln) + end + + for _, proj in ipairs(projects) do + self:generate_project(proj, projects) + self:generate_project_filters(proj) + self:generate_project_user(proj) + end +end + +function setup(version_short, version_year, hooks) + VERSION_NUMBER = version_short + VERSION_YEAR = version_year + if hooks then + HOOKS = hooks + end + nodegen.set_ide_backend(function(...) + local state = setmetatable({}, msvc_generator) + state:generate_files(...) + end) +end + diff --git a/ogl_editor/bin/win32/scripts/tundra/ide/msvc-rules.xml b/ogl_editor/bin/win32/scripts/tundra/ide/msvc-rules.xml new file mode 100644 index 0000000..b62155d --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/ide/msvc-rules.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + { + if (line.Data != null) + Log.LogMessageFromText(line.Data, MessageImportance.High); + }; + p.ErrorDataReceived += (object sender, DataReceivedEventArgs line) => { + if (line.Data != null) + Log.LogMessageFromText(line.Data, MessageImportance.High); + }; + p.Start(); + p.BeginOutputReadLine(); + p.BeginErrorReadLine(); + + while (!p.WaitForExit(100)) + { + if (m_Cancel) + { + // Keep sending CTRL+C events - sometimes it takes more than one.. + GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, 0); + } + } + + p.WaitForExit(); + return m_Cancel ? false : p.ExitCode == 0; + } + } + catch(Exception e) + { + Console.WriteLine(e); + return false; + } + } + } + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GetNativeTargetPath;%(Targets) + + + + + + + + + + + obj + lib + dll + xdc + + + + + + + $(TargetPath) + + + + + + + + + + + + + + Project + + + + + PropertySheet + + + + diff --git a/ogl_editor/bin/win32/scripts/tundra/ide/msvc100.lua b/ogl_editor/bin/win32/scripts/tundra/ide/msvc100.lua new file mode 100644 index 0000000..f2381b5 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/ide/msvc100.lua @@ -0,0 +1,7 @@ +-- Microsoft Visual Studio 2010 Solution/Project file generation + +module(..., package.seeall) + +local msvc_common = require "tundra.ide.msvc-common" +msvc_common.setup("11.00", "2010") + diff --git a/ogl_editor/bin/win32/scripts/tundra/ide/msvc110.lua b/ogl_editor/bin/win32/scripts/tundra/ide/msvc110.lua new file mode 100644 index 0000000..1410de9 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/ide/msvc110.lua @@ -0,0 +1,7 @@ +-- Microsoft Visual Studio 2012 Solution/Project file generation + +module(..., package.seeall) + +local msvc_common = require "tundra.ide.msvc-common" + +msvc_common.setup("12.00", "2012") diff --git a/ogl_editor/bin/win32/scripts/tundra/ide/msvc120.lua b/ogl_editor/bin/win32/scripts/tundra/ide/msvc120.lua new file mode 100644 index 0000000..14f55f2 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/ide/msvc120.lua @@ -0,0 +1,7 @@ +-- Microsoft Visual Studio 2013 Solution/Project file generation + +module(..., package.seeall) + +local msvc_common = require "tundra.ide.msvc-common" + +msvc_common.setup("12.00", "2013") diff --git a/ogl_editor/bin/win32/scripts/tundra/ide/xcode3.lua b/ogl_editor/bin/win32/scripts/tundra/ide/xcode3.lua new file mode 100644 index 0000000..643fe72 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/ide/xcode3.lua @@ -0,0 +1,735 @@ +-- Xcode 3 (works in 4 as well) Workspace/Project file generation + +module(..., package.seeall) + +local path = require "tundra.path" +local nodegen = require "tundra.nodegen" +local util = require "tundra.util" +local native = require "tundra.native" + +local xcode_generator = {} +local xcode_generator = {} +xcode_generator.__index = xcode_generator + +function xcode_generator:generate_workspace(fn, projects) + local sln = io.open(fn, 'wb') + + sln:write('\n') + sln:write('\n') + + for _, proj in ipairs(projects) do + local name = proj.Decl.Name + local fname = proj.RelativeFilename + if fname == '.' then fname = '' + else fname = fname ..'/' + end + sln:write('\t\n') + sln:write('\t\n') + end + + sln:write('\n') +end + +local project_types = util.make_lookup_table { + "Program", "SharedLibrary", "StaticLibrary", +} + +local function get_absolute_output_path(env) + local base_dir = env:interpolate('$(OBJECTROOT)$(SEP)') + local cwd = native.getcwd() + return cwd .. "/" .. base_dir +end + +local function newid(data) + local string = native.digest_guid(data) + -- a bit ugly but is to match the xcode style of UIds + return string.sub(string.gsub(string, '-', ''), 1, 24) +end + +local function getfiletype(name) + local types = { + [".c"] = "sourcecode.c.c", + [".cc"] = "sourcecode.cpp.cpp", + [".cpp"] = "sourcecode.cpp.cpp", + [".css"] = "text.css", + [".cxx"] = "sourcecode.cpp.cpp", + [".framework"] = "wrapper.framework", + [".gif"] = "image.gif", + [".h"] = "sourcecode.c.h", + [".html"] = "text.html", + [".lua"] = "sourcecode.lua", + [".m"] = "sourcecode.c.objc", + [".mm"] = "sourcecode.cpp.objc", + [".nib"] = "wrapper.nib", + [".pch"] = "sourcecode.c.h", + [".plist"] = "text.plist.xml", + [".strings"] = "text.plist.strings", + [".xib"] = "file.xib", + [".icns"] = "image.icns", + [""] = "compiled.mach-o.executable", + } + return types[path.get_extension(name)] or "text" +end + + +local function get_project_data(unit, env) + local decl = unit.Decl + + if decl.Name and project_types[unit.Keyword] then + + local relative_fn = decl.Name + local sources = util.flatten(decl.Sources) or {} + sources = util.filter(sources, function (x) return type(x) == "string" end) + + if decl.SourceDir then + sources = util.map(sources, function (x) return decl.SourceDir .. x end) + end + + local source_list = {} + + -- Rebuild source list with ids that is needed by the xcode project layout + for _, fn in ipairs(sources) do + source_list[newid(fn)] = fn + end + + return { + Type = unit.Keyword, + Decl = decl, + Sources = source_list, + RelativeFilename = relative_fn, + Guid = newid(decl.Name .. "ProjectId"), + } + + elseif unit.Keyword == "OsxBundle" then + + decl.Name = "OsxBundle" + + local source_list = {} + + source_list[newid(decl.InfoPList)] = decl.InfoPList + + for _, resource in ipairs(decl.Resources) do + if resource.Decl then + source_list[newid(resource.Decl.Source)] = resource.Decl.Source + end + end + + return { + Type = unit.Keyword, + Decl = decl, + Sources = source_list, + RelativeFilename = "$(OBJECTDIR)/MyApp.app", + Guid = newid("OsxBundle"), + } + else + return nil + end +end + +local function sort_filelist(source_list) + local dest = {} + for k, v in pairs(source_list) do table.insert(dest, { Key = k, Value = v }) end + table.sort(dest, function(a, b) return a.Value < b.Value end) + return dest +end + +local function write_file_refs(p, projects) + p:write('/* Begin FBXFileReference section */\n') + local cwd = native.getcwd(); + + -- build the source list + + local full_source_list = {} + + for _, project in pairs(projects) do + local sources = project.Sources + for key, fn in pairs(sources) do + full_source_list[key] = fn + end + + -- include executable names in the source list as well + + if project.Type == "Program" then + full_source_list[newid(project.Decl.Name .. "Program")] = project.Decl.Name + end + end + + local source_list = {} + + -- As we can't sort hashtables we need to move this over to a regular table + + source_list = sort_filelist(full_source_list) + + for _, entry in pairs(source_list) do + local key = entry.Key + local fn = entry.Value + local name = path.get_filename(fn) + local file_type = getfiletype(fn) + local str = "" + if file_type == "compiled.mach-o.executable" then + str = string.format('\t\t%s /* %s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = %s; name = "%s"; includeInIndex = 0; path = "%s"; sourceTree = BUILT_PRODUCTS_DIR; };', + key, fn, file_type, name, fn) + else + str = string.format('\t\t%s /* %s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = %s; name = "%s"; path = "%s"; sourceTree = ""; };', + key, fn, file_type, name, path.join(cwd, fn)) + end + p:write(str, '\n') + end + + p:write('/* End FBXFileReference section */\n\n') +end + +local function write_legacy_targets(p, projects, env) + p:write('/* Begin PBXLegacyTarget section */\n') + + local script_path = get_absolute_output_path(env) + + for _, project in pairs(projects) do + local decl = project.Decl + + if project.IsMeta then + --[[ + isa = PBXLegacyTarget; + buildArgumentsString = ""; + buildConfigurationList = D7D12762170E4CF98A79B5EF /* Build configuration list for PBXLegacyTarget "!UpdateWorkspace" */; + buildPhases = ( + ); + buildToolPath = /Users/danielcollin/unity_ps3/ps3/Projects/JamGenerated/_workspace.xcode_/updateworkspace; + dependencies = ( + ); + name = "!UpdateWorkspace"; + passBuildSettingsInEnvironment = 1; + productName = "!UpdateWorkspace"; + --]] + + p:write('\t\t', newid(decl.Name .. "Target"), ' /* ', decl.Name, ' */ = {\n') + p:write('\t\t\tisa = PBXLegacyTarget;\n') + p:write('\t\t\tbuildArgumentsString = "', project.MetaData.BuildArgs, '";\n') + p:write('\t\t\tbuildConfigurationList = ', newid(decl.Name .. 'Config'), ' /* Build configuration list for PBXLegacyTarget "',decl.Name, '" */;\n') + p:write('\t\t\tbuildPhases = (\n') + p:write('\t\t\t);\n'); + p:write('\t\t\tbuildToolPath = ', script_path .. project.MetaData.BuildTool, ';\n') + p:write('\t\t\tdependencies = (\n\t\t\t);\n') + p:write('\t\t\tname = "', decl.Name, '";\n') + p:write('\t\t\tpassBuildSettingsInEnvironment = 1;\n') + p:write('\t\t\tproductName = "', decl.Name or "", '";\n') + p:write('\t\t};\n') + end + end + + p:write('/* End PBXLegacyTarget section */\n') +end + +local function write_native_targes(p, projects) + p:write('/* Begin PBXNativeTarget section */\n') + + local categories = { + ["Program"] = "com.apple.product-type.tool", + ["StaticLibrary"] = "com.apple.product-type.library.static", + ["SharedLibrary"] = "com.apple.product-type.library.dynamic", + } + + for _, project in pairs(projects) do + local decl = project.Decl + + if not project.IsMeta then + p:write('\t\t', newid(decl.Name .. "Target"), ' /* ', decl.Name, ' */ = {\n') + p:write('\t\t\tisa = PBXNativeTarget;\n') + p:write('\t\t\tbuildConfigurationList = ', newid(decl.Name .. 'Config'), ' /* Build configuration list for PBXNativeTarget "',decl.Name, '" */;\n') + p:write('\t\t\tbuildPhases = (\n') + p:write('\t\t\t\t', newid(decl.Name .. "ShellScript"), ' /* ShellScript */,\n') + p:write('\t\t\t);\n'); + p:write('\t\t\tbuildRules = (\n\t\t\t);\n') + p:write('\t\t\tdependencies = (\n\t\t\t);\n') + p:write('\t\t\tname = "', decl.Name, '";\n') + p:write('\t\t\tProductName = "', decl.Name, '";\n') + p:write('\t\t\tproductReference = ', newid(decl.Name .. "Program"), ' /* ', decl.Name, ' */;\n ') + p:write('\t\t\tproductType = "', categories[project.Type] or "", '";\n') + p:write('\t\t};\n') + end + end + + p:write('/* End PBXNativeTarget section */\n') +end + + +local function write_header(p) + p:write('// !$*UTF8*$!\n') + p:write('{\n') + p:write('\tarchiveVersion = 1;\n') + p:write('\tclasses = {\n') + p:write('\t};\n') + p:write('\tobjectVersion = 45;\n') + p:write('\tobjects = {\n') + p:write('\n') +end + +local function get_projects(raw_nodes, env) + local projects = {} + + local source_list = {} + source_list[newid("tundra.lua")] = "tundra.lua" + local units = io.open("units.lua") + if units then + source_list[newid("units.lua")] = "units.lua" + io.close(units) + end + + local meta_name = "!BuildWorkspace" + + projects[#projects + 1] = { + Decl = { Name = meta_name, }, + Type = "LegacyTarget", + RelativeFilename = "", + Sources = source_list, + Guid = newid(meta_name .. 'ProjectId'), + IsMeta = true, + MetaData = { BuildArgs = "'' $(CONFIG) $(VARIANT) $(SUBVARIANT) $(ACTION)", + BuildTool = "xcodetundra" }, + } + + local meta_name = "!UpdateWorkspace" + + projects[#projects + 1] = { + Decl = { Name = "!UpdateWorkspace", }, + Type = "LegacyTarget", + RelativeFilename = "", + Sources = source_list, + Guid = newid(meta_name .. 'ProjectId'), + IsMeta = true, + MetaData = { BuildArgs = "", + BuildTool = "xcodeupdateproj" }, + } + + for _, unit in ipairs(raw_nodes) do + local data = get_project_data(unit, env) + if data then projects[#projects + 1] = data; end + end + + return projects +end + +local function split(fn) + local dir, file = fn:match("^(.*)[/\\]([^\\/]*)$") + if not dir then + return ".", fn + else + return dir, file + end +end + +local function split_str(str, pat, name) + local t = {} -- NOTE: use {n = 0} in Lua-5.0 + local fpat = "(.-)" .. pat + local last_end = 1 + local s, e, cap = str:find(fpat, 1) + table.insert(t,name) + while s do + if s ~= 1 or cap ~= "" then + table.insert(t,cap) + end + last_end = e+1 + s, e, cap = str:find(fpat, last_end) + end + if last_end <= #str then + cap = str:sub(last_end) + table.insert(t, cap) + end + return t +end + +local function build_name_id(entry, offset, end_count) + local entryname = "" + for p = offset, end_count, 1 do + if entry[p] ~= nil then + entryname = entryname .. entry[p] + end + end + return newid(entryname) +end + + +local function make_indent(level) + local indent = '\t'; + for i=1, level, 1 do + indent = indent .. '\t' + end + return indent +end + +local function make_full_path( grp ) + + local full_path_string = grp.Name + local gparent = grp.Parent + while gparent ~= nil do + full_path_string = gparent.Name ..'/'..full_path_string + gparent = gparent.Parent + end + return full_path_string .. ' : ' .. grp.Key +end + +local function write_group_ref(p, g, full_path) + + p:write('\t\t', g.Key, ' /* ', full_path .. '/' .. g.Name, ' */ = {\n') + p:write('\t\t\tisa = PBXGroup;\n') + p:write('\t\t\tchildren = (\n') + + local dirs = {} + local files = {} + + for _, ref in pairs(g.Children) do + if ref.IsDir then + local key = ref.Key + dirs[#dirs + 1] = { Key = key, Name = ref.Name } + else + local key = ref.Key + files[#files + 1] = { Key = key, Name = ref.Name } + end + end + + table.sort(dirs, function(a, b) return a.Name < b.Name end) + table.sort(files, function(a, b) return a.Name < b.Name end) + + for _, ref in pairs(dirs) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', ref.Key, full_path .. '/' .. ref.Name)) + end + + for _, ref in pairs(files) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', ref.Key, full_path .. '/' .. ref.Name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t\tname = "', g.Name, '"; \n'); + p:write('\t\t\tsourceTree = "";\n'); + p:write('\t\t};\n') +end + +local function print_children_2(p, children, path, level) + if children == nil then + return path + end + + local c + local local_path = ''--path + for _, c in pairs(children) do + local indent = make_indent(level) + local_path = print_children_2( p, c.Children, path .. '/' .. c.Name, level + 1 ) + if #c.Children ~= 0 then + write_group_ref(p, c, path) + end + + end + + return path +end + +local function find_group(groups, group, parent) + if groups == nil then return nil end + for _, g in pairs(groups) do + if g.Name == group and g.Parent == parent then + return g + end + local r = find_group( g.Children, group, parent ) + if r ~= nil then return r end + end + return nil +end + +local function write_sources(p, children, name, parent) + + local filelist = sort_filelist(children) + local groups = {}; + + table.insert(groups, {Name = name, Parent = nil, Key = parent, Children = {} }) + + for _, entry in pairs(filelist) do + local parent_group = nil + local path, filename = split(entry.Value) + local split_path = split_str(path, "/", name) + for i=1 , #split_path, 1 do + if split_path[i] ~= '.' then + local grp = find_group(groups, split_path[i], parent_group) + if grp == nil then + grp = { IsDir = true, Name=split_path[i], Parent=parent_group, Key=newid(util.tostring(parent_group)..split_path[i]), Children={} } + if parent_group == nil then + table.insert(groups, grp) + else + parent_group = grp.Parent + table.insert(parent_group.Children, grp) + end + end + parent_group = grp + end + end + if parent_group ~= nil then + table.insert(parent_group.Children, { IsDir = false, Name=filename, Parent=parent_group, Key = entry.Key, Children = {}} ) + end + end + + print_children_2(p, groups, '.', 0); +end + + +local function write_groups(p, projects) + p:write('/* Begin PBXGroup section */\n') + + local all_targets_name = "AllTargets.workspace" + local all_targets_id = newid(all_targets_name) + + for _, project in pairs(projects) do + write_sources(p, project.Sources, project.Decl.Name, project.Guid) + end + + -- write last group that links the projects names above + + p:write('\t\t', all_targets_id, ' /* ', all_targets_name, ' */ = {\n') + p:write('\t\t\tisa = PBXGroup;\n') + p:write('\t\t\tchildren = (\n') + + for _, project in pairs(projects) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', project.Guid, project.Decl.Name)) + end + p:write('\t\t\t);\n') + p:write('\t\t\tname = "', all_targets_name, '"; \n'); + p:write('\t\t\tsourceTree = "";\n'); + p:write('\t\t};\n') + + p:write('/* End PBXGroup section */\n\n') +end + +local function write_project(p, projects) + + local all_targets_name = "AllTargets.workspace" + local all_targets_id = newid(all_targets_name) + + local project_id = newid("ProjectObject") + local project_config_list_id = newid("ProjectObjectConfigList") + + p:write('/* Begin PBXProject section */\n') + p:write('\t\t', project_id, ' /* Project object */ = {\n') + p:write('\t\t\tisa = PBXProject;\n') + p:write('\t\t\tbuildConfigurationList = ', project_config_list_id, ' /* Build configuration list for PBXProject "', "Project Object", '" */;\n') + p:write('\t\t\tcompatibilityVersion = "Xcode 3.1";\n') + p:write('\t\t\thasScannedForEncodings = 1;\n') + p:write('\t\t\tmainGroup = ', all_targets_id, ' /* ', all_targets_name, ' */;\n') + p:write('\t\t\tprojectDirPath = "";\n') + p:write('\t\t\tprojectRoot = "";\n') + p:write('\t\t\ttargets = (\n') + + for _, project in pairs(projects) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', newid(project.Decl.Name .. "Target"), project.Decl.Name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t};\n') + p:write('/* End PBXProject section */\n') +end + +local function write_shellscripts(p, projects, env) + p:write('/* Begin PBXShellScriptBuildPhase section */\n') + + -- TODO: Do we really need to repead this for all projects? seems a bit wasteful + + local xcodetundra_filename = get_absolute_output_path(env) .. "xcodetundra" + + for _, project in pairs(projects) do + local name = project.Decl.Name + if not project.IsMeta then + p:write('\t\t', newid(name .. "ShellScript"), ' /* ShellScript */ = {\n') + p:write('\t\t\tisa = PBXShellScriptBuildPhase;\n') + p:write('\t\t\tbuildActionMask = 2147483647;\n') + p:write('\t\t\tfiles = (\n') + p:write('\t\t\t);\n') + p:write('\t\t\tinputPaths = (\n') + p:write('\t\t\t);\n') + p:write('\t\t\toutputPaths = (\n') + p:write('\t\t\t);\n') + p:write('\t\t\trunOnlyForDeploymentPostprocessing = 0;\n') + p:write('\t\t\tshellPath = /bin/sh;\n') + p:write('\t\t\tshellScript = "', xcodetundra_filename, ' $TARGET_NAME $CONFIG $VARIANT $SUBVARIANT $ACTION -v";\n') + p:write('\t\t};\n') + end + end + + p:write('/* Begin PBXShellScriptBuildPhase section */\n') +end + +local function get_full_config_name(config) + return config.Config.Name .. '-' .. config.Variant.Name .. '-' .. config.SubVariant +end + +local function write_configs(p, projects, config_tuples, env) + + p:write('/* Begin XCConfigurationList section */\n') + + -- I wonder if we really need to do it this way for all configs? + + for __, project in ipairs(projects) do + for _, tuple in ipairs(config_tuples) do + local full_config_name = get_full_config_name(tuple) + + local is_macosx_native = false + + for _, host in util.nil_ipairs(tuple.Config.SupportedHosts) do + if host == "macosx" then + is_macosx_native = true + end + end + + if "macosx" == tuple.Config.DefaultOnHost then + is_macosx_native = true + end + + local config_id = newid(project.Decl.Name .. full_config_name) + + p:write('\t\t', config_id, ' = {\n') + p:write('\t\t\tisa = XCBuildConfiguration;\n') + + -- Don't add any think extra if subvariant is default + + p:write('\t\t\tbuildSettings = {\n') + + if is_macosx_native then + p:write('\t\t\t\tARCHS = "$(NATIVE_ARCH_ACTUAL)";\n') + end + + p:write('\t\t\t\tVARIANT = "', tuple.Variant.Name, '";\n') + p:write('\t\t\t\tCONFIG = "', tuple.Config.Name, '";\n') + p:write('\t\t\t\tSUBVARIANT = "', tuple.SubVariant, '";\n') + + if is_macosx_native and not project.IsMeta then + p:write('\t\t\t\tCONFIGURATION_BUILD_DIR = "', full_config_name, '";\n') + end + + -- this is a little hack to get xcode to clean the whole output folder when using "FullBuild" + p:write('\t\t\t\tPRODUCT_NAME = "',project.Decl.Name , '";\n') + p:write('\t\t\t\tTARGET_NAME = "',project.Decl.Name , '";\n') + + p:write('\t\t\t};\n') + p:write('\t\t\tname = "',full_config_name , '";\n') + p:write('\t\t};\n') + end + end + + p:write('/* End XCConfigurationList section */\n') + +end + +local function write_config_list(p, projects, config_tuples) + + p:write('/* Begin XCConfigurationList section */\n') + + local default_config = ""; + + -- find the default config + + for _, tuple in ipairs(config_tuples) do + local is_macosx_native = tuple.Config.Name:match('^(%macosx)%-') + + if is_macosx_native and tuple.Variant.Name == "debug" then + default_config = get_full_config_name(tuple) + break + end + end + + -- if we did't find a default config just grab the first one + + if default_config == "" then + default_config = get_full_config_name(config_tuples[0]) + end + + for __, project in ipairs(projects) do + + local config_id = newid(project.Decl.Name .. 'Config') + + p:write('\t\t', config_id, ' /* Build config list for "', project.Decl.Name, '" */ = {\n') + p:write('\t\t\tisa = XCConfigurationList;\n') + + -- Don't add any think extra if subvariant is default + + p:write('\t\t\tbuildConfigurations = (\n') + + for _, tuple in ipairs(config_tuples) do + local full_config_name = get_full_config_name(tuple) + p:write(string.format('\t\t\t\t%s /* %s */,\n', newid(project.Decl.Name .. full_config_name), full_config_name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t\tdefaultConfigurationIsVisible = 1;\n') + p:write('\t\t\tdefaultConfigurationName = "', default_config, '";\n') + + p:write('\t\t};\n') + end + + p:write('/* End XCConfigurationList section */\n') + +end + +local function write_footer(p) + p:write('\t};\n') + p:write('\trootObject = ', newid("ProjectObject"), ' /* Project object */;\n') + p:write('}\n') +end + +local function generate_shellscript(env) + local filename = path.join(get_absolute_output_path(env), "xcodetundra") + local p = assert(io.open(filename, 'wb')) + p:write("#/bin/sh\n") + p:write("TARGET_NAME=$1\n") + p:write("CONFIG=$2\n") + p:write("VARIANT=$3\n") + p:write("SUBVARIANT=$4\n") + p:write("ACTION=$5\n") + p:write('if [ "$5" = "clean" ]; then\n') + p:write(' ACTION="-c"\n') + p:write("fi\n\n") + p:write('if [ "$5" = "build" ]; then\n') + p:write(' ACTION=""\n') + p:write("fi\n\n") + p:write(TundraExePath .. " --full-paths $TARGET_NAME $CONFIG-$VARIANT-$SUBVARIANT $ACTION -v\n") + p:close() + os.execute("chmod +x " .. filename) + local filename = path.join(get_absolute_output_path(env), "xcodeupdateproj") + local p = io.open(filename, 'wb') + p:write("#/bin/sh\n") + p:write(TundraExePath .. " --ide-gen xcode3 -a\n") + p:close() + os.execute("chmod +x " .. filename) +end + +function xcode_generator:generate_files(ngen, config_tuples, raw_nodes, env, default_names) + assert(config_tuples and #config_tuples > 0) + + -- TODO: Set the first default config as default + + local base_dir = env:interpolate('$(OBJECTROOT)$(SEP)') + local xcodeproj_dir = base_dir .. "tundra-generated.xcodeproj/" + + native.mkdir(base_dir) + native.mkdir(xcodeproj_dir) + + generate_shellscript(env) + + local p = io.open(path.join(xcodeproj_dir, "project.pbxproj"), 'wb') + + local projects = get_projects(raw_nodes, env) + + write_header(p) + write_file_refs(p, projects) + write_groups(p, projects) + write_legacy_targets(p, projects, env) + write_native_targes(p, projects) + write_project(p, projects) + write_shellscripts(p, projects, env) + write_configs(p, projects, config_tuples, env) + write_config_list(p, projects, config_tuples) + write_footer(p) +end + +nodegen.set_ide_backend(function(...) + local state = setmetatable({}, xcode_generator) + state:generate_files(...) +end) + diff --git a/ogl_editor/bin/win32/scripts/tundra/ide/xcode5.lua b/ogl_editor/bin/win32/scripts/tundra/ide/xcode5.lua new file mode 100644 index 0000000..6fb5353 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/ide/xcode5.lua @@ -0,0 +1,924 @@ +-- Xcode 5 Workspace/Project file generation + +module(..., package.seeall) + +local path = require "tundra.path" +local nodegen = require "tundra.nodegen" +local util = require "tundra.util" +local native = require "tundra.native" + +local xcode_generator = {} +xcode_generator.__index = xcode_generator + +function xcode_generator:generate_workspace(fn, projects) + local sln = io.open(fn, 'wb') + + sln:write('\n') + sln:write('\n') + + for _, proj in ipairs(projects) do + local name = proj.Decl.Name + local fname = proj.RelativeFilename + if fname == '.' then fname = '' + else fname = fname ..'/' + end + sln:write('\t\n') + sln:write('\t\n') + end + + sln:write('\n') +end + +local project_types = util.make_lookup_table { + "Program", "SharedLibrary", "StaticLibrary", +} +local toplevel_stuff = util.make_lookup_table { + ".exe", ".lib", ".dll", +} + +local binary_extension = util.make_lookup_table { + "", ".obj", ".o", ".a", +} + +local header_exts = util.make_lookup_table { + ".h", ".hpp", ".hh", ".inl", +} + +local function newid(data) + local string = native.digest_guid(data) + -- a bit ugly but is to match the xcode style of UIds + return string.sub(string.gsub(string, '-', ''), 1, 24) +end + +local file_types = { + [".c"] = "sourcecode.c.c", + [".cc"] = "sourcecode.cpp.cpp", + [".cpp"] = "sourcecode.cpp.cpp", + [".css"] = "text.css", + [".cxx"] = "sourcecode.cpp.cpp", + [".framework"] = "wrapper.framework", + [".gif"] = "image.gif", + [".h"] = "sourcecode.c.h", + [".html"] = "text.html", + [".lua"] = "sourcecode.lua", + [".m"] = "sourcecode.c.objc", + [".mm"] = "sourcecode.cpp.objc", + [".nib"] = "wrapper.nib", + [".pch"] = "sourcecode.c.h", + [".plist"] = "text.plist.xml", + [".strings"] = "text.plist.strings", + [".xib"] = "file.xib", + [".icns"] = "image.icns", + [""] = "compiled.mach-o.executable", +} + +local function getfiletype(name) + return file_types[path.get_extension(name)] or "text" +end + +-- Scan for sources, following dependencies until those dependencies seem to be a different top-level unit +local function get_sources(dag, sources, generated, dag_lut) + for _, output in ipairs(dag.outputs) do + local ext = path.get_extension(output) + if not binary_extension[ext] then + generated[output] = true + sources[output] = true -- pick up generated headers + end + end + + for _, input in ipairs(dag.inputs) do + local ext = path.get_extension(input) + if not binary_extension[ext] then + sources[input] = true + end + end + + for _, dep in util.nil_ipairs(dag.deps) do + if not dag_lut[dep] then -- don't go into other top-level DAGs + get_sources(dep, sources, generated, dag_lut) + end + end +end + +local function get_headers(unit, sources, dag_lut, name_to_dags) + local src_dir = '' + + if not unit.Decl then + -- Ignore ExternalLibrary and similar that have no data. + return + end + + if unit.Decl.SourceDir then + src_dir = unit.Decl.SourceDir .. '/' + end + for _, src in util.nil_ipairs(nodegen.flatten_list('*-*-*-*', unit.Decl.Sources)) do + if type(src) == "string" then + local ext = path.get_extension(src) + if header_exts[ext] then + local full_path = path.normalize(src_dir .. src) + sources[full_path] = true + end + end + end + + local function toplevel(u) + if type(u) == "string" then + return type(name_to_dags[u]) ~= "nil" + end + + for _, dag in pairs(u.Decl.__DagNodes) do + if dag_lut[dag] then + return true + end + end + return false + end + + -- Repeat for dependencies ObjGroups + for _, dep in util.nil_ipairs(nodegen.flatten_list('*-*-*-*', unit.Decl.Depends)) do + + if not toplevel(dep) then + get_headers(dep, sources, dag_lut) + end + end +end + +local function sort_filelist(source_list) + local dest = {} + for k, v in pairs(source_list) do table.insert(dest, { Key = k, Value = v }) end + table.sort(dest, function(a, b) return a.Value < b.Value end) + return dest +end + +local function write_file_refs(p, projects) + p:write('/* Begin FBXFileReference section */\n') + local cwd = native.getcwd(); + + -- build the source list + + local full_source_list = {} + + for _, project in ipairs(projects) do + local sources = project.Sources + for key, fn in pairs(sources) do + full_source_list[key] = fn + end + + -- include executable names in the source list as well + + if project.Type == "Program" then + full_source_list[newid(project.Decl.Name .. "Program")] = project.Decl.Name + end + end + + local source_list = {} + + -- As we can't sort hashtables we need to move this over to a regular table + + source_list = sort_filelist(full_source_list) + + for _, entry in pairs(source_list) do + local key = entry.Key + local fn = entry.Value + local name = path.get_filename(fn) + local file_type = getfiletype(fn) + local str = "" + if file_type == "compiled.mach-o.executable" then + str = string.format('\t\t%s /* %s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = %s; name = "%s"; includeInIndex = 0; path = "%s"; sourceTree = BUILT_PRODUCTS_DIR; };', + key, fn, file_type, name, fn) + else + str = string.format('\t\t%s /* %s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = %s; name = "%s"; path = "%s"; sourceTree = ""; };', + key, fn, file_type, name, path.join(cwd, fn)) + end + p:write(str, '\n') + end + + p:write('/* End FBXFileReference section */\n\n') +end + +local function write_legacy_targets(p, projects, env) + p:write('/* Begin PBXLegacyTarget section */\n') + + for _, project in ipairs(projects) do + local decl = project.Decl + + if project.IsMeta then + --[[ + isa = PBXLegacyTarget; + buildArgumentsString = ""; + buildConfigurationList = D7D12762170E4CF98A79B5EF /* Build configuration list for PBXLegacyTarget "!UpdateWorkspace" */; + buildPhases = ( + ); + buildToolPath = /Users/danielcollin/unity_ps3/ps3/Projects/JamGenerated/_workspace.xcode_/updateworkspace; + dependencies = ( + ); + name = "!UpdateWorkspace"; + passBuildSettingsInEnvironment = 1; + productName = "!UpdateWorkspace"; + --]] + + p:write('\t\t', newid(decl.Name .. "Target"), ' /* ', decl.Name, ' */ = {\n') + p:write('\t\t\tisa = PBXLegacyTarget;\n') + p:write('\t\t\tbuildArgumentsString = "', project.MetaData.BuildArgs, '";\n') + p:write('\t\t\tbuildConfigurationList = ', newid(decl.Name .. 'Config'), ' /* Build configuration list for PBXLegacyTarget "',decl.Name, '" */;\n') + p:write('\t\t\tbuildPhases = (\n') + p:write('\t\t\t);\n'); + p:write('\t\t\tbuildToolPath = ', project.MetaData.BuildTool, ';\n') + p:write('\t\t\tbuildWorkingDirectory = ', '..', ';\n') + + p:write('\t\t\tdependencies = (\n\t\t\t);\n') + p:write('\t\t\tname = "', decl.Name, '";\n') + p:write('\t\t\tpassBuildSettingsInEnvironment = 1;\n') + p:write('\t\t\tproductName = "', decl.Name or "", '";\n') + p:write('\t\t};\n') + end + end + + p:write('/* End PBXLegacyTarget section */\n') +end + +local function write_native_targes(p, projects) + p:write('/* Begin PBXNativeTarget section */\n') + + local categories = { + ["Program"] = "com.apple.product-type.tool", + ["StaticLibrary"] = "com.apple.product-type.library.static", + ["SharedLibrary"] = "com.apple.product-type.library.dynamic", + } + + for _, project in ipairs(projects) do + local decl = project.Decl + + if not project.IsMeta then + p:write('\t\t', newid(decl.Name .. "Target"), ' /* ', decl.Name, ' */ = {\n') + p:write('\t\t\tisa = PBXNativeTarget;\n') + p:write('\t\t\tbuildConfigurationList = ', newid(decl.Name .. 'Config'), ' /* Build configuration list for PBXNativeTarget "',decl.Name, '" */;\n') + p:write('\t\t\tbuildPhases = (\n') + p:write('\t\t\t\t', newid(decl.Name .. "ShellScript"), ' /* ShellScript */,\n') + p:write('\t\t\t);\n'); + p:write('\t\t\tbuildRules = (\n\t\t\t);\n') + p:write('\t\t\tdependencies = (\n\t\t\t);\n') + p:write('\t\t\tname = "', decl.Name, '";\n') + p:write('\t\t\tProductName = "', decl.Name, '";\n') + p:write('\t\t\tproductReference = ', newid(decl.Name .. "Program"), ' /* ', decl.Name, ' */;\n ') + p:write('\t\t\tproductType = "', categories[project.Type] or "", '";\n') + p:write('\t\t};\n') + end + end + + p:write('/* End PBXNativeTarget section */\n') +end + + +local function write_header(p) + p:write('// !$*UTF8*$!\n') + p:write('{\n') + p:write('\tarchiveVersion = 1;\n') + p:write('\tclasses = {\n') + p:write('\t};\n') + p:write('\tobjectVersion = 45;\n') + p:write('\tobjects = {\n') + p:write('\n') +end + +local function get_projects(raw_nodes, env, hints, ide_script) + local projects = {} + + -- Filter out stuff we don't care about. + local units = util.filter(raw_nodes, function (u) + return u.Decl.Name and project_types[u.Keyword] + end) + + local dag_node_lut = {} -- lookup table of all named, top-level DAG nodes + local name_to_dags = {} -- table mapping unit name to array of dag nodes (for configs) + + -- Map out all top-level DAG nodes + for _, unit in ipairs(units) do + local decl = unit.Decl + + local dag_nodes = assert(decl.__DagNodes, "no dag nodes for " .. decl.Name) + for build_id, dag_node in pairs(dag_nodes) do + dag_node_lut[dag_node] = unit + local array = name_to_dags[decl.Name] + if not array then + array = {} + name_to_dags[decl.Name] = array + end + array[#array + 1] = dag_node + end + end + + -- Sort units based on dependency complexity. We want to visit the leaf nodes + -- first so that any source file references are picked up as close to the + -- bottom of the dependency chain as possible. + local unit_weights = {} + for _, unit in ipairs(units) do + local decl = unit.Decl + local stack = { } + for _, dag in pairs(decl.__DagNodes) do + stack[#stack + 1] = dag + end + local weight = 0 + while #stack > 0 do + local node = table.remove(stack) + if dag_node_lut[node] then + weight = weight + 1 + end + for _, dep in util.nil_ipairs(node.deps) do + stack[#stack + 1] = dep + end + end + unit_weights[unit] = weight + end + + table.sort(units, function (a, b) + return unit_weights[a] < unit_weights[b] + end) + + -- Keep track of what source files have already been grabbed by other projects. + local grabbed_sources = {} + + for _, unit in ipairs(units) do + local decl = unit.Decl + local name = decl.Name + + local sources = {} + local generated = {} + for build_id, dag_node in pairs(decl.__DagNodes) do + get_sources(dag_node, sources, generated, dag_node_lut) + end + + -- Explicitly add all header files too as they are not picked up from the DAG + -- Also pick up headers from non-toplevel DAGs we're depending on + get_headers(unit, sources, dag_node_lut, name_to_dags) + + -- Figure out which project should get this data. + local output_name = name + local ide_hints = unit.Decl.IdeGenerationHints + if ide_hints then + if ide_hints.OutputProject then + output_name = ide_hints.OutputProject + end + end + + -- Rebuild source list with ids that are needed by the xcode project layout + local source_list = {} + for src, _ in pairs(sources) do + local norm_src = path.normalize(src) + -- if not grabbed_sources[norm_src] then + grabbed_sources[norm_src] = unit + source_list[newid(norm_src)] = norm_src + -- end + end + + projects[name] = { + Type = unit.Keyword, + Decl = decl, + Sources = source_list, + RelativeFilename = name, + Guid = newid(name .. "ProjectId"), + IdeGenerationHints = unit.Decl.IdeGenerationHints + } + end + + for _, unit in ipairs(raw_nodes) do + if unit.Keyword == "OsxBundle" then + local decl = unit.Decl + decl.Name = "OsxBundle" + + local source_list = {[newid(decl.InfoPList)] = decl.InfoPList} + for _, resource in util.nil_ipairs(decl.Resources) do + if resource.Decl then + source_list[newid(resource.Decl.Source)] = resource.Decl.Source + end + end + + projects["OsxBundle"] = { + Type = unit.Keyword, + Decl = decl, + Sources = source_list, + RelativeFilename = "$(OBJECTDIR)/MyApp.app", + Guid = newid("OsxBundle"), + } + end + end + + return projects +end + +local function split(fn) + local dir, file = fn:match("^(.*)[/\\]([^\\/]*)$") + if not dir then + return ".", fn + else + return dir, file + end +end + +local function split_str(str, pat) + local t = {} -- NOTE: use {n = 0} in Lua-5.0 + local fpat = "(.-)" .. pat + local last_end = 1 + local s, e, cap = str:find(fpat, 1) + while s do + if s ~= 1 or cap ~= "" then + table.insert(t,cap) + end + last_end = e+1 + s, e, cap = str:find(fpat, last_end) + end + if last_end <= #str then + cap = str:sub(last_end) + table.insert(t, cap) + end + return t +end + +local function print_children_2(p, groupname, key, children, path) + + for name, c in pairs(children) do + if c.Type > 0 then + print_children_2(p, name, c.Key, c.Children, c.Type == 1 and path..'/'..name or path) + end + end + + p:write('\t\t', key, ' /* ', path, ' */ = {\n') + p:write('\t\t\tisa = PBXGroup;\n') + p:write('\t\t\tchildren = (\n') + + local dirs = {} + local files = {} + + for name, ref in pairs(children) do + if ref.Type > 0 then + dirs[#dirs + 1] = { Key = ref.Key, Name = name } + else + files[#files + 1] = { Key = ref.Key, Name = name } + end + end + + table.sort(dirs, function(a, b) return a.Name < b.Name end) + table.sort(files, function(a, b) return a.Name < b.Name end) + + for i, ref in pairs(dirs) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', ref.Key, path .. '/' .. ref.Name)) + end + + for i, ref in pairs(files) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', ref.Key, path .. '/' .. ref.Name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t\tname = "', groupname, '"; \n'); + p:write('\t\t\tsourceTree = "";\n'); + p:write('\t\t};\n') +end + +local function prune_groups(group) + local i = 0 + local first_name + local first_child + + for name, child in pairs(group.Children) do + first_name = name + first_child = child + i = i + 1 + end + + if i == 1 and first_child.Type > 0 then + local new_name = prune_groups(first_child) + group.Children = first_child.Children; + if not new_name then + new_name = first_name + end + return new_name + + else + local children = {} + for name, child in pairs(group.Children) do + if child.Type > 0 then + local new_name = prune_groups(child) + if new_name then + name = new_name + end + end + children[name] = child + end + group.children = children + return nil + end + +end + + +local function make_groups(p, files, key) + local filelist = sort_filelist(files) + local group = { Type = 2, Key = key, Children = {} } + + for _, entry in pairs(filelist) do + local parent_group = group + local path, filename = split(entry.Value) + for i, part in ipairs(split_str(path, "/")) do + if part ~= '.' then + local grp = parent_group.Children[part] + if grp == nil then + grp = { Type = 1, Key=newid(util.tostring(parent_group)..part), Children={} } + parent_group.Children[part] = grp + end + parent_group = grp + end + end + parent_group.Children[filename] = { Type = 0, Key = entry.Key } + end + + -- prune single-entry groups + prune_groups(group) + + return group +end + + +local function write_groups(p, projects) + p:write('/* Begin PBXGroup section */\n') + + -- Map folder names to array of projects under that folder + local folders = {} + for _, project in ipairs(projects) do + local hints = project.IdeGenerationHints + local msvc_hints = hints and hints.Msvc + local fname = msvc_hints and msvc_hints.SolutionFolder + if fname == nil then + fname = "" + end + local folder = folders[fname] + if folder == nil then + folder = { Type = 2, Key = newid("Folder"..fname), Children = {} } + folders[fname] = folder + end + folder.Children[project.Decl.Name] = make_groups(p, project.Sources, project.Guid) + end + + local root = folders[""]; + for name, folder in pairs(folders) do + if folder ~= root then + root.Children[name] = folder + end + end + + local all_targets_name = "AllTargets.workspace" + local all_targets_id = newid(all_targets_name) + print_children_2(p, all_targets_name, all_targets_id, root.Children, '.'); + + -- write last group that links the projects names above + + -- local all_targets_name = "AllTargets.workspace" + -- local all_targets_id = newid(all_targets_name) + -- p:write('\t\t', all_targets_id, ' /* ', all_targets_name, ' */ = {\n') + -- p:write('\t\t\tisa = PBXGroup;\n') + -- p:write('\t\t\tchildren = (\n') + + -- for _, project in pairs(projects) do + -- p:write(string.format('\t\t\t\t%s /* %s */,\n', project.Guid, project.Decl.Name)) + -- end + -- p:write('\t\t\t);\n') + -- p:write('\t\t\tname = "', all_targets_name, '"; \n'); + -- p:write('\t\t\tsourceTree = "";\n'); + -- p:write('\t\t};\n') + + p:write('/* End PBXGroup section */\n\n') +end + +local function write_project(p, projects) + + local all_targets_name = "AllTargets.workspace" + local all_targets_id = newid(all_targets_name) + + local project_id = newid("ProjectObject") + local project_config_list_id = newid("ProjectObjectConfigList") + + p:write('/* Begin PBXProject section */\n') + p:write('\t\t', project_id, ' /* Project object */ = {\n') + p:write('\t\t\tisa = PBXProject;\n') + p:write('\t\t\tbuildConfigurationList = ', project_config_list_id, ' /* Build configuration list for PBXProject "', "Project Object", '" */;\n') + p:write('\t\t\tcompatibilityVersion = "Xcode 3.1";\n') + p:write('\t\t\thasScannedForEncodings = 1;\n') + p:write('\t\t\tmainGroup = ', all_targets_id, ' /* ', all_targets_name, ' */;\n') + p:write('\t\t\tprojectDirPath = "";\n') + p:write('\t\t\tprojectRoot = "";\n') + p:write('\t\t\ttargets = (\n') + + for _, project in ipairs(projects) do + p:write(string.format('\t\t\t\t%s /* %s */,\n', newid(project.Decl.Name .. "Target"), project.Decl.Name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t};\n') + p:write('/* End PBXProject section */\n') +end + +local function write_shellscripts(p, projects, env) + p:write('/* Begin PBXShellScriptBuildPhase section */\n') + + -- TODO: Do we really need to repeat this for all projects? seems a bit wasteful + + for _, project in ipairs(projects) do + local name = project.Decl.Name + if not project.IsMeta then + p:write('\t\t', newid(name .. "ShellScript"), ' /* ShellScript */ = {\n') + p:write('\t\t\tisa = PBXShellScriptBuildPhase;\n') + p:write('\t\t\tbuildActionMask = 2147483647;\n') + p:write('\t\t\tfiles = (\n') + p:write('\t\t\t);\n') + p:write('\t\t\tinputPaths = (\n') + p:write('\t\t\t);\n') + p:write('\t\t\toutputPaths = (\n') + p:write('\t\t\t);\n') + p:write('\t\t\trunOnlyForDeploymentPostprocessing = 0;\n') + p:write('\t\t\tshellPath = /bin/sh;\n') + p:write('\t\t\tshellScript = "cd ..\\n', TundraExePath, ' $(CONFIG)-$(VARIANT)-$(SUBVARIANT)";\n') + p:write('\t\t};\n') + end + end + + p:write('/* End PBXShellScriptBuildPhase section */\n') +end + +local function get_full_config_name(config) + return config.Config.Name .. '-' .. config.Variant.Name .. '-' .. config.SubVariant +end + +local function write_configs(p, projects, config_tuples, env, set_env) + + p:write('/* Begin XCBuildConfiguration section */\n') + + -- I wonder if we really need to do it this way for all configs? + + for _, project in ipairs(projects) do + for _, tuple in ipairs(config_tuples) do + local full_config_name = get_full_config_name(tuple) + local is_macosx_native = false + + for _, host in util.nil_ipairs(tuple.Config.SupportedHosts) do + if host == "macosx" then + is_macosx_native = true + end + end + + if "macosx" == tuple.Config.DefaultOnHost then + is_macosx_native = true + end + + local config_id = newid(project.Decl.Name .. full_config_name) + + p:write('\t\t', config_id, ' = {\n') + p:write('\t\t\tisa = XCBuildConfiguration;\n') + + -- Don't add any think extra if subvariant is default + + p:write('\t\t\tbuildSettings = {\n') + + if is_macosx_native then + p:write('\t\t\t\tARCHS = "$(NATIVE_ARCH_ACTUAL)";\n') + end + + p:write('\t\t\t\tVARIANT = "', tuple.Variant.Name, '";\n') + p:write('\t\t\t\tCONFIG = "', tuple.Config.Name, '";\n') + p:write('\t\t\t\tSUBVARIANT = "', tuple.SubVariant, '";\n') + + if is_macosx_native and not project.IsMeta then + p:write('\t\t\t\tCONFIGURATION_BUILD_DIR = "', full_config_name, '";\n') + end + + -- this is a little hack to get xcode to clean the whole output folder when using "FullBuild" + p:write('\t\t\t\tPRODUCT_NAME = "',project.Decl.Name , '";\n') + p:write('\t\t\t\tTARGET_NAME = "',project.Decl.Name , '";\n') + + for i, var in ipairs(set_env) do + p:write('\t\t\t\t', var, ' = "', os.getenv(var), '";\n') + end + + p:write('\t\t\t};\n') + p:write('\t\t\tname = "',full_config_name , '";\n') + p:write('\t\t};\n') + end + end + + -- PBXProject configurations + + for _, tuple in ipairs(config_tuples) do + local full_config_name = get_full_config_name(tuple) + local config_id = newid("ProjectObject" .. full_config_name) + + p:write('\t\t', config_id, ' = {\n') + p:write('\t\t\tisa = XCBuildConfiguration;\n') + + p:write('\t\t\tbuildSettings = {\n') + p:write('\t\t\t};\n') + p:write('\t\t\tname = "',full_config_name , '";\n') + p:write('\t\t};\n') + end + + p:write('/* End XCBuildConfiguration section */\n') + +end + +local function write_config_list(p, projects, config_tuples) + p:write('/* Begin XCConfigurationList section */\n') + + local default_config = ""; + + -- find the default config + + for _, tuple in ipairs(config_tuples) do + local is_macosx_native = tuple.Config.Name:match('^(%macosx)%-') + + if is_macosx_native and tuple.Variant.Name == "debug" then + default_config = get_full_config_name(tuple) + break + end + end + + -- if we did't find a default config just grab the first one + + if default_config == "" then + default_config = get_full_config_name(config_tuples[1]) + end + + for __, project in ipairs(projects) do + local config_id = newid(project.Decl.Name .. 'Config') + + p:write('\t\t', config_id, ' /* Build config list for "', project.Decl.Name, '" */ = {\n') + p:write('\t\t\tisa = XCConfigurationList;\n') + + -- Don't add any think extra if subvariant is default + + p:write('\t\t\tbuildConfigurations = (\n') + + for _, tuple in ipairs(config_tuples) do + local full_config_name = get_full_config_name(tuple) + p:write(string.format('\t\t\t\t%s /* %s */,\n', newid(project.Decl.Name .. full_config_name), full_config_name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t\tdefaultConfigurationIsVisible = 1;\n') + p:write('\t\t\tdefaultConfigurationName = "', default_config, '";\n') + + p:write('\t\t};\n') + end + + -- PBXProject configuration list + + local config_id = newid("ProjectObjectConfigList") + + p:write('\t\t', config_id, ' /* Build config list for PBXProject */ = {\n') + p:write('\t\t\tisa = XCConfigurationList;\n') + + -- Don't add any think extra if subvariant is default + + p:write('\t\t\tbuildConfigurations = (\n') + + for _, tuple in ipairs(config_tuples) do + local full_config_name = get_full_config_name(tuple) + p:write(string.format('\t\t\t\t%s /* %s */,\n', newid("ProjectObject" .. full_config_name), full_config_name)) + end + + p:write('\t\t\t);\n') + p:write('\t\t\tdefaultConfigurationIsVisible = 1;\n') + p:write('\t\t\tdefaultConfigurationName = "', default_config, '";\n') + p:write('\t\t};\n') + + p:write('/* End XCConfigurationList section */\n') + +end + +local function write_footer(p) + p:write('\t};\n') + p:write('\trootObject = ', newid("ProjectObject"), ' /* Project object */;\n') + p:write('}\n') +end + +local function make_meta_projects(ide_script) + local source_list = { + [newid("tundra.lua")] = "tundra.lua" + } + local units = io.open("units.lua") + if units then + source_list[newid("units.lua")] = "units.lua" + io.close(units) + end + + local meta_name1 = "!BuildWorkspace" + local meta_name2 = "!UpdateWorkspace" + return { + { + Decl = { Name = meta_name1, }, + Type = "LegacyTarget", + RelativeFilename = "", + Sources = source_list, + Guid = newid(meta_name1 .. 'ProjectId'), + IsMeta = true, + MetaData = { BuildArgs = "-v $(CONFIG)-$(VARIANT)-$(SUBVARIANT)", BuildTool = TundraExePath }, + }, + { + Decl = { Name = meta_name2, }, + Type = "LegacyTarget", + RelativeFilename = "", + Sources = source_list, + Guid = newid(meta_name2 .. 'ProjectId'), + IsMeta = true, + MetaData = { BuildArgs = "--g " .. ide_script, BuildTool = TundraExePath }, + } + } +end + +function xcode_generator:generate_files(ngen, config_tuples, raw_nodes, env, default_names, hints, ide_script) + assert(config_tuples and #config_tuples > 0) + + hints = hints or {} + hints = hints.Xcode or {} + local base_dir = hints.BaseDir and (hints.BaseDir .. '/') or env:interpolate('$(OBJECTROOT)$(SEP)') + native.mkdir(base_dir) + + local projects = get_projects(raw_nodes, env, hints, ide_script) + + local source_list = { + [newid("tundra.lua")] = "tundra.lua" + } + local units = io.open("units.lua") + if units then + source_list[newid("units.lua")] = "units.lua" + io.close(units) + end + + local meta_name = "!BuildWorkspace" + local build_project = { + Decl = { Name = meta_name, }, + Type = "LegacyTarget", + RelativeFilename = "", + Sources = source_list, + Guid = newid(meta_name .. 'ProjectId'), + IsMeta = true, + MetaData = { BuildArgs = "$(CONFIG)-$(VARIANT)-$(SUBVARIANT)", BuildTool = TundraExePath }, + } + local meta_name = "!UpdateWorkspace" + local generate_project = { + Decl = { Name = meta_name, }, + Type = "LegacyTarget", + RelativeFilename = "", + Sources = source_list, + Guid = newid(meta_name .. 'ProjectId'), + IsMeta = true, + MetaData = { BuildArgs = "--g " .. ide_script, BuildTool = TundraExePath }, + } + + local solution_hints = hints.Projects + if not solution_hints then + print("No IdeGenerationHints.Xcode.Projects specified - using defaults") + solution_hints = { + ['tundra-generated.sln'] = { } + } + end + + for name, data in pairs(solution_hints) do + local sln_projects = { build_project, generate_project } + + if data.Projects then + for _, pname in ipairs(data.Projects) do + local pp = projects[pname] + if not pp then + errorf("can't find project %s for inclusion in %s -- check your Projects data", pname, name) + end + sln_projects[#sln_projects + 1] = pp + end + else + -- All the projects (that are not meta) + for pname, pp in pairs(projects) do + sln_projects[#sln_projects + 1] = pp + end + end + + local proj_dir = base_dir .. path.drop_suffix(name) .. ".xcodeproj/" + native.mkdir(proj_dir) + + local p = io.open(path.join(proj_dir, "project.pbxproj"), 'wb') + + write_header(p) + write_file_refs(p, sln_projects) + write_groups(p, sln_projects) + write_legacy_targets(p, sln_projects, env) + write_native_targes(p, sln_projects) + write_project(p, sln_projects) + write_shellscripts(p, sln_projects, env) + write_configs(p, sln_projects, config_tuples, env, hints.EnvVars or {}) + write_config_list(p, sln_projects, config_tuples) + write_footer(p) + end +end + +nodegen.set_ide_backend(function(...) + local state = setmetatable({}, xcode_generator) + state:generate_files(...) +end) + diff --git a/ogl_editor/bin/win32/scripts/tundra/init.lua b/ogl_editor/bin/win32/scripts/tundra/init.lua new file mode 100644 index 0000000..5f22ca3 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/init.lua @@ -0,0 +1,34 @@ +module(..., package.seeall) + +init_tundra_lua = [====[ +local CFiles = { ".c", ".h" } +Build { + Configs = { + Config { + Name = "generic-gcc", + DefaultOnHost = "linux", + Tools = { "gcc" }, + }, + Config { + Name = "macosx-gcc", + DefaultOnHost = "macosx", + Tools = { "gcc-osx" }, + }, + Config { + Name = "win64-msvc", + DefaultOnHost = "windows", + Tools = { "msvc-vs2008"; TargetPlatform = "x64" }, + }, + }, + Units = function() + require "tundra.syntax.glob" + Program { + Name = "a.out", + Sources = { Glob { Dir = ".", Extensions = CFiles } }, + } + + Default "a.out" + end, +} +]====] + diff --git a/ogl_editor/bin/win32/scripts/tundra/nodegen.lua b/ogl_editor/bin/win32/scripts/tundra/nodegen.lua new file mode 100644 index 0000000..e560aa1 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/nodegen.lua @@ -0,0 +1,914 @@ +module(..., package.seeall) + +local unitgen = require "tundra.unitgen" +local util = require "tundra.util" +local path = require "tundra.path" +local depgraph = require "tundra.depgraph" +local buildfile = require "tundra.buildfile" +local native = require "tundra.native" + +local ide_backend = nil + +local current = nil + +local _nodegen = { } +_nodegen.__index = _nodegen + +local function syntax_error(msg, ...) + error { Class = 'syntax error', Message = string.format(msg, ...) } +end + +local function validate_boolean(name, value) + if type(value) == "boolean" then + return value + end + syntax_error("%s: expected boolean value, got %q", name, type(value)) +end + +local function validate_string(name, value) + if type(value) == "string" then + return value + end + syntax_error("%s: expected string value, got %q", name, type(value)) +end + +local function validate_pass(name, value) + if type(value) == "string" then + return value + else + syntax_error("%s: expected pass name, got %q", name, type(value)) + end +end + +local function validate_table(name, value) + -- A single string can be converted into a table value very easily + local t = type(value) + if t == "table" then + return value + elseif t == "string" then + return { value } + else + syntax_error("%s: expected table value, got %q", name, t) + end +end + +local function validate_config(name, value) + if type(value) == "table" or type(value) == "string" then + return value + end + syntax_error("%s: expected config, got %q", name, type(value)) +end + +local validators = { + ["string"] = validate_string, + ["pass"] = validate_pass, + ["table"] = validate_table, + ["filter_table"] = validate_table, + ["source_list"] = validate_table, + ["boolean"] = validate_boolean, + ["config"] = validate_config, +} + +function _nodegen:validate() + local decl = self.Decl + for name, detail in pairs(assert(self.Blueprint)) do + local val = decl[name] + if not val then + if detail.Required then + syntax_error("%s: missing argument: '%s'", self.Keyword, name) + end + -- ok, optional value + else + local validator = validators[detail.Type] + decl[name] = validator(name, val) + end + end + for name, detail in pairs(decl) do + if not self.Blueprint[name] then + syntax_error("%s: unsupported argument: '%s'", self.Keyword, name) + end + end +end + +function _nodegen:customize_env(env, raw_data) + -- available for subclasses +end + +function _nodegen:configure_env(env, deps) + local build_id = env:get('BUILD_ID') + local propagate_blocks = {} + local decl = self.Decl + + for _, dep_obj in util.nil_ipairs(deps) do + local data = dep_obj.Decl.Propagate + if data then + propagate_blocks[#propagate_blocks + 1] = data + end + end + + local function push_bindings(env_key, data) + if data then + for _, item in util.nil_ipairs(flatten_list(build_id, data)) do + env:append(env_key, item) + end + end + end + + local function replace_bindings(env_key, data) + if data then + local first = true + for _, item in util.nil_ipairs(flatten_list(build_id, data)) do + if first then + env:replace(env_key, item) + first = false + else + env:append(env_key, item) + end + end + end + end + + -- Push Libs, Defines and so in into the environment of this unit. + -- These are named for convenience but are aliases for syntax niceness. + for decl_key, env_key in util.nil_pairs(self.DeclToEnvMappings) do + -- First pick settings from our own unit. + push_bindings(env_key, decl[decl_key]) + + for _, data in ipairs(propagate_blocks) do + push_bindings(env_key, data[decl_key]) + end + end + + -- Push Env blocks as is + for k, v in util.nil_pairs(decl.Env) do + push_bindings(k, v) + end + + for k, v in util.nil_pairs(decl.ReplaceEnv) do + replace_bindings(k, v) + end + + for _, block in util.nil_ipairs(propagate_blocks) do + for k, v in util.nil_pairs(block.Env) do + push_bindings(k, v) + end + + for k, v in util.nil_pairs(block.ReplaceEnv) do + replace_bindings(k, v) + end + end +end + +local function resolve_sources(env, items, accum, base_dir) + local ignored_exts = util.make_lookup_table(env:get_list("IGNORED_AUTOEXTS", {})) + for _, item in util.nil_ipairs(items) do + local type_name = type(item) + + assert(type_name ~= "function") + + if type_name == "userdata" then + accum[#accum + 1] = item + elseif type_name == "table" then + if depgraph.is_node(item) then + accum[#accum + 1] = item + elseif getmetatable(item) then + accum[#accum + 1] = item:get_dag(env) + else + resolve_sources(env, item, accum, item.SourceDir or base_dir) + end + else + assert(type_name == "string") + local ext = path.get_extension(item) + if not ignored_exts[ext] then + if not base_dir or path.is_absolute(item) then + accum[#accum + 1] = item + else + local p = path.join(base_dir, item) + accum[#accum + 1] = p + end + end + end + end + return accum +end + +-- Analyze source list, returning list of input files and list of dependencies. +-- +-- This is so you can pass a mix of actions producing files and regular +-- filenames as inputs to the next step in the chain and the output files of +-- such nodes will be used automatically. +-- +-- list - list of source files and nodes that produce source files +-- suffixes - acceptable source suffixes to pick up from nodes in source list +local function analyze_sources(env, pass, list, suffixes) + if not list then + return nil + end + + list = util.flatten(list) + local deps = {} + + local function implicit_make(source_file) + local t = type(source_file) + if t == "table" then + return source_file + end + assert(t == "string") + + local make = env:get_implicit_make_fn(source_file) + if make then + return make(env, pass, source_file) + else + return nil + end + end + + local function transform(output, fn) + if type(fn) ~= "string" then + error(util.tostring(fn) .. " is not a string", 2) + end + + local t = implicit_make(fn) + if t then + deps[#deps + 1] = t + t:insert_output_files(output, suffixes) + else + output[#output + 1] = fn + end + end + + local files = {} + for _, src in ipairs(list) do + if depgraph.is_node(src) then + deps[#deps + 1] = src + src:insert_output_files(files, suffixes) + elseif type(src) == "table" then + error("non-DAG node in source list at this point") + else + files[#files + 1] = src + end + end + + while true do + local result = {} + local old_dep_count = #deps + for _, src in ipairs(files) do + transform(result, src) + end + files = result + if #deps == old_dep_count then + --print("scan", util.tostring(list), util.tostring(suffixes), util.tostring(result)) + return result, deps + end + end +end + +local function x_identity(self, name, info, value, env, out_deps) + return value +end + +local function x_source_list(self, name, info, value, env, out_deps) + local build_id = env:get('BUILD_ID') + local source_files + if build_id then + source_files = filter_structure(build_id, value) + else + source_files = value + end + local sources = resolve_sources(env, source_files, {}, self.Decl.SourceDir) + local source_exts = env:get_list(info.ExtensionKey) + local inputs, ideps = analyze_sources(env, resolve_pass(self.Decl.Pass), sources, source_exts) + if ideps then + util.append_table(out_deps, ideps) + end + return inputs +end + +local function x_filter_table(self, name, info, value, env, out_deps) + local build_id = env:get('BUILD_ID') + return flatten_list(build_id, value) +end + +local function find_named_node(name_or_dag) + if type(name_or_dag) == "table" then + return name_or_dag:get_dag(current.default_env) + elseif type(name_or_dag) == "string" then + local generator = current.units[name_or_dag] + if not generator then + errorf("unknown node specified: %q", tostring(name_or_dag)) + end + return generator:get_dag(current.default_env) + else + errorf("illegal node specified: %q", tostring(name_or_dag)) + end +end + +-- Special resolver for dependencies in a nested (config-filtered) list. +local function resolve_dependencies(decl, raw_deps, env) + if not raw_deps then + return {} + end + + local build_id = env:get('BUILD_ID') + local deps = flatten_list(build_id, raw_deps) + return util.map_in_place(deps, function (i) + if type(i) == "string" then + local n = current.units[i] + if not n then + errorf("%s: Unknown 'Depends' target %q", decl.Name, i) + end + return n + elseif type(i) == "table" and getmetatable(i) and i.Decl then + return i + else + errorf("bad 'Depends' value of type %q", type(i)) + end + end) +end + +local function x_pass(self, name, info, value, env, out_deps) + return resolve_pass(value) +end + +local decl_transformers = { + -- the x_identity data types have already been checked at script time through validate_xxx + ["string"] = x_identity, + ["table"] = x_identity, + ["config"] = x_identity, + ["boolean"] = x_identity, + ["pass"] = x_pass, + ["source_list"] = x_source_list, + ["filter_table"] = x_filter_table, +} + +-- Create input data for the generator's DAG creation function based on the +-- blueprint passed in when the generator was registered. This is done here +-- centrally rather than in all the different node generators to reduce code +-- duplication and keep the generators miminal. If you need to do something +-- special, you can override create_input_data() in your subclass. +function _nodegen:create_input_data(env) + local decl = self.Decl + local data = {} + local deps = {} + + for name, detail in pairs(assert(self.Blueprint)) do + local val = decl[name] + if val then + local xform = decl_transformers[detail.Type] + data[name] = xform(self, name, detail, val, env, deps) + end + end + + return data, deps +end + +function get_pass(self, name) + if not name then + return nil + end + +end + +local pattern_cache = {} +local function get_cached_pattern(p) + local v = pattern_cache[p] + if not v then + local comp = '[%w_]+' + local sub_pattern = p:gsub('*', '[%%w_]+') + local platform, tool, variant, subvariant = unitgen.match_build_id(sub_pattern, comp) + v = string.format('^%s%%-%s%%-%s%%-%s$', platform, tool, variant, subvariant) + pattern_cache[p] = v + end + return v +end + +local function config_matches(pattern, build_id) + local ptype = type(pattern) + if ptype == "nil" then + return true + elseif ptype == "string" then + local fpattern = get_cached_pattern(pattern) + return build_id:match(fpattern) + elseif ptype == "table" then + for _, pattern_item in ipairs(pattern) do + if config_matches(pattern_item, build_id) then + return true + end + end + return false + else + error("bad 'Config' pattern type: " .. ptype) + end +end + +local function make_unit_env(unit) + -- Select an environment for this unit based on its SubConfig tag + -- to support cross compilation. + local env + local subconfig = unit.Decl.SubConfig or current.default_subconfig + if subconfig and current.base_envs then + env = current.base_envs[subconfig] + if Options.VeryVerbose then + if env then + printf("%s: using subconfig %s (%s)", unit.Decl.Name, subconfig, env:get('BUILD_ID')) + else + if current.default_subconfig then + errorf("%s: couldn't find a subconfig env", unit.Decl.Name) + else + printf("%s: no subconfig %s found; using default env", unit.Decl.Name, subconfig) + end + end + end + end + + if not env then + env = current.default_env + end + + return env:clone() +end + +local anon_count = 1 +function _nodegen:get_dag(parent_env) + local build_id = parent_env:get('BUILD_ID') + local dag = self.DagCache[build_id] + + if not dag then + if build_id:len() > 0 and not config_matches(self.Decl.Config, build_id) then + -- Unit has been filtered out via Config attribute. + -- Create a fresh dummy node for it. + local name + if not self.Decl.Name then + name = string.format("Dummy node %d", anon_count) + else + name = string.format("Dummy node %d for %s", anon_count, self.Decl.Name) + end + anon_count = anon_count + 1 + + dag = depgraph.make_node { + Env = parent_env, + Pass = resolve_pass(self.Decl.Pass), + Label = name, + } + else + local unit_env = make_unit_env(self) + + if self.Decl.Name then + unit_env:set('UNIT_PREFIX', '__' .. self.Decl.Name) + end + + -- Before accessing the unit's dependencies, resolve them via filtering. + local deps = resolve_dependencies(self.Decl, self.Decl.Depends, unit_env) + + self:configure_env(unit_env, deps) + self:customize_env(unit_env, self.Decl, deps) + + local input_data, input_deps = self:create_input_data(unit_env, parent_env) + -- Copy over dependencies which have been pre-resolved + input_data.Depends = deps + + for _, dep in util.nil_ipairs(deps) do + input_deps[#input_deps + 1] = dep:get_dag(parent_env) + end + + dag = self:create_dag(unit_env, input_data, input_deps, parent_env) + + if not dag then + error("create_dag didn't generate a result node") + end + end + self.DagCache[build_id] = dag + end + + return dag +end + +local _generator = { + Evaluators = {}, +} +_generator.__index = _generator + +local function new_generator(s) + s = s or {} + s.units = {} + return setmetatable(s, _generator) +end + +local function create_unit_map(state, raw_nodes) + -- Build name=>decl mapping + for _, unit in ipairs(raw_nodes) do + assert(unit.Decl) + local name = unit.Decl.Name + if name and type(name) == "string" then + if state.units[name] then + errorf("duplicate unit name: %s", name) + end + state.units[name] = unit + end + end +end + +function _generate_dag(args) + local envs = assert(args.Envs) + local raw_nodes = assert(args.Declarations) + + local state = new_generator { + base_envs = envs, + root_env = envs["__default"], -- the outmost config's env in a cross-compilation scenario + config = assert(args.Config), + variant = assert(args.Variant), + passes = assert(args.Passes), + } + + current = state + + create_unit_map(state, raw_nodes) + + local subconfigs = state.config.SubConfigs + + -- Pick a default environment which is used for + -- 1. Nodes without a SubConfig declaration + -- 2. Nodes with a missing SubConfig declaration + -- 3. All nodes if there are no SubConfigs set for the current config + if subconfigs then + state.default_subconfig = assert(state.config.DefaultSubConfig) + state.default_env = assert(envs[state.default_subconfig], "unknown DefaultSubConfig specified") + else + state.default_env = assert(envs["__default"]) + end + + + local always_lut = util.make_lookup_table(args.AlwaysNodes) + local default_lut = util.make_lookup_table(args.DefaultNodes) + + local always_nodes = util.map(args.AlwaysNodes, find_named_node) + local default_nodes = util.map(args.DefaultNodes, find_named_node) + + local named_nodes = {} + for name, _ in pairs(state.units) do + named_nodes[name] = find_named_node(name) + end + + current = nil + + return { always_nodes, default_nodes, named_nodes } +end + +function generate_dag(args) + local success, result = xpcall(function () return _generate_dag(args) end, buildfile.syntax_error_catcher) + + if success then + return result[1], result[2], result[3] + else + croak("%s", result) + end + +end + +function resolve_pass(name) + assert(current) + if name then + local p = current.passes[name] + if not p then + syntax_error("%q is not a valid pass name", name) + end + return p + else + return nil + end +end + +function get_target(data, suffix, prefix) + local target = data.Target + if not target then + assert(data.Name) + target = "$(OBJECTDIR)/" .. (prefix or "") .. data.Name .. (suffix or "") + end + return target +end + +function get_evaluator(name) + return _generator.Evaluators[name] +end + +function is_evaluator(name) + if _generator.Evaluators[name] then return true else return false end +end + +local common_blueprint = { + Propagate = { + Help = "Declarations to propagate to dependent units", + Type = "filter_table", + }, + Depends = { + Help = "Dependencies for this node", + Type = "table", -- handled specially + }, + Env = { + Help = "Data to append to the environment for the unit", + Type = "filter_table", + }, + ReplaceEnv = { + Help = "Data to replace in the environment for the unit", + Type = "filter_table", + }, + Pass = { + Help = "Specify build pass", + Type = "pass", + }, + SourceDir = { + Help = "Specify base directory for source files", + Type = "string", + }, + Config = { + Help = "Specify configuration this unit will build in", + Type = "config", + }, + SubConfig = { + Help = "Specify sub-configuration this unit will build in", + Type = "config", + }, + __DagNodes = { + Help = "Internal node to keep track of DAG nodes generated so far", + Type = "table", + } +} + +function create_eval_subclass(meta_tbl, base) + base = base or _nodegen + setmetatable(meta_tbl, base) + meta_tbl.__index = meta_tbl + return meta_tbl +end + +function add_evaluator(name, meta_tbl, blueprint) + assert(type(name) == "string") + assert(type(meta_tbl) == "table") + assert(type(blueprint) == "table") + + -- Set up this metatable as a subclass of _nodegen unless it is already + -- configured. + if not getmetatable(meta_tbl) then + setmetatable(meta_tbl, _nodegen) + meta_tbl.__index = meta_tbl + end + + -- Install common blueprint items. + for name, val in pairs(common_blueprint) do + if not blueprint[name] then + blueprint[name] = val + end + end + + -- Expand environment shortcuts into options. + for decl_key, env_key in util.nil_pairs(meta_tbl.DeclToEnvMappings) do + blueprint[decl_key] = { + Type = "filter_table", + Help = "Shortcut for environment key " .. env_key, + } + end + + for name, val in pairs(blueprint) do + local type_ = assert(val.Type) + if not validators[type_] then + errorf("unsupported blueprint type %q", type_) + end + + if val.Type == "source_list" and not val.ExtensionKey then + errorf("%s: source_list must provide ExtensionKey", name) + end + end + + -- Record blueprint for use when validating user constructs. + meta_tbl.Keyword = name + meta_tbl.Blueprint = blueprint + + -- Store this evaluator under the keyword that will trigger it. + _generator.Evaluators[name] = meta_tbl +end + +-- Called when processing build scripts, keywords is something previously +-- registered as an evaluator here. +function evaluate(eval_keyword, data) + local meta_tbl = assert(_generator.Evaluators[eval_keyword]) + + -- Give the evaluator change to fix up the data before we validate it. + data = meta_tbl:preprocess_data(data) + + local object = setmetatable({ + DagCache = {}, -- maps BUILD_ID -> dag node + Decl = data + }, meta_tbl) + + -- Expose the dag cache to the raw input data so the IDE generator can find it later + data.__DagNodes = object.DagCache + object.__index = object + + -- Validate data according to Blueprint settings + object:validate() + return object +end + +-- Given a list of strings or nested lists, flatten the structure to a single +-- list of strings while applying configuration filters. Configuration filters +-- match against the current build identifier like this: +-- +-- { "a", "b", { "nixfile1", "nixfile2"; Config = "unix-*-*" }, "bar", { "debugfile"; Config = "*-*-debug" }, } +-- +-- If 'exclusive' is set, then: +-- If 'build_id' is set, only values _with_ a 'Config' filter are included. +-- If 'build_id' is nil, only values _without_ a 'Config' filter are included. +function flatten_list(build_id, list, exclusive) + if not list then return nil end + local filter_defined = build_id ~= nil + + -- Helper function to apply filtering recursively and append results to an + -- accumulator table. + local function iter(node, accum, filtered) + local node_type = type(node) + if node_type == "table" and not getmetatable(node) then + if node.Config then filtered = true end + if not filter_defined or config_matches(node.Config, build_id) then + for _, item in ipairs(node) do + iter(item, accum, filtered) + end + end + elseif not exclusive or (filtered == filter_defined) then + accum[#accum + 1] = node + end + end + + local results = {} + iter(list, results, false) + return results +end + +-- Conceptually similar to flatten_list(), but retains table structure. +-- Use to keep source tables as they are passed in, to retain nested SourceDir attributes. +local empty_leaf = {} -- constant +function filter_structure(build_id, data, exclusive) + if type(data) == "table" then + if getmetatable(data) then + return data -- it's already a DAG node; use as-is + end + + local filtered = data.Config and true or false + + if not data.Config or config_matches(data.Config, build_id) then + local result = {} + for k, item in pairs(data) do + if type(k) == "number" then + -- Filter array elements. + result[#result + 1] = filter_structure(build_id, item, filtered) + elseif k ~= "Config" then + -- Copy key-value data through. + result[k] = item + end + end + return result + else + return empty_leaf + end + else + return data + end +end + +-- Processes an "Env" table. For each value, the corresponding variable in +-- 'env' is appended to if its "Config" filter matches 'build_id'. If +-- 'build_id' is nil, filtered values are skipped. +function append_filtered_env_vars(env, values_to_append, build_id, exclusive) + for key, val in util.pairs(values_to_append) do + if type(val) == "table" then + local list = flatten_list(build_id, val, exclusive) + for _, subvalue in ipairs(list) do + env:append(key, subvalue) + end + elseif not (exclusive and build_id) then + env:append(key, val) + end + end +end + +-- Like append_filtered_env_vars(), but replaces existing variables instead +-- of appending to them. +function replace_filtered_env_vars(env, values_to_replace, build_id, exclusive) + for key, val in util.pairs(values_to_replace) do + if type(val) == "table" then + local list = flatten_list(build_id, val, exclusive) + if #list > 0 then + env:replace(key, list) + end + elseif not (exclusive and build_id) then + env:replace(key, val) + end + end +end + +function generate_ide_files(config_tuples, default_names, raw_nodes, env, hints, ide_script) + local state = new_generator { default_env = env } + assert(state.default_env) + create_unit_map(state, raw_nodes) + local backend_fn = assert(ide_backend) + backend_fn(state, config_tuples, raw_nodes, env, default_names, hints, ide_script) +end + +function set_ide_backend(backend_fn) + ide_backend = backend_fn +end + +-- Expose the DefRule helper which is used to register builder syntax in a +-- simplified way. + +function _G.DefRule(ruledef) + local name = assert(ruledef.Name, "Missing Name string in DefRule") + local setup_fn = assert(ruledef.Setup, "Missing Setup function in DefRule " .. name) + local cmd = assert(ruledef.Command, "Missing Command string in DefRule " .. name) + local blueprint = assert(ruledef.Blueprint, "Missing Blueprint in DefRule " .. name) + local mt = create_eval_subclass {} + local annot = ruledef.Annotation + + if not annot then + annot = name .. " $(<)" + end + + local preproc = ruledef.Preprocess + + local function verify_table(v, tag) + if not v then + errorf("No %s returned from DefRule %s", tag, name) + end + + if type(v) ~= "table" then + errorf("%s returned from DefRule %s is not a table", tag, name) + end + end + + local function make_node(input_files, output_files, env, data, deps, scanner) + return depgraph.make_node { + Env = env, + Label = annot, + Action = cmd, + Pass = data.Pass or resolve_pass(ruledef.Pass), + InputFiles = input_files, + OutputFiles = output_files, + ImplicitInputs = ruledef.ImplicitInputs, + Scanner = scanner, + Dependencies = deps, + } + end + + if ruledef.ConfigInvariant then + local cache = {} + + function mt:create_dag(env, data, deps) + local setup_data = setup_fn(env, data) + local input_files = setup_data.InputFiles + local output_files = setup_data.OutputFiles + verify_table(input_files, "InputFiles") + verify_table(output_files, "OutputFiles") + + local mashup = { } + for _, input in util.nil_ipairs(input_files) do + mashup[#mashup + 1] = input + end + mashup[#mashup + 1] = "@@" + for _, output in util.nil_ipairs(output_files) do + mashup[#mashup + 1] = output + end + mashup[#mashup + 1] = "@@" + for _, implicit_input in util.nil_ipairs(setup_data.ImplicitInputs) do + mashup[#mashup + 1] = implicit_input + end + local key = native.digest_guid(table.concat(mashup, ';')) + + local key = util.tostring(key) + if cache[key] then + return cache[key] + else + local node = make_node(input_files, output_files, env, data, deps, setup_data.Scanner) + cache[key] = node + return node + end + end + else + function mt:create_dag(env, data, deps) + local setup_data = setup_fn(env, data) + verify_table(setup_data.InputFiles, "InputFiles") + verify_table(setup_data.OutputFiles, "OutputFiles") + return make_node(setup_data.InputFiles, setup_data.OutputFiles, env, data, deps, setup_data.Scanner) + end + end + + if preproc then + function mt:preprocess_data(raw_data) + return preproc(raw_data) + end + end + + add_evaluator(name, mt, blueprint) +end + +function _nodegen:preprocess_data(data) + return data +end diff --git a/ogl_editor/bin/win32/scripts/tundra/path.lua b/ogl_editor/bin/win32/scripts/tundra/path.lua new file mode 100644 index 0000000..f5d7068 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/path.lua @@ -0,0 +1,50 @@ +module(..., package.seeall) + +local npath = require "tundra.native.path" + +split = npath.split +normalize = npath.normalize +join = npath.join +get_filename_dir = npath.get_filename_dir +get_filename = npath.get_filename +get_extension = npath.get_extension +drop_suffix = npath.drop_suffix +get_filename_base = npath.get_filename_base +is_absolute = npath.is_absolute + +function remove_prefix(prefix, fn) + if fn:find(prefix, 1, true) == 1 then + return fn:sub(#prefix + 1) + else + return fn + end +end + +function make_object_filename(env, src_fn, suffix) + local object_fn + + local src_suffix = get_extension(src_fn):sub(2) + + -- Drop leading $(OBJECTDIR)[/\\] in the input filename. + do + local pname = src_fn:match("^%$%(OBJECTDIR%)[/\\](.*)$") + if pname then + object_fn = pname + else + object_fn = src_fn + end + end + + -- Compute path under OBJECTDIR we want for the resulting object file. + -- Replace ".." with "dotdot" to avoid creating files outside the + -- object directory. Also salt the generated object name with the source + -- suffix, so that multiple source files with the same base name don't end + -- up clobbering each other (Tundra emits an error for this when checking + -- the DAG) + do + local relative_name = drop_suffix(object_fn:gsub("%.%.", "dotdot")) + object_fn = "$(OBJECTDIR)/$(UNIT_PREFIX)/" .. relative_name .. "__" .. src_suffix .. suffix + end + + return object_fn +end diff --git a/ogl_editor/bin/win32/scripts/tundra/platform.lua b/ogl_editor/bin/win32/scripts/tundra/platform.lua new file mode 100644 index 0000000..402b79d --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/platform.lua @@ -0,0 +1,7 @@ +module(..., package.seeall) + +local native = require "tundra.native" + +function host_platform() + return native.host_platform +end diff --git a/ogl_editor/bin/win32/scripts/tundra/scanner.lua b/ogl_editor/bin/win32/scripts/tundra/scanner.lua new file mode 100644 index 0000000..8750627 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/scanner.lua @@ -0,0 +1,57 @@ +module(..., package.seeall) + +local util = require "tundra.util" +local native = require "tundra.native" + +local _scanner_mt = {} +setmetatable(_scanner_mt, { __index = _scanner_mt }) + +local cpp_scanner_cache = {} +local generic_scanner_cache = {} + +function make_cpp_scanner(paths) + local key = table.concat(paths, '\0') + + if not cpp_scanner_cache[key] then + local data = { Kind = 'cpp', Paths = paths, Index = #cpp_scanner_cache } + cpp_scanner_cache[key] = setmetatable(data, _scanner_mt) + end + + return cpp_scanner_cache[key] +end + +function make_generic_scanner(data) + data.Kind = 'generic' + local mashup = { } + local function add_all(l) + for _, value in util.nil_ipairs(l) do + mashup[#mashup + 1] = value + end + end + add_all(data.Paths) + add_all(data.Keywords) + add_all(data.KeywordsNoFollow) + mashup[#mashup + 1] = '!!' + mashup[#mashup + 1] = data.RequireWhitespace and 'y' or 'n' + mashup[#mashup + 1] = data.UseSeparators and 'y' or 'n' + mashup[#mashup + 1] = data.BareMeansSystem and 'y' or 'n' + local key_str = table.concat(mashup, '\001') + local key = native.digest_guid(key_str) + local value = generic_scanner_cache[key] + if not value then + value = data + generic_scanner_cache[key] = data + end + return value +end + +function all_scanners() + local scanners = {} + for k, v in pairs(cpp_scanner_cache) do + scanners[v.Index + 1] = v + end + for k, v in pairs(generic_scanner_cache) do + scanners[v.Index + 1] = v + end + return scanners +end diff --git a/ogl_editor/bin/win32/scripts/tundra/selftest.lua b/ogl_editor/bin/win32/scripts/tundra/selftest.lua new file mode 100644 index 0000000..f506f92 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/selftest.lua @@ -0,0 +1,37 @@ +module(..., package.seeall) + +local error_count = 0 + +function _G.unit_test(label, fn) + local t_mt = { + check_equal = function (obj, a, b) + if a ~= b then + error { Message = "Equality test failed: " .. tostring(a) .. " != " .. tostring(b) } + end + end + } + t_mt.__index = t_mt + + local t = setmetatable({}, t_mt) + local function stack_dumper(err_obj) + if type(err_obj) == "table" then + return err_obj.Message + end + local debug = require 'debug' + return debug.traceback(err_obj, 2) + end + + io.stdout:write("Testing ", label, ": ") + io.stdout:flush() + local ok, err = xpcall(function () fn(t) end, stack_dumper) + if not ok then + io.stdout:write("failed\n") + io.stdout:write(tostring(err), "\n") + error_count = error_count + 1 + else + io.stderr:write("OK\n") + end +end + +require "tundra.test.t_env" +require "tundra.test.t_path" diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/alias.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/alias.lua new file mode 100644 index 0000000..361c07a --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/alias.lua @@ -0,0 +1,40 @@ +-- alias.lua -- support for named aliases in the DAG + +module(..., package.seeall) + +local nodegen = require "tundra.nodegen" +local depgraph = require "tundra.depgraph" +local util = require "tundra.util" + +local _alias_mt = nodegen.create_eval_subclass {} + +function _alias_mt:create_dag(env, data, input_deps) + + local deps = util.clone_table(input_deps) + + for _, dep in util.nil_ipairs(data.Depends) do + deps[#deps+1] = dep:get_dag(env:get_parent()) + end + + local dag = depgraph.make_node { + Env = env, + Label = "Named alias " .. data.Name .. " for " .. env:get('BUILD_ID'), + Pass = data.Pass, + Dependencies = deps, + } + + -- Remember this dag node for IDE file generation purposes + data.__DagNode = dag + + return dag +end + +local alias_blueprint = { + Name = { + Required = true, + Help = "Set alias name", + Type = "string", + }, +} + +nodegen.add_evaluator("Alias", _alias_mt, alias_blueprint) diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/bison.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/bison.lua new file mode 100644 index 0000000..a1bcd3c --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/bison.lua @@ -0,0 +1,42 @@ +module(..., package.seeall) + +local nodegen = require "tundra.nodegen" +local path = require "tundra.path" +local depgraph = require "tundra.depgraph" + +local _bison_mt = nodegen.create_eval_subclass {} + +local bison_blueprint = { + Source = { Required = true, Type = "string" }, + OutputFile = { Required = false, Type = "string" }, + TokenDefines = { Required = false, Type = "boolean" }, +} + +function _bison_mt:create_dag(env, data, deps) + local src = data.Source + local out_src + if data.OutputFile then + out_src = "$(OBJECTDIR)$(SEP)" .. data.OutputFile + else + local targetbase = "$(OBJECTDIR)$(SEP)bisongen_" .. path.get_filename_base(src) + out_src = targetbase .. ".c" + end + local defopt = "" + local outputs = { out_src } + if data.TokenDefines then + local out_hdr = path.drop_suffix(out_src) .. ".h" + defopt = "--defines=" .. out_hdr + outputs[#outputs + 1] = out_hdr + end + return depgraph.make_node { + Env = env, + Pass = data.Pass, + Label = "Bison $(@)", + Action = "$(BISON) $(BISONOPT) " .. defopt .. " --output-file=$(@:[1]) $(<)", + InputFiles = { src }, + OutputFiles = outputs, + Dependencies = deps, + } +end + +nodegen.add_evaluator("Bison", _bison_mt, bison_blueprint) diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/dotnet.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/dotnet.lua new file mode 100644 index 0000000..eef4722 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/dotnet.lua @@ -0,0 +1,117 @@ +module(..., package.seeall) + +local util = require "tundra.util" +local nodegen = require "tundra.nodegen" +local depgraph = require "tundra.depgraph" + +local _csbase_mt = nodegen.create_eval_subclass { + DeclToEnvMappings = { + References = "CSLIBS", + RefPaths = "CSLIBPATH", + }, +} + +local _csexe_mt = nodegen.create_eval_subclass({ + Label = "CSharpExe $(@)", + Suffix = "$(CSPROGSUFFIX)", + Action = "$(CSCEXECOM)" +}, _csbase_mt) + +local _cslib_mt = nodegen.create_eval_subclass({ + Label = "CSharpLib $(@)", + Suffix = "$(CSLIBSUFFIX)", + Action = "$(CSCLIBCOM)" +}, _csbase_mt) + + +local csSourceExts = { ".cs" } +local csResXExts = { ".resx" } + +local function setup_refs_from_dependencies(env, dep_nodes, deps) + local dll_exts = { env:interpolate("$(CSLIBSUFFIX)") } + local refs = {} + local parent_env = env:get_parent() + for _, x in util.nil_ipairs(dep_nodes) do + if x.Keyword == "CSharpLib" then + local outputs = {} + local dag = x:get_dag(parent_env) + deps[#deps + 1] = dag + dag:insert_output_files(refs, dll_exts) + end + end + for _, r in ipairs(refs) do + env:append("CSLIBS", r) + end +end + +local function setup_resources(generator, env, assembly_name, resx_files, pass) + local result_files = {} + local deps = {} + local i = 1 + for _, resx in util.nil_ipairs(resx_files) do + local basename = path.get_filename_base(resx) + local result_file = string.format("$(OBJECTDIR)/_rescompile/%s.%s.resources", assembly_name, basename) + result_files[i] = result_file + deps[i] = depgraph.make_node { + Env = env, + Pass = pass, + Label = "resgen $(@)", + Action = "$(CSRESGEN)", + InputFiles = { resx }, + OutputFiles = { result_file }, + } + env:append("CSRESOURCES", result_file) + i = i + 1 + end + return result_files, deps +end + +function _csbase_mt:create_dag(env, data, deps) + local sources = data.Sources + local resources = data.Resources or {} + for _, r in util.nil_ipairs(resources) do + env:append("CSRESOURCES", r) + end + + sources = util.merge_arrays_2(sources, resources) + + setup_refs_from_dependencies(env, data.Depends, deps) + + return depgraph.make_node { + Env = env, + Pass = data.Pass, + Label = self.Label, + Action = self.Action, + InputFiles = sources, + OutputFiles = { nodegen.get_target(data, self.Suffix, self.Prefix) }, + Dependencies = util.uniq(deps), + } +end + +do + local csblueprint = { + Name = { + Required = true, + Help = "Set output (base) filename", + Type = "string", + }, + Sources = { + Required = true, + Help = "List of source files", + Type = "source_list", + ExtensionKey = "DOTNET_SUFFIXES", + }, + Resources = { + Help = "List of resource files", + Type = "source_list", + ExtensionKey = "DOTNET_SUFFIXES_RESOURCE", + }, + Target = { + Help = "Override target location", + Type = "string", + }, + } + + nodegen.add_evaluator("CSharpExe", _csexe_mt, csblueprint) + nodegen.add_evaluator("CSharpLib", _cslib_mt, csblueprint) +end diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/embed_lua.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/embed_lua.lua new file mode 100644 index 0000000..9bc49c6 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/embed_lua.lua @@ -0,0 +1,65 @@ +module(..., package.seeall) + +local util = require "tundra.util" +local path = require "tundra.path" +local glob = require "tundra.syntax.glob" +local nodegen = require "tundra.nodegen" +local depgraph = require "tundra.depgraph" + +local lua_exts = { ".lua" } +local luac_mt_ = nodegen.create_eval_subclass {} + +local function luac(env, src, pass) + local target = "$(OBJECTDIR)/" .. path.drop_suffix(src) .. ".luac" + return target, depgraph.make_node { + Env = env, + Pass = pass, + Label = "LuaC $(@)", + Action = "$(LUAC) -o $(@) -- $(<)", + InputFiles = { src }, + OutputFiles = { target }, + ImplicitInputs = { "$(LUAC)" }, + } +end + +function luac_mt_:create_dag(env, data, deps) + local files = {} + local deps = {} + local inputs = {} + local action_fragments = {} + + for _, base_dir in ipairs(data.Dirs) do + local lua_files = glob.Glob { Dir = base_dir, Extensions = lua_exts } + local dir_len = base_dir:len() + for _, filename in pairs(lua_files) do + local rel_name = filename:sub(dir_len+2) + local pkg_name = rel_name:gsub("[/\\]", "."):gsub("%.lua$", "") + inputs[#inputs + 1] = filename + if env:get("LUA_EMBED_ASCII", "no") == "no" then + files[#files + 1], deps[#deps + 1] = luac(env, filename, data.Pass) + else + files[#files + 1] = filename + end + action_fragments[#action_fragments + 1] = pkg_name + action_fragments[#action_fragments + 1] = files[#files] + end + end + + return depgraph.make_node { + Env = env, + Label = "EmbedLuaSources $(@)", + Pass = data.Pass, + Action = "$(GEN_LUA_DATA) " .. table.concat(action_fragments, " ") .. " > $(@)", + InputFiles = inputs, + OutputFiles = { "$(OBJECTDIR)/" .. data.OutputFile }, + Dependencies = deps, + ImplicitInputs = { "$(GEN_LUA_DATA)" }, + } +end + +local blueprint = { + Dirs = { Type = "table", Required = "true" }, + OutputFile = { Type = "string", Required = "true" }, +} + +nodegen.add_evaluator("EmbedLuaSources", luac_mt_, blueprint) diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/files.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/files.lua new file mode 100644 index 0000000..05cdb40 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/files.lua @@ -0,0 +1,50 @@ +module(..., package.seeall) + +local decl = require "tundra.decl" +local depgraph = require "tundra.depgraph" + +local common_blueprint = { + Source = { + Required = true, + Help = "Source filename", + Type = "string", + }, + Target = { + Required = true, + Help = "Target filename", + Type = "string", + }, +} + +local function def_copy_rule(name, command, cfg_invariant) + DefRule { + Name = name, + ConfigInvariant = cfg_invariant, + Blueprint = common_blueprint, + Command = command, + Setup = function (env, data) + return { + InputFiles = { data.Source }, + OutputFiles = { data.Target }, + } + end, + } +end + +def_copy_rule('CopyFile', '$(_COPY_FILE)') +def_copy_rule('CopyFileInvariant', '$(_COPY_FILE)', true) +def_copy_rule('HardLinkFile', '$(_HARDLINK_FILE)') +def_copy_rule('HardLinkFileInvariant', '$(_HARDLINK_FILE)', true) + + +function hardlink_file(env, src, dst, pass, deps) + return depgraph.make_node { + Env = env, + Annotation = "HardLink $(<)", + Action = "$(_HARDLINK_FILE)", + InputFiles = { src }, + OutputFiles = { dst }, + Dependencies = deps, + Pass = pass, + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/flex.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/flex.lua new file mode 100644 index 0000000..6e796a9 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/flex.lua @@ -0,0 +1,28 @@ +-- flex.lua - Support for FLEX + +module(..., package.seeall) + +local path = require "tundra.path" + +DefRule { + Name = "Flex", + Command = "flex --outfile=$(@:[1]) --header-file=$(@:[2]) $(<)", + ConfigInvariant = true, + + Blueprint = { + Source = { Required = true, Type = "string" }, + OutputCFile = { Required = false, Type = "string" }, + OutputHeaderFile = { Required = false, Type = "string" }, + }, + + Setup = function (env, data) + local src = data.Source + local base_name = path.drop_suffix(src) + local gen_c = data.OutputCFile or (base_name .. '.c') + local gen_h = data.OutputHeaderFileFile or (base_name .. '.h') + return { + InputFiles = { src }, + OutputFiles = { gen_c, gen_h }, + } + end, +} diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/glob.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/glob.lua new file mode 100644 index 0000000..02a623f --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/glob.lua @@ -0,0 +1,122 @@ +-- glob.lua - Glob syntax elements for declarative tundra.lua usage + +module(..., package.seeall) + +local util = require "tundra.util" +local path = require "tundra.path" +local decl = require "tundra.decl" +local dirwalk = require "tundra.dirwalk" + +local ignored_dirs = util.make_lookup_table { ".git", ".svn", "CVS" } + +local function glob(directory, recursive, filter_fn) + local result = {} + + local function dir_filter(dir_name) + if not recursive or ignored_dirs[dir_name] then + return false + end + return true + end + + for _, path in ipairs(dirwalk.walk(directory, dir_filter)) do + if filter_fn(path) then + result[#result + 1] = path + end + end + return result +end + +-- Glob syntax - Search for source files matching extension list +-- +-- Synopsis: +-- Glob { +-- Dir = "...", +-- Extensions = { ".ext", ... }, +-- [Recursive = false,] +-- } +-- +-- Options: +-- Dir = "directory" (required) +-- - Base directory to search in +-- +-- Extensions = { ".ext1", ".ext2" } (required) +-- - List of file extensions to include +-- +-- Recursive = boolean (optional, default: true) +-- - Specified whether to recurse into subdirectories +function Glob(args) + local recursive = args.Recursive + if type(recursive) == "nil" then + recursive = true + end + if not args.Extensions then + croak("no 'Extensions' specified in Glob (Dir is '%s')", args.Dir) + end + local extensions = assert(args.Extensions) + local ext_lookup = util.make_lookup_table(extensions) + return glob(args.Dir, recursive, function (fn) + local ext = path.get_extension(fn) + return ext_lookup[ext] + end) +end + +-- FGlob syntax - Search for source files matching extension list with +-- configuration filtering +-- +-- Usage: +-- FGlob { +-- Dir = "...", +-- Extensions = { ".ext", .... }, +-- Filters = { +-- { Pattern = "/[Ww]in32/", Config = "win32-*-*" }, +-- { Pattern = "/[Dd]ebug/", Config = "*-*-debug" }, +-- ... +-- }, +-- [Recursive = false], +-- } +local function FGlob(args) + -- Use the regular glob to fetch the file list. + local files = Glob(args) + local pats = {} + local result = {} + + -- Construct a mapping from { Pattern = ..., Config = ... } + -- to { Pattern = { Config = ... } } with new arrays per config that can be + -- embedded in the source result. + for _, fitem in ipairs(args.Filters) do + if not fitem.Config then + croak("no 'Config' specified in FGlob (Pattern is '%s')", fitem.Pattern) + end + local tab = { Config = assert(fitem.Config) } + pats[assert(fitem.Pattern)] = tab + result[#result + 1] = tab + end + + -- Traverse all files and see if they match any configuration filters. If + -- they do, stick them in matching list. Otherwise, just keep them in the + -- main list. This has the effect of returning an array such as this: + -- { + -- { "foo.c"; Config = "abc-*-*" }, + -- { "bar.c"; Config = "*-*-def" }, + -- "baz.c", "qux.m" + -- } + for _, f in ipairs(files) do + local filtered = false + for filter, list in pairs(pats) do + if f:match(filter) then + filtered = true + list[#list + 1] = f + break + end + end + if not filtered then + result[#result + 1] = f + end + end + return result +end + +decl.add_function("Glob", Glob) +decl.add_function("FGlob", FGlob) + diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/install.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/install.lua new file mode 100644 index 0000000..c8dedcd --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/install.lua @@ -0,0 +1,40 @@ +-- install.lua - Express file copying in unit form. + +module(..., package.seeall) + +local nodegen = require "tundra.nodegen" +local files = require "tundra.syntax.files" +local path = require "tundra.path" +local util = require "tundra.util" +local depgraph = require "tundra.depgraph" + +local _mt = nodegen.create_eval_subclass {} + +local blueprint = { + Sources = { Type = "source_list", Required = true }, + TargetDir = { Type = "string", Required = true }, +} + +function _mt:create_dag(env, data, deps) + local my_pass = data.Pass + local sources = data.Sources + local target_dir = data.TargetDir + + local copies = {} + + -- all the copy operations will depend on all the incoming deps + for _, src in util.nil_ipairs(sources) do + local base_fn = select(2, path.split(src)) + local target = target_dir .. '/' .. base_fn + copies[#copies + 1] = files.copy_file(env, src, target, my_pass, deps) + end + + return depgraph.make_node { + Env = env, + Label = "Install group for " .. decl.Name, + Pass = my_pass, + Dependencies = copies + } +end + +nodegen.add_evaluator("Install", _mt, blueprint) diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/ispc.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/ispc.lua new file mode 100644 index 0000000..5c212b8 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/ispc.lua @@ -0,0 +1,25 @@ +-- ispc.lua - Support for Intel SPMD Program Compiler + +module(..., package.seeall) + +local path = require "tundra.path" + +DefRule { + Name = "ISPC", + Command = "$(ISPCCOM)", + + Blueprint = { + Source = { Required = true, Type = "string" }, + }, + + Setup = function (env, data) + local src = data.Source + local base_name = path.drop_suffix(src) + local objFile = "$(OBJECTDIR)$(SEP)" .. base_name .. "__" .. path.get_extension(src):sub(2) .. "$(OBJECTSUFFIX)" + local hFile = "$(OBJECTDIR)$(SEP)" .. base_name .. "_ispc.h" + return { + InputFiles = { src }, + OutputFiles = { objFile, hFile }, + } + end, +} diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/lemon.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/lemon.lua new file mode 100644 index 0000000..4cf173f --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/lemon.lua @@ -0,0 +1,27 @@ +-- lemon.lua - Support for the Lemon parser generator + +module(..., package.seeall) + +local path = require "tundra.path" + +DefRule { + Name = "Lemon", + Command = "lemon $(<)", + ConfigInvariant = true, + + Blueprint = { + Source = { Required = true, Type = "string" }, + }, + + Setup = function (env, data) + local src = data.Source + local base_name = path.drop_suffix(src) + local gen_c = base_name .. '.c' + local gen_h = base_name .. '.h' + local gen_out = base_name .. '.out' + return { + InputFiles = { src }, + OutputFiles = { gen_c, gen_h, gen_out }, + } + end, +} diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/native.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/native.lua new file mode 100644 index 0000000..e57c65c --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/native.lua @@ -0,0 +1,312 @@ +-- native.lua -- support for programs, static libraries and such + +module(..., package.seeall) + +local util = require "tundra.util" +local nodegen = require "tundra.nodegen" +local path = require "tundra.path" +local depgraph = require "tundra.depgraph" + +local _native_mt = nodegen.create_eval_subclass { + DeclToEnvMappings = { + Libs = "LIBS", + Defines = "CPPDEFS", + Includes = "CPPPATH", + Frameworks = "FRAMEWORKS", + LibPaths = "LIBPATH", + }, +} + +local _object_mt = nodegen.create_eval_subclass({ + Suffix = "$(OBJECTSUFFIX)", + Prefix = "", + Action = "$(OBJCOM)", + Label = "Object $(<)", + OverwriteOutputs = true, +}, _native_mt) + +local _program_mt = nodegen.create_eval_subclass({ + Suffix = "$(PROGSUFFIX)", + Prefix = "$(PROGPREFIX)", + Action = "$(PROGCOM)", + Label = "Program $(@)", + PreciousOutputs = true, + OverwriteOutputs = true, + Expensive = true, +}, _native_mt) + +local _staticlib_mt = nodegen.create_eval_subclass({ + Suffix = "$(LIBSUFFIX)", + Prefix = "$(LIBPREFIX)", + Action = "$(LIBCOM)", + Label = "StaticLibrary $(@)", + -- Play it safe and delete the output files of this node before re-running it. + -- Solves iterative issues with e.g. AR + OverwriteOutputs = false, + IsStaticLib = true, +}, _native_mt) + +local _objgroup_mt = nodegen.create_eval_subclass({ + Label = "ObjGroup $(<)", +}, _native_mt) + +local _shlib_mt = nodegen.create_eval_subclass({ + Suffix = "$(SHLIBSUFFIX)", + Prefix = "$(SHLIBPREFIX)", + Action = "$(SHLIBCOM)", + Label = "SharedLibrary $(@)", + PreciousOutputs = true, + OverwriteOutputs = true, + Expensive = true, +}, _native_mt) + +local _extlib_mt = nodegen.create_eval_subclass({ + Suffix = "", + Prefix = "", + Label = "", +}, _native_mt) + +local cpp_exts = util.make_lookup_table { ".cpp", ".cc", ".cxx", ".C" } + +local _is_native_mt = util.make_lookup_table { _object_mt, _program_mt, _staticlib_mt, _shlib_mt, _extlib_mt, _objgroup_mt } + +function _native_mt:customize_env(env, raw_data) + if env:get('GENERATE_PDB', '0') ~= '0' then + -- Figure out the final linked PDB (the one next to the dll or exe) + if raw_data.Target then + local target = env:interpolate(raw_data.Target) + local link_pdb = path.drop_suffix(target) .. '.pdb' + env:set('_PDB_LINK_FILE', link_pdb) + else + env:set('_PDB_LINK_FILE', "$(OBJECTDIR)/" .. raw_data.Name .. ".pdb") + end + -- Keep the compiler's idea of the PDB file separate + env:set('_PDB_CC_FILE', "$(OBJECTDIR)/" .. raw_data.Name .. "_ccpdb.pdb") + env:set('_USE_PDB_CC', '$(_USE_PDB_CC_OPT)') + env:set('_USE_PDB_LINK', '$(_USE_PDB_LINK_OPT)') + end + + local pch = raw_data.PrecompiledHeader + + if pch and env:get('_PCH_SUPPORTED', '0') ~= '0' then + assert(pch.Header) + if not nodegen.resolve_pass(pch.Pass) then + croak("%s: PrecompiledHeader requires a valid Pass", raw_data.Name) + end + env:set('_PCH_FILE', "$(OBJECTDIR)/" .. raw_data.Name .. ".pch") + env:set('_USE_PCH', '$(_USE_PCH_OPT)') + env:set('_PCH_SOURCE', path.normalize(pch.Source)) + env:set('_PCH_HEADER', pch.Header) + env:set('_PCH_PASS', pch.Pass) + if cpp_exts[path.get_extension(pch.Source)] then + env:set('PCHCOMPILE', '$(PCHCOMPILE_CXX)') + else + env:set('PCHCOMPILE', '$(PCHCOMPILE_CC)') + end + local pch_source = path.remove_prefix(raw_data.SourceDir or '', pch.Source) + if not util.array_contains(raw_data.Sources, pch_source) then + raw_data.Sources[#raw_data.Sources + 1] = pch_source + end + end + + if env:has_key('MODDEF') then + env:set('_USE_MODDEF', '$(_USE_MODDEF_OPT)') + end +end + +function _native_mt:create_dag(env, data, input_deps) + + local build_id = env:get("BUILD_ID") + local my_pass = data.Pass + local sources = data.Sources + local libsuffix = { env:get("LIBSUFFIX") } + local shlibsuffix = { env:get("SHLIBLINKSUFFIX") } + local my_extra_deps = {} + + -- Link with libraries in dependencies. + for _, dep in util.nil_ipairs(data.Depends) do + + if dep.Keyword == "SharedLibrary" then + -- On Win32 toolsets, we need foo.lib + -- On UNIX toolsets, we need -lfoo + -- + -- We only want to add this if the node actually produced any output (i.e + -- it's not completely filtered out.) + local node = dep:get_dag(env:get_parent()) + if #node.outputs > 0 then + my_extra_deps[#my_extra_deps + 1] = node + local target = dep.Decl.Target or dep.Decl.Name + target = target .. "$(SHLIBLINKSUFFIX)" + env:append('LIBS', target) + end + elseif dep.Keyword == "StaticLibrary" then + local node = dep:get_dag(env:get_parent()) + my_extra_deps[#my_extra_deps + 1] = node + if not self.IsStaticLib then + node:insert_output_files(sources, libsuffix) + end + elseif dep.Keyword == "ObjGroup" then + -- We want all .obj files + local objsuffix = { env:get("OBJECTSUFFIX") } + + -- And also .res files, if we know what that is + if env:has_key("W32RESSUFFIX") then + objsuffix[#objsuffix + 1] = env:get("W32RESSUFFIX") + end + + local node = dep:get_dag(env:get_parent()) + my_extra_deps[#my_extra_deps + 1] = node + if not sources then sources = {} end + for _, dep in util.nil_ipairs(node.deps) do + my_extra_deps[#my_extra_deps + 1] = dep + dep:insert_output_files(sources, objsuffix) + end + else + + --[[ + + A note about win32 import libraries: + + It is tempting to add an implicit input dependency on the import + library of the linked-to shared library here; but this would be + suboptimal: + + 1. Because there is a dependency between the nodes themselves, + the import library generation will always run before this link + step is run. Therefore, the import lib will always exist and be + updated before this link step runs. + + 2. Because the import library is regenerated whenever the DLL is + relinked we would have to custom-sign it (using a hash of the + DLLs export list) to avoid relinking the executable all the + time when only the DLL's internals change. + + 3. The DLL's export list should be available in headers anyway, + which is already covered in the compilation of the object files + that actually uses those APIs. + + Therefore the best way right now is to not tell Tundra about the + import lib at all and rely on header scanning to pick up API + changes. + + An implicit input dependency would be needed however if someone + is doing funky things with their import library (adding + non-linker-generated code for example). These cases are so rare + that we can safely put them off. + + ]]-- + end + end + + -- Make sure sources are not specified more than once. This can happen when + -- there are recursive dependencies on object groups. + if data.Sources and #data.Sources > 0 then + data.Sources = util.uniq(data.Sources) + end + + local aux_outputs = env:get_list("AUX_FILES_" .. self.Keyword:upper(), {}) + + if env:get('GENERATE_PDB', '0') ~= '0' then + aux_outputs[#aux_outputs + 1] = "$(_PDB_LINK_FILE)" + aux_outputs[#aux_outputs + 1] = "$(_PDB_CC_FILE)" + end + + local extra_inputs = {} + + if env:has_key('MODDEF') then + extra_inputs[#extra_inputs + 1] = "$(MODDEF)" + end + + local targets = nil + + if self.Action then + targets = { nodegen.get_target(data, self.Suffix, self.Prefix) } + end + + local deps = util.merge_arrays(input_deps, my_extra_deps) + + local dag = depgraph.make_node { + Env = env, + Label = self.Label, + Pass = data.Pass, + Action = self.Action, + PreAction = data.PreAction, + InputFiles = data.Sources, + InputFilesUntracked = data.UntrackedSources, + OutputFiles = targets, + AuxOutputFiles = aux_outputs, + ImplicitInputs = extra_inputs, + Dependencies = deps, + OverwriteOutputs = self.OverwriteOutputs, + PreciousOutputs = self.PreciousOutputs, + Expensive = self.Expensive, + } + + -- Remember this dag node for IDE file generation purposes + data.__DagNode = dag + + return dag +end + +local native_blueprint = { + Name = { + Required = true, + Help = "Set output (base) filename", + Type = "string", + }, + Sources = { + Required = true, + Help = "List of source files", + Type = "source_list", + ExtensionKey = "NATIVE_SUFFIXES", + }, + UntrackedSources = { + Help = "List of input files that are not tracked", + Type = "source_list", + ExtensionKey = "NATIVE_SUFFIXES", + }, + Target = { + Help = "Override target location", + Type = "string", + }, + PreAction = { + Help = "Optional action to run before main action.", + Type = "string", + }, + PrecompiledHeader = { + Help = "Enable precompiled header (if supported)", + Type = "table", + }, + IdeGenerationHints = { + Help = "Data to support control IDE file generation", + Type = "table", + }, +} + +local external_blueprint = { + Name = { + Required = true, + Help = "Set name of the external library", + Type = "string", + }, +} + +local external_counter = 1 + +function _extlib_mt:create_dag(env, data, input_deps) + local name = string.format("dummy node for %s (%d)", data.Name, external_counter) + external_counter = external_counter + 1 + return depgraph.make_node { + Env = env, + Label = name, + Pass = data.Pass, + Dependencies = input_deps, + } +end + +nodegen.add_evaluator("Object", _object_mt, native_blueprint) +nodegen.add_evaluator("Program", _program_mt, native_blueprint) +nodegen.add_evaluator("StaticLibrary", _staticlib_mt, native_blueprint) +nodegen.add_evaluator("SharedLibrary", _shlib_mt, native_blueprint) +nodegen.add_evaluator("ExternalLibrary", _extlib_mt, external_blueprint) +nodegen.add_evaluator("ObjGroup", _objgroup_mt, native_blueprint) diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/osx-bundle.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/osx-bundle.lua new file mode 100644 index 0000000..db1e052 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/osx-bundle.lua @@ -0,0 +1,93 @@ +-- osx-bundle.lua - Support for Max OS X bundles + +module(..., package.seeall) + +local nodegen = require "tundra.nodegen" +local files = require "tundra.syntax.files" +local path = require "tundra.path" +local util = require "tundra.util" +local depgraph = require "tundra.depgraph" + +_osx_bundle_mt = nodegen.create_eval_subclass { } +_compile_nib_mt = nodegen.create_eval_subclass { } + +function _osx_bundle_mt:create_dag(env, data, deps) + local bundle_dir = data.Target + local pass = data.Pass + local contents = bundle_dir .. "/Contents" + local copy_deps = {} + + local infoplist = data.InfoPList + copy_deps[#copy_deps+1] = files.hardlink_file(env, data.InfoPList, contents .. "/Info.plist", pass, deps) + + if data.PkgInfo then + copy_deps[#copy_deps+1] = files.hardlink_file(env, data.PkgInfo, contents .. "/PkgInfo", pass, deps) + end + + if data.Executable then + local basename = select(2, path.split(data.Executable)) + copy_deps[#copy_deps+1] = files.hardlink_file(env, data.Executable, contents .. "/MacOS/" .. basename, pass, deps) + end + + local dirs = { + { Tag = "Resources", Dir = contents .. "/Resources/" }, + { Tag = "MacOSFiles", Dir = contents .. "/MacOS/" }, + } + + for _, params in ipairs(dirs) do + local function do_copy(fn) + local basename = select(2, path.split(fn)) + copy_deps[#copy_deps+1] = files.hardlink_file(env, fn, params.Dir .. basename, pass, deps) + end + + local items = data[params.Tag] + for _, dep in util.nil_ipairs(nodegen.flatten_list(env:get('BUILD_ID'), items)) do + if type(dep) == "string" then + do_copy(dep) + else + local node = dep:get_dag(env) + print(node) + deps[#deps+1] = node + local files = {} + node:insert_output_files(files) + for _, fn in ipairs(files) do + do_copy(fn) + end + end + end + end + + return depgraph.make_node { + Env = env, + Pass = pass, + Label = "OsxBundle " .. data.Target, + Dependencies = util.merge_arrays_2(deps, copy_deps), + } +end + +function _compile_nib_mt:create_dag(env, data, deps) + return depgraph.make_node { + Env = env, + Pass = data.Pass, + Label = "CompileNib $(@)", + Action = "$(NIBCC)", + Dependencies = deps, + InputFiles = { data.Source }, + OutputFiles = { "$(OBJECTDIR)/" .. data.Target }, + } +end + +nodegen.add_evaluator("OsxBundle", _osx_bundle_mt, { + Target = { Type = "string", Required = true, Help = "Target .app directory name" }, + Executable = { Type = "string", Help = "Executable to embed" }, + InfoPList = { Type = "string", Required = true, Help = "Info.plist file" }, + PkgInfo = { Type = "string", Help = "PkgInfo file" }, + Resources = { Type = "filter_table", Help = "Files to copy to 'Resources'" }, + MacOSFiles = { Type = "filter_table", Help = "Files to copy to 'MacOS'" }, +}) + +nodegen.add_evaluator("CompileNib", _compile_nib_mt, { + Source = { Type = "string", Required = true }, + Target = { Type = "string", Required = true }, +}) + diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/pkgconfig.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/pkgconfig.lua new file mode 100644 index 0000000..485291c --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/pkgconfig.lua @@ -0,0 +1,51 @@ +module(..., package.seeall) + +function ConfigureRaw(cmdline, name, constructor) + local fh = assert(io.popen(cmdline)) + local data = fh:read("*all") + fh:close() + + local cpppath = {} + local libpath = {} + local libs = {} + local defines = {} + local frameworks = {} + + for kind, value in data:gmatch("-([ILlD])([^ \n\r]+)") do + if kind == "I" then + cpppath[#cpppath + 1] = value + elseif kind == "D" then + defines[#defines + 1] = value + elseif kind == "L" then + libpath[#libpath + 1] = value + elseif kind == "l" then + libs[#libs + 1] = value + end + end + + for value in data:gmatch("-framework ([^ \n\r]+)") do + frameworks[#frameworks + 1] = value + end + + -- We don't have access to ExternalLibrary here - user has to pass it in. + return constructor({ + Name = name, + Propagate = { + Env = { + FRAMEWORKS = frameworks, + CPPDEFS = defines, + CPPPATH = cpppath, + LIBS = libs, + LIBPATH = libpath + } + } + }) +end + +function Configure(name, ctor) + return internal_cfg("pkg-config " .. name .. " --cflags --libs", name, ctor) +end + +function ConfigureWithTool(tool, name, ctor) + return internal_cfg(tool .. " --cflags --libs", name, ctor) +end diff --git a/ogl_editor/bin/win32/scripts/tundra/syntax/testsupport.lua b/ogl_editor/bin/win32/scripts/tundra/syntax/testsupport.lua new file mode 100644 index 0000000..d79dd0f --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/syntax/testsupport.lua @@ -0,0 +1,29 @@ +-- testsupport.lua: A simple UpperCaseFile unit used for Tundra's test harness + +module(..., package.seeall) + +local util = require 'tundra.util' +local nodegen = require 'tundra.nodegen' +local depgraph = require 'tundra.depgraph' + +local mt = nodegen.create_eval_subclass {} + +function mt:create_dag(env, data, deps) + return depgraph.make_node { + Env = env, + Pass = data.Pass, + Label = "UpperCaseFile \$(@)", + Action = "tr a-z A-Z < \$(<) > \$(@)", + InputFiles = { data.InputFile }, + OutputFiles = { data.OutputFile }, + Dependencies = deps, + } +end + +nodegen.add_evaluator("UpperCaseFile", mt, { + Name = { Type = "string", Required = "true" }, + InputFile = { Type = "string", Required = "true" }, + OutputFile = { Type = "string", Required = "true" }, +}) + + diff --git a/ogl_editor/bin/win32/scripts/tundra/test/t_env.lua b/ogl_editor/bin/win32/scripts/tundra/test/t_env.lua new file mode 100644 index 0000000..1209e91 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/test/t_env.lua @@ -0,0 +1,62 @@ + +module(..., package.seeall) + +unit_test('scalar interpolation', function (t) + local e = require 'tundra.environment' + local e1, e2, e3 + e1 = e.create(nil, { Foo="Foo", Baz="Strut" }) + e2 = e1:clone({ Foo="Bar" }) + e3 = e1:clone({ Baz="c++" }) + + t:check_equal(e1:get("Foo"), "Foo") + t:check_equal(e1:get("Baz"), "Strut") + t:check_equal(e2:get("Foo"), "Bar") + t:check_equal(e2:get("Baz"), "Strut") + t:check_equal(e3:get("Fransos", "Ost"), "Ost") + + e1:set("Foo", "Foo") + t:check_equal(e1:interpolate("$(Foo)"), "Foo") + t:check_equal(e1:interpolate("$(Foo:u)"), "FOO") + t:check_equal(e1:interpolate("$(Foo:l)"), "foo") + + t:check_equal(e1:interpolate("$(Foo) $(Baz)"), "Foo Strut") + t:check_equal(e2:interpolate("$(Foo) $(Baz)"), "Bar Strut") + t:check_equal(e3:interpolate("$(Foo) $(Baz)"), "Foo c++") + t:check_equal(e1:interpolate("a $(<)", { ['<'] = "foo" }), "a foo") + + e1:set("FILE", "foo/bar.txt") + t:check_equal(e1:interpolate("$(FILE:B)"), "foo/bar") + t:check_equal(e1:interpolate("$(FILE:F)"), "bar.txt") + t:check_equal(e1:interpolate("$(FILE:D)"), "foo") +end) + +unit_test('list interpolation', function (t) + local e = require 'tundra.environment' + local e1 = e.create() + + e1:set("Foo", { "Foo" }) + t:check_equal(e1:interpolate("$(Foo)"), "Foo") + + e1:set("Foo", { "Foo", "Bar" } ) + t:check_equal(e1:interpolate("$(Foo)") , "Foo Bar") + t:check_equal(e1:interpolate("$(Foo:j,)"), "Foo,Bar") + t:check_equal(e1:interpolate("$(Foo:p!)") , "!Foo !Bar") + t:check_equal(e1:interpolate("$(Foo:a!)") , "Foo! Bar!") + t:check_equal(e1:interpolate("$(Foo:p-I:j__)") , "-IFoo__-IBar") + t:check_equal(e1:interpolate("$(Foo:j\\:)"), "Foo:Bar") + t:check_equal(e1:interpolate("$(Foo:u)"), "FOO BAR") + t:check_equal(e1:interpolate("$(Foo:[2])"), "Bar") + t:check_equal(e1:interpolate("$(Foo:Aoo)"), "Foo Baroo") + t:check_equal(e1:interpolate("$(Foo:PF)"), "Foo FBar") + + local lookaside = { + ['@'] = 'output', + ['<'] = { 'a', 'b' }, + } + + t:check_equal(e1:interpolate("$(Foo) $(<)=$(@)", lookaside), "Foo Bar a b=output") + + -- Verify interpolation caching is cleared when keys change. + e1:set("Foo", { "Baz" }) + t:check_equal(e1:interpolate("$(Foo) $(<)=$(@)", lookaside), "Baz a b=output") +end) diff --git a/ogl_editor/bin/win32/scripts/tundra/test/t_path.lua b/ogl_editor/bin/win32/scripts/tundra/test/t_path.lua new file mode 100644 index 0000000..087bb99 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/test/t_path.lua @@ -0,0 +1,81 @@ +module(..., package.seeall) + +local path = require "tundra.path" +local native = require "tundra.native" + +local function check_path(t, p, expected) + p = p:gsub('\\', '/') + t:check_equal(p, expected) +end + +unit_test('path.normalize', function (t) + check_path(t, path.normalize("foo"), "foo") + check_path(t, path.normalize("foo/bar"), "foo/bar") + check_path(t, path.normalize("foo//bar"), "foo/bar") + check_path(t, path.normalize("foo/./bar"), "foo/bar") + check_path(t, path.normalize("foo/../bar"), "bar") + check_path(t, path.normalize("../bar"), "../bar") + check_path(t, path.normalize("foo/../../bar"), "../bar") +end) + +unit_test('path.join', function (t) + check_path(t, path.join("foo", "bar"), "foo/bar") + check_path(t, path.join("foo", "../bar"), "bar") + check_path(t, path.join("/foo", "bar"), "/foo/bar") +end) + +unit_test('path.split', function (t) + local function check_split(p, expected_dir, expected_fn) + local dir, fn = path.split(p) + dir = dir:gsub('\\', '/') + fn = fn:gsub('\\', '/') + t:check_equal(dir, expected_dir) + t:check_equal(fn, expected_fn) + end + + check_split("", ".", "") + check_split("foo", ".", "foo") + check_split("foo/bar", "foo", "bar") + check_split("/foo/bar", "/foo", "bar") + check_split("x:\\foo\\bar", "x:/foo", "bar") +end) + + +unit_test('path.get_filename_dir', function (t) + t:check_equal(path.get_filename_dir("foo/bar"), "foo") + t:check_equal(path.get_filename_dir("foo"), "") +end) + +unit_test('path.get_filename', function (t) + t:check_equal(path.get_filename("foo/bar"), "bar") + t:check_equal(path.get_filename("foo"), "foo") +end) + +unit_test('path.get_extension', function (t) + t:check_equal(path.get_extension("foo"), "") + t:check_equal(path.get_extension("foo."), ".") + t:check_equal(path.get_extension("foo.c"), ".c") + t:check_equal(path.get_extension("foo/bar/.c"), ".c") + t:check_equal(path.get_extension("foo/bar/baz.cpp"), ".cpp") +end) + +unit_test('path.drop_suffix', function (t) + t:check_equal(path.drop_suffix("foo.c"), "foo") + t:check_equal(path.drop_suffix("foo/bar.c"), "foo/bar") + t:check_equal(path.drop_suffix("/foo/bar.c"), "/foo/bar") +end) + +unit_test('path.get_filename_base', function (t) + t:check_equal(path.get_filename_base("foo1"), "foo1") + t:check_equal(path.get_filename_base("foo2.c"), "foo2") + t:check_equal(path.get_filename_base("/path/to/foo3"), "foo3") + t:check_equal(path.get_filename_base("/path/to/foo4.c"), "foo4") +end) + +unit_test('path.is_absolute', function (t) + t:check_equal(path.is_absolute("/foo") and "true" or "false", "true") + t:check_equal(path.is_absolute("foo") and "true" or "false", "false") + if native.host_platform == "windows" then + t:check_equal(path.is_absolute("x:\\foo") and "true" or "false", "true") + end +end) diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/clang-osx.lua b/ogl_editor/bin/win32/scripts/tundra/tools/clang-osx.lua new file mode 100644 index 0000000..bd5b66b --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/clang-osx.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply(env, options) + tundra.unitgen.load_toolset("gcc-osx", env) + + env:set_many { + ["CC"] = "clang", + ["CXX"] = "clang++", + ["LD"] = "clang", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/dotnet.lua b/ogl_editor/bin/win32/scripts/tundra/tools/dotnet.lua new file mode 100644 index 0000000..e669e25 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/dotnet.lua @@ -0,0 +1,28 @@ +module(..., package.seeall) + +local frameworkDir = "c:\\Windows\\Microsoft.NET\\Framework" +local defaultFrameworkVersion = "v3.5" + +function apply(env, options) + tundra.unitgen.load_toolset("generic-dotnet", env) + + local version = options and assert(options.Version) or defaultFrameworkVersion + env:set_external_env_var('FrameworkDir', frameworkDir) + env:set_external_env_var('FrameworkVersion', version) + + local binPath = frameworkDir .. "\\" .. version + env:set_external_env_var('PATH', binPath .. ";" .. env:get_external_env_var('PATH')) + + -- C# support + env:set_many { + ["DOTNET_SUFFIXES"] = { ".cs" }, + ["DOTNET_SUFFIXES_RESOURCE"] = { ".resource" }, + ["CSC"] = "csc.exe", + ["CSPROGSUFFIX"] = ".exe", + ["CSLIBSUFFIX"] = ".dll", + ["CSRESGEN"] = "resgen $(<) $(@)", + ["_CSC_COMMON"] = "-warn:$(CSC_WARNING_LEVEL) /nologo $(CSLIBPATH:b:p/lib\\:) $(CSRESOURCES:b:p/resource\\:) $(CSLIBS:p/reference\\::A.dll)", + ["CSCLIBCOM"] = "$(CSC) $(_CSC_COMMON) $(CSCOPTS) -target:library -out:$(@:b) $(<:b)", + ["CSCEXECOM"] = "$(CSC) $(_CSC_COMMON) $(CSCOPTS) -target:exe -out:$(@:b) $(<:b)", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/gas.lua b/ogl_editor/bin/win32/scripts/tundra/tools/gas.lua new file mode 100644 index 0000000..59a655c --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/gas.lua @@ -0,0 +1,12 @@ +module(..., package.seeall) + +function apply(env, options) + -- load the generic assembly toolset first + tundra.unitgen.load_toolset("generic-asm", env) + + env:set_many { + ["ASM"] = "as", + ["ASMCOM"] = "$(ASM) -o $(@) $(ASMDEFS:p-D) $(ASMOPTS) $(<)", + ["ASMINC_KEYWORDS"] = { ".include" }, + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/gcc-osx.lua b/ogl_editor/bin/win32/scripts/tundra/tools/gcc-osx.lua new file mode 100644 index 0000000..fb12ddd --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/gcc-osx.lua @@ -0,0 +1,19 @@ +module(..., package.seeall) + +function apply(env, options) + -- load the generic GCC toolset first + tundra.unitgen.load_toolset("gcc", env) + + env:set_many { + ["NATIVE_SUFFIXES"] = { ".c", ".cpp", ".cc", ".cxx", ".m", ".mm", ".a", ".o" }, + ["CXXEXTS"] = { "cpp", "cxx", "cc", "mm" }, + ["FRAMEWORKS"] = "", + ["SHLIBPREFIX"] = "lib", + ["SHLIBOPTS"] = "-shared", + ["_OS_CCOPTS"] = "$(FRAMEWORKS:p-F)", + ["SHLIBCOM"] = "$(LD) $(SHLIBOPTS) $(LIBPATH:p-L) $(LIBS:p-l) $(FRAMEWORKS:p-framework ) -o $(@) $(<)", + ["PROGCOM"] = "$(LD) $(PROGOPTS) $(LIBPATH:p-L) $(LIBS:p-l) $(FRAMEWORKS:p-framework ) -o $(@) $(<)", + ["OBJCCOM"] = "$(CCCOM)", -- objc uses same commandline + ["NIBCC"] = "ibtool --output-format binary1 --compile $(@) $(<)", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/gcc.lua b/ogl_editor/bin/win32/scripts/tundra/tools/gcc.lua new file mode 100644 index 0000000..7849750 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/gcc.lua @@ -0,0 +1,30 @@ +module(..., package.seeall) + +function apply(env, options) + -- load the generic C toolset first + tundra.unitgen.load_toolset("generic-cpp", env) + + env:set_many { + ["NATIVE_SUFFIXES"] = { ".c", ".cpp", ".cc", ".cxx", ".a", ".o" }, + ["OBJECTSUFFIX"] = ".o", + ["LIBPREFIX"] = "lib", + ["LIBSUFFIX"] = ".a", + ["_GCC_BINPREFIX"] = "", + ["CC"] = "$(_GCC_BINPREFIX)gcc", + ["CXX"] = "$(_GCC_BINPREFIX)g++", + ["LIB"] = "$(_GCC_BINPREFIX)ar", + ["LD"] = "$(_GCC_BINPREFIX)gcc", + ["_OS_CCOPTS"] = "", + ["_OS_CXXOPTS"] = "", + ["CCCOM"] = "$(CC) $(_OS_CCOPTS) -c $(CPPDEFS:p-D) $(CPPPATH:f:p-I) $(CCOPTS) $(CCOPTS_$(CURRENT_VARIANT:u)) -o $(@) $(<)", + ["CXXCOM"] = "$(CXX) $(_OS_CXXOPTS) -c $(CPPDEFS:p-D) $(CPPPATH:f:p-I) $(CXXOPTS) $(CXXOPTS_$(CURRENT_VARIANT:u)) -o $(@) $(<)", + ["PROGOPTS"] = "", + ["PROGCOM"] = "$(LD) $(PROGOPTS) $(LIBPATH:p-L) -o $(@) $(<) $(LIBS:p-l)", + ["PROGPREFIX"] = "", + ["LIBOPTS"] = "", + ["LIBCOM"] = "$(LIB) -rs $(LIBOPTS) $(@) $(<)", + ["SHLIBPREFIX"] = "lib", + ["SHLIBOPTS"] = "-shared", + ["SHLIBCOM"] = "$(LD) $(SHLIBOPTS) $(LIBPATH:p-L) -o $(@) $(<) $(LIBS:p-l)", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/generic-asm.lua b/ogl_editor/bin/win32/scripts/tundra/tools/generic-asm.lua new file mode 100644 index 0000000..e542eee --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/generic-asm.lua @@ -0,0 +1,78 @@ +module(..., package.seeall) + +local path = require "tundra.path" +local util = require "tundra.util" +local boot = require "tundra.boot" +local scanner = require "tundra.scanner" +local depgraph = require "tundra.depgraph" + +local default_keywords = { "include" } +local default_bin_keywords = { "incbin" } + +local function get_asm_scanner(env, fn) + local function test_bool(name, default) + val = env:get(name, default) + if val == "yes" or val == "true" or val == "1" then + return 1 + else + return 0 + end + end + local function new_scanner() + local paths = util.map(env:get_list("ASMINCPATH"), function (v) return env:interpolate(v) end) + local data = { + Paths = paths, + Keywords = env:get_list("ASMINC_KEYWORDS", default_keywords), + KeywordsNoFollow = env:get_list("ASMINC_BINARY_KEYWORDS", default_bin_keywords), + RequireWhitespace = test_bool("ASMINC_REQUIRE_WHITESPACE", "yes"), + UseSeparators = test_bool("ASMINC_USE_SEPARATORS", "yes"), + BareMeansSystem = test_bool("ASMINC_BARE_MEANS_SYSTEM", "no"), + } + return scanner.make_generic_scanner(data) + end + return env:memoize("ASMINCPATH", "_asm_scanner", new_scanner) +end + +-- Register implicit make functions for assembly files. +-- These functions are called to transform source files in unit lists into +-- object files. This function is registered as a setup function so it will be +-- run after user modifications to the environment, but before nodes are +-- processed. This way users can override the extension lists. +local function generic_asm_setup(env) + local _assemble = function(env, pass, fn) + local object_fn = path.make_object_filename(env, fn, '$(OBJECTSUFFIX)') + + return depgraph.make_node { + Env = env, + Label = 'Asm $(@)', + Pass = pass, + Action = "$(ASMCOM)", + InputFiles = { fn }, + OutputFiles = { object_fn }, + Scanner = get_asm_scanner(env, fn), + } + end + + for _, ext in ipairs(env:get_list("ASM_EXTS")) do + env:register_implicit_make_fn(ext, _assemble) + end +end + +function apply(_outer_env, options) + + _outer_env:add_setup_function(generic_asm_setup) + + _outer_env:set_many { + ["ASM_EXTS"] = { ".s", ".asm" }, + ["ASMINCPATH"] = {}, + ["ASMDEFS"] = "", + ["ASMDEFS_DEBUG"] = "", + ["ASMDEFS_PRODUCTION"] = "", + ["ASMDEFS_RELEASE"] = "", + ["ASMOPTS"] = "", + ["ASMOPTS_DEBUG"] = "", + ["ASMOPTS_PRODUCTION"] = "", + ["ASMOPTS_RELEASE"] = "", + } +end + diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/generic-cpp.lua b/ogl_editor/bin/win32/scripts/tundra/tools/generic-cpp.lua new file mode 100644 index 0000000..548a61c --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/generic-cpp.lua @@ -0,0 +1,103 @@ +module(..., package.seeall) + +local nodegen = require "tundra.nodegen" +local boot = require "tundra.boot" +local util = require "tundra.util" +local path = require "tundra.path" +local scanner = require "tundra.scanner" +local depgraph = require "tundra.depgraph" + +local scanner_cache = {} + +function get_cpp_scanner(env, fn) + local paths = util.map(env:get_list("CPPPATH"), function (v) return env:interpolate(v) end) + return scanner.make_cpp_scanner(paths) +end + +-- Register implicit make functions for C, C++ and Objective-C files. +-- These functions are called to transform source files in unit lists into +-- object files. This function is registered as a setup function so it will be +-- run after user modifications to the environment, but before nodes are +-- processed. This way users can override the extension lists. +local function generic_cpp_setup(env) + local _anyc_compile = function(env, pass, fn, label, action) + local object_fn = path.make_object_filename(env, fn, '$(OBJECTSUFFIX)') + + local output_files = { object_fn } + + local pch_source = env:get('_PCH_SOURCE', '') + local implicit_inputs = nil + + if fn == pch_source then + + label = 'Precompiled header' + pass = nodegen.resolve_pass(env:get('_PCH_PASS', '')) + action = "$(PCHCOMPILE)" + output_files = { "$(_PCH_FILE)", object_fn } + + elseif pch_source ~= '' and fn ~= pch_source then + + -- It would be good to make all non-pch source files dependent upon the .pch node. + -- That would require that we generate the .pch node before generating these nodes. + -- As it stands presently, when .pch compilation fails, the remaining sources + -- fail to compile, but if the dependencies were correctly setup, then they wouldn't + -- even try to compile. + + end + + return depgraph.make_node { + Env = env, + Label = label .. ' $(<)', + Pass = pass, + Action = action, + InputFiles = { fn }, + OutputFiles = output_files, + ImplicitInputs = implicit_inputs, + Scanner = get_cpp_scanner(env, fn), + } + end + + local mappings = { + ["CCEXTS"] = { Label="Cc", Action="$(CCCOM)" }, + ["CXXEXTS"] = { Label="C++", Action="$(CXXCOM)" }, + ["OBJCEXTS"] = { Label="ObjC", Action="$(OBJCCOM)" }, + } + + for key, setup in pairs(mappings) do + for _, ext in ipairs(env:get_list(key)) do + env:register_implicit_make_fn(ext, function(env, pass, fn) + return _anyc_compile(env, pass, fn, setup.Label, setup.Action) + end) + end + end +end + +function apply(_outer_env, options) + + _outer_env:add_setup_function(generic_cpp_setup) + + _outer_env:set_many { + ["IGNORED_AUTOEXTS"] = { ".h", ".hpp", ".hh", ".hxx", ".inl" }, + ["CCEXTS"] = { "c" }, + ["CXXEXTS"] = { "cpp", "cxx", "cc" }, + ["OBJCEXTS"] = { "m" }, + ["PROGSUFFIX"] = "$(HOSTPROGSUFFIX)", + ["SHLIBSUFFIX"] = "$(HOSTSHLIBSUFFIX)", + ["CPPPATH"] = "", + ["CPPDEFS"] = "", + ["LIBS"] = "", + ["LIBPATH"] = "$(OBJECTDIR)", + ["CCOPTS"] = "", + ["CXXOPTS"] = "", + ["CPPDEFS_DEBUG"] = "", + ["CPPDEFS_PRODUCTION"] = "", + ["CPPDEFS_RELEASE"] = "", + ["CCOPTS_DEBUG"] = "", + ["CCOPTS_PRODUCTION"] = "", + ["CCOPTS_RELEASE"] = "", + ["CXXOPTS_DEBUG"] = "", + ["CXXOPTS_PRODUCTION"] = "", + ["CXXOPTS_RELEASE"] = "", + ["SHLIBLINKSUFFIX"] = "", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/generic-dotnet.lua b/ogl_editor/bin/win32/scripts/tundra/tools/generic-dotnet.lua new file mode 100644 index 0000000..2385440 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/generic-dotnet.lua @@ -0,0 +1,17 @@ +module(..., package.seeall) + +local function generic_dotnet_setup(env) + +end + +function apply(env, options) + env:add_setup_function(generic_dotnet_setup) + + env:set_many { + ["CSLIBS"] = "", -- assembly references + ["CSLIBPATH"] = {}, -- assembly directories + ["CSCOPTS"] = "-optimize", + ["CSRESOURCES"] = "", + ["CSC_WARNING_LEVEL"] = "4", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/ispc.lua b/ogl_editor/bin/win32/scripts/tundra/tools/ispc.lua new file mode 100644 index 0000000..3f0b1ea --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/ispc.lua @@ -0,0 +1,11 @@ +module(..., package.seeall) + +function apply(env, options) + env:set_many { + ["ISPC_SUFFIXES"] = { ".ispc", }, + ["ISPC"] = "ispc", + ["ISPCOPTS"] = "", + ["ISPCCOM"] = "$(ISPC) $(ISPCOPTS) -o $(@:[1]) -h $(@:[2]) $(<)", + } +end + diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/mingw.lua b/ogl_editor/bin/win32/scripts/tundra/tools/mingw.lua new file mode 100644 index 0000000..4aeff36 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/mingw.lua @@ -0,0 +1,24 @@ +module(..., package.seeall) + +function apply(env, options) + + -- load the generic GCC toolset first + tundra.unitgen.load_toolset("gcc", env) + + -- load support for win32 resource compilation + tundra.unitgen.load_toolset("win32-rc", env) + + env:set_many { + ["NATIVE_SUFFIXES"] = { ".c", ".cpp", ".cc", ".cxx", ".a", ".o", ".rc" }, + ["OBJECTSUFFIX"] = ".o", + ["LIBPREFIX"] = "", + ["LIBSUFFIX"] = ".a", + ["W32RESSUFFIX"] = ".o", + ["CPPDEFS"] = "_WIN32", + ["_CPPDEFS"] = "$(CPPDEFS:p/D) $(CPPDEFS_$(CURRENT_VARIANT:u):p/D)", + ["RC"] = "windres", + ["RCOPTS"] = "", + ["RCCOM"] = "$(RC) $(RCOPTS) --output=$(@:b) $(CPPPATH:b:p-I) --input=$(<:b)", + ["SHLIBLINKSUFFIX"] = ".a", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/mono.lua b/ogl_editor/bin/win32/scripts/tundra/tools/mono.lua new file mode 100644 index 0000000..553a1cd --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/mono.lua @@ -0,0 +1,17 @@ +module(..., package.seeall) + +function apply(env, options) + tundra.unitgen.load_toolset("generic-dotnet", env) + + env:set_many { + ["DOTNET_SUFFIXES"] = { ".cs" }, + ["DOTNET_SUFFIXES_RESOURCE"] = { ".resource" }, + ["CSC"] = "gmcs", + ["CSPROGSUFFIX"] = ".exe", + ["CSLIBSUFFIX"] = ".dll", + ["CSRESGEN"] = "resgen2 $(<) $(@)", + ["_CSC_COMMON"] = "-warn:$(CSC_WARNING_LEVEL) /nologo $(CSLIBPATH:p-lib\\:) $(CSRESOURCES:p-resource\\:) $(CSLIBS:p-reference\\::A.dll)", + ["CSCLIBCOM"] = "$(CSC) $(_CSC_COMMON) $(CSCOPTS) -target:library -out:$(@) $(<)", + ["CSCEXECOM"] = "$(CSC) $(_CSC_COMMON) $(CSCOPTS) -target:exe -out:$(@) $(<)", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vs2008.lua b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vs2008.lua new file mode 100644 index 0000000..99edf0f --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vs2008.lua @@ -0,0 +1,8 @@ + +module(..., package.seeall) + +local vscommon = require "tundra.tools.msvc-vscommon" + +function apply(env, options) + vscommon.apply_msvc_visual_studio("9.0", env, options) +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vs2010.lua b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vs2010.lua new file mode 100644 index 0000000..5c1ef88 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vs2010.lua @@ -0,0 +1,8 @@ + +module(..., package.seeall) + +local vscommon = require "tundra.tools.msvc-vscommon" + +function apply(env, options) + vscommon.apply_msvc_visual_studio("10.0", env, options) +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vs2012.lua b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vs2012.lua new file mode 100644 index 0000000..6c9d60a --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vs2012.lua @@ -0,0 +1,8 @@ + +module(..., package.seeall) + +local vscommon = require "tundra.tools.msvc-vscommon" + +function apply(env, options) + vscommon.apply_msvc_visual_studio("11.0", env, options) +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vs2013.lua b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vs2013.lua new file mode 100644 index 0000000..6abddda --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vs2013.lua @@ -0,0 +1,8 @@ + +module(..., package.seeall) + +local vscommon = require "tundra.tools.msvc-vscommon" + +function apply(env, options) + vscommon.apply_msvc_visual_studio("12.0", env, options) +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vscommon.lua b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vscommon.lua new file mode 100644 index 0000000..5fad59c --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vscommon.lua @@ -0,0 +1,267 @@ +-- msvc-vscommon.lua - utility code for all versions of Visual Studio + +module(..., package.seeall) + +local native = require "tundra.native" +local os = require "os" + +-- Visual Studio tooling layout + +local vc_bin_map = { + ["x86"] = { + ["x86"] = "", + ["x64"] = "x86_amd64", + ["arm"] = "x86_arm", + }, + ["x64"] = { + ["x86"] = "", + ["x64"] = "amd64", + ["arm"] = "x86_arm", -- is this really legal? + }, +} + +local vc_lib_map = { + ["x86"] = { + ["x86"] = "", + ["x64"] = "amd64", + ["arm"] = "arm", + }, + ["x64"] = { + ["x86"] = "", + ["x64"] = "amd64", + ["arm"] = "arm", + }, +} + +-- Windows SDK layout + +local pre_win8_sdk_dir = { + ["bin"] = "bin", + ["include"] = "include", + ["lib"] = "lib", +} + +local win8_sdk_dir = { + ["bin"] = "bin", + ["include"] = "include", + ["lib"] = "lib\\win8\\um", +} + +local win81_sdk_dir = { + ["bin"] = "bin", + ["include"] = "include", + ["lib"] = "lib\\winv6.3\\um", +} + +local pre_win8_sdk = { + ["x86"] = { + ["bin"] = "", + ["include"] = "", + ["lib"] = "", + }, + ["x64"] = { + ["bin"] = "x64", + ["include"] = "", + ["lib"] = "x64", + }, +} + +local post_win8_sdk = { + ["x86"] = { + ["bin"] = "x86", + ["include"] = { "shared", "um" }, + ["lib"] = "x86", + }, + ["x64"] = { + ["bin"] = "x64", + ["include"] = { "shared", "um" }, + ["lib"] = "x64", + }, + ["arm"] = { + ["bin"] = "arm", + ["include"] = { "shared", "um" }, + ["lib"] = "arm", + }, +} + +-- Each quadruplet specifies a registry key value that gets us the SDK location, +-- followed by a folder structure (for each supported target architecture) +-- and finally the corresponding bin, include and lib folder's relative location + +local sdk_map = { + ["9.0"] = { "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v6.0A", "InstallationFolder", pre_win8_sdk_dir, pre_win8_sdk }, + ["10.0"] = { "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v7.0A", "InstallationFolder", pre_win8_sdk_dir, pre_win8_sdk }, + ["11.0"] = { "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot", win8_sdk_dir, post_win8_sdk }, + ["12.0"] = { "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot81", win81_sdk_dir, post_win8_sdk }, +} + +local function get_host_arch() + local snative = native.getenv("PROCESSOR_ARCHITECTURE") + local swow = native.getenv("PROCESSOR_ARCHITEW6432", "") + if snative == "AMD64" or swow == "AMD64" then + return "x64" + elseif snative == "IA64" or swow == "IA64" then + return "itanium"; + else + return "x86" + end +end + +function path_combine(path, path_to_append) + if path == nil then + return path_to_append + end + if path:find("\\$") then + return path .. path_to_append + end + return path .. "\\" .. path_to_append +end + +function path_it(maybe_list) + if type(maybe_list) == "table" then + return ipairs(maybe_list) + end + return ipairs({maybe_list}) +end + +function apply_msvc_visual_studio(version, env, options) + + -- NOTE: don't make changes to `env` until you've asserted + -- that the requested version is in fact installed, + -- the `vs-wild` toolset will call this function + -- repeatedly with a the next version but the same `env`, + -- if a version fails (assert/error) + + if native.host_platform ~= "windows" then + error("the msvc toolset only works on windows hosts") + end + + -- Load basic MSVC environment setup first. + -- We're going to replace the paths to some tools. + tundra.unitgen.load_toolset('msvc', env) + + options = options or {} + + local target_arch = options.TargetArch or "x86" + local host_arch = options.HostArch or get_host_arch() + local sdk_version = options.SdkVersion or version -- we identify SDKs by VS version and fallback to current version + + -- We'll find any edition of VS (including Express) here + local vs_root = native.reg_query("HKLM", "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", version) + assert(vs_root, "The requested version of Visual Studio isn't installed") + vs_root = string.gsub(vs_root, "\\+$", "\\") + + local vc_lib + local vc_bin + + vc_bin = vc_bin_map[host_arch][target_arch] + if not vc_bin then + errorf("can't build target arch %s on host arch %s", target_arch, host_arch) + end + vc_bin = vs_root .. "vc\\bin\\" .. vc_bin + + vc_lib = vs_root .. "vc\\lib\\" .. vc_lib_map[host_arch][target_arch] + + -- + -- Now fix up the SDK + -- + + local sdk_root + local sdk_bin + local sdk_include = {} + local sdk_lib + + local sdk = sdk_map[sdk_version] + assert(sdk, "The requested version of Visual Studio isn't supported") + + sdk_root = native.reg_query("HKLM", sdk[1], sdk[2]) + assert(sdk_root, "The requested version of the SDK isn't installed") + sdk_root = string.gsub(sdk_root, "\\+$", "\\") + + local sdk_dir_base = sdk[3] + + local sdk_dir = sdk[4][target_arch] + assert(sdk_dir, "The target platform architecture isn't supported by the SDK") + + sdk_bin = sdk_root .. sdk_dir_base["bin"] .. "\\" .. sdk_dir["bin"] + + local sdk_dir_base_include = sdk_dir_base["include"] + for _, v in path_it(sdk_dir["include"]) do + sdk_include[#sdk_include + 1] = sdk_root .. sdk_dir_base_include .. "\\" .. v + end + + sdk_lib = sdk_root .. sdk_dir_base["lib"] .. "\\" .. sdk_dir["lib"] + + -- + -- Tools + -- + + local cl_exe = '"' .. path_combine(vc_bin, "cl.exe") .. '"' + local lib_exe = '"' .. path_combine(vc_bin, "lib.exe") .. '"' + local link_exe = '"' .. path_combine(vc_bin, "link.exe") .. '"' + local rc_exe = '"' .. path_combine(sdk_bin, "rc.exe") .. '"' -- pickup the Resource Compiler from the SDK + + env:set('CC', cl_exe) + env:set('CXX', cl_exe) + env:set('LIB', lib_exe) + env:set('LD', link_exe) + env:set('RC', rc_exe) + + if sdk_version == "9.0" then + env:set("RCOPTS", "") -- clear the "/nologo" option (it was first added in VS2010) + end + + if version == "12.0" then + -- Force MSPDBSRV.EXE + env:set("CCOPTS", "/FS") + env:set("CXXOPTS", "/FS") + end + + -- Wire-up the external environment + + env:set_external_env_var('VSINSTALLDIR', vs_root) + env:set_external_env_var('VCINSTALLDIR', vs_root .. "\\vc") + env:set_external_env_var('DevEnvDir', vs_root .. "Common7\\IDE") + + local include = {} + + for _, v in ipairs(sdk_include) do + include[#include + 1] = v + end + + include[#include + 1] = vs_root .. "VC\\ATLMFC\\INCLUDE" + include[#include + 1] = vs_root .. "VC\\INCLUDE" + + env:set_external_env_var("WindowsSdkDir", sdk_root) + env:set_external_env_var("INCLUDE", table.concat(include, ';')) + + -- if MFC isn't installed with VS + -- the linker will throw an error when looking for libs + -- Lua does not have a "does directory exist function" + -- we could use one here + local lib_str = sdk_lib .. ";" .. vs_root .. "\\VC\\ATLMFC\\lib\\" .. vc_lib_map[host_arch][target_arch] .. ";" .. vc_lib + env:set_external_env_var("LIB", lib_str) + env:set_external_env_var("LIBPATH", lib_str) + + -- Modify %PATH% + + local path = {} + + path[#path + 1] = sdk_root + path[#path + 1] = vs_root .. "Common7\\IDE" + + if "x86" == host_arch then + path[#path + 1] = vs_root .. "\\VC\\Bin" + elseif "x64" == host_arch then + path[#path + 1] = vs_root .. "\\VC\\Bin\\amd64" + elseif "arm" == host_arch then + path[#path + 1] = vs_root .. "\\VC\\Bin\\arm" + end + + path[#path + 1] = vs_root .. "\\Common7\\IDE" + + path[#path + 1] = env:get_external_env_var('PATH') + + env:set_external_env_var("PATH", table.concat(path, ';')) + +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vswild.lua b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vswild.lua new file mode 100644 index 0000000..1804445 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-vswild.lua @@ -0,0 +1,23 @@ + +module(..., package.seeall) + +local vscommon = require "tundra.tools.msvc-vscommon" + +function apply(env, options) + + local vsvs = options.VsVersions or { "12.0", "11.0", "10.0", "9.0" } + + for _, v in ipairs(vsvs) do + local v1 = v + local success, result = xpcall(function() vscommon.apply_msvc_visual_studio(v1, env, options) end, function(err) return err end) + if success then + print("Visual Studio version " .. v1 .. " found ") + return + else + print("Visual Studio version " .. v1 .. " does not appear to be installed (" .. result .. ")") + end + end + + error("Unable to find suitable version of Visual Studio (please install either version " .. table.concat(vsvs, ", ") .. " of Visual Studio to continue)") + +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/msvc-winsdk.lua b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-winsdk.lua new file mode 100644 index 0000000..5d7f7b1 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/msvc-winsdk.lua @@ -0,0 +1,150 @@ +-- msvc-winsdk.lua - Use Microsoft Windows SDK 7.1 or later to build. + +module(..., package.seeall) + +local native = require "tundra.native" +local os = require "os" + +if native.host_platform ~= "windows" then + error("the msvc toolset only works on windows hosts") +end + +local function get_host_arch() + local snative = native.getenv("PROCESSOR_ARCHITECTURE") + local swow = native.getenv("PROCESSOR_ARCHITEW6432", "") + if snative == "AMD64" or swow == "AMD64" then + return "x64" + elseif snative == "IA64" or swow == "IA64" then + return "itanium"; + else + return "x86" + end +end + +local compiler_dirs = { + ["x86"] = { + ["x86"] = "bin\\", + ["x64"] = "bin\\x86_amd64\\", + ["itanium"] = "bin\\x86_ia64\\", + }, + ["x64"] = { + ["x86"] = "bin\\", + ["x64"] = { + ["11.0"] = "bin\\x86_amd64\\", + "bin\\amd64\\" + }, + ["itanium"] = "bin\\x86_ia64\\", + }, + ["itanium"] = { + ["x86"] = "bin\\x86_ia64\\", + ["itanium"] = "bin\\ia64\\", + }, +} + +local function setup(env, options) + options = options or {} + local target_arch = options.TargetArch or "x86" + local host_arch = options.HostArch or get_host_arch() + local vcversion = options.VcVersion or "10.0" + + local binDir = + compiler_dirs[host_arch][target_arch][vcversion] + or compiler_dirs[host_arch][target_arch][1] + or compiler_dirs[host_arch][target_arch] + + if not binDir then + errorf("can't build target arch %s on host arch %s", target_arch, host_arch) + end + + local sdkDir; + local sdkDirIncludes; + local sdkLibDir; + local vcLibDir; + + if vcversion == "11.0" then + local sdk_key = "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v8.0" + sdkDir = assert(native.reg_query("HKLM", sdk_key, "InstallationFolder")) + sdkDirIncludes = { sdkDir .. "\\INCLUDE\\UM", sdkDir .. "\\INCLUDE\\SHARED" } + + sdkLibDir = "LIB\\win8\\um\\" + vcLibDir = "LIB" + + if "x86" == target_arch then + sdkLibDir = sdkLibDir .. "x86" + elseif "x64" == target_arch then + sdkLibDir = sdkLibDir .. "x64" + vcLibDir = "LIB\\amd64" + elseif "arm" == target_arch then + sdkLibDir = sdkLibDir .. "arm" + end + else + local sdk_key = "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows" + sdkDir = assert(native.reg_query("HKLM", sdk_key, "CurrentInstallFolder")) + sdkDirIncludes = { sdkDir .. "\\INCLUDE" }; + + sdkLibDir = "LIB" + vcLibDir = "LIB" + + if "x64" == target_arch then + sdkLibDir = "LIB\\x64" + vcLibDir = "LIB\\amd64" + elseif "itanium" == target_arch then + sdkLibDir = "LIB\\IA64" + vcLibDir = "LIB\\IA64" + end + end + + + local vc_key = "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VC7" + local vc_dir = assert(native.reg_query("HKLM", vc_key, vcversion)) + if vc_dir:sub(-1) ~= '\\' then + vc_dir = vc_dir .. '\\' + end + + local cl_exe = '"' .. vc_dir .. binDir .. "cl.exe" ..'"' + local lib_exe = '"' .. vc_dir .. binDir .. "lib.exe" ..'"' + local link_exe = '"' .. vc_dir .. binDir .. "link.exe" ..'"' + + env:set('CC', cl_exe) + env:set('CXX', cl_exe) + env:set('LIB', lib_exe) + env:set('LD', link_exe) + + -- Set up the MS SDK associated with visual studio + + env:set_external_env_var("WindowsSdkDir", sdkDir) + env:set_external_env_var("INCLUDE", table.concat(sdkDirIncludes, ";") .. ";" .. vc_dir .. "\\INCLUDE") + + local rc_exe + print("vcversion", vcversion) + if vcversion == "11.0" then + rc_exe = '"' .. sdkDir .. "\\bin\\x86\\rc.exe" ..'"' + else + rc_exe = '"' .. sdkDir .. "\\bin\\rc.exe" ..'"' + end + env:set('RC', rc_exe) + + local libString = sdkDir .. "\\" .. sdkLibDir .. ";" .. vc_dir .. "\\" .. vcLibDir + env:set_external_env_var("LIB", libString) + env:set_external_env_var("LIBPATH", libString) + + local path = { } + local vc_root = vc_dir:sub(1, -4) + if binDir ~= "\\bin\\" then + path[#path + 1] = vc_dir .. "\\bin" + end + path[#path + 1] = vc_root .. "Common7\\Tools" -- drop vc\ at end + path[#path + 1] = vc_root .. "Common7\\IDE" -- drop vc\ at end + path[#path + 1] = sdkDir + path[#path + 1] = vc_dir .. binDir + path[#path + 1] = env:get_external_env_var('PATH') + + env:set_external_env_var("PATH", table.concat(path, ';')) +end + +function apply(env, options) + -- Load basic MSVC environment setup first. We're going to replace the paths to + -- some tools. + tundra.unitgen.load_toolset('msvc', env) + setup(env, options) +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/msvc.lua b/ogl_editor/bin/win32/scripts/tundra/tools/msvc.lua new file mode 100644 index 0000000..ac64254 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/msvc.lua @@ -0,0 +1,53 @@ +-- msvc.lua - common definitions for all flavors of MSVC +module(..., package.seeall) + +function apply(env, options) + + -- load the generic C toolset first + tundra.unitgen.load_toolset("generic-cpp", env) + + -- load support for win32 resource compilation + tundra.unitgen.load_toolset("win32-rc", env) + + env:set_many { + ["NATIVE_SUFFIXES"] = { ".c", ".cpp", ".cc", ".cxx", ".lib", ".obj", ".res", ".rc" }, + ["OBJECTSUFFIX"] = ".obj", + ["LIBPREFIX"] = "", + ["LIBSUFFIX"] = ".lib", + ["CC"] = "cl", + ["CXX"] = "cl", + ["LIB"] = "lib", + ["LD"] = "link", + ["CPPDEFS"] = "_WIN32", + ["_CPPDEFS"] = "$(CPPDEFS:p/D) $(CPPDEFS_$(CURRENT_VARIANT:u):p/D)", + ["_PCH_SUPPORTED"] = "1", + ["_USE_PCH_OPT"] = "/Fp$(_PCH_FILE:b) /Yu$(_PCH_HEADER)", + ["_USE_PCH"] = "", + ["_USE_PDB_CC_OPT"] = "/Zi /Fd$(_PDB_CC_FILE:b)", + ["_USE_PDB_LINK_OPT"] = "/DEBUG /PDB:$(_PDB_LINK_FILE)", + ["_USE_PDB_CC"] = "", + ["_USE_PDB_LINK"] = "", + ["_USE_MODDEF_OPT"] = "/DEF:$(MODDEF)", + ["_USE_MODDEF"] = "", + ["RC"] = "rc", + ["RCOPTS"] = "/nologo", + ["W32RESSUFFIX"] = ".res", + ["RCCOM"] = "$(RC) $(RCOPTS) /fo$(@:b) $(_CPPDEFS) $(CPPPATH:b:p/i) $(<:b)", + ["CCCOM"] = "$(CC) /c @RESPONSE|@|$(_CPPDEFS) $(CPPPATH:b:p/I) /nologo $(CCOPTS) $(CCOPTS_$(CURRENT_VARIANT:u)) $(_USE_PCH) $(_USE_PDB_CC) /Fo$(@:b) $(<:b)", + ["CXXCOM"] = "$(CC) /c @RESPONSE|@|$(_CPPDEFS) $(CPPPATH:b:p/I) /nologo $(CXXOPTS) $(CXXOPTS_$(CURRENT_VARIANT:u)) $(_USE_PCH) $(_USE_PDB_CC) /Fo$(@:b) $(<:b)", + ["PCHCOMPILE_CC"] = "$(CC) /c $(_CPPDEFS) $(CPPPATH:b:p/I) /nologo $(CCOPTS) $(CCOPTS_$(CURRENT_VARIANT:u)) $(_USE_PDB_CC) /Yc$(_PCH_HEADER) /Fp$(@:[1]:b) /Fo$(@:[2]:b) $(<:[1]:b)", + ["PCHCOMPILE_CXX"] = "$(CXX) /c $(_CPPDEFS) $(CPPPATH:b:p/I) /nologo $(CXXOPTS) $(CXXOPTS_$(CURRENT_VARIANT:u)) $(_USE_PDB_CC) /Yc$(_PCH_HEADER) /Fp$(@:[1]:b) /Fo$(@:[2]:b) $(<:[1]:b)", + ["LIBS"] = "", + ["PROGOPTS"] = "", + ["PROGCOM"] = "$(LD) /nologo @RESPONSE|@|$(_USE_PDB_LINK) $(PROGOPTS) $(LIBPATH:b:p/LIBPATH\\:) $(_USE_MODDEF) $(LIBS) /out:$(@:b) $(<:b:p\n)", + ["LIBOPTS"] = "", + ["LIBCOM"] = "$(LIB) /nologo @RESPONSE|@|$(LIBOPTS) /out:$(@:b) $(<:b:p\n)", + ["PROGPREFIX"] = "", + ["SHLIBLINKSUFFIX"] = ".lib", + ["SHLIBPREFIX"] = "", + ["SHLIBOPTS"] = "", + ["SHLIBCOM"] = "$(LD) /DLL /nologo @RESPONSE|@|$(_USE_PDB_LINK) $(SHLIBOPTS) $(LIBPATH:b:p/LIBPATH\\:) $(_USE_MODDEF) $(LIBS) /out:$(@:b) $(<:b)", + ["AUX_FILES_PROGRAM"] = { "$(@:B:a.exe.manifest)", "$(@:B:a.pdb)", "$(@:B:a.exp)", "$(@:B:a.lib)", "$(@:B:a.ilk)", }, + ["AUX_FILES_SHAREDLIBRARY"] = { "$(@:B:a.dll.manifest)", "$(@:B:a.pdb)", "$(@:B:a.exp)", "$(@:B:a.lib)", "$(@:B:a.ilk)", }, + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/msvc6.lua b/ogl_editor/bin/win32/scripts/tundra/tools/msvc6.lua new file mode 100644 index 0000000..17c3019 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/msvc6.lua @@ -0,0 +1,101 @@ +-- msvc6.lua - Visual Studio 6 + +module(..., package.seeall) + +local native = require "tundra.native" +local os = require "os" + +function path_combine(path, path_to_append) + if path == nil then + return path_to_append + end + if path:find("\\$") then + return path .. path_to_append + end + return path .. "\\" .. path_to_append +end + +function path_it(maybe_list) + if type(maybe_list) == "table" then + return ipairs(maybe_list) + end + return ipairs({maybe_list}) +end + +function apply(env, options) + + if native.host_platform ~= "windows" then + error("the msvc6 toolset only works on windows hosts") + end + + -- Load basic MSVC environment setup first. + -- We're going to replace the paths to some tools. + tundra.unitgen.load_toolset('msvc', env) + + options = options or {} + + -- We'll find any edition of VS (including Express) here + local vs_root = native.reg_query("HKLM", "SOFTWARE\\Microsoft\\VisualStudio\\6.0\\Setup\\Microsoft Visual C++", "ProductDir") + assert(vs_root, "The requested version of Visual Studio isn't installed") + vs_root = string.gsub(vs_root, "\\+$", "\\") + + local common_root = native.reg_query("HKLM", "SOFTWARE\\Microsoft\\VisualStudio\\6.0\\Setup", "VsCommonDir") + assert(common_root, "The requested version of Visual Studio isn't installed") + common_root = string.gsub(common_root, "\\+$", "\\") + + local vc_lib + local vc_bin + + vc_bin = vs_root .. "\\bin" + vc_lib = vs_root .. "\\lib" + + -- Tools + + local cl_exe = '"' .. path_combine(vc_bin, "cl.exe") .. '"' + local lib_exe = '"' .. path_combine(vc_bin, "lib.exe") .. '"' + local link_exe = '"' .. path_combine(vc_bin, "link.exe") .. '"' + local rc_exe = '"' .. path_combine(common_root, "MSDev98\\Bin\\rc.exe") .. '"' + + env:set('CC', cl_exe) + env:set('CXX', cl_exe) + env:set('LIB', lib_exe) + env:set('LD', link_exe) + env:set('RC', rc_exe) + + env:set("RCOPTS", "") -- clear the "/nologo" option (it was first added in VS2010) + + -- Wire-up the external environment + env:set_external_env_var('VSINSTALLDIR', vs_root) + env:set_external_env_var('VCINSTALLDIR', vs_root .. "\\vc") + --env:set_external_env_var('DevEnvDir', vs_root .. "Common7\\IDE") + + do + local include = { + path_combine(vs_root, "ATL\\INCLUDE"), + path_combine(vs_root, "INCLUDE"), + path_combine(vs_root, "MFC\\INCLUDE"), + } + env:set_external_env_var("INCLUDE", table.concat(include, ';')) + end + + do + local lib = { + path_combine(vs_root, "LIB"), + path_combine(vs_root, "MFC\\LIB"), + } + local lib_str = table.concat(lib, ';') + env:set_external_env_var("LIB", lib_str) + env:set_external_env_var("LIBPATH", lib_str) + end + + -- Modify %PATH% + + do + local path = { + path_combine(vs_root, "BIN"), + path_combine(common_root, "MSDev98\\BIN"), + env:get_external_env_var('PATH'), + } + env:set_external_env_var("PATH", table.concat(path, ';')) + end +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/openwatcom.lua b/ogl_editor/bin/win32/scripts/tundra/tools/openwatcom.lua new file mode 100644 index 0000000..849024e --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/openwatcom.lua @@ -0,0 +1,61 @@ +-- openwatcom.lua - Support for the Open Watcom compiler C/C++ compiler + +module(..., package.seeall) + +local native = require "tundra.native" +local os = require "os" + +local function setup(env, options) + + if native.host_platform ~= "windows" then + error("the openwatcom toolset only works on windows hosts") + end + + assert(options, "No Options provided") + local dir = assert(options.InstallDir) + env:set_external_env_var("WATCOM", dir) + env:set_external_env_var("EDPATH", dir .. "\\EDDAT") + env:set_external_env_var("WIPFC", dir .. "\\WIPFC") + local p = native.getenv("PATH") .. ";" .. dir .. "\\BINNT\\;" .. dir .. "\\BINW\\" + print(p) + env:set_external_env_var("PATH", p) + local inc = native.getenv("INCLUDE", "") + if inc then + inc = inc .. ";" + end + env:set_external_env_var("INCLUDE", inc .. dir .. "\\H;" .. dir .. "\\H\\NT;" .. dir .. "\\H\\NT\\DIRECTX;" .. dir .. "\\H\\NT\\DDK") + +end + +function apply(env, options) + -- load the generic C toolset first + tundra.unitgen.load_toolset("generic-cpp", env) + + setup(env, options) + + env:set_many { + ["NATIVE_SUFFIXES"] = { ".c", ".cpp", ".cc", ".cxx", ".lib", ".obj" }, + ["OBJECTSUFFIX"] = ".obj", + ["LIBSUFFIX"] = ".lib", + ["CC"] = "wcl386.exe", + ["CXX"] = "wcl386.exe", + ["LIB"] = "wlib.exe", + ["LD"] = "wlink.exe", + ["CPPDEFS"] = "_WIN32", + ["CCOPTS"] = "-wx -we", + ["_CPPDEFS"] = "$(CPPDEFS:p-d) $(CPPDEFS_$(CURRENT_VARIANT:u):p-d)", + ["_USE_PCH_OPT"] = "", + ["_USE_PCH"] = "", + ["_CCCOM"] = "$(CC) /c @RESPONSE|@|$(_CPPDEFS) $(CPPPATH:b:p-i) $(CCOPTS) $(CCOPTS_$(CURRENT_VARIANT:u)) $(_USE_PCH) -fo=$(@:b) $(<:b)", + ["CCCOM"] = "$(_CCCOM)", + ["CXXCOM"] = "$(_CCCOM)", + ["PCHCOMPILE"] = "", + ["LIBS"] = "", + ["PROGOPTS"] = "", + ["PROGCOM"] = "", -- "$(LD) @RESPONSE|@|$(PROGOPTS) $(LIBS) /out:$(@:b) $(<:b)", + ["LIBOPTS"] = "", + ["LIBCOM"] = "", -- "$(LIB) @RESPONSE|@|$(LIBOPTS) /out:$(@:b) $(<:b)", + ["SHLIBOPTS"] = "", + ["SHLIBCOM"] = "", -- "$(LD) /nologo @RESPONSE|@|$(SHLIBOPTS) $(LIBPATH:b:p/LIBPATH\\:) $(LIBS) /out:$(@:b) $(<:b)", + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/vbcc.lua b/ogl_editor/bin/win32/scripts/tundra/tools/vbcc.lua new file mode 100644 index 0000000..1c43a22 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/vbcc.lua @@ -0,0 +1,36 @@ +module(..., package.seeall) + +local native = require "tundra.native" + +function apply(env, options) + -- load the generic C toolset first + tundra.unitgen.load_toolset("generic-cpp", env) + -- Also add assembly support. + tundra.unitgen.load_toolset("generic-asm", env) + + local vbcc_root = assert(native.getenv("VBCC"), "VBCC environment variable must be set") + + env:set_many { + ["NATIVE_SUFFIXES"] = { ".c", ".cpp", ".cc", ".cxx", ".s", ".asm", ".a", ".o" }, + ["OBJECTSUFFIX"] = ".o", + ["LIBPREFIX"] = "", + ["LIBSUFFIX"] = ".a", + ["VBCC_ROOT"] = vbcc_root, + ["CC"] = vbcc_root .. "$(SEP)bin$(SEP)vc$(HOSTPROGSUFFIX)", + ["LIB"] = vbcc_root .. "$(SEP)bin$(SEP)vlink$(HOSTPROGSUFFIX)", + ["LD"] = vbcc_root .. "$(SEP)bin$(SEP)vc$(HOSTPROGSUFFIX)", + ["ASM"] = vbcc_root .. "$(SEP)bin$(SEP)vasmm68k_mot$(HOSTPROGSUFFIX)", + ["VBCC_SDK_INC"] = vbcc_root .. "$(SEP)include$(SEP)sdk", + ["_OS_CCOPTS"] = "", + ["_OS_CXXOPTS"] = "", + ["CCCOM"] = "$(CC) $(_OS_CCOPTS) -c $(CPPDEFS:p-D) $(CPPPATH:f:p-I) $(CCOPTS) $(CCOPTS_$(CURRENT_VARIANT:u)) -o $(@) $(<)", + ["ASMCOM"] = "$(ASM) -quiet -Fhunk -phxass $(ASMOPTS) $(ASMOPTS_$(CURRENT_VARIANT:u)) $(ASMDEFS:p-D) $(ASMINCPATH:f:p-I) -I$(VBCC_SDK_INC) -o $(@) $(<)", + ["PROGOPTS"] = "", + ["PROGCOM"] = "$(LD) $(PROGOPTS) $(LIBPATH:p-L) $(LIBS:p-l) -o $(@) $(<)", + ["PROGPREFIX"] = "", + ["LIBOPTS"] = "", + ["LIBCOM"] = "$(LIB) @RESPONSE|@|-r $(LIBOPTS) -o $(@) $(<)", + ["ASMINC_KEYWORDS"] = { "INCLUDE", "include" }, + ["ASMINC_BINARY_KEYWORDS"] = { "INCBIN", "incbin" }, + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/win32-rc.lua b/ogl_editor/bin/win32/scripts/tundra/tools/win32-rc.lua new file mode 100644 index 0000000..f546ec2 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/win32-rc.lua @@ -0,0 +1,21 @@ +module(..., package.seeall) + +local path = require("tundra.path") +local depgraph = require("tundra.depgraph") +local gencpp = require("tundra.tools.generic-cpp") + +local function compile_resource_file(env, pass, fn) + return depgraph.make_node { + Env = env, + Label = 'Rc $(@)', + Pass = pass, + Action = "$(RCCOM)", + InputFiles = { fn }, + OutputFiles = { path.make_object_filename(env, fn, env:get('W32RESSUFFIX')) }, + Scanner = gencpp.get_cpp_scanner(env, fn), + } +end + +function apply(env, options) + env:register_implicit_make_fn("rc", compile_resource_file) +end diff --git a/ogl_editor/bin/win32/scripts/tundra/tools/yasm.lua b/ogl_editor/bin/win32/scripts/tundra/tools/yasm.lua new file mode 100644 index 0000000..fe01c55 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/tools/yasm.lua @@ -0,0 +1,12 @@ +module(..., package.seeall) + +function apply(env, options) + -- load the generic assembly toolset first + tundra.unitgen.load_toolset("generic-asm", env) + + env:set_many { + ["YASM"] = "yasm", + ["ASMCOM"] = "$(YASM) -o $(@) $(ASMDEFS:p-D ) $(ASMOPTS) $(<)", + ["ASMINC_KEYWORDS"] = { "%include" }, + } +end diff --git a/ogl_editor/bin/win32/scripts/tundra/unitgen.lua b/ogl_editor/bin/win32/scripts/tundra/unitgen.lua new file mode 100644 index 0000000..ba8b251 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/unitgen.lua @@ -0,0 +1,197 @@ +module(..., package.seeall) + +local util = require "tundra.util" +local buildfile = require "tundra.buildfile" +local decl = require "tundra.decl" +local nodegen = require "tundra.nodegen" + +function match_build_id(id, default) + assert(id) + local i = id:gmatch("[^-]+") + local platform_name, toolset, variant, subvariant = i() or default, i() or default, i() or default, i() or default + return platform_name, toolset, variant, subvariant +end + +local function iter_inherits(config, name) + local tab = config + return function() + while tab do + local my_tab = tab + if not my_tab then break end + tab = my_tab.Inherit + local v = my_tab[name] + if v then return v end + end + end +end + +function load_toolset(id, ...) + -- For non-qualified packages, use a default package + if not id:find("%.") then + id = "tundra.tools." .. id + end + + local pkg, err = require(id) + + if err then + errorf("couldn't load extension module %s: %s", id, err) + end + + pkg.apply(...) +end + +local function setup_env(env, build_data, tuple, build_id) + local config = tuple.Config + local variant_name = tuple.Variant.Name + + if not build_id then + build_id = config.Name .. "-" .. variant_name .. "-" .. tuple.SubVariant + end + + local naked_platform, naked_toolset = match_build_id(build_id) + + env:set("CURRENT_PLATFORM", naked_platform) -- e.g. linux or macosx + env:set("CURRENT_TOOLSET", naked_toolset) -- e.g. gcc or msvc + env:set("CURRENT_VARIANT", tuple.Variant.Name) -- e.g. debug or release + env:set("BUILD_ID", build_id) -- e.g. linux-gcc-debug + env:set("OBJECTDIR", "$(OBJECTROOT)" .. SEP .. "$(BUILD_ID)") + + for tools in iter_inherits(config, "Tools") do + for k, v in pairs(tools) do + if type(k) == "string" then + error("Tools must be a plain array - to include options keys wrap them in their own tables:\n " .. + "e.g. Tools = { { 'foo'; Option = ... }, ... }.\n Your Tools:\n" .. util.tostring(tools)) + end + end + for _, data in ipairs(tools) do + local id, options + + if type(data) == "table" then + id = assert(data[1]) + options = data + data = id + end + + if type(data) == "string" then + load_toolset(data, env, options) + elseif type(data) == "function" then + data(env, options) + else + error("bad parameters") + end + end + end + + -- Incorporate matching values from the build data's Env and ReplaceEnv. + if build_data.Env then + nodegen.append_filtered_env_vars(env, build_data.Env, build_id, false) + end + if build_data.ReplaceEnv then + nodegen.replace_filtered_env_vars(env, build_data.ReplaceEnv, build_id, false) + end + + -- Incorporate matching values from the config's Env and ReplaceEnv. + for env_tab in iter_inherits(config, "Env") do + nodegen.append_filtered_env_vars(env, env_tab, build_id, false) + end + for env_tab in iter_inherits(config, "ReplaceEnv") do + nodegen.replace_filtered_env_vars(env, env_tab, build_id, false) + end + + -- Run post-setup functions. This typically sets up implicit make functions. + env:run_setup_functions() + + return env +end + + + +local function setup_envs(tuple, configs, default_env, build_data) + local result = {} + + local top_env = setup_env(default_env:clone(), build_data, tuple) + result["__default"] = top_env + + -- Use the same build id for all subconfigurations + local build_id = top_env:get("BUILD_ID") + + local cfg = configs[tuple.Config.Name] + for moniker, x in util.nil_pairs(cfg.SubConfigs) do + if result[x] then + croak("duplicate subconfig name: %s", x) + end + local sub_tuple = { Config = configs[x], Variant = tuple.Variant, SubVariant = tuple.SubVariant } + if not sub_tuple.Config then + errorf("%s: no such config (in SubConfigs specification)", x) + end + local sub_env = setup_env(default_env:clone(), build_data, sub_tuple, build_id) + result[moniker] = sub_env + end + return result +end + + +function parse_units(build_tuples, args, passes) + if args.SyntaxExtensions then + print("*WARNING* SyntaxExtensions has been deprecated. Use require instead.") + end + for _, id in util.nil_ipairs(args.SyntaxExtensions) do + require(id) + end + + local function chunk () + local raw_nodes, default_nodes, always_nodes = decl.parse(args.Units or "units.lua") + assert(#default_nodes > 0 or #always_nodes > 0, "no default unit name to build was set") + return { raw_nodes, default_nodes, always_nodes } + end + + local success, result = xpcall(chunk, buildfile.syntax_error_catcher) + + if success then + return result[1], result[2], result[3] + else + print("Build script execution failed") + croak("%s", result or "") + end +end + + +-- Inputs +-- build_tuples - the config/variant/subvariant pairs to include in the DAG +-- args - Raw data from Build() call +-- passes - Passes specified in Build() call +-- configs - Configs specified in Build() call +function generate_dag(build_tuples, args, passes, configs, default_env) + local raw_nodes, default_nodes, always_nodes = parse_units(build_tuples, args, passes) + + local results = {} + + -- Let the nodegen code generate DAG nodes for all active + -- configurations/variants. + for _, tuple in pairs(build_tuples) do + printf("Generating DAG for %s-%s-%s", tuple.Config.Name, tuple.Variant.Name, tuple.SubVariant) + local envs = setup_envs(tuple, configs, default_env, args) + local always_nodes, default_nodes, named_nodes = nodegen.generate_dag { + Envs = envs, + Config = tuple.Config, + Variant = tuple.Variant, + Declarations = raw_nodes, + DefaultNodes = default_nodes, + AlwaysNodes = always_nodes, + Passes = passes, + } + + results[#results + 1] = { + Config = assert(tuple.Config), + Variant = assert(tuple.Variant), + SubVariant = assert(tuple.SubVariant), + AlwaysNodes = always_nodes, + DefaultNodes = default_nodes, + NamedNodes = named_nodes, + } + end + + return raw_nodes, results +end + + diff --git a/ogl_editor/bin/win32/scripts/tundra/util.lua b/ogl_editor/bin/win32/scripts/tundra/util.lua new file mode 100644 index 0000000..c6e7073 --- /dev/null +++ b/ogl_editor/bin/win32/scripts/tundra/util.lua @@ -0,0 +1,360 @@ +local _tostring = tostring +module(..., package.seeall) + +function tostring(value, stack) + local str = '' + stack = stack or {} + + if type(value) ~= 'table' then + if type(value) == 'string' then + str = string.format("%q", value) + else + str = _tostring(value) + end + elseif stack[value] then + return '' + else + stack[value] = true + local auxTable = {} + for k, v in pairs(value) do + auxTable[#auxTable + 1] = k + end + table.sort(auxTable, function (a, b) return _tostring(a) < _tostring(b) end) + + str = str..'{' + local separator = "" + local entry = "" + for index, fieldName in ipairs(auxTable) do + if ((tonumber(fieldName)) and (tonumber(fieldName) > 0)) then + entry = tostring(value[tonumber(fieldName)], stack) + else + entry = tostring(fieldName) .. " = " .. tostring(rawget(value, fieldName), stack) + end + str = str..separator..entry + separator = ", " + end + str = str..'}' + + local mt = getmetatable(value) + if mt then + str = str .. ' @meta = ' .. tostring(mt, stack) + end + end + return str +end + +function map_in_place(t, fn) + for x = 1, #t do + t[x] = fn(t[x]) + end + return t +end + +function map(t, fn) + local result = {} + for idx = 1, #t do + result[idx] = fn(t[idx]) + end + return result +end + +function mapnil(table, fn) + if not table then + return nil + else + return map(table, fn) + end +end + +function get_named_arg(tab, name, context) + local v = tab[name] + if v then + return v + else + if context then + error(context .. ": argument " .. name .. " must be specified", 3) + else + error("argument " .. name .. " must be specified", 3) + end + end +end + +function parse_cmdline(args, blueprint) + local index, max = 2, #args + local options, targets = {}, {} + local lookup = {} + + for _, opt in ipairs(blueprint) do + if opt.Short then + lookup[opt.Short] = opt + end + if opt.Long then + lookup[opt.Long] = opt + end + end + + while index <= max do + local s = args[index] + local key, val + + if s:sub(1, 2) == '--' then + key, val = s:match("^%-%-([-a-zA-Z0-9]+)=(.*)$") + if not key then + key = s:sub(3) + end + elseif s:sub(1, 1) == '-' then + key = s:sub(2,2) + if s:len() > 2 then + val = s:sub(3) + end + else + table.insert(targets, s) + end + + if key then + local opt = lookup[key] + if not opt then + return nil, nil, "Unknown option " .. s + end + if opt.HasValue then + if not val then + index = index + 1 + val = args[index] + end + if val then + options[opt.Name] = val + else + return nil, nil, "Missing value for option "..s + end + else + local v = options[opt.Name] or 0 + options[opt.Name] = v + 1 + end + end + + index = index + 1 + end + + return options, targets +end + +function clone_table(t) + if t then + local r = {} + for k, v in pairs(t) do + r[k] = v + end + for k, v in ipairs(t) do + r[k] = v + end + return r + else + return nil + end +end + +function deep_clone_table(t) + local function clone_value(v) + if type(v) == "table" then + return deep_clone_table(v) + else + return v + end + end + if t then + local r = {} + for k, v in pairs(t) do + r[clone_value(k)] = clone_value(v) + end + for k, v in ipairs(t) do + r[k] = clone_value(v) + end + return r + else + return nil + end +end + +function clone_array(t) + local r = {} + for k, v in ipairs(t) do + r[k] = v + end + return r +end + +function merge_arrays(...) + local result = {} + local count = select('#', ...) + for i = 1, count do + local tab = select(i, ...) + if tab then + for _, v in ipairs(tab) do + result[#result + 1] = v + end + end + end + return result +end + +function merge_arrays_2(a, b) + if a and b then + return merge_arrays(a, b) + elseif a then + return a + elseif b then + return b + else + return {} + end +end + +function matches_any(str, patterns) + for _, pattern in ipairs(patterns) do + if str:match(pattern) then + return true + end + end + return false +end + +function return_nil() +end + +function nil_pairs(t) + if t then + return next, t + else + return return_nil + end +end + +function nil_ipairs(t) + if t then + return ipairs(t) + else + return return_nil + end +end + +function clear_table(tab) + local key, val = next(tab) + while key do + tab[key] = nil + key, val = next(tab, key) + end + return tab +end + +function filter(tab, predicate) + local result = {} + for _, x in ipairs(tab) do + if predicate(x) then + result[#result + 1] = x + end + end + return result +end + +function filter_nil(tab, predicate) + if not predicate then return nil end + local result = {} + for _, x in ipairs(tab) do + if predicate(x) then + result[#result + 1] = x + end + end + return result +end + +function filter_in_place(tab, predicate) + local i, limit = 1, #tab + while i <= limit do + if not predicate(tab[i]) then + table.remove(tab, i) + limit = limit - 1 + else + i = i + 1 + end + end + return tab +end + +function append_table(result, items) + local offset = #result + for i = 1, #items do + result[offset + i] = items[i] + end + return result +end + +function flatten(array) + local function iter(item, accum) + if type(item) == 'table' and not getmetatable(item) then + for _, sub_item in ipairs(item) do + iter(sub_item, accum) + end + else + accum[#accum + 1] = item + end + end + local accum = {} + iter(array, accum) + return accum +end + +function memoize(closure) + local result = nil + return function(...) + if not result then + result = assert(closure(...)) + end + return result + end +end + +function uniq(array) + local seen = {} + local result = {} + for _, val in nil_ipairs(array) do + if not seen[val] then + seen[val] = true + result[#result + 1] = val + end + end + return result +end + +function make_lookup_table(array) + local result = {} + for _, item in nil_ipairs(array) do + result[item] = true + end + return result +end + +function table_keys(array) + local result = {} + for k, _ in nil_pairs(array) do + result[#result + 1] = k + end + return result +end + +function table_values(array) + local result = {} + for _, v in nil_pairs(array) do + result[#result + 1] = v + end + return result +end + +function array_contains(array, find) + for _, val in ipairs(array) do + if val == find then + return true + end + end + return false +end + diff --git a/ogl_editor/bin/win32/t2-inspect.exe b/ogl_editor/bin/win32/t2-inspect.exe new file mode 100644 index 0000000..2b33729 Binary files /dev/null and b/ogl_editor/bin/win32/t2-inspect.exe differ diff --git a/ogl_editor/bin/win32/t2-lua.exe b/ogl_editor/bin/win32/t2-lua.exe new file mode 100644 index 0000000..3224ba6 Binary files /dev/null and b/ogl_editor/bin/win32/t2-lua.exe differ diff --git a/ogl_editor/bin/win32/tundra2.exe b/ogl_editor/bin/win32/tundra2.exe new file mode 100644 index 0000000..d1c91fe Binary files /dev/null and b/ogl_editor/bin/win32/tundra2.exe differ