Ported key-value handling to new configuration system

This commit is contained in:
Jason Perkins 2012-11-14 12:42:53 -05:00
parent 5e48d05e3d
commit 2cf609c6ac
6 changed files with 193 additions and 86 deletions

View File

@ -46,8 +46,7 @@
end
-- make sure there is a handler available for this kind of value
local kind = api.getbasekind(field)
if not api["set" .. kind] then
if not api.getsetter(field) then
error("invalid kind '" .. kind .. "'", 2)
end
@ -68,8 +67,10 @@
-- if the field needs special handling, tell the config
-- set system about it
local merge = field.kind:endswith("-list")
configset.registerfield(field.name, { merge = merge })
configset.registerfield(field.name, {
keyed = api.iskeyedfield(field),
merge = api.islistfield(field),
})
end
@ -102,19 +103,10 @@
end
local status, result = pcall(function ()
-- A keyed value is a table containing key-value pairs, where the
-- type of the value is defined by the field.
if api.iskeyedfield(field) then
target[field.name] = target[field.name] or {}
api.setkeyvalue(target[field.name], field, value)
-- Lists is an array containing values of another type
elseif api.islistfield(field) then
api.setlist(target, field.name, field, value)
-- Otherwise, it is a "simple" value defined by the field
api.setkeyvalue(target, field, value)
else
local setter = api["set" .. field.kind]
local setter = api.getsetter(field, true)
setter(target, field.name, field, value)
end
end)
@ -227,6 +219,27 @@
end
--
-- Retrieve the "set" function for a field.
--
-- @param field
-- The field to query.
-- @param lists
-- If true, will return the list setter for list fields (i.e. string-list);
-- else returns base type setter (i.e. string).
-- @return
-- The setter for the field.
--
function api.getsetter(field, lists)
if lists and api.islistfield(field) then
return api.setlist
else
return api["set" .. api.getbasekind(field)]
end
end
--
-- Set a new array value. Arrays are lists of values stored by "value",
-- in that new values overwrite old ones, rather than merging like lists.
@ -294,19 +307,14 @@
error({ msg="value must be a table of key-value pairs" })
end
-- remove the "key-" prefix from the field kind
local kind = api.getbasekind(field)
local newval = {}
if api.islistfield(field) then
local setter = api.getsetter(field, true)
for key, value in pairs(values) do
api.setlist(target, key, field, value)
end
else
local setter = api["set" .. kind]
for key, value in pairs(values) do
setter(target, key, field, value)
end
setter(newval, key, field, value)
end
configset.addvalue(target.configset, field.name, newval)
end
@ -316,9 +324,7 @@
--
function api.setlist(target, name, field, value)
-- find the contained data type
local kind = api.getbasekind(field)
local setter = api["set" .. kind]
local setter = api.getsetter(field)
-- am I setting a configurable object, or some kind of subfield?
local result
@ -496,15 +502,6 @@
tokens = true,
}
--[[
api.register {
name = "excludes",
scope = "config",
kind = "file-list",
tokens = true,
}
--]]
-- For backward compatibility, excludes() is now an alias for removefiles()
function excludes(value)
removefiles(value)
@ -971,15 +968,6 @@
table.insert(cfg.keywords, path.wildcards(word):lower())
end
--[[
-- initialize list-type fields to empty tables
for name, field in pairs(premake.fields) do
if field.kind:endswith("-list") then
cfg[name] = { }
end
end
--]]
-- this is the new place for storing scoped objects
api.scope.configuration = cfg

View File

