Initial implementation of a minimal custom rules API (currently Visual Studio only)

This commit is contained in:
Jason Perkins 2014-06-10 16:38:16 -04:00
parent 026b75bd3f
commit efe5f1e292
8 changed files with 362 additions and 47 deletions

View File

@ -136,6 +136,19 @@
tokens = true,
}
api.register {
name = "customRule",
scope = "config",
kind = "string",
}
api.register {
name = "customRules",
scope = "project",
kind = "list:file",
tokens = true,
}
api.register {
name = "debugargs",
scope = "config",

View File

@ -482,6 +482,43 @@
m.simplefilesgroup(prj, "None")
m.simplefilesgroup(prj, "ResourceCompile")
m.customBuildFilesGroup(prj)
m.customRuleFileGroups(prj)
end
function m.customRuleFileGroups(prj)
local group = m.getfilegroup(prj, "CustomRule")
local vars = p.api.getCustomVars()
for rule, files in pairs(group) do
if #files > 0 then
_p(1,'<ItemGroup>')
for i, file in ipairs(files) do
local contents = p.capture(function ()
for i, var in ipairs(vars) do
for cfg in project.eachconfig(prj) do
local condition = m.condition(cfg)
local fcfg = fileconfig.getconfig(file, cfg)
if fcfg and fcfg[var] then
local key = var:sub(9)
_x(3,'<%s %s>%s</%s>', key, m.condition(fcfg.config), fcfg[var], key)
end
end
end
end)
if #contents > 0 then
_p(2,'<%s Include=\"%s\">', rule, path.translate(file.relpath))
_p('%s', contents)
_p(2,'</%s>', rule)
else
_p(2,'<%s Include=\"%s\" />', rule, path.translate(file.relpath))
end
end
_p(1,'</ItemGroup>')
end
end
end
@ -594,58 +631,117 @@
function m.getfilegroup(prj, group)
-- check for a cached copy before creating
local groups = prj.vc2010_file_groups
if not groups then
groups = {
ClCompile = {},
ClInclude = {},
None = {},
ResourceCompile = {},
CustomBuild = {},
}
prj.vc2010_file_groups = groups
-- Have I already created the groups?
local groups = prj._vc2010_file_groups
if groups then
return groups[group]
end
local tr = project.getsourcetree(prj)
tree.traverse(tr, {
onleaf = function(node)
-- if any configuration of this file uses a custom build rule,
-- then they all must be marked as custom build
local hasbuildrule = false
for cfg in project.eachconfig(prj) do
local filecfg = fileconfig.getconfig(node, cfg)
if fileconfig.hasCustomBuildRule(filecfg) then
hasbuildrule = true
break
end
end
groups = {
ClCompile = {},
ClInclude = {},
None = {},
ResourceCompile = {},
CustomBuild = {},
CustomRule = {},
}
if hasbuildrule then
table.insert(groups.CustomBuild, node)
elseif path.iscppfile(node.name) then
table.insert(groups.ClCompile, node)
elseif path.iscppheader(node.name) then
table.insert(groups.ClInclude, node)
elseif path.isresourcefile(node.name) then
table.insert(groups.ResourceCompile, node)
else
table.insert(groups.None, node)
local tr = project.getsourcetree(prj)
tree.traverse(tr, {
onleaf = function(node)
-- if any configuration of this file uses a custom build rule,
-- then they all must be marked as custom build
local customBuild, customRule
for cfg in project.eachconfig(prj) do
local fcfg = fileconfig.getconfig(node, cfg)
if fileconfig.hasCustomBuildRule(fcfg) then
customBuild = true
break
elseif fcfg and fcfg.customRule then
customRule = fcfg.customRule
break
end
end
})
-- sort by relative to path; otherwise VS will reorder the files
for group, files in pairs(groups) do
table.sort(files, function (a, b)
return a.relpath < b.relpath
end)
if customBuild then
table.insert(groups.CustomBuild, node)
elseif customRule then
groups.CustomRule[customRule] = groups.CustomRule[customRule] or {}
table.insert(groups.CustomRule[customRule], node)
elseif path.iscppfile(node.name) then
table.insert(groups.ClCompile, node)
elseif path.iscppheader(node.name) then
table.insert(groups.ClInclude, node)
elseif path.isresourcefile(node.name) then
table.insert(groups.ResourceCompile, node)
else
table.insert(groups.None, node)
end
end
})
-- sort by relative to path; otherwise VS will reorder the files
for group, files in pairs(groups) do
table.sort(files, function (a, b)
return a.relpath < b.relpath
end)
end
prj._vc2010_file_groups = groups
return groups[group]
end
function m.categorize(prj, file)
-- If any configuration for this file uses a custom build step or a
-- custom, externally defined rule, that's the category to use
for cfg in project.eachconfig(prj) do
local fcfg = fileconfig.getconfig(file, cfg)
if fileconfig.hasCustomBuildRule(fcfg) then
return "CustomBuild"
elseif fcfg and fcfg.customRule then
return fcfg.customRule
end
end
return groups[group]
-- Otherwise use the file extension to deduce a category
if path.iscppfile(node.name) then
return "ClCompile"
elseif path.iscppheader(node.name) then
return "ClInclude"
elseif path.isresourcefile(node.name) then
return "ResourceCompile"
else
return "None"
end
end
function m.categorizeSources(prj)
local groups = {}
local tr = project.getsourcetree(prj)
tree.traverse(tr, {
onleaf = function(node)
local cat = m.categorize(prj, node)
groups[cat] = groups[cat] or {}
table.insert(groups[cat], node)
end
})
-- sort by relative to path; otherwise VS will reorder the files
for group, files in pairs(groups) do
table.sort(files, function (a, b)
return a.relpath < b.relpath
end)
end
return groups
end
--
-- Generate the list of project dependencies.
--
@ -966,9 +1062,14 @@
function m.importExtensionSettings(prj)
_p(1,'<Import Project="$(VCTargetsPath)\\Microsoft.Cpp.props" />')
_p(1,'<ImportGroup Label="ExtensionSettings">')
_p(1,'</ImportGroup>')
p.w('<Import Project="$(VCTargetsPath)\\Microsoft.Cpp.props" />')
p.push('<ImportGroup Label="ExtensionSettings">')
table.foreachi(prj.customRules, function(value)
value = path.translate(project.getrelative(prj, value))
value = path.appendExtension(value, ".props")
p.x('<Import Project="%s" />', value)
end)
p.pop('</ImportGroup>')
end

View File

@ -125,6 +125,8 @@
return api.remove(field, value)
end
end
return field
end
@ -925,3 +927,46 @@
function newoption(opt)
premake.option.add(opt)
end
-----------------------------------------------------------------------------
--
-- The custom*() functions act as wrappers that define new ad-hoc fields
-- on the fly, to support arbitrary custom rule variables. These should be
-- considered experimental and temporary for now as a more complete rule-
-- generating solution will certainly be needed, but I will do my best to
-- deprecate them cleanly when that time comes.
--
-----------------------------------------------------------------------------
function customVar(value)
if type(value) ~= "table" or #value ~= 2 then
error { msg="invalid value for customVar()" }
end
local name = value[1]
local value = value[2]
local fieldName = "_custom_" .. name
local field = premake.field.get(fieldName)
if not field then
field = api.register {
name = fieldName,
scope = "config",
kind = "string"
}
end
_G[fieldName](value)
end
function api.getCustomVars()
local vars = {}
for f in premake.field.each() do
if f.name:startswith("_custom_") then
table.insert(vars, f.name)
end
end
return vars
end

View File

@ -102,6 +102,20 @@
---
-- Returns an iterator for the list of register fields.
---
function field.each()
local index
return function ()
index = next(field._list, index)
return field._list[index]
end
end
---
-- Register a new kind of data for field storage.
--

View File

@ -1,7 +1,7 @@
--
-- path.lua
-- Path manipulation functions.
-- Copyright (c) 2002-2013 Jason Perkins and the Premake project
-- Copyright (c) 2002-2014 Jason Perkins and the Premake project
--
@ -10,7 +10,7 @@
-- isn't already present, and adjusts quotes as necessary.
--
function path.appendextension(p, ext)
function path.appendExtension(p, ext)
-- if the extension is nil or empty, do nothing
if not ext or ext == "" then
return p
@ -36,6 +36,9 @@
return p
end
path.appendextension = path.appendExtension
--
-- Retrieve the filename portion of a path, without any extension.

View File

@ -69,6 +69,11 @@
]]
end
--
-- Check handling of files with custom build rules.
--
function suite.customBuild_onBuildRule()
files { "hello.cg" }
filter "files:**.cg"
@ -88,7 +93,6 @@
]]
end
function suite.customBuild_onBuildRuleWithMessage()
files { "hello.cg" }
filter "files:**.cg"
@ -490,3 +494,56 @@
</ItemGroup>
]]
end
--
-- Check handling of files using custom rule definitions.
--
function suite.correctlyCategorized_onCustomRule()
files { "hello.dae" }
filter "files:**.dae"
customRule "Animation"
prepare()
test.capture [[
<ItemGroup>
<Animation Include="hello.dae" />
</ItemGroup>
]]
end
function suite.customRule_onLiteralVars()
files { "hello.dae" }
filter "files:**.dae"
customRule "Animation"
customVar { "GenerateDebugInfo", "True" }
prepare()
test.capture [[
<ItemGroup>
<Animation Include="hello.dae">
<GenerateDebugInfo Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">True</GenerateDebugInfo>
<GenerateDebugInfo Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">True</GenerateDebugInfo>
</Animation>
</ItemGroup>
]]
end
function suite.customRule_onPerConfigLiteralVars()
files { "hello.dae" }
filter { "files:**.dae" }
customRule "Animation"
filter { "files:**.dae", "configurations:Debug" }
customVar { "GenerateDebugInfo", "True" }
prepare()
test.capture [[
<ItemGroup>
<Animation Include="hello.dae">
<GenerateDebugInfo Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">True</GenerateDebugInfo>
</Animation>
</ItemGroup>
]]
end

