Require configuration set fetch/store to use registered fields only; phase out direct table access

This commit is contained in:
Jason Perkins 2014-03-08 09:28:37 -05:00
parent c42c87a4ee
commit 974b622b82
7 changed files with 128 additions and 109 deletions

View File

@ -306,7 +306,7 @@
local target = api.gettarget(field.scope)
if not value then
return target.configset[field.name]
return configset.fetchvalue(target.configset, field.name)
end
local status, result = pcall(function ()
@ -367,7 +367,7 @@
end
if value:contains("*") then
local current = target.configset[field.name]
local current = configset.fetchvalue(target.configset, field.name)
local mask = path.wildcards(value)
for _, item in ipairs(current) do
if item:match(mask) == item then
@ -561,18 +561,16 @@
--
function api.settable(target, name, field, value)
-- 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)
target = target.configset or target
-- put simple values in an array
if type(value) ~= "table" then
value = { value }
end
-- store it, overwriting any existing value
target[name] = value
if target.configset then
configset.addvalue(target.configset, field.name, value)
else
target[name] = value
end
end
@ -641,13 +639,6 @@
function api.setlist(target, name, field, value)
local setter = api.getSetter(field, api.ListLevel)
-- 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
target = target.configset
end
-- process all of the values, according to the data type
local result = {}
local function recurse(value)
@ -661,7 +652,14 @@
end
recurse(value)
target[name] = result
-- 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
@ -713,8 +711,11 @@
-- 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)
target = target.configset or target
target[name] = value
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

View File

@ -5,7 +5,7 @@
-- I've had a chance to build it out more before using.
--
-- A configuration set manages a collection of configuration values, which
-- are organized into "blocks". Each block stores a set of field-value pairs,
-- are organized into "blocks". Each block stores a set of field-value pairs,
-- along with a list of terms which indicate the context in which those
-- values should be applied.
--
@ -35,19 +35,46 @@
function configset.new(parent)
local cset = {}
cset._parent = parent or false
cset._parent = parent
cset._blocks = {}
cset._current = false
cset._current = nil
cset.compiled = false
-- enable config sets to be treat like plain old tables; storing
-- a value will place it into the current block
setmetatable(cset, configset.__mt)
return cset
end
---
-- Create and return a metatable which allows a configuration set to act as a
-- "backing store" for a regular Lua table. Table operations that access a
-- registered field will fetch from or store to the configurations set, while
-- unknown keys are get and set to the table normally.
---
function configset.metatable(cset)
return {
__newindex = function(tbl, key, value)
local f = premake.field.get(key)
if f then
return configset.addvalue(cset, f.name, value)
else
rawset(tbl, key, value)
return value
end
end,
__index = function(tbl, key)
local f = premake.field.get(key)
if f then
return configset.fetchvalue(cset, f.name, cset._current._criteria.terms)
else
return nil
end
end
}
end
--
-- Register a field that requires special handling.
--
@ -66,7 +93,7 @@
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.
@ -86,10 +113,10 @@
function configset.addblock(cset, terms, basedir)
local block = {}
block._basedir = basedir
-- attach a criteria object to the block to control its application
block._criteria = criteria.new(terms)
table.insert(cset._blocks, block)
cset._current = block
return block
@ -195,8 +222,8 @@
-- @param cset
-- The configuration set to query.
-- @param context
-- A list of lowercase context terms to use during the fetch. Only those
-- blocks with terms fully contained by this list will be considered in
-- A list of lowercase context terms to use during the fetch. Only those
-- blocks with terms fully contained by this list will be considered in
-- determining the returned value. Terms should be lower case to make
-- the context filtering case-insensitive.
-- @param filename
@ -222,7 +249,7 @@
table.insert(result._blocks, block)
end
end
result.compiled = true
return result
end
@ -240,14 +267,14 @@
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
@ -268,7 +295,7 @@
return block[fieldname]
end
end
if cset._parent then
return fetchassign(cset._parent, fieldname, context, filename)
end
@ -280,14 +307,14 @@
-- a single result; values may optionally be merged too.
--
local function fetchkeyed(cset, fieldname, context, filename, mergevalues)
local function fetchkeyed(cset, fieldname, context, filename, mergevalues)
local result = {}
-- grab values from the parent set first
if cset._parent 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
@ -347,14 +374,14 @@
if block._removes and block._removes[fieldname] then
remove(block._removes[fieldname])
end
local value = block[fieldname]
if value then
merge(result, value)
end
end
end
return result
end
@ -368,8 +395,8 @@
-- The name of the field to query. The field should have already been
-- defined using the api.register() function.
-- @param context
-- A list of lowercase context terms to use during the fetch. Only those
-- blocks with terms fully contained by this list will be considered in
-- A list of lowercase context terms to use during the fetch. Only those
-- blocks with terms fully contained by this list will be considered in
-- determining the returned value. Terms should be lower case to make
-- the context filtering case-insensitive.
-- @param filename
@ -382,37 +409,27 @@
function configset.fetchvalue(cset, fieldname, context, filename)
local value
if not context then
context = cset._current._criteria.terms
end
-- 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 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)
value = fetchassign(cset, fieldname, context, filename)
-- if value is an object, return a copy of it, so that they can
-- modified by the caller without altering the source data
if type(value) == "table" then
value = table.deepcopy(value)
end
end
return value
end
--
-- Metatable allows configuration sets to be used like objects in the API.
-- Setting a value adds it to the currently active block. Getting a value
-- retrieves it using the currently active's block list of filter terms.
--
configset.__mt = {
__newindex = configset.addvalue,
__index = function(cset, fieldname)
return configset.fetchvalue(cset, fieldname, cset._current._criteria.terms)
end
}

