premake/modules/d/actions/gmake.lua

400 lines
10 KiB
Lua

--
-- d/actions/gmake.lua
-- Define the D makefile action(s).
-- Copyright (c) 2013-2015 Andrew Gough, Manu Evans, and the Premake project
--
local p = premake
local m = p.modules.d
m.make = {}
local dmake = m.make
require ("gmake")
local make = p.make
local cpp = p.make.cpp
local project = p.project
local config = p.config
local fileconfig = p.fileconfig
-- This check may be unnecessary as we only 'require' this file from d.lua
-- IFF the action already exists, however this may help if this file is
-- directly required, rather than d.lua itself.
local gmake = p.action.get( 'gmake' )
if gmake == nil then
error( "Failed to locate prequisite action 'gmake'" )
end
--
-- Patch the gmake action with the allowed tools...
--
gmake.valid_languages = table.join(gmake.valid_languages, { p.D } )
gmake.valid_tools.dc = { "dmd", "gdc", "ldc" }
function m.make.separateCompilation(prj)
local some = false
local all = true
for cfg in project.eachconfig(prj) do
if cfg.compilationmodel == "File" then
some = true
else
all = false
end
end
return iif(all, "all", iif(some, "some", "none"))
end
--
-- Override the GMake action 'onProject' funtion to provide
-- D knowledge...
--
p.override( gmake, "onProject", function(oldfn, prj)
p.escaper(make.esc)
if project.isd(prj) then
local makefile = make.getmakefilename(prj, true)
p.generate(prj, makefile, m.make.generate)
return
end
oldfn(prj)
end)
p.override( make, "objdir", function(oldfn, cfg)
if cfg.project.language ~= "D" or cfg.compilationmodel == "File" then
oldfn(cfg)
end
end)
p.override( make, "objDirRules", function(oldfn, prj)
if prj.language ~= "D" or m.make.separateCompilation(prj) ~= "none" then
oldfn(prj)
end
end)
--
-- Don't create $(OBJDIR) in the d-file rules
--
p.override(make, "objDirInFileRules", function(oldfn, prj, node)
-- node is nil when making pch rules
if not node or not path.isdfile(node.abspath) then
oldfn(prj, node)
end
end)
---
-- Add namespace for element definition lists for p.callarray()
---
m.elements = {}
--
-- Generate a GNU make C++ project makefile, with support for the new platforms API.
--
m.elements.makefile = function(prj)
return {
make.header,
make.phonyRules,
m.make.configs,
m.make.objects, -- TODO: This is basically identical to make.cppObjects(), and should ideally be merged/shared
make.shellType,
m.make.targetRules,
make.targetDirRules,
make.objDirRules,
make.cppCleanRules, -- D clean code is identical to C/C++
make.preBuildRules,
make.preLinkRules,
m.make.dFileRules,
}
end
function m.make.generate(prj)
p.callArray(m.elements.makefile, prj)
end
function m.make.buildRule(prj)
_p('$(TARGET): $(SOURCEFILES) $(LDDEPS)')
_p('\t@echo Building %s', prj.name)
_p('\t$(SILENT) $(BUILDCMD)')
_p('\t$(POSTBUILDCMDS)')
end
function m.make.linkRule(prj)
_p('$(TARGET): $(OBJECTS) $(LDDEPS)')
_p('\t@echo Linking %s', prj.name)
_p('\t$(SILENT) $(LINKCMD)')
_p('\t$(POSTBUILDCMDS)')
end
function m.make.targetRules(prj)
local separateCompilation = m.make.separateCompilation(prj)
if separateCompilation == "all" then
m.make.linkRule(prj)
elseif separateCompilation == "none" then
m.make.buildRule(prj)
else
for cfg in project.eachconfig(prj) do
_x('ifeq ($(config),%s)', cfg.shortname)
if cfg.compilationmodel == "File" then
m.make.linkRule(prj)
else
m.make.buildRule(prj)
end
_p('endif')
end
end
_p('')
end
function m.make.dFileRules(prj)
local separateCompilation = m.make.separateCompilation(prj)
if separateCompilation ~= "none" then
make.cppFileRules(prj)
end
end
--
-- Override the 'standard' file rule to support D source files
--
p.override(cpp, "standardFileRules", function(oldfn, prj, node)
-- D file
if path.isdfile(node.abspath) then
_p('\t$(SILENT) $(DC) $(ALL_DFLAGS) $(OUTPUTFLAG) -c $<')
else
oldfn(prj, node)
end
end)
--
-- Let make know it can compile D source files
--
p.override(make, "fileType", function(oldfn, node)
if path.isdfile(node.abspath) then
return "objects"
else
return oldfn(node)
end
end)
--
-- Write out the settings for a particular configuration.
--
m.elements.makeconfig = function(cfg)
return {
m.make.dTools,
make.target,
m.make.target,
make.objdir,
m.make.versions,
m.make.debug,
m.make.imports,
m.make.stringImports,
m.make.dFlags,
make.libs,
make.ldDeps,
make.ldFlags,
m.make.linkCmd,
make.preBuildCmds,
make.preLinkCmds,
make.postBuildCmds,
m.make.allRules,
make.settings,
}
end
function m.make.configs(prj)
for cfg in project.eachconfig(prj) do
-- identify the toolset used by this configurations (would be nicer if
-- this were computed and stored with the configuration up front)
local toolset = p.tools[_OPTIONS.dc or cfg.toolset or "dmd"]
if not toolset then
error("Invalid toolset '" + (_OPTIONS.dc or cfg.toolset) + "'")
end
_x('ifeq ($(config),%s)', cfg.shortname)
p.callArray(m.elements.makeconfig, cfg, toolset)
_p('endif')
_p('')
end
end
function m.make.dTools(cfg, toolset)
local tool = toolset.gettoolname(cfg, "dc")
if tool then
_p(' DC = %s', tool)
end
end
function m.make.target(cfg, toolset)
if cfg.compilationmodel == "File" then
_p(' OUTPUTFLAG = %s', toolset.gettarget('"$@"'))
end
end
function m.make.versions(cfg, toolset)
_p(' VERSIONS +=%s', make.list(toolset.getversions(cfg.versionconstants, cfg.versionlevel)))
end
function m.make.debug(cfg, toolset)
_p(' DEBUG +=%s', make.list(toolset.getdebug(cfg.debugconstants, cfg.debuglevel)))
end
function m.make.imports(cfg, toolset)
local imports = p.esc(toolset.getimportdirs(cfg, cfg.importdirs))
_p(' IMPORTS +=%s', make.list(imports))
end
function m.make.stringImports(cfg, toolset)
local stringImports = p.esc(toolset.getstringimportdirs(cfg, cfg.stringimportdirs))
_p(' STRINGIMPORTS +=%s', make.list(stringImports))
end
function m.make.dFlags(cfg, toolset)
_p(' ALL_DFLAGS += $(DFLAGS)%s $(VERSIONS) $(DEBUG) $(IMPORTS) $(STRINGIMPORTS) $(ARCH)', make.list(table.join(toolset.getdflags(cfg), cfg.buildoptions)))
end
function m.make.linkCmd(cfg, toolset)
if cfg.compilationmodel == "File" then
_p(' LINKCMD = $(DC) ' .. toolset.gettarget("$(TARGET)") .. ' $(ALL_LDFLAGS) $(LIBS) $(OBJECTS)')
-- local cc = iif(p.languages.isc(cfg.language), "CC", "CXX")
-- _p(' LINKCMD = $(%s) -o $(TARGET) $(OBJECTS) $(RESOURCES) $(ARCH) $(ALL_LDFLAGS) $(LIBS)', cc)
else
_p(' BUILDCMD = $(DC) ' .. toolset.gettarget("$(TARGET)") .. ' $(ALL_DFLAGS) $(ALL_LDFLAGS) $(LIBS) $(SOURCEFILES)')
end
end
function m.make.allRules(cfg, toolset)
-- TODO: The C++ version has some special cases for OSX and Windows... check whether they should be here too?
if cfg.compilationmodel == "File" then
_p('all: $(TARGETDIR) $(OBJDIR) prebuild prelink $(TARGET)')
else
_p('all: $(TARGETDIR) prebuild prelink $(TARGET)')
end
_p('\t@:')
-- _p('')
end
--
-- List the objects file for the project, and each configuration.
--
-- TODO: This is basically identical to make.cppObjects(), and should ideally be merged/shared
function m.make.objects(prj)
-- create lists for intermediate files, at the project level and
-- for each configuration
local root = { sourcefiles={}, objects={} }
local configs = {}
for cfg in project.eachconfig(prj) do
configs[cfg] = { sourcefiles={}, objects={} }
end
-- now walk the list of files in the project
local tr = project.getsourcetree(prj)
p.tree.traverse(tr, {
onleaf = function(node, depth)
-- figure out what configurations contain this file, and
-- if it uses custom build rules
local incfg = {}
local inall = true
local custom = false
for cfg in project.eachconfig(prj) do
local filecfg = fileconfig.getconfig(node, cfg)
if filecfg and not filecfg.flags.ExcludeFromBuild then
incfg[cfg] = filecfg
custom = fileconfig.hasCustomBuildRule(filecfg)
else
inall = false
end
end
if not custom then
-- skip files that aren't compiled
if not path.isdfile(node.abspath) then
return
end
local sourcename = node.relpath
-- TODO: assign a unique object file name to avoid collisions
local objectname = "$(OBJDIR)/" .. node.objname .. ".o"
-- if this file exists in all configurations, write it to
-- the project's list of files, else add to specific cfgs
if inall then
table.insert(root.sourcefiles, sourcename)
table.insert(root.objects, objectname)
else
for cfg in project.eachconfig(prj) do
if incfg[cfg] then
table.insert(configs[cfg].sourcefiles, sourcename)
table.insert(configs[cfg].objects, objectname)
end
end
end
else
for cfg in project.eachconfig(prj) do
local filecfg = incfg[cfg]
if filecfg then
-- if the custom build outputs an object file, add it to
-- the link step automatically to match Visual Studio
local output = project.getrelative(prj, filecfg.buildoutputs[1])
if path.isobjectfile(output) then
table.insert(configs[cfg].objects, output)
end
end
end
end
end
})
local separateCompilation = m.make.separateCompilation(prj)
-- now I can write out the lists, project level first...
function listobjects(var, list)
_p('%s \\', var)
for _, objectname in ipairs(list) do
_x('\t%s \\', objectname)
end
_p('')
end
if separateCompilation ~= "all" then
listobjects('SOURCEFILES :=', root.sourcefiles)
end
if separateCompilation ~= "none" then
listobjects('OBJECTS :=', root.objects, 'o')
end
-- ...then individual configurations, as needed
for cfg in project.eachconfig(prj) do
local files = configs[cfg]
if (#files.sourcefiles > 0 and separateCompilation ~= "all") or (#files.objects > 0 and separateCompilation ~= "none") then
_x('ifeq ($(config),%s)', cfg.shortname)
if #files.sourcefiles > 0 and separateCompilation ~= "all" then
listobjects(' SOURCEFILES +=', files.sourcefiles)
end
if #files.objects > 0 and separateCompilation ~= "none" then
listobjects(' OBJECTS +=', files.objects)
end
_p('endif')
end
end
_p('')
end