-- -- Name: premake-ninja/ninja.lua -- Purpose: Define the ninja action. -- Author: Dmitry Ivanov -- Created: 2015/07/04 -- Copyright: (c) 2015 Dmitry Ivanov -- local p = premake local tree = p.tree local project = p.project local config = p.config local fileconfig = p.fileconfig -- Some toolset fixes/helper p.tools.clang.objectextension = '.o' p.tools.gcc.objectextension = '.o' p.tools.msc.objectextension = '.obj' p.tools.clang.tools.rc = p.tools.clang.tools.rc or 'windres' p.tools.msc.gettoolname = function(cfg, name) local map = { cc = 'cl', cxx = 'cl', ar = 'lib', rc = 'rc' } return map[name] end -- Ninja module premake.modules.ninja = {} local ninja = p.modules.ninja ninja.handlers = {} function ninja.register_handler(kind, compilation_rules, target_rules) ninja.handlers[kind] = { compilation_rules = compilation_rules, target_rules = target_rules } end local function get_key(cfg, name) local name = name or cfg.project.name if cfg.platform then return name .. '_' .. cfg.buildcfg .. '_' .. cfg.platform else return name .. '_' .. cfg.buildcfg end end local build_cache = {} function ninja.add_build(cfg, out, implicit_outputs, command, inputs, implicit_inputs, dependencies, vars) implicit_outputs = ninja.list(table.translate(implicit_outputs, ninja.esc)) if #implicit_outputs > 0 then implicit_outputs = ' |' .. implicit_outputs else implicit_outputs = '' end inputs = ninja.list(table.translate(inputs, ninja.esc)) implicit_inputs = ninja.list(table.translate(implicit_inputs, ninja.esc)) if #implicit_inputs > 0 then implicit_inputs = ' |' .. implicit_inputs else implicit_inputs = '' end dependencies = ninja.list(table.translate(dependencies, ninja.esc)) if #dependencies > 0 then dependencies = ' ||' .. dependencies else dependencies = '' end build_line = 'build ' .. ninja.esc(out) .. implicit_outputs .. ': ' .. command .. inputs .. implicit_inputs .. dependencies local cached = build_cache[out] if cached ~= nil then if build_line == cached.build_line and table.equals(vars or {}, cached.vars or {}) then -- custom_command/copy rule are identical for each configuration (contrary to other rules) -- So we can compare extra parameter if command == 'custom_command' or command == 'copy' then p.outln('# INFO: Rule ignored, same as ' .. cached.cfg_key) else local cfg_key = cfg and get_key(cfg) or 'Global scope' p.warn(cached.cfg_key .. ' and ' .. cfg_key .. ' both generate (differently?) ' .. out .. '. Ignoring ' .. cfg_key) p.outln('# WARNING: Rule ignored, using the one from ' .. cached.cfg_key) end else local cfg_key = cfg and get_key(cfg) or 'Global scope' p.warn(cached.cfg_key .. ' and ' .. cfg_key .. ' both generate differently ' .. out .. '. Ignoring ' .. cfg_key) p.outln('# ERROR: Rule ignored, using the one from ' .. cached.cfg_key) end p.outln('# ' .. build_line) for i, var in ipairs(vars or {}) do p.outln('# ' .. var) end return end p.outln(build_line) for i, var in ipairs(vars or {}) do p.outln(' ' .. var) end build_cache[out] = { cfg_key = cfg and get_key(cfg) or 'Global scope', build_line = build_line, vars = vars, } end local function build_command(commands, command_sep) command_sep = command_sep or '&&' if #commands > 1 then local command = iif(os.shell() == 'cmd', 'cmd /c ', 'sh -c ') return command .. ninja.quote(table.implode(commands, '', '', ' ' .. command_sep .. '$\n ')) else return commands[1] end end function ninja.emit_rule(name, command, description, opts) opts = opts or {} p.outln('rule ' .. name) p.outln(' command = ' .. command) p.outln(' description = ' .. description) for key, value in pairs(opts) do p.outln(' ' .. key .. ' = ' .. value) end p.outln('') end function ninja.emit_flags(name, value) p.outln(name .. '=' .. value) end function ninja.esc(value) value = value:gsub('%$', '$$') -- TODO maybe there is better way value = value:gsub(':', '$:') value = value:gsub('\n', '$\n') value = value:gsub(' ', '$ ') return value end function ninja.quote(value) value = value:gsub('\\', '\\\\') value = value:gsub("'", "\\'") value = value:gsub('"', '\\"') return '"' .. value .. '"' end -- in some cases we write file names in rule commands directly -- so we need to propely escape them function ninja.shesc(value) if type(value) == 'table' then return table.translate(value, ninja.shesc) end if value:find(' ') or value:find('"') or value:find('(', 1, true) or value:find(')') or value:find('|') or value:find('&') then return ninja.quote(value) end return value end function ninja.can_generate(prj) return p.action.supports(prj.kind) and prj.kind ~= p.NONE end -- generate solution that will call ninja for projects function ninja.generateWorkspace(wks) local oldGetDefaultSeparator = path.getDefaultSeparator path.getDefaultSeparator = function() return '/' end p.outln('# solution build file') p.outln('# generated with premake ninja') p.outln('') p.outln('# build projects') local cfgs = {} -- key is concatenated name or variant name, value is string of outputs names local key = '' local cfg_first = nil local cfg_first_lib = nil local subninjas = {} for prj in p.workspace.eachproject(wks) do if ninja.can_generate(prj) then for cfg in p.project.eachconfig(prj) do key = get_key(cfg) if not cfgs[cfg.buildcfg] then cfgs[cfg.buildcfg] = {} end table.insert(cfgs[cfg.buildcfg], key) -- set first configuration name if wks.defaultplatform == nil then if (cfg_first == nil) and (cfg.kind == p.CONSOLEAPP or cfg.kind == p.WINDOWEDAPP) then cfg_first = key end end if (cfg_first_lib == nil) and (cfg.kind == p.STATICLIB or cfg.kind == p.SHAREDLIB) then cfg_first_lib = key end if prj.name == wks.startproject then if wks.defaultplatform == nil then cfg_first = key elseif cfg.platform == wks.defaultplatform then if cfg_first == nil then cfg_first = key end end end -- include other ninja file table.insert(subninjas, ninja.esc(ninja.projectCfgFilename(cfg, true))) p.outln('subninja ' .. ninja.esc(ninja.projectCfgFilename(cfg, true))) end end end if cfg_first == nil then cfg_first = cfg_first_lib end p.outln('') p.outln('# targets') for cfg, outputs in spairs(cfgs) do p.outln('build ' .. ninja.esc(cfg) .. ': phony' .. ninja.list(table.translate(outputs, ninja.esc))) end p.outln('') if wks.editorintegration then -- we need to filter out the 'file' argument, since we already output -- the script separately. local args = {} for _, arg in ipairs(_ARGV) do if not (arg:startswith('--file') or arg:startswith('/file')) then table.insert(args, arg) end end table.sort(args) p.outln('# Rule') ninja.emit_rule('premake', ninja.shesc(p.workspace.getrelative(wks, _PREMAKE_COMMAND)) .. ' --file=$in ' .. table.concat(ninja.shesc(args), ' '), 'run premake', { generator = 'true', restat = 'true' }) ninja.add_build(nil, 'build.ninja', subninjas, 'premake', { p.workspace.getrelative(wks, _MAIN_SCRIPT) }, {}, {}, {}) p.outln('') end if cfg_first then p.outln('# default target') p.outln('default ' .. ninja.esc(cfg_first)) p.outln('') end path.getDefaultSeparator = oldGetDefaultSeparator end function ninja.list(value) if #value > 0 then return ' ' .. table.concat(value, ' ') else return '' end end local function shouldcompileasc(filecfg) if filecfg.compileas and filecfg.compileas ~= 'Default' then return p.languages.isc(filecfg.compileas) end return path.iscfile(filecfg.abspath) end local function shouldcompileascpp(filecfg) if filecfg.compileas and filecfg.compileas ~= 'Default' then return p.languages.iscpp(filecfg.compileas) end return path.iscppfile(filecfg.abspath) end local function getFileDependencies(cfg) local dependencies = {} if #cfg.prebuildcommands > 0 or cfg.prebuildmessage then dependencies = { 'prebuild_' .. get_key(cfg) } end for i = 1, #cfg.dependson do local dependpostfix = '' if cfg.platform then dependpostfix = '_' .. cfg.platform end table.insert(dependencies, cfg.dependson[i] .. '_' .. cfg.buildcfg .. dependpostfix) end return dependencies end local function getcflags(toolset, cfg, filecfg) p.escaper(ninja.shesc) local buildopt = ninja.list(filecfg.buildoptions) local cppflags = ninja.list(toolset.getcppflags(filecfg)) local cflags = ninja.list(toolset.getcflags(filecfg)) local defines = ninja.list(table.join(toolset.getdefines(filecfg.defines, filecfg), toolset.getundefines(filecfg.undefines))) local includes = ninja.list(toolset.getincludedirs(cfg, filecfg.includedirs, filecfg.externalincludedirs, filecfg.frameworkdirs, filecfg.includedirsafter)) local forceincludes = ninja.list(toolset.getforceincludes(cfg)) p.escaper(nil) return buildopt .. cppflags .. cflags .. defines .. includes .. forceincludes end local function getcxxflags(toolset, cfg, filecfg) p.escaper(ninja.shesc) local buildopt = ninja.list(filecfg.buildoptions) local cppflags = ninja.list(toolset.getcppflags(filecfg)) local cxxflags = ninja.list(toolset.getcxxflags(filecfg)) local defines = ninja.list(table.join(toolset.getdefines(filecfg.defines, filecfg), toolset.getundefines(filecfg.undefines))) local includes = ninja.list(toolset.getincludedirs(cfg, filecfg.includedirs, filecfg.externalincludedirs, filecfg.frameworkdirs, filecfg.includedirsafter)) local forceincludes = ninja.list(toolset.getforceincludes(cfg)) p.escaper(nil) return buildopt .. cppflags .. cxxflags .. defines .. includes .. forceincludes end local function getldflags(toolset, cfg) local ldflags = ninja.list(table.join(toolset.getLibraryDirectories(cfg), toolset.getrunpathdirs(cfg, table.join(cfg.runpathdirs, config.getsiblingtargetdirs(cfg))), toolset.getldflags(cfg), cfg.linkoptions)) -- experimental feature, change install_name of shared libs --[[ if toolset == p.tools.clang and cfg.kind == p.SHAREDLIB and cfg.buildtarget.name:endswith('.dylib') then ldflags = ldflags .. ' -install_name ' .. cfg.buildtarget.name end --]] return ldflags end local function getresflags(toolset, cfg, filecfg) p.escaper(ninja.shesc) local defines = ninja.list(toolset.getdefines(table.join(filecfg.defines, filecfg.resdefines), filecfg)) local includes = ninja.list(toolset.getincludedirs(cfg, table.join(filecfg.externalincludedirs, filecfg.includedirsafter, filecfg.includedirs, filecfg.resincludedirs), {}, {}, {})) local options = ninja.list(cfg.resoptions) p.escaper(nil) return defines .. includes .. options end local function prebuild_rule(cfg) if #cfg.prebuildcommands > 0 or cfg.prebuildmessage then local commands = {} if cfg.prebuildmessage then commands = { os.translateCommandsAndPaths('{ECHO} ' .. cfg.prebuildmessage, cfg.workspace.basedir, cfg.workspace.location) } end commands = table.join(commands, os.translateCommandsAndPaths(cfg.prebuildcommands, cfg.workspace.basedir, cfg.workspace.location)) ninja.emit_rule('run_prebuild', build_command(commands, ';'), 'prebuild') end end local function prelink_rule(cfg) if #cfg.prelinkcommands > 0 or cfg.prelinkmessage then local commands = {} if cfg.prelinkmessage then commands = { os.translateCommandsAndPaths('{ECHO} ' .. cfg.prelinkmessage, cfg.workspace.basedir, cfg.workspace.location) } end commands = table.join(commands, os.translateCommandsAndPaths(cfg.prelinkcommands, cfg.workspace.basedir, cfg.workspace.location)) ninja.emit_rule('run_prelink', build_command(commands, ';'), 'prelink') end end local function postbuild_rule(cfg) if #cfg.postbuildcommands > 0 or cfg.postbuildmessage then local commands = {} if cfg.postbuildmessage then commands = { os.translateCommandsAndPaths('{ECHO} ' .. cfg.postbuildmessage, cfg.workspace.basedir, cfg.workspace.location) } end commands = table.join(commands, os.translateCommandsAndPaths(cfg.postbuildcommands, cfg.workspace.basedir, cfg.workspace.location)) ninja.emit_rule('run_postbuild', build_command(commands, ';'), 'postbuild') end end local function c_cpp_compilation_rules(cfg, toolset, pch) ---------------------------------------------------- figure out toolset executables local cc = toolset.gettoolname(cfg, 'cc') local cxx = toolset.gettoolname(cfg, 'cxx') local ar = toolset.gettoolname(cfg, 'ar') local link = toolset.gettoolname(cfg, iif(cfg.language == 'C', 'cc', 'cxx')) local rc = toolset.gettoolname(cfg, 'rc') -- all paths need to be relative to the workspace output location, -- and not relative to the project output location. -- override the toolset getrelative function to achieve this local getrelative = p.tools.getrelative p.tools.getrelative = function(cfg, value) return p.workspace.getrelative(cfg.workspace, value) end local all_cflags = getcflags(toolset, cfg, cfg) local all_cxxflags = getcxxflags(toolset, cfg, cfg) local all_ldflags = getldflags(toolset, cfg) local all_resflags = getresflags(toolset, cfg, cfg) if toolset == p.tools.msc then ninja.emit_flags('CFLAGS', all_cflags) local cc_command = cc .. ' $CFLAGS /nologo /showIncludes -c /Tc$in /Fo$out' ninja.emit_rule('cc', cc_command, 'cc $out', { deps = 'msvc' }) ninja.emit_flags('CXXFLAGS', all_cxxflags) local cxx_command = cxx .. ' $CXXFLAGS /nologo /showIncludes -c /Tp$in /Fo$out' ninja.emit_rule('cxx', cxx_command, 'cxx $out', { deps = 'msvc' }) ninja.emit_flags('CFLAGS', all_cflags) ninja.emit_rule('clangtidy_cc', build_command({ 'clang-tidy $in -- -x c $CFLAGS', cc_command }, ';'), 'cc $out', { deps = 'msvc' }, ';') ninja.emit_flags('CXXFLAGS', all_cxxflags) ninja.emit_rule('clangtidy_cxx', build_command({ 'clang-tidy $in -- -x c++ $CFLAGS', cxx_command }, ';'), 'cxx $out', { deps = 'msvc' }, ';') ninja.emit_flags('RESFLAGS', all_resflags) ninja.emit_rule('rc', rc .. ' /nologo /fo$out $in $RESFLAGS', 'rc $out') if cfg.kind == p.STATICLIB then ninja.emit_rule('ar', ar .. ' $in /nologo -OUT:$out', 'ar $out') else ninja.emit_rule('link', link .. ' $in ' .. ninja.list(ninja.shesc(toolset.getlinks(cfg, true))) .. ' /link ' .. all_ldflags .. ' /nologo /out:$out', 'link $out') end elseif toolset == p.tools.clang or toolset == p.tools.gcc or toolset == p.tools.emcc then local force_include = pch and (' -include ' .. ninja.shesc(pch.placeholder)) or '' if pch then ninja.emit_rule('build_pch', iif(cfg.language == 'C', cc .. all_cflags .. ' -x c-header', cxx .. all_cxxflags .. ' -x c++-header') .. ' -H -MF $out.d -c -o $out $in', 'build_pch $out', { depfile = '$out.d', deps = 'gcc' }) end ninja.emit_flags('CFLAGS', all_cflags) local cc_command = cc .. ' $CFLAGS' .. force_include .. ' -x c -MF $out.d -c -o $out $in' ninja.emit_rule('cc', cc_command, 'cc $out', { depfile = '$out.d', deps = 'gcc' }) ninja.emit_flags('CXXFLAGS', all_cxxflags) local cxx_command = cxx .. ' $CXXFLAGS' .. force_include .. ' -x c++ -MF $out.d -c -o $out $in' ninja.emit_rule('cxx', cxx_command, 'cxx $out', { depfile = '$out.d', deps = 'gcc' }) ninja.emit_flags('CFLAGS', all_cflags) ninja.emit_rule('clangtidy_cc', build_command({ 'clang-tidy $in -- -x c $CFLAGS' .. force_include, cc_command }, ';'), 'cc $out', { depfile = '$out.d', deps = 'gcc' }, ';') ninja.emit_flags('CXXFLAGS', all_cxxflags) ninja.emit_rule('clangtidy_cxx', build_command({ 'clang-tidy $in -- -x c++ $CFLAGS' .. force_include, cxx_command }, ';'), 'cxx $out', { depfile = '$out.d', deps = 'gcc' }, ';') ninja.emit_flags('RESFLAGS', all_resflags) if rc then ninja.emit_rule('rc', rc .. ' -i $in -o $out $RESFLAGS', 'rc $out') end if ar and cfg.kind == p.STATICLIB then ninja.emit_rule('ar', ar .. ' rcs $out $in', 'ar $out') else local groups = iif(cfg.linkgroups == premake.ON, { '-Wl,--start-group ', ' -Wl,--end-group' }, { '', '' }) ninja.emit_rule('link', link .. ' -o $out ' .. groups[1] .. '$in' .. ninja.list(ninja.shesc(toolset.getlinks(cfg, true, true))) .. all_ldflags .. groups[2], 'link $out') end end p.tools.getrelative = getrelative end local function custom_command_rule() ninja.emit_rule('custom_command', '$CUSTOM_COMMAND', '$CUSTOM_DESCRIPTION') end local function copy_rule() ninja.emit_rule('copy', os.translateCommands('{COPYFILE} $in $out'), 'copy $in $out') end local function collect_generated_files(prj, cfg) local generated_files = {} tree.traverse(project.getsourcetree(prj), { onleaf = function(node, depth) function append_to_generated_files(filecfg) local outputs = project.getrelative(prj.workspace, filecfg.buildoutputs) generated_files = table.join(generated_files, outputs) end local filecfg = fileconfig.getconfig(node, cfg) if not filecfg or filecfg.flags.ExcludeFromBuild then return end local rule = p.global.getRuleForFile(node.name, prj.rules) if fileconfig.hasCustomBuildRule(filecfg) then append_to_generated_files(filecfg) elseif rule then local environ = table.shallowcopy(filecfg.environ) if rule.propertydefinition then p.rule.prepareEnvironment(rule, environ, cfg) p.rule.prepareEnvironment(rule, environ, filecfg) end local rulecfg = p.context.extent(rule, environ) append_to_generated_files(rulecfg) end end, }, false, 1) return generated_files end local function pch_build(cfg, pch) local pch_dependency = {} if pch then pch_dependency = { pch.gch } ninja.add_build(cfg, pch.gch, {}, 'build_pch', { pch.input }, {}, {}, {}) end return pch_dependency end local function custom_command_build(prj, cfg, filecfg, filename, file_dependencies) local outputs = project.getrelative(prj.workspace, filecfg.buildoutputs) local output = outputs[1] table.remove(outputs, 1) local commands = {} if filecfg.buildmessage then commands = { os.translateCommandsAndPaths('{ECHO} ' .. filecfg.buildmessage, prj.workspace.basedir, prj.workspace.location) } end commands = table.join(commands, os.translateCommandsAndPaths(filecfg.buildcommands, prj.workspace.basedir, prj.workspace.location)) if #commands > 1 then commands = 'sh -c ' .. ninja.quote(table.implode(commands, '', '', ';')) else commands = commands[1] end ninja.add_build(cfg, output, outputs, 'custom_command', { filename }, project.getrelative(prj.workspace, filecfg.buildinputs), file_dependencies, { 'CUSTOM_COMMAND = ' .. commands, 'CUSTOM_DESCRIPTION = custom build ' .. ninja.shesc(output) }) end local function compile_file_build(cfg, filecfg, toolset, pch_dependency, regular_file_dependencies, objfiles, extrafiles) local obj_file = filecfg.objname .. (toolset.objectextension or '.o') local obj_dir = project.getrelative(cfg.workspace, cfg.objdir) local filepath = project.getrelative(cfg.workspace, filecfg.abspath) local has_custom_settings = fileconfig.hasFileSettings(filecfg) local use_clangtidy = filecfg.clangtidy or (filecfg.clangtidy == nil and cfg.clangtidy) if use_clangtidy and toolset == p.tools.msc then premake.warnOnce('clang-tidy_msc', 'Turn off clang-tidy when using msc (flags might differ too much between the tools).') use_clangtidy = false end if filecfg.buildaction == 'None' then return elseif filecfg.buildaction == 'Copy' then local target = project.getrelative(cfg.workspace, path.join(cfg.targetdir, filecfg.name)) ninja.add_build(cfg, target, {}, 'copy', { filepath }, {}, {}, {}) extrafiles[#extrafiles + 1] = target elseif shouldcompileasc(filecfg) then local objfilename = obj_dir .. '/' .. obj_file objfiles[#objfiles + 1] = objfilename local vars = {} if has_custom_settings then vars = { 'CFLAGS = $CFLAGS ' .. getcflags(toolset, cfg, filecfg) } end ninja.add_build(cfg, objfilename, {}, iif(use_clangtidy, 'clangtidy_cc', 'cc'), { filepath }, pch_dependency, regular_file_dependencies, vars) elseif shouldcompileascpp(filecfg) then local objfilename = obj_dir .. '/' .. obj_file objfiles[#objfiles + 1] = objfilename local vars = {} if has_custom_settings then vars = { 'CXXFLAGS = $CXXFLAGS ' .. getcxxflags(toolset, cfg, filecfg) } end ninja.add_build(cfg, objfilename, {}, iif(use_clangtidy, 'clangtidy_cxx', 'cxx'), { filepath }, pch_dependency, regular_file_dependencies, vars) elseif path.isresourcefile(filecfg.abspath) then local objfilename = obj_dir .. '/' .. filecfg.basename .. '.res' objfiles[#objfiles + 1] = objfilename local resflags = {} if has_custom_settings then resflags = { 'RESFLAGS = $RESFLAGS ' .. getresflags(toolset, cfg, filecfg) } end local rc = toolset.gettoolname(cfg, 'rc') if rc then ninja.add_build(cfg, objfilename, {}, 'rc', { filepath }, {}, {}, resflags) else p.warnOnce(filepath, string.format('Ignored resource: "%s"', filepath)) end end end local function files_build(prj, cfg, toolset, pch_dependency, regular_file_dependencies, file_dependencies) local objfiles = {} local extrafiles = {} tree.traverse(project.getsourcetree(prj), { onleaf = function(node, depth) local filecfg = fileconfig.getconfig(node, cfg) if not filecfg or filecfg.flags.ExcludeFromBuild then return end local rule = p.global.getRuleForFile(node.name, prj.rules) local filepath = project.getrelative(cfg.workspace, node.abspath) if fileconfig.hasCustomBuildRule(filecfg) then custom_command_build(prj, cfg, filecfg, filepath, file_dependencies) elseif rule then local environ = table.shallowcopy(filecfg.environ) if rule.propertydefinition then p.rule.prepareEnvironment(rule, environ, cfg) p.rule.prepareEnvironment(rule, environ, filecfg) end local rulecfg = p.context.extent(rule, environ) custom_command_build(prj, cfg, rulecfg, filepath, file_dependencies) else compile_file_build(cfg, filecfg, toolset, pch_dependency, regular_file_dependencies, objfiles, extrafiles) end end, }, false, 1) p.outln('') return objfiles, extrafiles end local function generated_files_build(cfg, generated_files, key) local final_dependency = {} if #generated_files > 0 then p.outln('# generated files') ninja.add_build(cfg, 'generated_files_' .. key, {}, 'phony', generated_files, {}, {}, {}) final_dependency = { 'generated_files_' .. key } end return final_dependency end -- generate project + config build file function ninja.generateProjectCfg(cfg) local oldGetDefaultSeparator = path.getDefaultSeparator path.getDefaultSeparator = function() return '/' end local prj = cfg.project local key = get_key(cfg) local toolset, toolset_version = p.tools.canonical(cfg.toolset) if not toolset then p.error('Unknown toolset ' .. cfg.toolset) end -- Some toolset fixes cfg.gccprefix = cfg.gccprefix or '' p.outln('# project build file') p.outln('# generated with premake ninja') p.outln('') -- premake-ninja relies on scoped rules -- and they were added in ninja v1.6 p.outln('ninja_required_version = 1.6') p.outln('') local is_c_or_cpp = cfg.language == p.C or cfg.language == p.CPP ---------------------------------------------------- figure out settings local pch = nil if is_c_or_cpp and toolset ~= p.tools.msc then pch = p.tools.gcc.getpch(cfg) if pch then pch = { input = pch, placeholder = project.getrelative(cfg.workspace, path.join(cfg.objdir, path.getname(pch))), gch = project.getrelative(cfg.workspace, path.join(cfg.objdir, path.getname(pch) .. '.gch')), } end end ---------------------------------------------------- write rules p.outln('# core rules for ' .. cfg.name) prebuild_rule(cfg) prelink_rule(cfg) postbuild_rule(cfg) if is_c_or_cpp then c_cpp_compilation_rules(cfg, toolset, pch) else local handler = ninja.handlers[cfg.language] if not handler then p.error('expected registered ninja handler action for target ' .. cfg.language) end handler.compilation_rules(cfg, toolset) end copy_rule() custom_command_rule() ---------------------------------------------------- build all files p.outln('# build files') local pch_dependency = is_c_or_cpp and pch_build(cfg, pch) or {} local generated_files = collect_generated_files(prj, cfg) local file_dependencies = getFileDependencies(cfg) local regular_file_dependencies = table.join(iif(#generated_files > 0, { 'generated_files_' .. key }, {}), file_dependencies) local obj_dir = project.getrelative(cfg.workspace, cfg.objdir) local objfiles, extrafiles = files_build(prj, cfg, toolset, pch_dependency, regular_file_dependencies, file_dependencies) local final_dependency = generated_files_build(cfg, generated_files, key) ---------------------------------------------------- build final target if #cfg.prebuildcommands > 0 or cfg.prebuildmessage then p.outln('# prebuild') ninja.add_build(cfg, 'prebuild_' .. get_key(cfg), {}, 'run_prebuild', {}, {}, {}, {}) end local prelink_dependency = {} if #cfg.prelinkcommands > 0 or cfg.prelinkmessage then p.outln('# prelink') ninja.add_build(cfg, 'prelink_' .. get_key(cfg), {}, 'run_prelink', {}, objfiles, final_dependency, {}) prelink_dependency = { 'prelink_' .. get_key(cfg) } end if #cfg.postbuildcommands > 0 or cfg.postbuildmessage then p.outln('# postbuild') ninja.add_build(cfg, 'postbuild_' .. get_key(cfg), {}, 'run_postbuild', {}, { ninja.outputFilename(cfg) }, {}, {}) end if is_c_or_cpp then -- we don't pass getlinks(cfg) through dependencies -- because system libraries are often not in PATH so ninja can't find them local libs = table.translate(config.getlinks(cfg, 'siblings', 'fullpath'), function(p) return project.getrelative(cfg.workspace, path.join(cfg.project.location, p)) end) local cfg_output = ninja.outputFilename(cfg) local extra_outputs = {} local command_rule = '' if cfg.kind == p.STATICLIB then p.outln('# link static lib') command_rule = 'ar' elseif cfg.kind == p.SHAREDLIB then p.outln('# link shared lib') command_rule = 'link' extra_outputs = iif(os.target() == 'windows', { path.replaceextension(cfg_output, '.lib'), path.replaceextension(cfg_output, '.exp') }, {}) elseif (cfg.kind == p.CONSOLEAPP) or (cfg.kind == p.WINDOWEDAPP) then p.outln('# link executable') command_rule = 'link' else p.error("ninja action doesn't support this kind of target " .. cfg.kind) end local deps = table.join(final_dependency, extrafiles, prelink_dependency) ninja.add_build(cfg, cfg_output, extra_outputs, command_rule, table.join(objfiles, libs), {}, deps, {}) outputs = { cfg_output } else local handler = ninja.handlers[cfg.language] if not handler then p.error('expected registered ninja handler action for target ' .. cfg.language) end outputs = handler.target_rules(cfg, toolset) end p.outln('') if #cfg.postbuildcommands > 0 or cfg.postbuildmessage then ninja.add_build(cfg, key, {}, 'phony', { 'postbuild_' .. get_key(cfg) }, {}, {}, {}) else ninja.add_build(cfg, key, {}, 'phony', outputs, {}, {}, {}) end p.outln('') path.getDefaultSeparator = oldGetDefaultSeparator end -- return name of output binary relative to build folder function ninja.outputFilename(cfg) return project.getrelative(cfg.workspace, cfg.buildtarget.directory) .. '/' .. cfg.buildtarget.name end -- return name of build file for configuration function ninja.projectCfgFilename(cfg, relative) if relative ~= nil then relative = project.getrelative(cfg.workspace, cfg.location) .. '/' else relative = '' end return relative .. get_key(cfg, cfg.project.filename) .. '.ninja' end -- generate all build files for every project configuration function ninja.generateProject(prj) if not ninja.can_generate(prj) then return end for cfg in project.eachconfig(prj) do p.generate(cfg, ninja.projectCfgFilename(cfg), ninja.generateProjectCfg) end end include('_preload.lua') return ninja