-- -- GENie - Project generator tool -- https://github.com/bkaradzic/GENie#license -- premake.ninja.cpp = { } local ninja = premake.ninja local cpp = premake.ninja.cpp local p = premake local function wrap_ninja_cmd(c) if os.is("windows") then return 'cmd /c "' .. c .. '"' else return c end end -- generate project + config build file function ninja.generate_cpp(prj) local pxy = ninja.get_proxy("prj", prj) local tool = premake.gettool(prj) -- build a list of supported target platforms that also includes a generic build local platforms = premake.filterplatforms(prj.solution, tool.platforms, "Native") for _, platform in ipairs(platforms) do for cfg in p.eachconfig(pxy, platform) do p.generate(cfg, cfg:getprojectfilename(), function() cpp.generate_config(prj, cfg) end) end end end function cpp.generate_config(prj, cfg) local tool = premake.gettool(prj) _p('# Ninja build project file autogenerated by GENie') _p('# https://github.com/bkaradzic/GENie') _p("") -- needed for implicit outputs, introduced in 1.7 _p("ninja_required_version = 1.7") _p("") local flags = { defines = ninja.list(tool.getdefines(cfg.defines)), includes = ninja.list(table.join(tool.getincludedirs(cfg.includedirs), tool.getquoteincludedirs(cfg.userincludedirs), tool.getsystemincludedirs(cfg.systemincludedirs))), cppflags = ninja.list(tool.getcppflags(cfg)), asmflags = ninja.list(table.join(tool.getcflags(cfg), cfg.buildoptions, cfg.buildoptions_asm)), cflags = ninja.list(table.join(tool.getcflags(cfg), cfg.buildoptions, cfg.buildoptions_c)), cxxflags = ninja.list(table.join(tool.getcflags(cfg), tool.getcxxflags(cfg), cfg.buildoptions, cfg.buildoptions_cpp)), objcflags = ninja.list(table.join(tool.getcflags(cfg), tool.getcxxflags(cfg), cfg.buildoptions, cfg.buildoptions_objc)), } _p("") _p("# core rules for " .. cfg.name) _p("rule cc") _p(" command = " .. wrap_ninja_cmd(tool.cc .. " $defines $includes $flags -MMD -MF $out.d -c -o $out $in")) _p(" description = cc $out") _p(" depfile = $out.d") _p(" deps = gcc") _p("") _p("rule cxx") _p(" command = " .. wrap_ninja_cmd(tool.cxx .. " $defines $includes $flags -MMD -MF $out.d -c -o $out $in")) _p(" description = cxx $out") _p(" depfile = $out.d") _p(" deps = gcc") _p("") if cfg.flags.UseObjectResponseFile then _p("rule ar") _p(" command = " .. wrap_ninja_cmd(tool.ar .. " $flags $out @$out.rsp " .. (os.is("MacOSX") and " 2>&1 > /dev/null | sed -e '/.o) has no symbols$$/d'" or ""))) _p(" description = ar $out") _p(" rspfile = $out.rsp") _p(" rspfile_content = $in $libs") _p("") else _p("rule ar") _p(" command = " .. wrap_ninja_cmd(tool.ar .. " $flags $out $in $libs" .. (os.is("MacOSX") and " 2>&1 > /dev/null | sed -e '/.o) has no symbols$$/d'" or ""))) _p(" description = ar $out") _p("") end local link = iif(cfg.language == "C", tool.cc, tool.cxx) _p("rule link") local startgroup = '' local endgroup = '' if (cfg.flags.LinkSupportCircularDependencies) then startgroup = '-Wl,--start-group' endgroup = '-Wl,--end-group' end local rspfile_content = "$all_outputfiles $walibs " .. string.format("%s $libs %s", startgroup, endgroup) if cfg.flags.UseLDResponseFile then _p(" command = " .. wrap_ninja_cmd("$pre_link " .. link .. " -o $out @$out.rsp $all_ldflags $post_build")) _p(" description = link $out") _p(" rspfile = $out.rsp") _p(" rspfile_content = %s", rspfile_content) _p("") else _p(" command = " .. wrap_ninja_cmd("$pre_link " .. link .. " -o $out " .. rspfile_content .. " $all_ldflags $post_build")) _p(" description = link $out") _p("") end _p("rule exec") _p(" command = " .. wrap_ninja_cmd("$command")) _p(" description = Run $type commands") _p("") if #cfg.prebuildcommands > 0 then _p("build __prebuildcommands_" .. premake.esc(prj.name) .. ": exec") _p(1, "command = " .. wrap_ninja_cmd("echo Running pre-build commands && " .. table.implode(cfg.prebuildcommands, "", "", " && "))) _p(1, "type = pre-build") _p("") end cfg.pchheader_full = cfg.pchheader for _, incdir in ipairs(cfg.includedirs) do -- convert this back to an absolute path for os.isfile() local abspath = path.getabsolute(path.join(cfg.project.location, cfg.shortname, incdir)) local testname = path.join(abspath, cfg.pchheader_full) if os.isfile(testname) then cfg.pchheader_full = path.getrelative(cfg.location, testname) break end end cpp.custombuildtask(prj, cfg) cpp.dependencyRules(prj, cfg) cpp.file_rules(prj, cfg, flags) local objfiles = {} for _, file in ipairs(cfg.files) do if path.issourcefile(file) then table.insert(objfiles, cpp.objectname(cfg, file)) end end _p('') cpp.linker(prj, cfg, objfiles, tool, flags) _p("") end function cpp.custombuildtask(prj, cfg) local cmd_index = 1 local seen_commands = {} local command_by_name = {} local command_files = {} local prebuildsuffix = #cfg.prebuildcommands > 0 and "||__prebuildcommands_" .. premake.esc(prj.name) or "" for _, custombuildtask in ipairs(prj.custombuildtask or {}) do for _, buildtask in ipairs(custombuildtask or {}) do for _, cmd in ipairs(buildtask[4] or {}) do local num = 1 -- replace dependencies in the command with actual file paths for _, depdata in ipairs(buildtask[3] or {}) do cmd = string.gsub(cmd,"%$%(" .. num .."%)", string.format("%s ", path.getrelative(cfg.location, depdata))) num = num + 1 end -- replace $(<) and $(@) with $in and $out cmd = string.gsub(cmd, '%$%(<%)', '$in') cmd = string.gsub(cmd, '%$%(@%)', '$out') local cmd_name -- shortened command name -- generate shortened rule names for the command, may be nonsensical -- in some cases but it will at least be unique. if seen_commands[cmd] == nil then local _, _, name = string.find(cmd, '([.%w]+)%s') name = 'cmd' .. cmd_index .. '_' .. string.gsub(name, '[^%w]', '_') seen_commands[cmd] = { name = name, index = cmd_index, } cmd_index = cmd_index + 1 cmd_name = name else cmd_name = seen_commands[cmd].name end local index = seen_commands[cmd].index if command_files[index] == nil then command_files[index] = {} end local cmd_set = command_files[index] table.insert(cmd_set, { buildtask[1], buildtask[2], buildtask[3], seen_commands[cmd].name, }) command_files[index] = cmd_set command_by_name[cmd_name] = cmd end end end _p("# custom build rules") for command, details in pairs(seen_commands) do _p("rule " .. details.name) _p(1, "command = " .. wrap_ninja_cmd(command)) end for cmd_index, cmdsets in ipairs(command_files) do for _, cmdset in ipairs(cmdsets) do local file_in = path.getrelative(cfg.location, cmdset[1]) local file_out = path.getrelative(cfg.location, cmdset[2]) local deps = '' for i, dep in ipairs(cmdset[3]) do deps = deps .. path.getrelative(cfg.location, dep) .. ' ' end _p("build " .. file_out .. ': ' .. cmdset[4] .. ' ' .. file_in .. ' | ' .. deps .. prebuildsuffix) _p("") end end end function cpp.dependencyRules(prj, cfg) local extra_deps = {} local order_deps = {} local extra_flags = {} for _, dependency in ipairs(prj.dependency or {}) do for _, dep in ipairs(dependency or {}) do -- This is assuming that the depending object is (going to be) an .o file local objfilename = cpp.objectname(cfg, path.getrelative(prj.location, dep[1])) local dependency = path.getrelative(cfg.location, dep[2]) -- ensure a table exists for the dependent object file if extra_deps[objfilename] == nil then extra_deps[objfilename] = {} end table.insert(extra_deps[objfilename], dependency) end end local pchfilename = cfg.pchheader_full and cpp.pchname(cfg, cfg.pchheader_full) or '' for _, file in ipairs(cfg.files) do local objfilename = file == cfg.pchheader and cpp.pchname(cfg, file) or cpp.objectname(cfg, file) if path.issourcefile(file) or file == cfg.pchheader then if #cfg.prebuildcommands > 0 then if order_deps[objfilename] == nil then order_deps[objfilename] = {} end table.insert(order_deps[objfilename], '__prebuildcommands_' .. premake.esc(prj.name)) end end if path.issourcefile(file) then if cfg.pchheader_full and not cfg.flags.NoPCH then local nopch = table.icontains(prj.nopch, file) if not nopch then local suffix = path.isobjcfile(file) and '_objc' or '' if extra_deps[objfilename] == nil then extra_deps[objfilename] = {} end table.insert(extra_deps[objfilename], pchfilename .. suffix .. ".gch") if extra_flags[objfilename] == nil then extra_flags[objfilename] = {} end table.insert(extra_flags[objfilename], '-include ' .. pchfilename .. suffix) end end end end -- store prepared deps for file_rules() phase cfg.extra_deps = extra_deps cfg.order_deps = order_deps cfg.extra_flags = extra_flags end function cpp.objectname(cfg, file) return path.join(cfg.objectsdir, path.trimdots(path.removeext(file)) .. ".o") end function cpp.pchname(cfg, file) return path.join(cfg.objectsdir, path.trimdots(file)) end function cpp.file_rules(prj,cfg, flags) _p("# build files") for _, file in ipairs(cfg.files) do _p("# FILE: " .. file) if cfg.pchheader_full == file then local pchfilename = cpp.pchname(cfg, file) local extra_deps = #cfg.extra_deps and '| ' .. table.concat(cfg.extra_deps[pchfilename] or {}, ' ') or '' local order_deps = #cfg.order_deps and '|| ' .. table.concat(cfg.order_deps[pchfilename] or {}, ' ') or '' local extra_flags = #cfg.extra_flags and ' ' .. table.concat(cfg.extra_flags[pchfilename] or {}, ' ') or '' _p("build " .. pchfilename .. ".gch : cxx " .. file .. extra_deps .. order_deps) _p(1, "flags = " .. flags['cxxflags'] .. extra_flags .. iif(prj.language == "C", "-x c-header", "-x c++-header")) _p(1, "includes = " .. flags.includes) _p(1, "defines = " .. flags.defines) _p("build " .. pchfilename .. "_objc.gch : cxx " .. file .. extra_deps .. order_deps) _p(1, "flags = " .. flags['objcflags'] .. extra_flags .. iif(prj.language == "C", "-x objective-c-header", "-x objective-c++-header")) _p(1, "includes = " .. flags.includes) _p(1, "defines = " .. flags.defines) elseif path.issourcefile(file) then local objfilename = cpp.objectname(cfg, file) local extra_deps = #cfg.extra_deps and '| ' .. table.concat(cfg.extra_deps[objfilename] or {}, ' ') or '' local order_deps = #cfg.order_deps and '|| ' .. table.concat(cfg.order_deps[objfilename] or {}, ' ') or '' local extra_flags = #cfg.extra_flags and ' ' .. table.concat(cfg.extra_flags[objfilename] or {}, ' ') or '' local cflags = "cflags" if path.isobjcfile(file) then _p("build " .. objfilename .. ": cxx " .. file .. extra_deps .. order_deps) cflags = "objcflags" elseif path.isasmfile(file) then _p("build " .. objfilename .. ": cc " .. file .. extra_deps .. order_deps) cflags = "asmflags" elseif path.iscfile(file) and not cfg.options.ForceCPP then _p("build " .. objfilename .. ": cc " .. file .. extra_deps .. order_deps) else _p("build " .. objfilename .. ": cxx " .. file .. extra_deps .. order_deps) cflags = "cxxflags" end _p(1, "flags = " .. flags[cflags] .. extra_flags) _p(1, "includes = " .. flags.includes) _p(1, "defines = " .. flags.defines) elseif path.isresourcefile(file) then -- TODO end end _p("") end function cpp.linker(prj, cfg, objfiles, tool) local all_ldflags = ninja.list(table.join(tool.getlibdirflags(cfg), tool.getldflags(cfg), cfg.linkoptions)) local prebuildsuffix = #cfg.prebuildcommands > 0 and ("||__prebuildcommands_" .. premake.esc(prj.name)) or "" local libs = {} local walibs = {} local lddeps = {} if #cfg.wholearchive > 0 then for _, linkcfg in ipairs(premake.getlinks(cfg, "siblings", "object")) do local linkpath = path.rebase(linkcfg.linktarget.fullpath, linkcfg.location, cfg.location) table.insert(lddeps, linkpath) if table.icontains(cfg.wholearchive, linkcfg.project.name) then table.insert(walibs, table.concat(tool.wholearchive(linkpath), ' ')) else table.insert(libs, linkpath) end end else lddeps = premake.getlinks(cfg, "siblings", "fullpath") libs = lddeps end lddeps = ninja.list(lddeps) libs = ninja.list(libs) .. " " .. ninja.list(tool.getlinkflags(cfg)) walibs = ninja.list(walibs) local function writevars() _p(1, "all_ldflags = " .. all_ldflags) _p(1, "libs = " .. libs) _p(1, "walibs = " .. walibs) _p(1, "all_outputfiles = " .. table.concat(objfiles, " ")) if #cfg.prelinkcommands > 0 then _p(1, 'pre_link = echo Running pre-link commands && ' .. table.implode(cfg.prelinkcommands, "", "", " && ") .. " && ") end if #cfg.postbuildcommands > 0 then _p(1, 'post_build = && echo Running post-build commands && ' .. table.implode(cfg.postbuildcommands, "", "", " && ")) end end if cfg.kind == "StaticLib" then local ar_flags = ninja.list(tool.getarchiveflags(cfg, cfg, false)) _p("# link static lib") _p("build " .. cfg:getoutputfilename() .. ": ar " .. table.concat(objfiles, " ") .. " | " .. lddeps .. prebuildsuffix) _p(1, "flags = " .. ninja.list(tool.getarchiveflags(cfg, cfg, false))) _p(1, "all_outputfiles = " .. table.concat(objfiles, " ")) elseif cfg.kind == "SharedLib" or cfg.kind == "Bundle" then local output = cfg:getoutputfilename() _p("# link shared lib") _p("build " .. output .. ": link " .. table.concat(objfiles, " ") .. " | " .. lddeps .. prebuildsuffix) writevars() elseif (cfg.kind == "ConsoleApp") or (cfg.kind == "WindowedApp") then _p("# link executable") _p("build " .. cfg:getoutputfilename() .. ": link " .. table.concat(objfiles, " ") .. " | " .. lddeps .. prebuildsuffix) writevars() else p.error("ninja action doesn't support this kind of target " .. cfg.kind) end end