Move config set value stores to new field framework

This commit is contained in:
Jason Perkins 2014-03-08 16:03:34 -05:00
parent 9378d31295
commit aacafa8fe7
5 changed files with 258 additions and 366 deletions

View File

@ -17,20 +17,6 @@
api.scope = {}
--
-- Levels of "setters" for populating configuration objects. The top-level
-- API contains keyed, list, and plain values; keyed values can contain
-- lists and plain values; lists can contain only plain values. Each time
-- a setter function is fetched one of these values are passed along to
-- indicate the current resolution level.
--
api.TopLevel = 0
api.KeyedLevel = 1
api.ListLevel = 2
api.ValueLevel = 3
--
-- Create a "root" configuration set, to hold the global configuration. Values
-- that are added to this set become available for all add-ons, solution, projects,
@ -118,11 +104,6 @@
field.list = true
end
-- make sure there is a handler available for this kind of value
if not api.getSetter(field, api.ValueLevel) then
error("invalid kind '" .. field.kind .. "'", 2)
end
-- add this new field to my master list
field = premake.field.new(field)
@ -137,13 +118,6 @@
return api.remove(field, value)
end
end
-- if the field needs special handling, tell the config
-- set system about it
configset.registerfield(field.name, {
keyed = api.isKeyedField(field),
merge = api.isListField(field),
})
end
@ -310,8 +284,7 @@
end
local status, result = pcall(function ()
local setter = api.getSetter(field, api.TopLevel)
setter(target, field.name, field, value)
configset.store(target.configset, field, value)
end)
if not status then
@ -513,32 +486,6 @@
end
--
-- Retrieve the "set" function for a field, at a given level of resolution.
--
-- @param field
-- The field to query.
-- @param level
-- One of the setter resolution levels TopLevel, KeyedLevel, or ListLevel,
-- indicating who is asking for a setter. At the top, the API contains
-- keyed, list, and plain values and desires a setter for any of them.
-- Keyed values can contain only lists and plain values, and so should only
-- receive those setter. Finally, lists can contain only plain values.
-- @return
-- The setter for the field.
--
function api.getSetter(field, level)
if level < api.KeyedLevel and api.isKeyedField(field) then
return api.setkeyvalue
end
if level < api.ListLevel and api.isListField(field) then
return api.setlist
end
return api["set" .. field.kind]
end
--
-- Clears all active API objects; resets to root configuration block.
--
@ -555,54 +502,11 @@
api.reset()
--
-- Set a new table value. Tables are arbitrary Lua tables; new values replace
-- old ones, rather than merging like lists.
--
function api.settable(target, name, field, value)
-- put simple values in an array
if type(value) ~= "table" then
value = { value }
end
if target.configset then
configset.addvalue(target.configset, field.name, value)
else
target[name] = value
end
end
--
-- Set a new file value on an API field. Unlike paths, file value can
-- use wildcards (and so must always be a list).
--
function api.setfile(target, name, field, value)
if value:find("*") then
local values = os.matchfiles(value)
table.foreachi(values, function(v)
api.setfile(target, name, field, v)
name = name + 1
end)
else
target[name] = path.getabsolute(value)
end
end
function api.setdirectory(target, name, field, value)
if value:find("*") then
local values = os.matchdirs(value)
table.foreachi(values, function(v)
api.setdirectory(target, name, field, v)
name = name + 1
end)
else
target[name] = path.getabsolute(value)
end
end
function api.removefile(target, value)
table.insert(target, path.getabsolute(value))
end
@ -610,198 +514,79 @@
api.removedirectory = api.removefile
--
-- Update a keyed value. Iterate over the keys in the new value, and use
-- the corresponding values to update the target object.
--
function api.setkeyvalue(target, name, field, values)
if type(values) ~= "table" then
error({ msg="value must be a table of key-value pairs" })
end
local newval = {}
local setter = api.getSetter(field, api.KeyedLevel)
for key, value in pairs(values) do
setter(newval, key, field, value)
end
configset.addvalue(target.configset, name, newval)
end
--
-- Set a new list value. Lists are arrays of values, with new values
-- appended to any previous values.
-- Directory data kind; performs wildcard directory searches, converts
-- results to absolute paths.
--
function api.setlist(target, name, field, value)
local setter = api.getSetter(field, api.ListLevel)
-- process all of the values, according to the data type
local result = {}
local function recurse(value)
if type(value) == "table" then
table.foreachi(value, function (value)
recurse(value)
end)
else
setter(result, #result + 1, field, value)
end
end
recurse(value)
-- If this target is just a wrapper for a configuration set,
-- apply the new values to that set instead. The current
-- solution and project objects act this way.
if target.configset then
configset.addvalue(target.configset, field.name, result)
else
target[name] = result
end
end
--
-- Set a new value into a mixed value field, which contain both
-- simple strings and paths.
--
function api.setmixed(target, name, field, value)
-- if the value contains a '/' treat it as a path
if type(value) == "string" and value:find('/', nil, true) then
value = path.getabsolute(value)
end
return api.setstring(target, name, field, value)
end
--
-- Set a new path value on an API field.
--
function api.setpath(target, name, field, value)
api.setstring(target, name, field, path.getabsolute(value))
end
--
-- Set a new string value on an API field.
--
function api.setstring(target, name, field, value)
if type(value) == "table" then
error({ msg="expected string; got table" })
end
local value, err, additional = api.checkvalue(value, field)
if err then error({ msg=err }) end
if field.deprecated and field.deprecated[value] then
local handler = field.deprecated[value]
handler.add(value)
if api._deprecations ~= "off" then
local key = field.name .. "_" .. value
premake.warnOnce(key, "the %s value %s has been deprecated.\n %s", field.name, value, handler.message or "")
if api._deprecations == "error" then error({ msg="deprecation errors enabled" }) end
end
end
-- if the target is the project, configset will be set and I can push
-- the value there. Otherwise I was called to store into some other kind
-- of object (i.e. an array or list)
if target.configset then
configset.addvalue(target.configset, field.name, value)
else
target[name] = value
end
-- Odd case: the FatalWarnings flag expands to multiple values now
-- (FatalCompileWarnings, FatalLinkWarnings). Generalize this
-- behavior by running multiple return values back through again.
if additional then
api.callback(field, additional)
end
end
--
-- Set a number value on an API field.
--
function api.setnumber(target, name, field, value)
local t = type(value)
if t ~= "number" then
error({ msg="expected number; got " .. t })
end
target = target.configset or target
target[name] = value
end
--
-- Set a integer value on an API field.
--
function api.setinteger(target, name, field, value)
local t = type(value)
if t ~= "number" then
error({ msg="expected number; got " .. t })
end
if math.floor(value) ~= value then
error({ msg="expected integer; got " .. tostring(value) })
end
target = target.configset or target
target[name] = value
end
--
-- Set a boolean value on an API field.
--
function api.setboolean(target, name, field, value)
local t = type(value)
if t ~= "boolean" then
error({ msg="expected boolean; got " .. t })
end
target = target.configset or target
target[name] = value
end
premake.field.kind("boolean", {
})
premake.field.kind("directory", {
store = function(field, current, value, processor)
if value:find("*") then
value = os.matchdirs(value)
for i, file in ipairs(value) do
value[i] = path.getabsolute(value[i])
end
else
value = path.getabsolute(value)
end
return value
end
})
--
-- File data kind; performs wildcard file searches, converts results
-- to absolute paths.
--
premake.field.kind("file", {
store = function(field, current, value, processor)
if value:find("*") then
value = os.matchfiles(value)
for i, file in ipairs(value) do
value[i] = path.getabsolute(value[i])
end
else
value = path.getabsolute(value)
end
return value
end
})
--
-- Integer data kind; validates inputs.
--
premake.field.kind("integer", {
store = function(field, current, value, processor)
local t = type(value)
if t ~= "number" then
error { msg="expected number; got " .. t }
end
if math.floor(value) ~= value then
error { msg="expected integer; got " .. tostring(value) }
end
return value
end
})
--
-- Key-value data kind definition. Merges key domains; values may be any kind.
--
local function setKeyed(field, current, value, processor)
local function storeKeyed(field, current, value, processor)
current = current or {}
for k, v in pairs(value) do
if type(k) == "number" then
current = setKeyed(field, current, v, processor)
current = storeKeyed(field, current, v, processor)
else
if processor then
v = processor(field, current[k], v)
@ -814,7 +599,8 @@
end
premake.field.kind("keyed", {
merge = setKeyed
store = storeKeyed,
merge = storeKeyed,
})
@ -824,50 +610,145 @@
-- contain any other kind of data.
--
local function setList(field, current, value, processor)
local function storeList(field, current, value, processor)
if type(value) == "table" then
for _, item in ipairs(value) do
current = setList(field, current, item, processor)
end
table.foreachi(value, function(item)
current = storeList(field, current, item, processor)
end)
return current
end
local function store(item)
if current[item] then
table.remove(current, table.indexof(current, item))
end
table.insert(current, item)
current[item] = item
end
current = current or {}
if current[value] then
table.remove(current, table.indexof(current, value))
if processor then
value = processor(field, nil, value)
end
if type(value) == "table" then
table.foreachi(value, store)
else
store(value)
end
table.insert(current, value)
current[value] = value
return current
end
premake.field.kind("list", {
merge = setList,
store = storeList,
merge = storeList,
})
--
-- Mixed data kind; values containing a directory separator "/" are converted
-- to absolute paths, other values left as-is. Used for links, where system
-- libraries and local library paths can be mixed into a single list.
--
premake.field.kind("mixed", {
store = function(field, current, value, processor)
if type(value) == "string" and value:find('/', nil, true) then
value = path.getabsolute(value)
end
return value
end
})
--
-- Number data kind; validates inputs.
--
premake.field.kind("number", {
store = function(field, current, value, processor)
local t = type(value)
if t ~= "number" then
error { msg="expected number; got " .. t }
end
return value
end
})
--
-- Path data kind; converts all inputs to absolute paths.
--
premake.field.kind("path", {
store = function(field, current, value, processor)
return path.getabsolute(value)
end
})
--
-- String data kind; performs validation against allowed fields, checks for
-- value deprecations.
--
premake.field.kind("string", {
store = function(field, current, value, processor)
if type(value) == "table" then
error({ msg="expected string; got table" })
end
local value, err, additional = api.checkvalue(value, field)
if err then
error { msg=err }
end
if field.deprecated and field.deprecated[value] then
local handler = field.deprecated[value]
handler.add(value)
if api._deprecations ~= "off" then
local key = field.name .. "_" .. value
premake.warnOnce(key, "the %s value %s has been deprecated.\n %s", field.name, value, handler.message or "")
if api._deprecations == "error" then
error { msg="deprecation errors enabled" }
end
end
end
-- If my single value also returned additional results (aliases, or
-- see FatalWarnings flag), return them all together. This is an
-- unusual but occasionally useful ability.
if additional then
return table.flatten { value, additional }
end
return value
end
})
--
-- Table data kind; wraps simple values into a table, returns others as-is.
--
premake.field.kind("table", {
store = function(field, current, value, processor)
if type(value) ~= "table" then
value = { value }
end
return value
end
})
--
-- Start a new block of configuration settings.
--

View File

@ -17,8 +17,6 @@
local configset = premake.configset
local criteria = premake.criteria
configset._fields = {}
--
-- Create a new configuration set.
@ -149,7 +147,7 @@
__newindex = function(tbl, key, value)
local f = premake.field.get(key)
if f then
return configset.addvalue(cset, f.name, value)
return configset.store(cset, f, value)
else
rawset(tbl, key, value)
return value
@ -168,26 +166,6 @@
--
-- Register a field that requires special handling.
--
-- @param name
-- The name of the field to register.
-- @param behavior
-- A table containing the flags:
--
-- merge - if set, the field will be treated as a list, and multiple
-- values will be merged together when fetched.
-- keys - if set, the field will be treated an associative array (sets
-- of key-value pairs) instead of an indexed array.
--
function configset.registerfield(name, behavior)
configset._fields[name] = behavior
end
--
-- Create a new block of configuration field-value pairs, with the provided
-- set of context terms to control their application.
@ -232,20 +210,14 @@
-- The new value for the field.
--
function configset.addvalue(cset, fieldname, value)
-- make sure there is an active block
function configset.store(cset, field, value)
if not cset._current then
configset.addblock(cset, {})
end
local key = field.name
local current = cset._current
local field = configset._fields[fieldname]
if field and (field.keyed or field.merge) then
current[fieldname] = current[fieldname] or {}
table.insert(current[fieldname], value)
else
current[fieldname] = value
end
current[key] = premake.field.store(field, current[key], value)
end

View File

@ -64,6 +64,11 @@
f._kind = kind
-- All fields must have a valid store() function
if not field.accessor(f, "store") then
error("invalid field kind '" .. f.kind .. "'")
end
field._list[f.name] = f
return f
end
@ -123,6 +128,11 @@
-- cached from earlier calls are reused again.
local function accessorForKind(kind)
-- I'll end up with a kind of "" when I hit the end of the string
if kind == "" then
return nil
end
-- Have I already cached a result from an earlier call?
if cache[kind] then
return cache[kind]
@ -185,7 +195,6 @@
end
function field.merge(f, current, value)
local processor = field.accessor(f, "merge")
if processor then
@ -205,3 +214,14 @@
function field.merges(f)
return (field.accessor(f, "merge") ~= nil)
end
function field.store(f, current, value)
local processor = field.accessor(f, "store")
if processor then
return processor(f, current, value)
else
return value
end
end

View File

@ -47,8 +47,9 @@
--
function suite.canRoundtrip_onDefaultBlock()
configset.addvalue(cset, "targetextension", ".so")
test.isequal(".so", configset.fetch(cset, field.get("targetextension"), {}))
local f = field.get("targetextension")
configset.store(cset, f, ".so")
test.isequal(".so", configset.fetch(cset, f, {}))
end
@ -58,9 +59,10 @@
--
function suite.canRoundtrip_onSimpleTermMatch()
local f = field.get("targetextension")
configset.addblock(cset, { "Windows" })
configset.addvalue(cset, "targetextension", ".dll")
test.isequal(".dll", configset.fetch(cset, field.get("targetextension"), { "windows" }))
configset.store(cset, f, ".dll")
test.isequal(".dll", configset.fetch(cset, f, { "windows" }))
end
@ -70,10 +72,11 @@
--
function suite.skipsBlock_onTermMismatch()
configset.addvalue(cset, "targetextension", ".so")
local f = field.get("targetextension")
configset.store(cset, f, ".so")
configset.addblock(cset, { "Windows" })
configset.addvalue(cset, "targetextension", ".dll")
test.isequal(".so", configset.fetch(cset, field.get("targetextension"), { "linux" }))
configset.store(cset, f, ".dll")
test.isequal(".so", configset.fetch(cset, f, { "linux" }))
end
@ -82,8 +85,9 @@
--
function suite.canRoundtrip_fromParentToChild()
configset.addvalue(parentset, "targetextension", ".so")
test.isequal(".so", configset.fetch(cset, field.get("targetextension"), {}))
local f = field.get("targetextension")
configset.store(parentset, f, ".so")
test.isequal(".so", configset.fetch(cset, f, {}))
end
@ -92,9 +96,10 @@
--
function suite.child_canOverrideStringValueFromParent()
configset.addvalue(parentset, "targetextension", ".so")
configset.addvalue(cset, "targetextension", ".dll")
test.isequal(".dll", configset.fetch(cset, field.get("targetextension"), {}))
local f = field.get("targetextension")
configset.store(parentset, f, ".so")
configset.store(cset, f, ".dll")
test.isequal(".dll", configset.fetch(cset, f, {}))
end
@ -104,9 +109,10 @@
--
function suite.filenameMadeRelative_onBaseDirSet()
local f = field.get("buildaction")
configset.addblock(cset, { "hello.c" }, os.getcwd())
configset.addvalue(cset, "buildaction", "copy")
test.isequal("copy", configset.fetch(cset, field.get("buildaction"), {}, path.join(os.getcwd(), "hello.c")))
configset.store(cset, f, "Copy")
test.isequal("Copy", configset.fetch(cset, f, {}, path.join(os.getcwd(), "hello.c")))
end
@ -124,10 +130,11 @@
--
function suite.lists_mergeValues_onFetch()
configset.addvalue(cset, "buildoptions", "v1")
local f = field.get("buildoptions")
configset.store(cset, f, "v1")
configset.addblock(cset, { "windows" })
configset.addvalue(cset, "buildoptions", "v2")
test.isequal({"v1", "v2"}, configset.fetch(cset, field.get("buildoptions"), {"windows"}))
configset.store(cset, f, "v2")
test.isequal({"v1", "v2"}, configset.fetch(cset, f, {"windows"}))
end
@ -136,9 +143,10 @@
--
function suite.lists_mergeValues_onAdd()
configset.addvalue(cset, "buildoptions", "v1")
configset.addvalue(cset, "buildoptions", "v2")
test.isequal({"v1", "v2"}, configset.fetch(cset, field.get("buildoptions"), {"windows"}))
local f = field.get("buildoptions")
configset.store(cset, f, "v1")
configset.store(cset, f, "v2")
test.isequal({"v1", "v2"}, configset.fetch(cset, f, {"windows"}))
end
@ -147,8 +155,9 @@
--
function suite.lists_includeValueKeys()
configset.addvalue(cset, "buildoptions", { "v1", "v2" })
local x = configset.fetch(cset, field.get("buildoptions"), {})
local f = field.get("buildoptions")
configset.store(cset, f, { "v1", "v2" })
local x = configset.fetch(cset, f, {})
test.isequal("v2", x.v2)
end
@ -158,15 +167,17 @@
--
function suite.remove_onExactValueMatch()
configset.addvalue(cset, "flags", { "Symbols", "Unsafe", "NoRTTI" })
local f = field.get("flags")
configset.store(cset, f, { "Symbols", "Unsafe", "NoRTTI" })
configset.removevalues(cset, "flags", { "Unsafe" })
test.isequal({ "Symbols", "NoRTTI" }, configset.fetch(cset, field.get("flags"), {}))
test.isequal({ "Symbols", "NoRTTI" }, configset.fetch(cset, f, {}))
end
function suite.remove_onMultipleValues()
configset.addvalue(cset, "flags", { "Symbols", "NoExceptions", "Unsafe", "NoRTTI" })
local f = field.get("flags")
configset.store(cset, f, { "Symbols", "NoExceptions", "Unsafe", "NoRTTI" })
configset.removevalues(cset, "flags", { "NoExceptions", "NoRTTI" })
test.isequal({ "Symbols", "Unsafe" }, configset.fetch(cset, field.get("flags"), {}))
test.isequal({ "Symbols", "Unsafe" }, configset.fetch(cset, f, {}))
end
@ -175,9 +186,10 @@
--
function suite.remove_onWildcard()
configset.addvalue(cset, "defines", { "WIN32", "WIN64", "LINUX", "MACOSX" })
local f = field.get("defines")
configset.store(cset, f, { "WIN32", "WIN64", "LINUX", "MACOSX" })
configset.removevalues(cset, "defines", { "WIN*" })
test.isequal({ "LINUX", "MACOSX" }, configset.fetch(cset, field.get("defines"), {}))
test.isequal({ "LINUX", "MACOSX" }, configset.fetch(cset, f, {}))
end
@ -186,10 +198,11 @@
--
function suite.keyed_mergesKeys_onFetch()
configset.addvalue(cset, "configmap", { Debug="Debug", Release="Release" })
local f = field.get("configmap")
configset.store(cset, f, { Debug="Debug", Release="Release" })
configset.addblock(cset, { "windows" })
configset.addvalue(cset, "configmap", { Profile="Profile" })
local x = configset.fetch(cset, field.get("configmap"), {"windows"})
configset.store(cset, f, { Profile="Profile" })
local x = configset.fetch(cset, f, {"windows"})
test.istrue(x.Debug and x.Release and x.Profile)
end
@ -199,9 +212,10 @@
--
function suite.keyed_mergesKeys_onAdd()
configset.addvalue(cset, "configmap", { Debug="Debug", Release="Release" })
configset.addvalue(cset, "configmap", { Profile="Profile" })
local x = configset.fetch(cset, field.get("configmap"), {"windows"})
local f = field.get("configmap")
configset.store(cset, f, { Debug="Debug", Release="Release" })
configset.store(cset, f, { Profile="Profile" })
local x = configset.fetch(cset, f, {"windows"})
test.istrue(x.Debug and x.Release and x.Profile)
end
@ -211,18 +225,20 @@
--
function suite.keyed_overwritesValues_onNonMergeFetch()
configset.addvalue(cset, "configmap", { Debug="Debug" })
local f = field.get("configmap")
configset.store(cset, f, { Debug="Debug" })
configset.addblock(cset, { "windows" })
configset.addvalue(cset, "configmap", { Debug="Development" })
local x = configset.fetch(cset, field.get("configmap"), {"windows"})
test.isequal("Development", x.Debug)
configset.store(cset, f, { Debug="Development" })
local x = configset.fetch(cset, f, {"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.fetch(cset, field.get("configmap"), {"windows"})
test.isequal("Development", x.Debug)
local f = field.get("configmap")
configset.store(cset, f, { Debug="Debug" })
configset.store(cset, f, { Debug="Development" })
local x = configset.fetch(cset, f, {"windows"})
test.isequal({"Development"}, x.Debug)
end
@ -231,16 +247,18 @@
--
function suite.keyed_mergesValues_onMergeFetch()
configset.addvalue(cset, "vpaths", { includes="*.h" })
local f = field.get("vpaths")
configset.store(cset, f, { includes="*.h" })
configset.addblock(cset, { "windows" })
configset.addvalue(cset, "vpaths", { includes="*.hpp" })
local x = configset.fetch(cset, field.get("vpaths"), {"windows"})
test.isequal({ "*.h", "*.hpp" }, x.includes)
configset.store(cset, f, { includes="*.hpp" })
local x = configset.fetch(cset, f, {"windows"})
test.isequal({ os.getcwd().."/*.h", os.getcwd().."/*.hpp" }, x.includes)
end
function suite.keyed_mergesValues_onMergeAdd()
configset.addvalue(cset, "vpaths", { includes="*.h" })
configset.addvalue(cset, "vpaths", { includes="*.hpp" })
local x = configset.fetch(cset, field.get("vpaths"), {"windows"})
test.isequal({ "*.h", "*.hpp" }, x.includes)
local f = field.get("vpaths")
configset.store(cset, f, { includes="*.h" })
configset.store(cset, f, { includes="*.hpp" })
local x = configset.fetch(cset, f, {"windows"})
test.isequal({ os.getcwd().."/*.h", os.getcwd().."/*.hpp" }, x.includes)
end

View File

@ -8,6 +8,7 @@
local context = premake.context
local configset = premake.configset
local field = premake.field
--
@ -37,7 +38,7 @@
--
function suite.returnsConfigValue_onExistingValue()
configset.addvalue(cset, "targetextension", ".so")
configset.store(cset, field.get("targetextension"), ".so")
test.isequal(".so", ctx.targetextension)
end
@ -47,6 +48,6 @@
--
function suite.doesExpandTokens()
configset.addvalue(cset, "targetname", "MyProject%{1 + 1}")
configset.store(cset, field.get("targetname"), "MyProject%{1 + 1}")
test.isequal("MyProject2", ctx.targetname)
end