2014-09-14 08:36:58 +02:00

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)