Port validation logic to walk the new container hierarchy

This commit is contained in:
Jason Perkins 2014-11-21 17:04:43 -05:00
parent a956ea740e
commit 011b0d9fd8
6 changed files with 181 additions and 169 deletions

View File

@ -45,6 +45,7 @@
-- project script processing
"base/oven.lua",
"base/validation.lua",
"base/premake.lua",
"base/help.lua",

View File

@ -142,7 +142,7 @@
-- Sanity check the current project setup
premake.validate()
p.container.validate(p.api.rootContainer())
-- Hand over control to the action

View File

@ -232,3 +232,25 @@
return container.classes[name]
end
---
-- Call out to the container validation to make sure everything
-- is as it should be before handing off to the actions.
---
function container.validate(self)
if type(self.class.validate) == "function" then
self.class.validate(self)
end
end
function container.validateChildren(self)
for class in container.eachChildClass(self.class) do
local children = self[class.pluralName]
for i = 1, #children do
container.validate(children[i])
end
end
end

View File

@ -303,158 +303,6 @@ end
--
-- Sanity check the project information loaded from the scripts, to
-- make sure it all meets some minimum requirements. Raises an error if
-- an insane state is detected.
--
function premake.validate()
local ctx = {}
for sln in premake.global.eachSolution() do
premake.validateSolution(sln, ctx)
for prj in solution.eachproject(sln) do
premake.validateProject(prj, ctx)
for cfg in project.eachconfig(prj) do
premake.validateConfig(cfg, ctx)
end
end
end
end
--
-- Sanity check the settings of a specific solution. Raises an error if
-- an insane state is detected.
--
-- @param sln
-- The solution to be checked.
-- @param ctx
-- The validation context; keeps track of what has already been checked.
--
function premake.validateSolution(sln, ctx)
-- there must be at least one build configuration
if not sln.configurations or #sln.configurations == 0 then
premake.error("solution '%s' does not contain any configurations", sln.name)
end
-- all project UUIDs must be unique
local uuids = {}
for prj in solution.eachproject(sln) do
if uuids[prj.uuid] then
premake.error("projects '%s' and '%s' have the same UUID", uuids[prj.uuid], prj.name)
end
uuids[prj.uuid] = prj.name
end
end
--
-- Sanity check the settings of a specific project. Raises an error if
-- an insane state is detected.
--
-- @param prj
-- The project to be checked.
-- @param ctx
-- The validation context; keeps track of what has already been checked.
--
function premake.validateProject(prj, ctx)
-- must have a language
if not prj.language then
premake.error("project '%s' does not have a language", prj.name)
end
-- all rules must exist
for i = 1, #prj.rules do
local rule = prj.rules[i]
if not p.global.getRule(rule) then
premake.error("project '%s' uses missing rule '%s'", prj.name, rule)
end
end
-- check for out of scope fields
premake.validateScopes(prj, "project", ctx)
end
--
-- Sanity check the settings of a specific configuration. Raises an error
-- if an insane state is detected.
--
-- @param cfg
-- The configuration to be checked.
-- @param ctx
-- The validation context; keeps track of what has already been checked.
--
function premake.validateConfig(cfg, ctx)
-- must have a kind
if not cfg.kind then
premake.error("project '%s' needs a kind in configuration '%s'", cfg.project.name, cfg.name)
end
-- makefile configuration can only appear in C++ projects; this is the
-- default now, so should only be a problem if overridden.
if (cfg.kind == premake.MAKEFILE or cfg.kind == premake.NONE) and not project.iscpp(cfg.project) then
premake.error("project '%s' uses %s kind in configuration '%s'; language must be C++", cfg.project.name, cfg.kind, cfg.name)
end
-- check for out of scope fields
premake.validateScopes(cfg, "config", ctx)
end
--
-- Check the values stored in a configuration object (solution, project, or
-- configuration) for values that might have been set out of scope.
--
-- @param cfg
-- The configuration object to validate.
-- @param expected
-- The expected scope of values in this object; one of "project" or "config".
-- @param ctx
-- The validation context; used to prevent multiple warnings on same field.
--
function premake.validateScopes(cfg, expected, ctx)
for f in field.each() do
-- Get the field's scope
-- TODO: This whole scope validation needs to be generalized
-- now that containers are in place. For now, ignore rule
-- containers until I can make things work properly.
local scope
if f.scopes[1] ~= "rule" then
scope = f.scopes[1]
end
-- Skip fields that are at or below the expected scope. Config-
-- level fields are the most general (can be applied to projects
-- or solutions) and so can never be out of scope.
local okay = (not scope or scope == "config" or scope == expected or p.oven.bubbledFields[f.name])
-- this one needs to checked
if not okay then
okay = field.compare(f, cfg[scope][f.name], cfg[f.name])
end
-- found a problem?
if not okay then
local key = "validate." .. f.name
premake.warnOnce(key, "'%s' on %s '%s' differs from %s '%s'; may be set out of scope", f.name, expected, cfg.name, scope, cfg[scope].name)
end
end
end
---
-- Write a formatted string to the exported file, at the current
-- level of indentation, and appends an end of line sequence.

