Initial work on an expanded configuration API; added configset, criteria, and context objects, initial usage for target naming parameters

This commit is contained in:
Jason Perkins 2012-10-19 18:53:03 -04:00
parent 752adb830b
commit 91ba9c899f
14 changed files with 534 additions and 61 deletions

View File

@ -78,8 +78,8 @@
-- load the manifest of script files
scripts = dofile("src/_manifest.lua")
-- main script always goes at the end
table.insert(scripts, "_premake_main.lua")
-- main script always goes first
table.insert(scripts, 1, "_premake_main.lua")
-- open scripts.c and write the file header
local out = io.open("src/host/scripts.c", "w+b")

View File

@ -9,25 +9,35 @@
return
{
-- core files
-- Lua extensions
"base/os.lua",
"base/path.lua",
"base/string.lua",
"base/table.lua",
-- core files
"base/io.lua",
"base/globals.lua",
"base/action.lua",
"base/criteria.lua",
"base/option.lua",
"base/tree.lua",
"base/project.lua",
"base/config.lua",
"base/bake.lua",
"base/api.lua",
"base/cmdline.lua",
"base/validate.lua",
"base/help.lua",
"base/premake.lua",
-- configuration APIs
"base/api.lua",
"base/configset.lua",
"base/context.lua",
-- runtime environment setup
"_premake_init.lua",
"base/cmdline.lua",
-- project APIs
"project/oven.lua",
"project/project.lua",

89
src/_premake_init.lua Normal file
View File

@ -0,0 +1,89 @@
--
-- _premake_init.lua
--
-- Prepares the runtime environment for the add-ons and user project scripts.
--
-- Copyright (c) 2012 Jason Perkins and the Premake project
--
local configset = premake.configset
--
-- 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,
-- and on down the line.
--
premake.root = configset.new()
local root = premake.root
--
-- Set up the global environment for the systems I know about. I would like to see
-- at least some if not all of this moved into add-ons in the future.
--
-- TODO: use the same configuration API as the user project scripts, once they
-- have been ported, like:
--
-- configuration { "Windows or Xbox360", "SharedLib" }
-- targetprefix ""
-- targetextension ".dll"
-- implibextension ".lib"
--
configset.addblock(root, { "SharedLib" })
configset.addvalue(root, "targetprefix", "lib")
configset.addvalue(root, "targetextension", ".so")
configset.addblock(root, { "StaticLib" })
configset.addvalue(root, "targetprefix", "lib")
configset.addvalue(root, "targetextension", ".a")
configset.addblock(root, { "MacOSX", "SharedLib" })
configset.addvalue(root, "targetextension", ".dylib")
configset.addblock(root, { "PS3", "ConsoleApp" })
configset.addvalue(root, "targetextension", ".elf")
configset.addblock(root, { "Windows", "ConsoleApp" })
configset.addvalue(root, "targetextension", ".exe")
configset.addblock(root, { "Windows", "WindowedApp" })
configset.addvalue(root, "targetextension", ".exe")
configset.addblock(root, { "Windows", "SharedLib" })
configset.addvalue(root, "targetprefix", "")
configset.addvalue(root, "targetextension", ".dll")
configset.addvalue(root, "implibextension", ".lib")
configset.addblock(root, { "Windows", "StaticLib" })
configset.addvalue(root, "targetprefix", "")
configset.addvalue(root, "targetextension", ".lib")
configset.addblock(root, { "Xbox360", "ConsoleApp" })
configset.addvalue(root, "targetextension", ".exe")
configset.addblock(root, { "Xbox360", "WindowedApp" })
configset.addvalue(root, "targetextension", ".exe")
configset.addblock(root, { "Xbox360", "SharedLib" })
configset.addvalue(root, "targetprefix", "")
configset.addvalue(root, "targetextension", ".dll")
configset.addvalue(root, "implibextension", ".lib")
configset.addblock(root, { "Xbox360", "StaticLib" })
configset.addvalue(root, "targetprefix", "")
configset.addvalue(root, "targetextension", ".lib")

View File

@ -760,14 +760,17 @@
name = "system",
scope = "config",
kind = "string",
allowed = function(value)
value = value:lower()
if premake.systems[value] then
return value
else
return nil, "unknown system"
end
end,
allowed = {
"bsd",
"haiku",
"linux",
"macosx",
"ps3",
"solaris",
"wii",
"windows",
"xbox360",
},
}
api.register {

110
src/base/configset.lua Normal file
View File

@ -0,0 +1,110 @@
--
-- configset.lua
--
-- DO NOT USE THIS YET! I am just getting started here; please wait until
-- 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,
-- along with a list of terms which indicate the context in which those
-- values should be applied.
--
-- Configurations use the API definition to know what fields are available,
-- and the corresponding value types for those fields. Only fields that have
-- been registered via api.register() can be stored.
--
-- Copyright (c) 2012 Jason Perkins and the Premake project
--
premake.configset = {}
local configset = premake.configset
local criteria = premake.criteria
--
-- Create a new configuration set.
--
function configset.new()
local cfgset = {}
cfgset.blocks = {}
configset.addblock(cfgset, {})
return cfgset
end
--
-- Create a new block of configuration field-value pairs, with the provided
-- set of context terms to control their application.
--
-- @param cfgset
-- The configuration set to hold the new block.
-- @param terms
-- A set of context terms to control the application of values contained
-- in the block.
-- @return
-- The new configuration data block.
--
function configset.addblock(cfgset, terms)
local block = {}
-- convert terms to a simple, all-lower-case array
terms = table.flatten(terms)
for i, term in ipairs(terms) do
terms[i] = term:lower()
end
block.terms = terms
table.insert(cfgset.blocks, block)
cfgset.current = block
return block
end
--
-- Add a new field-value pair to the current configuration data block. The
-- data type of the field is taken into account when adding the values:
-- strings are replaced, arrays are merged, etc.
--
-- @param cfgset
-- The configuration set to hold the new value.
-- @param fieldname
-- The name of the field being set. The field should have already been
-- defined using the api.register() function.
-- @param value
-- The new value for the field.
--
function configset.addvalue(cfgset, fieldname, value)
cfgset.current[fieldname] = value
end
--
-- Retrieve a value from the configuration set.
--
-- @param cfgset
-- The configuration set to query.
-- @param fieldname
-- 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
-- determining the returned value. Terms should be lower case to make
-- the context filtering case-insensitive.
--
function configset.fetchvalue(cfgset, fieldname, context)
local value = ""
for _, block in ipairs(cfgset.blocks) do
if criteria.matches(block.terms, context) then
value = block[fieldname] or value
end
end
return value
end

53
src/base/context.lua Normal file
View File

@ -0,0 +1,53 @@
--
-- context.lua
--
-- DO NOT USE THIS YET! I am just getting started here; please wait until
-- I've had a chance to build it out more before using.
--
-- Provide a context for pulling out values from a configuration set. Each
-- context has an associated list of terms which constrain the values that
-- it will retrieve, i.e. "Windows, "Debug", "x64", and so on.
--
-- The context also provides caching for the values returned from the set.
--
-- Copyright (c) 2012 Jason Perkins and the Premake project
--
premake.context = {}
local context = premake.context
local configset = premake.configset
--
-- Create a new context object.
--
-- @param cfgset
-- The configuration set to provide the data from this context.
-- @param terms
-- A list of terms to describe this context. Only configuration blocks
-- matching these terms will be considered when querying values.
-- @return
-- A new context object.
--
function context.new(cfgset, terms)
local ctx = {}
ctx.cfgset = cfgset
-- make future tests case-insensitive
terms = table.flatten(terms)
for i, term in ipairs(terms) do
terms[i] = term:lower()
end
ctx.terms = terms
-- attach field lookup metatable
setmetatable(ctx, {
__index = function(ctx, key)
ctx[key] = configset.fetchvalue(cfgset, key, terms)
return ctx[key]
end
})
return ctx
end

67
src/base/criteria.lua Normal file
View File

@ -0,0 +1,67 @@
--
-- criteria.lua
--
-- DO NOT USE THIS YET! I am just getting started here; please wait until
-- I've had a chance to build it out more before using.
--
-- Stores a list of criteria terms with support for negation, conjunction,
-- and wildcard matches. Provides functions match match these criteria
-- against various contexts.
--
-- Copyright (c) 2012 Jason Perkins and the Premake project
--
premake.criteria = {}
local criteria = premake.criteria
--
-- Create a new criteria object.
--
-- @param terms
-- A list of criteria terms.
-- @return
-- A new criteria object.
--
function criteria.new(terms)
terms = table.flatten(terms)
-- make future tests case-insensitive
for i, term in ipairs(terms) do
terms[i] = term:lower()
end
return terms
end
--
-- Determine if this criteria is met by the provided list of context terms.
--
-- @param crit
-- The criteria to be tested.
-- @param context
-- The list of context terms to test against, provided as a list of
-- lowercase strings.
-- @return
-- True if all criteria are satisfied by the context.
--
function criteria.matches(crit, context)
local checkterm = function(term)
for _, value in ipairs(context) do
if value:match(term) then
return true
end
end
end
for _, term in ipairs(crit) do
if not checkterm(term) then
return false
end
end
return true
end

View File

@ -28,47 +28,6 @@
premake.XBOX360 = "xbox360"
--
-- The list of known systems, with metadata to help drive the generation process.
--
premake.systems = {
linux =
{
sharedlib = { prefix = "lib", extension = ".so" },
staticlib = { prefix = "lib", extension = ".a" },
},
macosx =
{
sharedlib = { prefix = "lib", extension = ".dylib" },
staticlib = { prefix = "lib", extension = ".a" },
},
ps3 =
{
consoleapp = { extension = ".elf" },
sharedlib = { prefix = "lib" },
staticlib = { prefix = "lib", extension = ".a" },
},
windows =
{
consoleapp = { extension = ".exe" },
windowedapp = { extension = ".exe" },
sharedlib = { extension = ".dll" },
staticlib = { extension = ".lib" },
}
}
premake.systems.bsd = premake.systems.linux
premake.systems.haiku = premake.systems.linux
premake.systems.solaris = premake.systems.linux
premake.systems.wii = premake.systems.linux
premake.systems.xbox360 = premake.systems.windows
--
-- Open a file for output, and call a function to actually do the writing.
-- Used by the actions to generate solution and project files.

View File

@ -55,10 +55,8 @@
local bundlename = ""
local bundlepath = ""
local suffix = ""
local sysinfo = premake.systems[cfg.system][kind:lower()] or {}
local prefix = sysinfo.prefix or ""
local extension = sysinfo.extension or ""
local prefix = cfg.context[field.."prefix"]
local extension = cfg.context[field.."extension"]
-- Mac .app requires more logic than I can bundle up in a table right now
if cfg.system == premake.MACOSX and kind == premake.WINDOWEDAPP then

View File

@ -4,8 +4,9 @@
-- Copyright (c) 2011-2012 Jason Perkins and the Premake project
--
premake5.project = { }
premake5.project = {}
local project = premake5.project
local context = premake.context
local oven = premake5.oven
@ -82,10 +83,24 @@
cfg.solution = prj.solution
cfg.project = prj
cfg.architecture = cfg.architecture or architecture
-- Temporary: Create a context for this configuration. Eventually, the context
-- will become the configuration object and much of this baking code will go
-- away. For right now, it provides a way to access the global settings that
-- match this configuration's setup.
filter = { cfg.buildcfg, cfg.platform, _ACTION, cfg.system, cfg.architecture, cfg.kind }
local terms = {}
for k, v in pairs(filter) do
if filter[k] then
table.insert(terms, v)
end
end
cfg.context = context.new(premake.root, terms)
-- fill in any calculated values
premake5.config.bake(cfg)
return cfg
end

View File

@ -0,0 +1,75 @@
--
-- tests/base/test_configset.lua
-- Test suite for the configset API.
-- Copyright (c) 2012 Jason Perkins and the Premake project
--
T.configset = {}
local suite = T.configset
local configset = premake.configset
--
-- Setup and teardown
--
local cfgset
function suite.setup()
cfgset = configset.new()
end
--
-- Make sure that new() returns a valid object.
--
function suite.new_returnsValidObject()
test.isequal("table", type(cfgset))
end
--
-- Check the default values for different field types.
--
function suite.defaultValue_onString()
test.isequal("", configset.fetchvalue(cfgset, "targetextension", {}))
end
--
-- Make sure that I can roundtrip a value stored into the
-- initial, default configuration.
--
function suite.canRoundtrip_onDefaultBlock()
configset.addvalue(cfgset, "targetextension", ".so")
test.isequal(".so", configset.fetchvalue(cfgset, "targetextension", {}))
end
--
-- Make sure that I can roundtrip a value stored into a block
-- with a simple matching term.
--
function suite.canRoundtrip_onSimpleTermMatch()
configset.addblock(cfgset, { "Windows" })
configset.addvalue(cfgset, "targetextension", ".dll")
test.isequal(".dll", configset.fetchvalue(cfgset, "targetextension", { "windows" }))
end
--
-- Make sure that blocks that do not match the context terms
-- do not contribute to the result.
--
function suite.skipsBlock_onTermMismatch()
configset.addvalue(cfgset, "targetextension", ".so")
configset.addblock(cfgset, { "Windows" })
configset.addvalue(cfgset, "targetextension", ".dll")
test.isequal(".so", configset.fetchvalue(cfgset, "targetextension", { "linux" }))
end

View File

@ -0,0 +1,43 @@
--
-- tests/base/test_context.lua
-- Test suite for the configuration context API.
-- Copyright (c) 2012 Jason Perkins and the Premake project
--
T.context = {}
local suite = T.context
local context = premake.context
local configset = premake.configset
--
-- Setup and teardown
--
local ctx, cfgset
function suite.setup()
cfgset = configset.new()
ctx = context.new(cfgset, {"Windows"})
end
--
-- Make sure that new() returns a valid object.
--
function suite.new_returnsValidObject()
test.isequal("table", type(ctx))
end
--
-- Context should be able to retrieve a default value from
-- the configuration set, using the field name.
--
function suite.returnsConfigValue_onExistingValue()
configset.addvalue(cfgset, "targetextension", ".so")
test.isequal(".so", ctx.targetextension)
end

View File

@ -0,0 +1,47 @@
--
-- tests/base/test_criteria.lua
-- Test suite for the criteria matching API.
-- Copyright (c) 2012 Jason Perkins and the Premake project
--
T.criteria = {}
local suite = T.criteria
local criteria = premake.criteria
--
-- Setup and teardown
--
local crit
--
-- Make sure that new() returns a valid object.
--
function suite.new_returnsValidObject()
crit = criteria.new {}
test.isequal("table", type(crit))
end
--
-- A criteria with no terms should satisfy any context.
--
function suite.matches_onEmptyCriteria()
crit = criteria.new {}
test.istrue(criteria.matches(crit, { "apple", "orange" }))
end
--
-- Should not match if any term is missing in the context.
--
function suite.fails_onMissingContext()
crit = criteria.new { "orange", "pear" }
test.isfalse(criteria.matches(crit, { "apple", "orange" }))
end

View File

@ -47,6 +47,9 @@
dofile("base/test_api.lua")
dofile("base/test_action.lua")
dofile("base/test_config.lua")
dofile("base/test_configset.lua")
dofile("base/test_context.lua")
dofile("base/test_criteria.lua")
dofile("base/test_include.lua")
dofile("base/test_location.lua")
dofile("base/test_os.lua")
@ -219,7 +222,8 @@
msg = string.format("%d tests passed, %d failed", passed, failed)
if (failed > 0) then
error(msg, 0)
-- should probably return an error code here somehow
print(msg)
else
print(msg)
end