Clean up current container implementation before expanding into existing code

- use existing objects (premake.solution, premake.project) as the container classes
- drop metatables and inheritance setup, more trouble than its worth
- create explicit containers for global scope and project groups
This commit is contained in:
Jason Perkins 2014-10-09 18:16:14 -04:00
parent 352b5ba4ca
commit 68c106bb5b
10 changed files with 287 additions and 365 deletions

View File

@ -35,7 +35,9 @@
"base/api.lua",
-- project objects
"base/global.lua",
"base/solution.lua",
"base/group.lua",
"base/project.lua",
"base/config.lua",
"base/fileconfig.lua",

View File

@ -88,7 +88,7 @@
end
end
for rule in p.rules.each() do
for rule in p.rule.each() do
if act.onrule then
act.onrule(rule)
end

View File

@ -4,94 +4,72 @@
-- Copyright (c) 2002-2014 Jason Perkins and the Premake project
--
premake.api = {}
local api = premake.api
local p = premake
p.api = {}
local api = premake.api
local configset = p.configset
---
-- Set up a place to store the current active objects in each configuration
-- scope (e.g. solutions, projects, groups, and configurations). Initialize
-- it with a "root" container to contain the other containers, as well as the
-- global configuration settings which should apply to all of them.
--
-- TODO: this should be hidden, with a perhaps a read-only accessor to fetch
-- individual scopes for testing.
-- scope (e.g. solutions, projects, groups, and configurations). This likely
-- ought to be internal scope, but it is useful for testing.
---
api.scope = {}
api.scope.root = p.containerClass.define("root"):new("root")
---
-- Register a new class of configuration container. A configuration container
-- can hold configuration settings, as well as child configuration containers.
-- Solutions, projects, and rules are all examples of containers.
-- Define a new class of configuration container. A container can receive and
-- store configuration blocks, which are what hold the individial settings
-- from the scripts. A container can also hold one or more kinds of child
-- containers; a solution can contain projects, for instance.
--
-- @param def
-- The container definition; see premake.container.define().
-- @param containerName
-- The name of the new container type, e.g. "solution". Used to define a
-- corresponding global function, e.g. solution() to create new instances
-- of the container.
-- @param parentContainer (optional)
-- The container that can contain this one. For a project, this would be
-- the solution container class.
-- @returns
-- The newly defined container class.
---
function api.container(def)
-- for now, everything inherits the root configuration
if not def.parent then
def.parent = "root"
end
-- register the new class; validation checks may cause a failure
local cc, err = p.containerClass.define(def)
if not cc then
function api.container(containerName, parentContainer)
local class, err = p.container.newClass(containerName, parentContainer)
if not class then
error(err, 2)
end
-- create a global function to create new instances, e.g project()
_G[cc.name] = function(name)
return api._setScope(cc, name)
_G[containerName] = function(name)
return api._setContainer(class, name)
end
return cc
return class
end
---
-- Return the currently active configuration container instance.
---
function api.currentContainer()
return api.scope.current or api.scope.root
end
---
-- Return the root container, which contains the global configuration and
-- all of the child containers (solutions, rules).
-- Return the global configuration container. You could just call global()
-- too, but this is much faster.
---
function api.rootContainer()
return api.scope.root
return api.scope.global
end
---
-- Recursively clear any child scopes for a container class. For example,
-- if a solution is being activated, clear the project and group scopes,
-- as those are children of solutions. If the root scope is made active,
-- all child scopes will be cleared.
---
function api._clearChildScopes(cc)
for ch in cc:eachChildClass() do
api.scope[ch.name] = nil
api._clearChildScopes(ch)
function api._clearContainerChildren(class)
for childClass in p.container.eachChildClass(class) do
api.scope[childClass.name] = nil
api._clearContainerChildren(childClass)
end
end
@ -103,7 +81,7 @@
-- to active a container, that call comes here (see api.container() for the
-- details on how that happens).
--
-- @param cc
-- @param class
-- The container class being activated, e.g. a project or solution.
-- @param name
-- The name of the container instance to be activated. If a container
@ -114,47 +92,58 @@
-- The container instance.
---
function api._setScope(cc, name)
function api._setContainer(class, name)
local instance
-- for backward compatibility, "*" activates the parent container
if name == "*" then
return api._setScope(cc.parent)
return api._setContainer(class.parent)
end
-- if name is not set, use whatever was last made current
local container
if not name then
container = api.scope[cc.name]
if not container then
error("no " .. cc.name .. " in scope", 3)
instance = api.scope[class.name]
if not instance then
error("no " .. class.name .. " in scope", 3)
end
end
if not container then
-- all containers should be contained within a parent
local parent = api.scope[cc.parent.name]
-- otherwise, look up the instance by name
local parent
if not instance and class.parent then
parent = api.scope[class.parent.name]
if not parent then
error("no active " .. cc.parent.name, 3)
error("no " .. class.parent.name .. " in scope", 3)
end
instance = p.container.getChild(parent, class, name)
end
-- fetch (creating if necessary) the container
container = parent:fetchChild(cc, name)
if not container then
container = parent:createChild(cc, name, parent)
else
configset.addFilter(container, {}, os.getcwd())
-- if I have an existing instance, create a new configuration
-- block for it so I don't pick up an old filter
if instance then
configset.addFilter(instance, {}, os.getcwd())
end
-- otherwise, a new instance
if not instance then
instance = class.new(name)
if parent then
p.container.addChild(parent, instance)
end
end
-- clear out any active child container types
api._clearChildScopes(cc)
-- clear out any active child containers that might be active
-- (recursive call, so needs to be its own function)
api._clearContainerChildren(class)
-- activate the container, as well as its ancestors
if not cc.placeholder then
api.scope.current = container
-- active this container, as well as it ancestors
if not class.placeholder then
api.scope.current = instance
end
while container.parent do
api.scope[container.class.name] = container
container = container.parent
while instance do
api.scope[instance.class.name] = instance
instance = instance.parent
end
return api.scope.current
@ -162,26 +151,6 @@
---
-- Find the closest active scope for the given container class. If no
-- exact instance of this container class is in scope, searches up the
-- class hierarchy to find the closest parent that is in scope.
--
-- @param cc
-- The container class to target.
-- @return
-- The closest available active container instance.
---
function api._target(cc)
while not api.scope[cc.name] do
cc = cc.parent
end
return api.scope[cc.name]
end
---
-- Register a new API function. See the built-in API definitions in
-- _premake_init.lua for lots of usage examples.
@ -458,36 +427,29 @@
---
function api.target(field)
-- if nothing is in scope, use the global root scope
local scopes = field.scopes
for i = 1, #scopes do
local scope = scopes[i]
if not api.scope.current then
return api.scope.root
end
-- use the current scope if it falls within the container hierarchy
-- of one of this field's target scopes; it is okay to set a value of
-- the parent of container (e.g. a project-level value can be set on a
-- solution, since it will be inherited).
local currentClass = api.scope.current.class
for i = 1, #field.scopes do
-- temporary: map config scope to the desired container class; will
-- go away once everything is ported to containers
local targetScope = field.scopes[i]
if targetScope == "config" then
targetScope = "project"
-- TODO: rules should be able to contain filter blocks too, but
-- to all the existing code expects the "config" scope to mean
-- project settings. Will revisit.
if scope == "config" then
scope = "project"
end
local targetClass = p.containerClass.get(targetScope)
repeat
if currentClass == targetClass then
-- If anything in the currently active container's hierarchy is
-- compatibile with this scope, then I can use it.
local currentClass = api.scope.current.class
local targetClass = p.container.getClass(scope)
while targetClass do
if targetClass.name == currentClass.name then
return api.scope.current
end
targetClass = targetClass.parent
until not targetClass
end
end
-- this field's scope isn't available
return nil
end
@ -678,7 +640,7 @@
function api.reset()
-- Clear out all top level objects
api.scope.root.solutions = {}
api.scope.global.solutions = {}
-- Remove all custom variables
local vars = api.getCustomVars()
@ -1056,12 +1018,11 @@
---
function configuration(terms)
local target = api.currentContainer()
if terms then
if terms == "*" then terms = nil end
configset.addblock(target, {terms}, os.getcwd())
configset.addblock(api.scope.current, {terms}, os.getcwd())
end
return target
return api.scope.current
end
@ -1072,10 +1033,9 @@
---
function filter(terms)
local target = api.currentContainer()
if terms then
if terms == "*" then terms = nil end
local ok, err = configset.addFilter(target, {terms}, os.getcwd())
local ok, err = configset.addFilter(api.scope.current, {terms}, os.getcwd())
if not ok then
error(err, 2)
end

