313 lines
9.6 KiB
Lua
313 lines
9.6 KiB
Lua
-- 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)
|