@ -114,7 +114,7 @@
function configset.addvalue(cset, fieldname, value)
local current = cset._current
local field = configset._fields[fieldname]
if field and field.merge then
if field and (field.keyed or field.merge) then
current[fieldname] = current[fieldname] or {}
table.insert(current[fieldname], value)
else
@ -167,12 +167,39 @@
end
--
-- Merges two lists of values together. The merged list is both indexed
-- and keyed for faster lookups. If duplicate values are encountered,
-- the earlier value is removed.
--
local function merge(a, b)
-- if b is itself a list, flatten it out
if type(b) == "table" then
for _, v in ipairs(b) do
merge(a, v)
end
-- if b is a simple value, insert it
else
-- find and remove earlier values
if a[b] then
table.remove(a, table.indexof(a, b))
end
table.insert(a, b)
a[b] = b
end
end
--
-- Retrieve a directly assigned value from the configuration set. No merging
-- takes place; the last value set is the one returned.
--
local function fetchassign(cset, fieldname, context, filename)
-- walk the loop backwards and return on first value encountered
local n = #cset._blocks
for i = n, 1, -1 do
local block = cset._blocks[i]
@ -187,6 +214,45 @@
end
--
-- Retrieve a keyed from the configuration set; keys are assembled into
-- a single result; values may optionally be merged too.
--
local function fetchkeyed(cset, fieldname, context, filename, mergevalues)
local result = {}
-- grab values from the parent set first
if cset._parent ~= cset then
result = fetchkeyed(cset._parent, fieldname, context, filename, merge)
end
function process(values)
for k, v in pairs(values) do
if type(k) == "number" then
process(v)
elseif mergevalues then
result[k] = result[k] or {}
merge(result[k], v)
else
result[k] = v
end
end
end
for _, block in ipairs(cset._blocks) do
if testblock(block, context, filename) then
local value = block[fieldname]
if value then
process(value)
end
end
end
return result
end
--
-- Retrieve a merged value from the configuration set; all values are
-- assembled together into a single result.
@ -200,25 +266,6 @@
result = fetchmerge(cset._parent, fieldname, context, filename)
end
function add(value)
-- recurse into tables to flatten out the list as I go
if type(value) == "table" then
for _, v in ipairs(value) do
add(v)
end
else
-- if the value is already in the result, remove it; enables later
-- blocks to adjust the order of earlier values
if result[value] then
table.remove(result, table.indexof(result, value))
end
-- add the value with both an index and a key (for fast existence checks)
table.insert(result, value)
result[value] = value
end
end
function remove(patterns)
for _, pattern in ipairs(patterns) do
local i = 1
@ -242,7 +289,7 @@
local value = block[fieldname]
if value then
add(value)
merge(result, value)
end
end
end
@ -276,9 +323,12 @@
-- should this field be merged or assigned?
local field = configset._fields[fieldname]
local keyed = field and field.keyed
local merge = field and field.merge
if merge then
if keyed then
value = fetchkeyed(cset, fieldname, context, filename, merge)
elseif merge then
value = fetchmerge(cset, fieldname, context, filename)
else
value = fetchassign(cset, fieldname, context, filename)

View File

@ -662,6 +662,8 @@
if stars == 0 then
leaf = path.getname(leaf)
end
else
leaf = path.getname(leaf)
end
vpath = path.join(stem, leaf)

View File

@ -124,19 +124,6 @@
end
--
-- On key-value APIs, the keyed object value should be the target.
--
function suite.keyObjectTarget_onKeyValue()
api.register { name = "testapi", kind = "key-test", scope = "project" }
local sln = solution "MySolution"
testapi { key = "test" }
test.istrue(sln.testapi == test_args.target)
end
--
-- On key-value APIs, the field name should be the key value from the supplied table.
--

View File