View File

@ -10,7 +10,7 @@
--
-- The context also provides caching for the values returned from the set.
--
-- Copyright (c) 2012 Jason Perkins and the Premake project
-- Copyright (c) 2012-2014 Jason Perkins and the Premake project
--
premake.context = {}
@ -152,10 +152,21 @@
--
function context.fetchvalue(ctx, key)
-- The underlying configuration set will only hold registered fields.
-- If the requested key doesn't have a corresponding field, it is just
-- a regular value to be stored and fetched from the table.
local field = premake.field.get(key)
if not field then
return rawget(ctx, key)
end
-- If there is a matching field, then go fetch the aggregated value
-- from my configuration set, and then cache it future lookups.
local value = configset.fetchvalue(ctx._cfgset, key, ctx.terms, ctx._filename[1])
if value then
-- do I need to expand tokens?
local field = premake.fields[key]
-- do I need to expand tokens? -- local field = premake.fields[key]
if field and field.tokens then
local kind = field.kind
local ispath = kind:startswith("path") or kind:startswith("file") or kind:startswith("mixed")

View File

@ -29,18 +29,19 @@
prj.solution = sln
prj.script = _SCRIPT
local cset = configset.new(sln.configset)
cset.basedir = os.getcwd()
cset.filename = name
cset.uuid = os.uuid(name)
prj.configset = cset
-- Start a new configuration set to hold this project's info. For
-- convenience and backward compatibility with the old Premake 3.x
-- way of doing things, allow the project to be treated like a
-- regular table.
-- attach a type descriptor
setmetatable(prj, {
__index = function(prj, key)
return prj.configset[key]
end,
})
local cset = configset.new(sln.configset)
prj.configset = cset
setmetatable(prj, configset.metatable(cset))
-- And set defaults for some of the fields
prj.basedir = os.getcwd()
prj.filename = name
prj.uuid = os.uuid(name)
return prj
end

View File

@ -1,10 +1,10 @@
--
-- solution.lua
-- Work with the list of solutions loaded from the script.
-- Copyright (c) 2002-2013 Jason Perkins and the Premake project
-- Copyright (c) 2002-2014 Jason Perkins and the Premake project
--
premake.solution = { }
premake.solution = {}
local solution = premake.solution
local project = premake.project
local configset = premake.configset
@ -14,7 +14,7 @@
-- The list of defined solutions (which contain projects, etc.)
premake.solution.list = {}
solution.list = {}
--
@ -29,24 +29,25 @@
function solution.new(name)
local sln = {}
-- add to master list keyed by both name and index
table.insert(premake.solution.list, sln)
premake.solution.list[name] = sln
sln.name = name
sln.projects = {}
local cset = configset.new(configset.root)
cset.basedir = os.getcwd()
cset.filename = name
sln.configset = cset
-- Start a new configuration set to hold this project's info. For
-- convenience and backward compatibility with the old Premake 3.x
-- way of doing things, allow the solution to be treated like a
-- regular table.
-- attach a type descriptor
setmetatable(sln, {
__index = function(sln, key)
return sln.configset[key]
end,
})
local cset = configset.new(configset.root)
sln.configset = cset
setmetatable(sln, configset.metatable(cset))
-- Set defaults for some of the fields
sln.basedir = os.getcwd()
sln.filename = name
-- Add to master list keyed by both name and index
table.insert(premake.solution.list, sln)
premake.solution.list[name] = sln
return sln
end

View File

@ -50,11 +50,6 @@
test.isequal(".so", configset.fetchvalue(cset, "targetextension", {}))
end
function suite.canRoundtrip_onDefaultBlock_usingDirectSet()
cset.targetextension = ".so"
test.isequal(".so", configset.fetchvalue(cset, "targetextension", {}))
end
--
-- Make sure that I can roundtrip a value stored into a block
@ -67,12 +62,6 @@
test.isequal(".dll", configset.fetchvalue(cset, "targetextension", { "windows" }))
end
function suite.canRoundtrip_onSimpleTermMatch_usingDirectGet()
configset.addblock(cset, { "Windows" })
configset.addvalue(cset, "targetextension", ".dll")
test.isequal(".dll", cset.targetextension)
end
--
-- Make sure that blocks that do not match the context terms

View File

@ -1,11 +1,10 @@
--
-- tests/base/test_context.lua
-- Test suite for the configuration context API.
-- Copyright (c) 2012 Jason Perkins and the Premake project
-- Copyright (c) 2012-2014 Jason Perkins and the Premake project
--
T.context = {}
local suite = T.context
local suite = test.declare("context")
local context = premake.context
local configset = premake.configset
@ -15,11 +14,11 @@
-- Setup and teardown
--
local ctx, cfgset
local ctx, cset
function suite.setup()
cfgset = configset.new()
ctx = context.new(cfgset)
cset = configset.new()
ctx = context.new(cset)
end
@ -38,7 +37,7 @@
--
function suite.returnsConfigValue_onExistingValue()
cfgset.targetextension = ".so"
configset.addvalue(cset, "targetextension", ".so")
test.isequal(".so", ctx.targetextension)
end
@ -48,6 +47,6 @@
--
function suite.doesExpandTokens()
cfgset.targetname = "MyProject%{1 + 1}"
configset.addvalue(cset, "targetname", "MyProject%{1 + 1}")
test.isequal("MyProject2", ctx.targetname)
end