1--
2-- _cmake.lua
3-- Define the CMake action(s).
4-- Copyright (c) 2015 Miodrag Milanovic
5-- Modifications and additions in 2017 by Maurizio Petrarota
6--
7
8local cmake = premake.cmake
9local tree = premake.tree
10
11local includestr = 'include_directories(../%s)'
12local definestr = 'add_definitions(-D%s)'
13
14
15local function is_excluded(prj, cfg, file)
16    if table.icontains(prj.excludes, file) then
17        return true
18    end
19
20    if table.icontains(cfg.excludes, file) then
21        return true
22    end
23
24    return false
25end
26
27function cmake.excludedFiles(prj, cfg, src)
28    for _, v in ipairs(src) do
29        if (is_excluded(prj, cfg, v)) then
30            _p(1, 'list(REMOVE_ITEM source_list ../%s)', v)
31        end
32
33    end
34end
35
36function cmake.list(value)
37    if #value > 0 then
38        return " " .. table.concat(value, " ")
39    else
40        return ""
41    end
42end
43
44function cmake.files(prj)
45    local ret = {}
46    local tr = premake.project.buildsourcetree(prj)
47    tree.traverse(tr, {
48        onbranchenter = function(node, depth)
49        end,
50        onbranchexit = function(node, depth)
51        end,
52        onleaf = function(node, depth)
53            assert(node, "unexpected empty node")
54            if node.cfg then
55                table.insert(ret, node.cfg.name)
56                _p(1, '../%s', node.cfg.name)
57            end
58        end,
59    }, true, 1)
60
61    return ret
62end
63
64function cmake.header(prj)
65    _p('# %s project autogenerated by GENie', premake.action.current().shortname)
66    _p('cmake_minimum_required(VERSION 2.8.4)')
67    _p('')
68    _p('project(%s)', premake.esc(prj.name))
69end
70
71function cmake.customtasks(prj)
72    local dirs = {}
73    local tasks = {}
74    for _, custombuildtask in ipairs(prj.custombuildtask or {}) do
75        for _, buildtask in ipairs(custombuildtask or {}) do
76            table.insert(tasks, buildtask)
77            local d = string.format("${CMAKE_CURRENT_SOURCE_DIR}/../%s", path.getdirectory(path.getrelative(prj.location, buildtask[2])))
78            if not table.contains(dirs, d) then
79                table.insert(dirs, d)
80                _p('file(MAKE_DIRECTORY \"%s\")', d)
81            end
82        end
83    end
84    _p('')
85
86    for _, buildtask in ipairs(tasks) do
87        local deps = string.format("${CMAKE_CURRENT_SOURCE_DIR}/../%s ", path.getrelative(prj.location, buildtask[1]))
88        local outputs = string.format("${CMAKE_CURRENT_SOURCE_DIR}/../%s ", path.getrelative(prj.location, buildtask[2]))
89        local msg = ""
90
91        for _, depdata in ipairs(buildtask[3] or {}) do
92            deps = deps .. string.format("${CMAKE_CURRENT_SOURCE_DIR}/../%s ", path.getrelative(prj.location, depdata))
93        end
94
95        _p('add_custom_command(')
96        _p(1, 'OUTPUT %s', outputs)
97        _p(1, 'DEPENDS %s', deps)
98
99        for _, cmdline in ipairs(buildtask[4] or {}) do
100            if (cmdline:sub(1, 1) ~= "@") then
101                local cmd = cmdline
102                local num = 1
103                for _, depdata in ipairs(buildtask[3] or {}) do
104                    cmd = string.gsub(cmd, "%$%(" .. num .. "%)", string.format("${CMAKE_CURRENT_SOURCE_DIR}/../%s ", path.getrelative(prj.location, depdata)))
105                    num = num + 1
106                end
107
108                cmd = string.gsub(cmd, "%$%(<%)", string.format("${CMAKE_CURRENT_SOURCE_DIR}/../%s ", path.getrelative(prj.location, buildtask[1])))
109                cmd = string.gsub(cmd, "%$%(@%)", outputs)
110
111                _p(1, 'COMMAND %s', cmd)
112            else
113                msg = cmdline
114            end
115        end
116        _p(1, 'COMMENT \"%s\"', msg)
117        _p(')')
118        _p('')
119    end
120end
121
122function cmake.depRules(prj)
123    local maintable = {}
124    for _, dependency in ipairs(prj.dependency) do
125        for _, dep in ipairs(dependency) do
126            if path.issourcefile(dep[1]) then
127                local dep1 = premake.esc(path.getrelative(prj.location, dep[1]))
128                local dep2 = premake.esc(path.getrelative(prj.location, dep[2]))
129                if not maintable[dep1] then maintable[dep1] = {} end
130                table.insert(maintable[dep1], dep2)
131            end
132        end
133    end
134
135    for key, _ in pairs(maintable) do
136        local deplist = {}
137        local depsname = string.format('%s_deps', path.getname(key))
138
139        for _, d2 in pairs(maintable[key]) do
140            table.insert(deplist, d2)
141        end
142        _p('set(')
143        _p(1, depsname)
144        for _, v in pairs(deplist) do
145            _p(1, '${CMAKE_CURRENT_SOURCE_DIR}/../%s', v)
146        end
147        _p(')')
148        _p('')
149        _p('set_source_files_properties(')
150        _p(1, '\"${CMAKE_CURRENT_SOURCE_DIR}/../%s\"', key)
151        _p(1, 'PROPERTIES OBJECT_DEPENDS \"${%s}\"', depsname)
152        _p(')')
153        _p('')
154    end
155end
156
157function cmake.commonRules(conf, str)
158    local Dupes = {}
159    local t2 = {}
160    for _, cfg in ipairs(conf) do
161        local cfgd = iif(str == includestr, cfg.includedirs, cfg.defines)
162        for _, v in ipairs(cfgd) do
163            if(t2[v] == #conf - 1) then
164                _p(str, v)
165                table.insert(Dupes, v)
166            end
167            if not t2[v] then
168                t2[v] = 1
169            else
170                t2[v] = t2[v] + 1
171            end
172        end
173    end
174    return Dupes
175end
176
177function cmake.cfgRules(cfg, dupes, str)
178    for _, v in ipairs(cfg) do
179        if (not table.icontains(dupes, v)) then
180            _p(1, str, v)
181        end
182    end
183end
184
185function cmake.removeCrosscompiler(platforms)
186    for i = #platforms, 1, -1 do
187        if premake.platforms[platforms[i]].iscrosscompiler then
188            table.remove(platforms, i)
189        end
190    end
191end
192
193function cmake.project(prj)
194    io.indent = "  "
195    cmake.header(prj)
196    _p('set(')
197    _p('source_list')
198    local source_files = cmake.files(prj)
199    _p(')')
200    _p('')
201
202    local nativeplatform = iif(os.is64bit(), "x64", "x32")
203    local cc = premake.gettool(prj)
204    local platforms = premake.filterplatforms(prj.solution, cc.platforms, "Native")
205
206    cmake.removeCrosscompiler(platforms)
207
208    local configurations = {}
209
210    for _, platform in ipairs(platforms) do
211        for cfg in premake.eachconfig(prj, platform) do
212            -- TODO: Extend support for 32-bit targets on 64-bit hosts
213            if cfg.platform == nativeplatform then
214                table.insert(configurations, cfg)
215            end
216        end
217    end
218
219    local commonIncludes = cmake.commonRules(configurations, includestr)
220    local commonDefines = cmake.commonRules(configurations, definestr)
221    _p('')
222
223    for _, cfg in ipairs(configurations) do
224        _p('if(CMAKE_BUILD_TYPE MATCHES \"%s\")', cfg.name)
225
226        -- list excluded files
227        cmake.excludedFiles(prj, cfg, source_files)
228
229        -- add includes directories
230        cmake.cfgRules(cfg.includedirs, commonIncludes, includestr)
231
232        -- add build defines
233        cmake.cfgRules(cfg.defines, commonDefines, definestr)
234
235        -- set CXX flags
236        _p(1, 'set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} %s\")', cmake.list(table.join(cc.getcppflags(cfg), cc.getcflags(cfg), cc.getcxxflags(cfg), cfg.buildoptions, cfg.buildoptions_cpp)))
237
238        -- set C flags
239        _p(1, 'set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} %s\")', cmake.list(table.join(cc.getcppflags(cfg), cc.getcflags(cfg), cfg.buildoptions, cfg.buildoptions_c)))
240
241        _p('endif()')
242        _p('')
243    end
244
245    -- force CPP if needed
246    if (prj.options.ForceCPP) then
247        _p('set_source_files_properties(${source_list} PROPERTIES LANGUAGE CXX)')
248    end
249
250    -- add custom tasks
251    cmake.customtasks(prj)
252
253    -- per-dependency build rules
254    cmake.depRules(prj)
255
256    for _, cfg in ipairs(configurations) do
257        _p('if(CMAKE_BUILD_TYPE MATCHES \"%s\")', cfg.name)
258
259        if (prj.kind == 'StaticLib') then
260            _p(1, 'add_library(%s STATIC ${source_list})', premake.esc(cfg.buildtarget.basename))
261        end
262
263        if (prj.kind == 'SharedLib') then
264            _p(1, 'add_library(%s SHARED ${source_list})', premake.esc(cfg.buildtarget.basename))
265        end
266        if (prj.kind == 'ConsoleApp' or prj.kind == 'WindowedApp') then
267            _p(1, 'add_executable(%s ${source_list})', premake.esc(cfg.buildtarget.basename))
268            _p(1, 'target_link_libraries(%s%s%s)', premake.esc(cfg.buildtarget.basename), cmake.list(premake.esc(premake.getlinks(cfg, "siblings", "basename"))), cmake.list(cc.getlinkflags(cfg)))
269        end
270        _p('endif()')
271        _p('')
272    end
273end