View File

@ -5,218 +5,114 @@
---
local p = premake
p.containerClass = {}
p.container = {}
local container = p.container
-- The master list of registered container classes
---
-- Keep a master dictionary of container class, so they can be easily looked
-- up by name (technically you could look at premake["name"] but that is just
-- a coding convention and I don't want to count on it)
---
container._classes = {}
container.classes = {}
---
-- The metatable allows container functions to be called with the ":" syntax,
-- and also allows API field values to be get and set as if they were direct
-- properties.
--
-- TODO: I think I'd like to get away from treating the fields as direct
-- properties on the containers (fine on the baked contexts later) and require
-- explicit fetch() and store() calls instead.
---
p.containerClass.__index = p.containerClass
p.container.__index = function(c, key)
local f = p.field.get(key)
if f then
return p.configset.fetch(c, f)
else
return p.container[key]
end
end
p.container.__newindex = function(c, key, value)
local f = p.field.get(key)
if f then
local status, err = p.configset.store(c, f, value)
if err then
error(err, 2)
end
else
rawset(c, key, value)
return value
end
end
---
-- A container class holds and operates on the metadata about a particular
-- type of container, including its name, parent container, and any child
-- containers. The container class is responsible for creating new instances
-- of its kind of containers.
--
-- @param def
-- A table containing metadata about the container class being created.
-- Supported keys are:
--
-- name (required)
-- The name of the new container class (e.g. "solution").
-- parent (optional)
-- The name of the parent container class (e.g. "solution").
-- init (optional)
-- An initializer function to call for new instances of this class.
-- Should accept the new instance object as its only argument.
--
-- Other keys are allowed and will be left intact.
--
-- @return
-- A new container class object if successful, else nil and an
-- error message.
---
function p.containerClass.define(def)
-- If the class has no special properties, allow it to be set using
-- just the class name instead of the whole key-value list.
if type(def) == "string" then
def = { name = def }
end
-- Sanity check my inputs
if not def.name then
return nil, "name is required"
end
if container._classes[def.name] then
return nil, "container class name already in use"
end
if def.parent and not container._classes[def.parent] then
return nil, "parent class does not exist"
end
-- Looks good, set myself up and add to master list
def._children = {}
def._listKey = def.name:plural()
setmetatable(def, p.containerClass)
container._classes[def.name] = def
-- Wire myself to my parent class
def.parent = container._classes[def.parent]
if def.parent then
table.insert(def.parent._children, def)
end
return def
end
---
-- Enumerate the child container class of a given class.
---
function p.containerClass:eachChildClass()
local children = self._children
local i = 0
return function ()
i = i + 1
if i <= #children then
return children[i]
end
end
end
---
-- Retrieve a container class by name.
-- Define a new class of containers.
--
-- @param name
-- The class name.
-- The name of the new container class. Used wherever the class needs to
-- be shown to the end user in a readable way.
-- @param parent (optional)
-- If this class of container is intended to be contained within another,
-- the containing class object.
-- @return
-- The corresponding container class if found, nil otherwise.
-- If successful, the new class descriptor object (a table). Otherwise,
-- returns nil and an error message.
---
function p.containerClass.get(name)
return container._classes[name]
end
function container.newClass(name, parent)
local class = p.configset.new(parent)
class.name = name
class.pluralName = name:plural()
class.containedClasses = {}
---
-- Create a new instance of a container class.
--
-- @param name
-- The name for the container instance.
-- @return
-- A new instance of the container class.
---
function p.containerClass:new(name, parent)
local c = p.configset.new(parent)
setmetatable(c, p.container)
c.class = self
c.name = name
c.script = _SCRIPT
c.basedir = os.getcwd()
c.filename = name
if type(self.init) == "function" then
self.init(c)
if parent then
table.insert(parent.containedClasses, class)
end
return c
container.classes[name] = class
return class
end
---
-- Create a new child container of the given class, with the specified name.
-- Create a new instance of a configuration container. This is just the
-- generic base implementation, each container class will define their
-- own version.
--
-- @param cc
-- The class of child container to be fetched.
-- @param key
-- A string key or array index for the container.
-- @param parent
-- The parent container instance.
-- The class of container being instantiated.
-- @param name
-- The name for the new container instance.
-- @return
-- The child container instance.
-- A new container instance.
---
function container:createChild(cc, key, parent)
self[cc._listKey] = self[cc._listKey] or {}
local list = self[cc._listKey]
function container.new(class, name)
local self = p.configset.new()
setmetatable(self, p.configset.metatable(self))
local child = cc:new(key, parent)
child[parent.class.name] = parent -- i.e. child.solution = sln
self.class = class
self.name = name
self.script = _SCRIPT
self.basedir = os.getcwd()
table.insert(list, child)
list[key] = child
return child
for childClass in container.eachChildClass(class) do
self[childClass.pluralName] = {}
end
return self
end
---
-- Return an iterator for the child containers of a particular class.
-- Add a new child to an existing container instance.
--
-- @param cc
-- The class of child container to be enumerated.
-- @param self
-- The container instance to hold the child.
-- @param child
-- The child container instance.
---
function container:eachChild(cc)
local children = self[cc._listKey] or {}
function container.addChild(self, child)
local children = self[child.class.pluralName]
table.insert(children, child)
children[child.name] = child
child.parent = self
child[self.class.name] = self
end
---
-- Enumerate all of the registered child classes of a specific container class.
--
-- @param class
-- The container class to be enumerated.
-- @return
-- An iterator function for the container's child classes.
---
function container.eachChildClass(class)
local children = class.containedClasses
local i = 0
return function ()
i = i + 1
@ -229,17 +125,59 @@
---
-- Fetch the child container with the given container class and instance name.
-- Enumerate all of the registered child instances of a specific container.
--
-- @param cc
-- The class of child container to be fetched.
-- @param key
-- A string key or array index for the container.
-- @param self
-- The container to be queried.
-- @param class
-- The class of child containers to be enumerated.
-- @return
-- The child container instance.
-- An iterator function for the container's child classes.
---
function container:fetchChild(cc, key)
self[cc._listKey] = self[cc._listKey] or {}
return self[cc._listKey][key]
function container.eachChild(self, class)
local children = self[class.pluralName]
local i = 0
return function ()
i = i + 1
if i <= #children then
return children[i]
end
end
end
---
-- Retrieve the child container with the specified class and name.
--
-- @param self
-- The container instance to query.
-- @param class
-- The class of the child container to be fetched.
-- @param name
-- The name of the child container to be fetched.
-- @return
-- The child instance if it exists, nil otherwise.
---
function container.getChild(self, class, name)
local children = self[class.pluralName]
return children[name]
end
---
-- Retrieve a container class object.
--
-- @param name
-- The name of the container class to retrieve.
-- @return
-- The container class object if it exists, nil otherwise.
---
function container.getClass(name)
return container.classes[name]
end

14
src/base/global.lua Normal file
View File

@ -0,0 +1,14 @@
---
-- global.lua
-- The global container holds solutions and rules.
-- Copyright (c) 2014 Jason Perkins and the Premake project
---
local p = premake
p.global = p.api.container("global")
local global = p.global
function global.new(name)
return p.container.new(p.global, name)
end

22
src/base/group.lua Normal file
View File

@ -0,0 +1,22 @@
---
-- group.lua
-- A psuedo-configuration container to represent project groups.
-- Copyright (c) 2014 Jason Perkins and the Premake project
---
local p = premake
p.group = p.api.container("group", p.solution)
local group = p.group
---
-- Bit of a hack: prevent groups from holding any configuration data.
---
group.placeholder = true
function group.new(name)
return p.container.new(group, name)
end

View File

@ -25,6 +25,10 @@
local _indentString = "\t"
local _indentLevel = 0
-- Set up the global configuration scope. There can be only one.
global("root")
---

View File

@ -1,33 +1,34 @@
--
---
-- project.lua
-- Premake project object API
-- Copyright (c) 2011-2014 Jason Perkins and the Premake project
--
premake.project = {}
local project = premake.project
---
local p = premake
p.project = p.api.container("project", p.solution)
local project = p.project
local tree = p.tree
---
-- Register a new container class to represent projects.
-- Create a new project container instance.
---
local _prjClass = p.api.container {
name = "project",
parent = "solution",
init = function(prj)
prj.uuid = os.uuid(prj.name)
if p.api.scope.group then
prj.group = p.api.scope.group.name
else
prj.group = ""
end
function project.new(name)
local prj = p.container.new(project, name)
prj.uuid = os.uuid(name)
prj.filename = name
if p.api.scope.group then
prj.group = p.api.scope.group.name
else
prj.group = ""
end
}
return prj
end

View File

@ -1,25 +1,24 @@
---
-- base/rules.lua
--
-- Defines rule sets for generated custom rule files.
--
-- Copyright (c) 2014 Jason Perkins and the Premake project
---
premake.rules = {}
local rules = premake.rules
local p = premake
p.rule = p.api.container("rule", p.global)
local rule = p.rule
---
-- Register a new container class to hold the rules.
-- Create a new rule container instance.
---
local _ruleContainerClass = p.api.container {
name = "rule",
}
function rule.new(name)
return p.container.new(rule, name)
end
@ -30,21 +29,7 @@
-- An iterator function.
---
function rules.each()
return p.api.rootContainer():eachChild(_ruleContainerClass)
end
---
-- Retrieve a rule set by name or numeric index.
--
-- @param key
-- The rule key, either a string name or integer index.
-- @returns
-- The rule set with the provided key.
---
function rules.fetch(key)
return p.api.rootContainer():fetchChild(_ruleContainerClass, key)
function rule.each()
local root = p.api.rootContainer()
return p.container.eachChild(root, rule)
end

View File

@ -1,30 +1,26 @@
--
---
-- solution.lua
-- Work with the list of solutions loaded from the script.
-- Copyright (c) 2002-2014 Jason Perkins and the Premake project
--
premake.solution = {}
local solution = premake.solution
---
local p = premake
p.solution = p.api.container("solution", p.global)
local solution = p.solution
local tree = p.tree
---
-- Register a new container class to represent solutions.
-- Create a new solution container instance.
---
local _slnClass = p.api.container {
name = "solution",
}
p.api.container {
name = "group",
parent = "solution",
placeholder = true,
}
function solution.new(name)
local sln = p.container.new(solution, name)
sln.filename = name
return sln
end