145
src/base/validation.lua Normal file
View File

@ -0,0 +1,145 @@
---
-- base/validation.lua
--
-- Verify the contents of the project object before handing them off to
-- the action/exporter.
--
-- Copyright (c) 2002-2014 Jason Perkins and the Premake project
---
local p = premake
---
-- Validate the global container and all of its contents.
---
function p.global.validate(self)
p.container.validateChildren(self)
end
---
-- Validate a solution and its projects.
---
function p.solution.validate(self)
-- there must be at least one build configuration
if not self.configurations or #self.configurations == 0 then
p.error("solution '%s' does not contain any configurations", self.name)
end
-- all project UUIDs must be unique
local uuids = {}
for prj in p.solution.eachproject(self) do
if uuids[prj.uuid] then
p.error("projects '%s' and '%s' have the same UUID", uuids[prj.uuid], prj.name)
end
uuids[prj.uuid] = prj.name
end
p.container.validateChildren(self)
end
---
-- Validate a project and its configurations.
---
function p.project.validate(self)
-- must have a language
if not self.language then
p.error("project '%s' does not have a language", self.name)
end
-- all rules must exist
for i = 1, #self.rules do
local rule = self.rules[i]
if not p.global.getRule(rule) then
p.error("project '%s' uses missing rule '%s'", self.name, rule)
end
end
-- check for out of scope fields
p.config.validateScopes(self, "project")
for cfg in p.project.eachconfig(self) do
p.config.validate(cfg)
end
end
---
-- Validate a project configuration.
---
function p.config.validate(self)
-- must have a kind
if not self.kind then
p.error("project '%s' needs a kind in configuration '%s'", self.project.name, self.name)
end
-- makefile configuration can only appear in C++ projects; this is the
-- default now, so should only be a problem if overridden.
if (self.kind == p.MAKEFILE or self.kind == p.NONE) and not p.project.iscpp(self.project) then
p.error("project '%s' uses %s kind in configuration '%s'; language must be C++", self.project.name, self.kind, self.name)
end
-- check for out of scope fields
p.config.validateScopes(self, "config")
end
---
-- Check the values stored in a configuration for values that might have
-- been set out of scope.
--
-- @param expected
-- The expected scope of values in this object; one of "project" or "config".
---
function p.config.validateScopes(self, expected)
for f in p.field.each() do
-- Get the field's scope
-- TODO: This whole scope validation needs to be generalized
-- now that containers are in place. For now, ignore rule
-- containers until I can make things work properly.
local scope
if f.scopes[1] ~= "rule" then
scope = f.scopes[1]
end
-- Skip fields that are at or below the expected scope. Config-
-- level fields are the most general (can be applied to projects
-- or solutions) and so can never be out of scope.
local okay = (not scope or scope == "config" or scope == expected or p.oven.bubbledFields[f.name])
-- this one needs to checked
if not okay then
okay = p.field.compare(f, self[scope][f.name], self[f.name])
end
-- found a problem?
if not okay then
local key = "validate." .. f.name
p.warnOnce(key, "'%s' on %s '%s' differs from %s '%s'; may be set out of scope", f.name, expected, self.name, scope, self[scope].name)
end
end
end
---
-- Validate a rule.
---
function p.rule.validate(self)
-- TODO: fill this in
end

View File

@ -6,14 +6,15 @@
local suite = test.declare("premake_validation")
local p = premake
--
-- Setup
--
local function verify()
ok, err = pcall(premake.validate)
return ok and not test.stderr()
local function validate()
return pcall(function() p.container.validate(p.api.rootContainer()) end)
end
@ -27,9 +28,7 @@
project "MyProject"
kind "ConsoleApp"
language "C++"
test.istrue(pcall(premake.validate))
test.stderr()
test.istrue(validate())
end
@ -42,8 +41,7 @@
project "MyProject"
kind "ConsoleApp"
language "C++"
test.isfalse(pcall(premake.validate))
test.isfalse(validate())
end
@ -60,8 +58,7 @@
uuid "D4110D7D-FB18-4A1C-A75B-CA432F4FE770"
project "MyProject2"
uuid "D4110D7D-FB18-4A1C-A75B-CA432F4FE770"
test.isfalse(pcall(premake.validate))
test.isfalse(validate())
end
@ -74,8 +71,7 @@
configurations { "Debug", "Release" }
project "MyProject"
language "C++"
test.isfalse(pcall(premake.validate))
test.isfalse(validate())
end
@ -91,7 +87,7 @@
project "MyProject"
kind "ConsoleApp"
language "C++"
premake.validate()
validate()
test.stderr("'startproject' on config")
end
@ -103,7 +99,7 @@
language "C++"
filter "Debug"
location "MyProject"
premake.validate()
validate()
test.stderr("'location' on config")
end
@ -115,7 +111,7 @@
language "C++"
filter "Debug"
configurations "Deployment"
premake.validate()
validate()
test.stderr("'configurations' on config")
end
@ -129,6 +125,6 @@
configurations { "Debug", "Release" }
project "MyProject"
rules { "NoSuchRule" }
test.isfalse(pcall(premake.validate))
test.isfalse(validate())
end