254 lines
7.4 KiB
Lua
254 lines
7.4 KiB
Lua
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 <platform>-<toolset> 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
|
|
|