1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
|
--
-- configs.lua
--
-- Once the project scripts have been run, flatten all of the configuration
-- data down into simpler objects, keeping only the settings that apply to
-- the current runtime environment.
--
-- Copyright (c) 2008, 2009 Jason Perkins and the Premake project
--
-- do not copy these fields into the configurations
local nocopy =
{
blocks = true,
keywords = true,
projects = true,
__configs = true,
}
-- leave these paths as absolute, rather than converting to project relative
local nofixup =
{
basedir = true,
location = true,
}
--
-- Returns a list of all of the active terms from the current environment.
-- See the docs for configuration() for more information about the terms.
--
function premake.getactiveterms()
local terms = { _ACTION:lower(), os.get() }
-- add option keys or values
for key, value in pairs(_OPTIONS) do
if value ~= "" then
table.insert(terms, value:lower())
else
table.insert(terms, key:lower())
end
end
return terms
end
--
-- Escape a keyword in preparation for testing against a list of terms.
-- Converts from Premake's simple pattern syntax to Lua's syntax.
--
function premake.escapekeyword(keyword)
keyword = keyword:gsub("([%.%-%^%$%(%)%%])", "%%%1")
if keyword:find("**", nil, true) then
keyword = keyword:gsub("%*%*", ".*")
else
keyword = keyword:gsub("%*", "[^/]*")
end
return keyword:lower()
end
--
-- Test a single configuration block keyword against a list of terms.
-- The terms are a mix of key/value pairs. The keyword is tested against
-- the values; on a match, the corresponding key is returned. This
-- enables testing for required values in iskeywordsmatch(), below.
--
function premake.iskeywordmatch(keyword, terms)
-- is it negated?
if keyword:startswith("not ") then
return not premake.iskeywordmatch(keyword:sub(5), terms)
end
for _, word in ipairs(keyword:explode(" or ")) do
local pattern = "^" .. word .. "$"
for termkey, term in pairs(terms) do
if term:match(pattern) then
return termkey
end
end
end
end
--
-- Checks a set of configuration block keywords against a list of terms.
-- I've already forgotten the purpose of the required terms (d'oh!) but
-- I'll see if I can figure it out on a future refactoring.
--
function premake.iskeywordsmatch(keywords, terms)
local hasrequired = false
for _, keyword in ipairs(keywords) do
local matched = premake.iskeywordmatch(keyword, terms)
if not matched then
return false
end
if matched == "required" then
hasrequired = true
end
end
if terms.required and not hasrequired then
return false
else
return true
end
end
--
-- Converts path fields from absolute to location-relative paths.
--
-- @param location
-- The base location, paths will be relative to this directory.
-- @param obj
-- The object containing the fields to be adjusted.
--
local function adjustpaths(location, obj)
for name, value in pairs(obj) do
local field = premake.fields[name]
if field and value and not nofixup[name] then
if field.kind == "path" then
obj[name] = path.getrelative(location, value)
elseif field.kind == "dirlist" or field.kind == "filelist" then
for i, p in ipairs(value) do
value[i] = path.getrelative(location, p)
end
end
end
end
end
--
-- Merge all of the fields from one object into another. String values are overwritten,
-- while list values are merged. Fields listed in premake.nocopy are skipped.
--
-- @param dest
-- The destination object, to contain the merged settings.
-- @param src
-- The source object, containing the settings to added to the destination.
--
local function mergeobject(dest, src)
if not src then return end
for field, value in pairs(src) do
if not nocopy[field] then
if type(value) == "table" then
dest[field] = table.join(dest[field] or {}, value)
else
dest[field] = value
end
end
end
end
--
-- Merges the settings from a solution's or project's list of configuration blocks,
-- for all blocks that match the provided set of environment terms.
--
-- @param dest
-- The destination object, to contain the merged settings.
-- @param obj
-- The solution or project object being collapsed.
-- @param basis
-- "Root" level settings, from the solution, which act as a starting point for
-- all of the collapsed settings built during this call.
-- @param cfgname
-- The name of the configuration being collapsed. May be nil.
-- @param pltname
-- The name of the platform being collapsed. May be nil.
--
local function merge(dest, obj, basis, cfgname, pltname)
pltname = pltname or "Native"
local key = cfgname or ""
if pltname ~= "Native" then
key = key .. pltname
end
local cfg = {}
mergeobject(cfg, basis[key])
adjustpaths(obj.location, cfg)
mergeobject(cfg, obj)
local terms = premake.getactiveterms()
terms.config = (cfgname or ""):lower()
terms.platform = pltname:lower()
for _, blk in ipairs(obj.blocks) do
if (premake.iskeywordsmatch(blk.keywords, terms)) then
mergeobject(cfg, blk)
end
end
cfg.name = cfgname
cfg.platform = pltname
cfg.terms = terms
dest[key] = cfg
end
--
-- Collapse a solution or project object down to a canonical set of configuration settings,
-- keyed by configuration block/platform pairs, and taking into account the current
-- environment settings.
--
-- @param obj
-- The solution or project to be collapsed.
-- @param basis
-- "Root" level settings, from the solution, which act as a starting point for
-- all of the collapsed settings built during this call.
-- @returns
-- The collapsed list of settings, keyed by configuration block/platform pair.
--
local function collapse(obj, basis)
local result = {}
basis = basis or {}
-- find the solution, which contains the configuration and platform lists
local sln = obj.solution or obj
merge(result, obj, basis)
for _, cfgname in ipairs(sln.configurations) do
merge(result, obj, basis, cfgname, "Native")
for _, pltname in ipairs(sln.platforms or {}) do
if pltname ~= "Native" then
merge(result, obj, basis, cfgname, pltname)
end
end
end
return result
end
--
-- Post-process a project configuration, applying path fix-ups and other adjustments
-- to the "raw" setting data pulled from the project script.
--
-- @param prj
-- The project object which contains the configuration.
-- @param cfg
-- The configuration object to be fixed up.
--
local function postprocess(prj, cfg)
cfg.project = prj
cfg.shortname = premake.getconfigname(cfg.name, cfg.platform, true)
cfg.longname = premake.getconfigname(cfg.name, cfg.platform)
-- set the project location, if not already set
cfg.location = cfg.location or cfg.basedir
-- figure out the target system
local platform = premake.platforms[cfg.platform]
if platform.iscrosscompiler then
cfg.system = cfg.platform
else
cfg.system = os.get()
end
-- adjust the kind as required by the target system
if cfg.kind == "SharedLib" and platform.nosharedlibs then
cfg.kind = "StaticLib"
end
-- remove excluded files from the file list
local files = { }
for _, fname in ipairs(cfg.files) do
local excluded = false
for _, exclude in ipairs(cfg.excludes) do
excluded = (fname == exclude)
if (excluded) then break end
end
if (not excluded) then
table.insert(files, fname)
end
end
cfg.files = files
-- fixup the data
for name, field in pairs(premake.fields) do
-- re-key flag fields for faster lookups
if field.isflags then
local values = cfg[name]
for _, flag in ipairs(values) do values[flag] = true end
end
end
-- build configuration objects for all files
cfg.__fileconfigs = { }
for _, fname in ipairs(cfg.files) do
cfg.terms.required = fname:lower()
local fcfg = {}
for _, blk in ipairs(cfg.project.blocks) do
if (premake.iskeywordsmatch(blk.keywords, cfg.terms)) then
mergeobject(fcfg, blk)
end
end
-- add indexed by name and integer
fcfg.name = fname
cfg.__fileconfigs[fname] = fcfg
table.insert(cfg.__fileconfigs, fcfg)
end
end
--
-- Computes a unique objects directory for every configuration.
--
local function builduniquedirs()
local num_variations = 4
-- Start by listing out each possible object directory for each configuration.
-- Keep a count of how many times each path gets used across the session.
local cfg_dirs = {}
local hit_counts = {}
for _, sln in ipairs(_SOLUTIONS) do
for _, prj in ipairs(sln.projects) do
for _, cfg in pairs(prj.__configs) do
local dirs = { }
dirs[1] = path.getabsolute(path.join(cfg.location, cfg.objdir or cfg.project.objdir or "obj"))
dirs[2] = path.join(dirs[1], iif(cfg.platform == "Native", "", cfg.platform))
dirs[3] = path.join(dirs[2], cfg.name)
dirs[4] = path.join(dirs[3], cfg.project.name)
cfg_dirs[cfg] = dirs
for v = 1, num_variations do
local d = dirs[v]
if hit_counts[d] then
hit_counts[d] = hit_counts[d] + 1
else
hit_counts[d] = 1
end
end
end
end
end
-- Now assign an object directory to each configuration, skipping those
-- that are in use somewhere else in the session
for _, sln in ipairs(_SOLUTIONS) do
for _, prj in ipairs(sln.projects) do
for _, cfg in pairs(prj.__configs) do
local dir
for v = 1, num_variations do
dir = cfg_dirs[cfg][v]
if hit_counts[dir] == 1 then break end
end
cfg.objectsdir = path.getrelative(cfg.location, dir)
end
end
end
end
--
-- Pre-computes the build and link targets for a configuration.
--
local function buildtargets()
for _, sln in ipairs(_SOLUTIONS) do
for _, prj in ipairs(sln.projects) do
for _, cfg in pairs(prj.__configs) do
-- determine which conventions the target should follow for this config
local pathstyle = premake.getpathstyle(cfg)
local namestyle = premake.getnamestyle(cfg)
-- build the targets
cfg.buildtarget = premake.gettarget(cfg, "build", pathstyle, namestyle, cfg.system)
cfg.linktarget = premake.gettarget(cfg, "link", pathstyle, namestyle, cfg.system)
if pathstyle == "windows" then
cfg.objectsdir = path.translate(cfg.objectsdir, "\\")
end
end
end
end
end
--
-- Takes the configuration information stored in solution->project->block
-- hierarchy and flattens it all down into one object per configuration.
-- These objects are cached with the project, and can be retrieved by
-- calling the getconfig() or the eachconfig() iterator function.
--
function premake.buildconfigs()
-- convert project path fields to be relative to project location
for _, sln in ipairs(_SOLUTIONS) do
for _, prj in ipairs(sln.projects) do
adjustpaths(prj.location, prj)
for _, blk in ipairs(prj.blocks) do
adjustpaths(prj.location, blk)
end
end
end
-- collapse configuration blocks, so that there is only one block per build
-- configuration/platform pair, filtered to the current operating environment
for _, sln in ipairs(_SOLUTIONS) do
local basis = collapse(sln)
for _, prj in ipairs(sln.projects) do
prj.__configs = collapse(prj, basis)
for _, cfg in pairs(prj.__configs) do
postprocess(prj, cfg)
end
end
end
-- assign unique object directories to each configuration
builduniquedirs()
-- walk it again and build the targets and unique directories
buildtargets(cfg)
end
|