1-- 2-- gmake2.lua 3-- (c) 2016-2017 Jason Perkins, Blizzard Entertainment and the Premake project 4-- 5 6 local p = premake 7 local project = p.project 8 9 p.modules.gmake2 = {} 10 p.modules.gmake2._VERSION = p._VERSION 11 local gmake2 = p.modules.gmake2 12 13-- 14-- Write out the default configuration rule for a workspace or project. 15-- 16-- @param target 17-- The workspace or project object for which a makefile is being generated. 18-- 19 20 function gmake2.defaultconfig(target) 21 -- find the right configuration iterator function for this object 22 local eachconfig = iif(target.project, project.eachconfig, p.workspace.eachconfig) 23 local defaultconfig = nil 24 25 -- find the right default configuration platform, grab first configuration that matches 26 if target.defaultplatform then 27 for cfg in eachconfig(target) do 28 if cfg.platform == target.defaultplatform then 29 defaultconfig = cfg 30 break 31 end 32 end 33 end 34 35 -- grab the first configuration and write the block 36 if not defaultconfig then 37 local iter = eachconfig(target) 38 defaultconfig = iter() 39 end 40 41 if defaultconfig then 42 _p('ifndef config') 43 _x(' config=%s', defaultconfig.shortname) 44 _p('endif') 45 _p('') 46 end 47 end 48 49 50--- 51-- Escape a string so it can be written to a makefile. 52--- 53 54 function gmake2.esc(value) 55 result = value:gsub("\\", "\\\\") 56 result = result:gsub("\"", "\\\"") 57 result = result:gsub(" ", "\\ ") 58 result = result:gsub("%(", "\\(") 59 result = result:gsub("%)", "\\)") 60 61 -- leave $(...) shell replacement sequences alone 62 result = result:gsub("$\\%((.-)\\%)", "$(%1)") 63 return result 64 end 65 66 67-- 68-- Get the makefile file name for a workspace or a project. If this object is the 69-- only one writing to a location then I can use "Makefile". If more than one object 70-- writes to the same location I use name + ".make" to keep it unique. 71-- 72 73 function gmake2.getmakefilename(this, searchprjs) 74 local count = 0 75 for wks in p.global.eachWorkspace() do 76 if wks.location == this.location then 77 count = count + 1 78 end 79 80 if searchprjs then 81 for _, prj in ipairs(wks.projects) do 82 if prj.location == this.location then 83 count = count + 1 84 end 85 end 86 end 87 end 88 89 if count == 1 then 90 return "Makefile" 91 else 92 return ".make" 93 end 94 end 95 96 97-- 98-- Output a makefile header. 99-- 100-- @param target 101-- The workspace or project object for which the makefile is being generated. 102-- 103 104 function gmake2.header(target) 105 local kind = iif(target.project, "project", "workspace") 106 107 _p('# %s %s makefile autogenerated by Premake', p.action.current().shortname, kind) 108 _p('') 109 110 gmake2.defaultconfig(target) 111 112 _p('ifndef verbose') 113 _p(' SILENT = @') 114 _p('endif') 115 _p('') 116 end 117 118 119-- 120-- Rules for file ops based on the shell type. Can't use defines and $@ because 121-- it screws up the escaping of spaces and parethesis (anyone know a fix?) 122-- 123 124 function gmake2.mkdir(dirname) 125 _p('ifeq (posix,$(SHELLTYPE))') 126 _p('\t$(SILENT) mkdir -p %s', dirname) 127 _p('else') 128 _p('\t$(SILENT) mkdir $(subst /,\\\\,%s)', dirname) 129 _p('endif') 130 end 131 132 function gmake2.mkdirRules(dirname) 133 _p('%s:', dirname) 134 _p('\t@echo Creating %s', dirname) 135 gmake2.mkdir(dirname) 136 _p('') 137 end 138 139-- 140-- Format a list of values to be safely written as part of a variable assignment. 141-- 142 143 function gmake2.list(value, quoted) 144 quoted = false 145 if #value > 0 then 146 if quoted then 147 local result = "" 148 for _, v in ipairs (value) do 149 if #result then 150 result = result .. " " 151 end 152 result = result .. p.quoted(v) 153 end 154 return result 155 else 156 return " " .. table.concat(value, " ") 157 end 158 else 159 return "" 160 end 161 end 162 163 164-- 165-- Convert an arbitrary string (project name) to a make variable name. 166-- 167 168 function gmake2.tovar(value) 169 value = value:gsub("[ -]", "_") 170 value = value:gsub("[()]", "") 171 return value 172 end 173 174 175 176 function gmake2.path(cfg, value) 177 cfg = cfg.project or cfg 178 local dirs = path.translate(project.getrelative(cfg, value)) 179 180 if type(dirs) == 'table' then 181 dirs = table.filterempty(dirs) 182 end 183 184 return dirs 185 end 186 187 188 function gmake2.getToolSet(cfg) 189 local default = iif(cfg.system == p.MACOSX, "clang", "gcc") 190 local toolset = p.tools[_OPTIONS.cc or cfg.toolset or default] 191 if not toolset then 192 error("Invalid toolset '" .. cfg.toolset .. "'") 193 end 194 return toolset 195 end 196 197 198 function gmake2.outputSection(prj, callback) 199 local root = {} 200 201 for cfg in project.eachconfig(prj) do 202 -- identify the toolset used by this configurations (would be nicer if 203 -- this were computed and stored with the configuration up front) 204 205 local toolset = gmake2.getToolSet(cfg) 206 207 local settings = {} 208 local funcs = callback(cfg) 209 for i = 1, #funcs do 210 local c = p.capture(function () 211 funcs[i](cfg, toolset) 212 end) 213 if #c > 0 then 214 table.insert(settings, c) 215 end 216 end 217 218 if not root.settings then 219 root.settings = table.arraycopy(settings) 220 else 221 root.settings = table.intersect(root.settings, settings) 222 end 223 224 root[cfg] = settings 225 end 226 227 if #root.settings > 0 then 228 for _, v in ipairs(root.settings) do 229 p.outln(v) 230 end 231 p.outln('') 232 end 233 234 local first = true 235 for cfg in project.eachconfig(prj) do 236 local settings = table.difference(root[cfg], root.settings) 237 if #settings > 0 then 238 if first then 239 _x('ifeq ($(config),%s)', cfg.shortname) 240 first = false 241 else 242 _x('else ifeq ($(config),%s)', cfg.shortname) 243 end 244 245 for k, v in ipairs(settings) do 246 p.outln(v) 247 end 248 249 _p('') 250 end 251 end 252 253 if not first then 254 p.outln('endif') 255 p.outln('') 256 end 257 end 258 259 260 -- convert a rule property into a string 261 262 function gmake2.expandRuleString(rule, prop, value) 263 -- list? 264 if type(value) == "table" then 265 if #value > 0 then 266 if prop.switch then 267 return prop.switch .. table.concat(value, " " .. prop.switch) 268 else 269 prop.separator = prop.separator or " " 270 return table.concat(value, prop.separator) 271 end 272 else 273 return nil 274 end 275 end 276 277 -- bool just emits the switch 278 if prop.switch and type(value) == "boolean" then 279 if value then 280 return prop.switch 281 else 282 return nil 283 end 284 end 285 286 local switch = prop.switch or "" 287 288 -- enum? 289 if prop.values then 290 value = table.findKeyByValue(prop.values, value) 291 if value == nil then 292 value = "" 293 end 294 end 295 296 -- primitive 297 value = tostring(value) 298 if #value > 0 then 299 return switch .. value 300 else 301 return nil 302 end 303 end 304 305 306 function gmake2.prepareEnvironment(rule, environ, cfg) 307 for _, prop in ipairs(rule.propertydefinition) do 308 local fld = p.rule.getPropertyField(rule, prop) 309 local value = cfg[fld.name] 310 if value ~= nil then 311 312 if fld.kind == "path" then 313 value = gmake2.path(cfg, value) 314 elseif fld.kind == "list:path" then 315 value = gmake2.path(cfg, value) 316 end 317 318 value = gmake2.expandRuleString(rule, prop, value) 319 if value ~= nil and #value > 0 then 320 environ[prop.name] = p.esc(value) 321 end 322 end 323 end 324 end 325 326 327--------------------------------------------------------------------------- 328-- 329-- Handlers for the individual makefile elements that can be shared 330-- between the different language projects. 331-- 332--------------------------------------------------------------------------- 333 334 function gmake2.phonyRules(prj) 335 _p('.PHONY: clean prebuild') 336 _p('') 337 end 338 339 340 function gmake2.shellType() 341 _p('SHELLTYPE := posix') 342 _p('ifeq (.exe,$(findstring .exe,$(ComSpec)))') 343 _p('\tSHELLTYPE := msdos') 344 _p('endif') 345 _p('') 346 end 347 348 349 function gmake2.target(cfg, toolset) 350 p.outln('TARGETDIR = ' .. project.getrelative(cfg.project, cfg.buildtarget.directory)) 351 p.outln('TARGET = $(TARGETDIR)/' .. cfg.buildtarget.name) 352 end 353 354 355 function gmake2.objdir(cfg, toolset) 356 p.outln('OBJDIR = ' .. project.getrelative(cfg.project, cfg.objdir)) 357 end 358 359 360 function gmake2.settings(cfg, toolset) 361 if #cfg.makesettings > 0 then 362 for _, value in ipairs(cfg.makesettings) do 363 p.outln(value) 364 end 365 end 366 367 local value = toolset.getmakesettings(cfg) 368 if value then 369 p.outln(value) 370 end 371 end 372 373 374 function gmake2.buildCmds(cfg, event) 375 _p('define %sCMDS', event:upper()) 376 local steps = cfg[event .. "commands"] 377 local msg = cfg[event .. "message"] 378 if #steps > 0 then 379 steps = os.translateCommandsAndPaths(steps, cfg.project.basedir, cfg.project.location) 380 msg = msg or string.format("Running %s commands", event) 381 _p('\t@echo %s', msg) 382 _p('\t%s', table.implode(steps, "", "", "\n\t")) 383 end 384 _p('endef') 385 end 386 387 388 function gmake2.preBuildCmds(cfg, toolset) 389 gmake2.buildCmds(cfg, "prebuild") 390 end 391 392 393 function gmake2.preLinkCmds(cfg, toolset) 394 gmake2.buildCmds(cfg, "prelink") 395 end 396 397 398 function gmake2.postBuildCmds(cfg, toolset) 399 gmake2.buildCmds(cfg, "postbuild") 400 end 401 402 403 function gmake2.targetDirRules(cfg, toolset) 404 gmake2.mkdirRules("$(TARGETDIR)") 405 end 406 407 408 function gmake2.objDirRules(cfg, toolset) 409 gmake2.mkdirRules("$(OBJDIR)") 410 end 411 412 413 function gmake2.preBuildRules(cfg, toolset) 414 _p('prebuild: | $(OBJDIR)') 415 _p('\t$(PREBUILDCMDS)') 416 _p('') 417 end 418 419 420 421 include("gmake2_cpp.lua") 422 include("gmake2_csharp.lua") 423 include("gmake2_makefile.lua") 424 include("gmake2_utility.lua") 425 include("gmake2_workspace.lua") 426 427 return gmake2 428