View File

@ -0,0 +1,75 @@
--
-- tests/actions/vstudio/vc2010/test_import_extensions.lua
-- Check the import extension settings block of a VS 2010 project.
-- Copyright (c) 2014 Jason Perkins and the Premake project
--
local suite = test.declare("vs2010_import_extensions")
local vc2010 = premake.vstudio.vc2010
local project = premake.project
--
-- Setup
--
local sln
function suite.setup()
sln = test.createsolution()
end
local function prepare()
local prj = test.getproject(sln)
vc2010.importExtensionSettings(prj)
end
--
-- Writes an empty element when no custom rules are specified.
--
function suite.structureIsCorrect_onDefaultValues()
prepare()
test.capture [[
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
]]
end
--
-- Writes entries for each project scoped custom rules path.
--
function suite.addsImport_onEachRulesFile()
customRules "MyRules"
customRules "MyOtherRules"
prepare()
test.capture [[
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
<Import Project="MyRules.props" />
<Import Project="MyOtherRules.props" />
</ImportGroup>
]]
end
--
-- Rule files use a project relative path.
--
function suite.usesProjectRelativePaths()
customRules "path/to/MyRules"
location "build"
prepare()
test.capture [[
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
<Import Project="..\path\to\MyRules.props" />
</ImportGroup>
]]
end

View File

@ -48,6 +48,12 @@
end
test.getproject = function(sln, i)
local sln = premake.oven.bakeSolution(sln)
return premake.solution.getproject(sln, i or 1)
end
test.getconfig = function(prj, buildcfg, platform)
local sln = premake.oven.bakeSolution(prj.solution)
prj = premake.solution.getproject(sln, prj.name)
@ -167,6 +173,7 @@
dofile("actions/vstudio/vc2010/test_files.lua")
dofile("actions/vstudio/vc2010/test_filter_ids.lua")
dofile("actions/vstudio/vc2010/test_filters.lua")
dofile("actions/vstudio/vc2010/test_import_extensions.lua")
dofile("actions/vstudio/vc2010/test_item_def_group.lua")
dofile("actions/vstudio/vc2010/test_link.lua")
dofile("actions/vstudio/vc2010/test_manifest.lua")