@ -128,6 +128,7 @@
test.isequal({}, configset.fetchvalue(cset, "buildoptions", {}))
end
--
-- List fields should merge values fetched from different blocks.
--
@ -188,3 +189,68 @@
configset.removevalues(cset, "defines", { "WIN*" })
test.isequal({ "LINUX", "MACOSX" }, configset.fetchvalue(cset, "defines", {}))
end
--
-- Keyed values should merge keys fetched from different blocks.
--
function suite.keyed_mergesKeys_onFetch()
configset.addvalue(cset, "configmap", { Debug="Debug", Release="Release" })
configset.addblock(cset, { "windows" })
configset.addvalue(cset, "configmap", { Profile="Profile" })
local x = configset.fetchvalue(cset, "configmap", {"windows"})
test.istrue(x.Debug and x.Release and x.Profile)
end
--
-- Multiple adds to a keyed value field in the same block should be merged.
--
function suite.keyed_mergesKeys_onAdd()
configset.addvalue(cset, "configmap", { Debug="Debug", Release="Release" })
configset.addvalue(cset, "configmap", { Profile="Profile" })
local x = configset.fetchvalue(cset, "configmap", {"windows"})
test.istrue(x.Debug and x.Release and x.Profile)
end
--
-- Keyed values should overwrite when non-merged fields are fetched.
--
function suite.keyed_overwritesValues_onNonMergeFetch()
configset.addvalue(cset, "configmap", { Debug="Debug" })
configset.addblock(cset, { "windows" })
configset.addvalue(cset, "configmap", { Debug="Development" })
local x = configset.fetchvalue(cset, "configmap", {"windows"})
test.isequal("Development", x.Debug)
end
function suite.keyed_overwritesValues_onNonMergeAdd()
configset.addvalue(cset, "configmap", { Debug="Debug" })
configset.addvalue(cset, "configmap", { Debug="Development" })
local x = configset.fetchvalue(cset, "configmap", {"windows"})
test.isequal("Development", x.Debug)
end
--
-- Keyed values should merge when merged fields are fetched.
--
function suite.keyed_mergesValues_onMergeFetch()
configset.addvalue(cset, "vpaths", { includes="*.h" })
configset.addblock(cset, { "windows" })
configset.addvalue(cset, "vpaths", { includes="*.hpp" })
local x = configset.fetchvalue(cset, "vpaths", {"windows"})
test.isequal({ "*.h", "*.hpp" }, x.includes)
end
function suite.keyed_mergesValues_onMergeAdd()
configset.addvalue(cset, "vpaths", { includes="*.h" })
configset.addvalue(cset, "vpaths", { includes="*.hpp" })
local x = configset.fetchvalue(cset, "vpaths", {"windows"})
test.isequal({ "*.h", "*.hpp" }, x.includes)
end

View File

@ -44,10 +44,17 @@
test.isequal(cfg.files[1], project.getvpath(prj, cfg.files[1]))
end
function suite.CanTrimLeadingPaths()
function suite.CanStripPaths()
files { "src/myproject/hello.c" }
vpaths { [""] = "src" }
prepare()
test.isequal("hello.c", project.getvpath(prj, cfg.files[1]))
end
function suite.CanTrimLeadingPaths()
files { "src/myproject/hello.c" }
vpaths { ["*"] = "src" }
prepare()
test.isequal("myproject/hello.c", project.getvpath(prj, cfg.files[1]))
end
@ -84,6 +91,13 @@
test.isequal("Headers/hello.h", project.getvpath(prj, cfg.files[1]))
end
function suite.MatchFilePattern_ToNone_Flat()
files { "src/myproject/hello.h" }
vpaths { [""] = "**.h" }
prepare()
test.isequal("hello.h", project.getvpath(prj, cfg.files[1]))
end
function suite.MatchFilePattern_ToNestedGroup_Flat()
files { "src/myproject/hello.h" }
vpaths { ["Source/Headers"] = "**.h" }
@ -107,7 +121,7 @@
function suite.MatchFilePattern_ToGroup_Nested()
files { "src/myproject/hello.h" }
vpaths { ["Headers/**"] = "**.h" }
vpaths { ["Headers/*"] = "**.h" }
prepare()
test.isequal("Headers/src/myproject/hello.h", project.getvpath(prj, cfg.files[1]))
end
@ -121,7 +135,7 @@
function suite.MatchFilePatternWithPath_ToGroup_Nested()
files { "src/myproject/hello.h" }
vpaths { ["Headers/**"] = "src/**.h" }
vpaths { ["Headers/*"] = "src/**.h" }
prepare()
test.isequal("Headers/myproject/hello.h", project.getvpath(prj, cfg.files[1]))
end