From aacafa8fe7efdb95a84aaaa73c70aec9f546221c Mon Sep 17 00:00:00 2001 From: Jason Perkins Date: Sat, 8 Mar 2014 16:03:34 -0500 Subject: [PATCH] Move config set value stores to new field framework --- src/base/api.lua | 443 +++++++++++++--------------------- src/base/configset.lua | 36 +-- src/base/field.lua | 22 +- tests/base/test_configset.lua | 118 +++++---- tests/base/test_context.lua | 5 +- 5 files changed, 258 insertions(+), 366 deletions(-) diff --git a/src/base/api.lua b/src/base/api.lua index 326e4712..16ee1930 100755 --- a/src/base/api.lua +++ b/src/base/api.lua @@ -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. -- diff --git a/src/base/configset.lua b/src/base/configset.lua index fdea8e6d..11c86a5f 100644 --- a/src/base/configset.lua +++ b/src/base/configset.lua @@ -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 diff --git a/src/base/field.lua b/src/base/field.lua index f6308b99..5093fdb2 100644 --- a/src/base/field.lua +++ b/src/base/field.lua @@ -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 diff --git a/tests/base/test_configset.lua b/tests/base/test_configset.lua index a64e4fe9..3fcbcaa7 100644 --- a/tests/base/test_configset.lua +++ b/tests/base/test_configset.lua @@ -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 diff --git a/tests/base/test_context.lua b/tests/base/test_context.lua index 5b75c718..6369fa1f 100644 --- a/tests/base/test_context.lua +++ b/tests/base/test_context.lua @@ -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