Improve rule support:

- move rule code from gmake2.lua to rule.lua
- Add UTs
- Fix enum case
- uniformise code for list.
- Add support of rules for Codelite.
This commit is contained in:
Jarod42 2021-10-28 14:41:02 +02:00
parent 6c9fda87b5
commit 29fa743f19
12 changed files with 517 additions and 174 deletions

View File

@ -379,38 +379,51 @@
_p(4, '<CustomPostBuild/>')
local dependencies = {}
local rules = {}
local function addrule(dependencies, rules, config, filename)
local makefilerules = {}
local function addrule(dependencies, makefilerules, config, filename)
if #config.buildcommands == 0 or #config.buildOutputs == 0 then
return
return false
end
local inputs = table.implode(config.buildInputs,"",""," ")
local inputs = table.implode(project.getrelative(cfg.project, config.buildInputs), "", "", " ")
if filename ~= "" and inputs ~= "" then
filename = filename .. " "
end
local outputs = config.buildOutputs[1]
local outputs = project.getrelative(cfg.project, config.buildOutputs[1])
local buildmessage = ""
if config.buildmessage then
buildmessage = "\t@{ECHO} " .. config.buildmessage .. "\n"
end
local commands = table.implode(config.buildCommands,"\t","\n","")
table.insert(rules, os.translateCommandsAndPaths(outputs .. ": " .. filename .. inputs .. "\n" .. buildmessage .. commands, cfg.project.basedir, cfg.project.location))
table.insertflat(dependencies, config.buildOutputs[1])
table.insert(makefilerules, os.translateCommandsAndPaths(outputs .. ": " .. filename .. inputs .. "\n" .. buildmessage .. commands, cfg.project.basedir, cfg.project.location))
table.insertflat(dependencies, outputs)
return true
end
local tr = project.getsourcetree(cfg.project)
p.tree.traverse(tr, {
onleaf = function(node, depth)
local filecfg = p.fileconfig.getconfig(node, cfg)
addrule(dependencies, rules, filecfg, node.relpath)
local prj = cfg.project
local rule = p.global.getRuleForFile(node.name, prj.rules)
if not addrule(dependencies, makefilerules, filecfg, node.relpath) and rule then
local environ = table.shallowcopy(filecfg.environ)
if rule.propertydefinition then
p.rule.prepareEnvironment(rule, environ, cfg)
p.rule.prepareEnvironment(rule, environ, filecfg)
end
local rulecfg = p.context.extent(rule, environ)
addrule(dependencies, makefilerules, rulecfg, node.relpath)
end
end
})
addrule(dependencies, rules, cfg, "")
addrule(dependencies, makefilerules, cfg, "")
if #rules == 0 and #dependencies == 0 then
if #makefilerules == 0 and #dependencies == 0 then
_p(4, '<CustomPreBuild/>')
else
_p(4, '<CustomPreBuild>' .. table.implode(dependencies,"",""," "))
_p(0, table.implode(rules,"","","\n") .. '</CustomPreBuild>')
_p(0, table.implode(makefilerules,"","","\n") .. '</CustomPreBuild>')
end
_p(3, '</AdditionalRules>')
end

View File

@ -4,4 +4,5 @@ return {
"test_codelite_workspace.lua",
"test_codelite_project.lua",
"test_codelite_config.lua",
"test_codelite_additional_rules.lua",
}

View File

@ -0,0 +1,256 @@
---
-- codelite/tests/test_codelite_config.lua
-- Automated test suite for CodeLite project generation.
-- Copyright (c) 2021 Joris Dauphin and the Premake project
---
local suite = test.declare("codelite_cproj_additional_rules")
local p = premake
local codelite = p.modules.codelite
---------------------------------------------------------------------------
-- Setup/Teardown
---------------------------------------------------------------------------
local wks, prj, cfg
local function prepare_rule()
rule "TestRule"
display "Test Rule"
fileextension ".rule"
propertydefinition {
name = "TestProperty",
kind = "boolean",
value = false,
switch = "-p"
}
propertydefinition {
name = "TestProperty2",
kind = "boolean",
value = false,
switch = "-p2"
}
propertydefinition {
name = "TestListProperty",
kind = "list"
}
propertydefinition {
name = "TestListPropertyWithSwitch",
kind = "list",
switch = "-S"
}
propertydefinition {
name = "TestListPropertySeparator",
kind = "list",
separator = ","
}
propertydefinition {
name = "TestListPropertySeparatorWithSwitch",
kind = "list",
separator = ",",
switch = "-O"
}
propertydefinition {
name = "TestEnumProperty",
values = { [0] = "V0", [1] = "V1"},
switch = { [0] = "S0", [1] = "S1"},
value = 0
}
buildmessage 'Rule-ing %{file.name}'
buildcommands 'dorule %{TestProperty} %{TestProperty2} %{TestListProperty} %{TestListPropertyWithSwitch} %{TestListPropertySeparator} %{TestListPropertySeparatorWithSwitch} %{TestEnumProperty} "%{file.path}"'
buildoutputs { "%{file.basename}.obj" }
end
function suite.setup()
p.action.set("codelite")
p.escaper(codelite.esc)
p.indent(" ")
prepare_rule()
wks = test.createWorkspace()
end
local function prepare()
prj = test.getproject(wks, 1)
cfg = test.getconfig(prj, "Debug")
end
function suite.customRuleEmpty()
prepare()
codelite.project.additionalRules(prj)
test.capture [[
<AdditionalRules>
<CustomPostBuild/>
<CustomPreBuild/>
</AdditionalRules>
]]
end
function suite.customRuleWithPropertyDefinition()
rules { "TestRule" }
files { "test.rule", "test2.rule" }
testRuleVars {
TestProperty = true
}
filter "files:test2.rule"
testRuleVars {
TestProperty2 = true
}
prepare()
codelite.project.additionalRules(cfg)
test.capture [[
<AdditionalRules>
<CustomPostBuild/>
<CustomPreBuild>test.obj test2.obj
test.obj: test.rule
@echo Rule-ing test.rule
dorule -p "test.rule"
test2.obj: test2.rule
@echo Rule-ing test2.rule
dorule -p -p2 "test2.rule"
</CustomPreBuild>
</AdditionalRules>
]]
end
function suite.customRuleWithPropertyDefinitionSeparator()
rules { "TestRule" }
files { "test.rule", "test2.rule", "test3.rule", "test4.rule" }
filter "files:test.rule"
testRuleVars {
TestListProperty = { "testValue1", "testValue2" }
}
filter "files:test2.rule"
testRuleVars {
TestListPropertyWithSwitch = { "testValue1", "testValue2" }
}
filter "files:test3.rule"
testRuleVars {
TestListPropertySeparator = { "testValue1", "testValue2" }
}
filter "files:test4.rule"
testRuleVars {
TestListPropertySeparatorWithSwitch = { "testValue1", "testValue2" }
}
prepare()
codelite.project.additionalRules(cfg)
test.capture [[
<AdditionalRules>
<CustomPostBuild/>
<CustomPreBuild>test.obj test2.obj test3.obj test4.obj
test.obj: test.rule
@echo Rule-ing test.rule
dorule testValue1 testValue2 "test.rule"
test2.obj: test2.rule
@echo Rule-ing test2.rule
dorule -StestValue1 -StestValue2 "test2.rule"
test3.obj: test3.rule
@echo Rule-ing test3.rule
dorule testValue1,testValue2 "test3.rule"
test4.obj: test4.rule
@echo Rule-ing test4.rule
dorule -OtestValue1,testValue2 "test4.rule"
</CustomPreBuild>
</AdditionalRules>
]]
end
function suite.customRuleWithPropertyDefinitionEnum()
rules { "TestRule" }
files { "test.rule", "test2.rule" }
testRuleVars {
TestEnumProperty = "V0"
}
filter "files:test2.rule"
testRuleVars {
TestEnumProperty = "V1"
}
prepare()
codelite.project.additionalRules(cfg)
test.capture [[
<AdditionalRules>
<CustomPostBuild/>
<CustomPreBuild>test.obj test2.obj
test.obj: test.rule
@echo Rule-ing test.rule
dorule S0 "test.rule"
test2.obj: test2.rule
@echo Rule-ing test2.rule
dorule S1 "test2.rule"
</CustomPreBuild>
</AdditionalRules>
]]
end
function suite.buildCommand()
files {"foo.txt", "bar.txt"}
buildinputs { "toto.txt", "extra_dependency" }
buildoutputs { "toto.c" }
buildcommands { "test", "test toto.c" }
buildmessage "Some message"
prepare()
codelite.project.additionalRules(cfg)
test.capture [[
<AdditionalRules>
<CustomPostBuild/>
<CustomPreBuild>toto.c
toto.c: toto.txt extra_dependency
@echo Some message
test
test toto.c
</CustomPreBuild>
</AdditionalRules>]]
end
function suite.buildCommandPerFile()
files {"foo.txt", "bar.txt"}
filter "files:**.txt"
buildinputs { "%{file.basename}.h", "extra_dependency" }
buildoutputs { "%{file.basename}.c" }
buildcommands { "test", "test %{file.basename}" }
buildmessage "Some message"
prepare()
codelite.project.additionalRules(cfg)
test.capture [[
<AdditionalRules>
<CustomPostBuild/>
<CustomPreBuild>bar.c foo.c
bar.c: bar.txt bar.h extra_dependency
@echo Some message
test
test bar
foo.c: foo.txt foo.h extra_dependency
@echo Some message
test
test foo
</CustomPreBuild>
</AdditionalRules>]]
end

View File

@ -203,52 +203,6 @@
]]
end
function suite.OnProjectCfg_BuildCommand()
files {"/c/foo.txt", "/c/bar.txt"}
buildinputs { "/c/toto.txt", "/c/extra_dependency" }
buildoutputs { "/c/toto.c" }
buildcommands { "test", "test /c/toto.c" }
buildmessage "Some message"
prepare()
codelite.project.additionalRules(cfg)
test.capture [[
<AdditionalRules>
<CustomPostBuild/>
<CustomPreBuild>/c/toto.c
/c/toto.c: /c/toto.txt /c/extra_dependency
@echo Some message
test
test /c/toto.c
</CustomPreBuild>
</AdditionalRules>]]
end
function suite.OnProjectCfg_BuildCommandPerFile()
files {"/c/foo.txt", "/c/bar.txt"}
filter "files:**.txt"
buildinputs { "/c/%{file.basename}.h", "/c/extra_dependency" }
buildoutputs { "/c/%{file.basename}.c" }
buildcommands { "test", "test /c/%{file.basename}" }
buildmessage "Some message"
prepare()
codelite.project.additionalRules(cfg)
test.capture [[
<AdditionalRules>
<CustomPostBuild/>
<CustomPreBuild>/c/bar.c /c/foo.c
/c/bar.c: /c/bar.txt /c/bar.h /c/extra_dependency
@echo Some message
test
test /c/bar
/c/foo.c: /c/foo.txt /c/foo.h /c/extra_dependency
@echo Some message
test
test /c/foo
</CustomPreBuild>
</AdditionalRules>]]
end
function suite.OnProjectCfg_General()
system "Windows"
prepare()
@ -386,20 +340,6 @@ cmd2</StartupCommands>
]]
end
-- TODO: test custom build
function suite.OnProjectCfg_AdditionalRules()
prepare()
codelite.project.additionalRules(prj)
test.capture [[
<AdditionalRules>
<CustomPostBuild/>
<CustomPreBuild/>
</AdditionalRules>
]]
end
function suite.OnProjectCfg_Completion()
language "C++"
cppdialect "C++11"

View File

@ -171,20 +171,6 @@
return value
end
function gmake2.path(cfg, value)
cfg = cfg.project or cfg
local dirs = path.translate(project.getrelative(cfg, value))
if type(dirs) == 'table' then
dirs = table.filterempty(dirs)
end
return dirs
end
function gmake2.getToolSet(cfg)
local default = iif(cfg.system == p.MACOSX, "clang", "gcc")
local toolset = p.tools[_OPTIONS.cc or cfg.toolset or default]
@ -259,71 +245,6 @@
-- convert a rule property into a string
function gmake2.expandRuleString(rule, prop, value)
-- list?
if type(value) == "table" then
if #value > 0 then
if prop.switch then
return prop.switch .. table.concat(value, " " .. prop.switch)
else
prop.separator = prop.separator or " "
return table.concat(value, prop.separator)
end
else
return nil
end
end
-- bool just emits the switch
if prop.switch and type(value) == "boolean" then
if value then
return prop.switch
else
return nil
end
end
local switch = prop.switch or ""
-- enum?
if prop.values then
value = table.findKeyByValue(prop.values, value)
if value == nil then
value = ""
end
end
-- primitive
value = tostring(value)
if #value > 0 then
return switch .. value
else
return nil
end
end
function gmake2.prepareEnvironment(rule, environ, cfg)
for _, prop in ipairs(rule.propertydefinition) do
local fld = p.rule.getPropertyField(rule, prop)
local value = cfg[fld.name]
if value ~= nil then
if fld.kind == "path" then
value = gmake2.path(cfg, value)
elseif fld.kind == "list:path" then
value = gmake2.path(cfg, value)
end
value = gmake2.expandRuleString(rule, prop, value)
if value ~= nil and #value > 0 then
environ[prop.name] = p.esc(value)
end
end
end
end
---------------------------------------------------------------------------
--
-- Handlers for the individual makefile elements that can be shared

View File

@ -269,8 +269,8 @@
local environ = table.shallowcopy(filecfg.environ)
if rule.propertydefinition then
gmake2.prepareEnvironment(rule, environ, cfg)
gmake2.prepareEnvironment(rule, environ, filecfg)
p.rule.prepareEnvironment(rule, environ, cfg)
p.rule.prepareEnvironment(rule, environ, filecfg)
end
local shadowContext = p.context.extent(rule, environ)

View File

@ -175,8 +175,8 @@
local environ = table.shallowcopy(filecfg.environ)
if rule.propertydefinition then
gmake2.prepareEnvironment(rule, environ, cfg)
gmake2.prepareEnvironment(rule, environ, filecfg)
p.rule.prepareEnvironment(rule, environ, cfg)
p.rule.prepareEnvironment(rule, environ, filecfg)
end
local shadowContext = p.context.extent(rule, environ)

View File

@ -36,20 +36,36 @@
value = false,
switch = "-p2"
}
propertydefinition {
name = "TestListProperty",
kind = "list"
}
propertydefinition {
name = "TestListPropertyWithSwitch",
kind = "list",
switch = "-S"
}
propertydefinition {
name = "TestListPropertySeparator",
kind = "list",
separator = ","
}
propertydefinition {
name = "TestListPropertySeparatorWithSwitch",
kind = "list",
separator = ",",
switch = "-O"
}
propertydefinition {
name = "TestEnumProperty",
values = { [0] = "V0", [1] = "V1"},
switch = { [0] = "S0", [1] = "S1"},
value = 0
}
buildmessage 'Rule-ing %{file.name}'
buildcommands 'dorule %{TestProperty} %{TestProperty2} %{TestListProperty} %{TestListPropertySeparator} "%{file.path}"'
buildcommands 'dorule %{TestProperty} %{TestProperty2} %{TestListProperty} %{TestListPropertyWithSwitch} %{TestListPropertySeparator} %{TestListPropertySeparatorWithSwitch} %{TestEnumProperty} "%{file.path}"'
buildoutputs { "%{file.basename}.obj" }
wks = test.createWorkspace()
@ -281,10 +297,10 @@ endif
test.obj: test.rule
@echo Rule-ing test.rule
$(SILENT) dorule -p "test.rule"
$(SILENT) dorule -p "test.rule"
test2.obj: test2.rule
@echo Rule-ing test2.rule
$(SILENT) dorule -p -p2 "test2.rule"
$(SILENT) dorule -p -p2 "test2.rule"
]]
end
@ -292,7 +308,7 @@ test2.obj: test2.rule
rules { "TestRule" }
files { "test.rule", "test2.rule" }
files { "test.rule", "test2.rule", "test3.rule", "test4.rule" }
filter "files:test.rule"
testRuleVars {
@ -300,9 +316,18 @@ test2.obj: test2.rule
}
filter "files:test2.rule"
testRuleVars {
TestListPropertyWithSwitch = { "testValue1", "testValue2" }
}
filter "files:test3.rule"
testRuleVars {
TestListPropertySeparator = { "testValue1", "testValue2" }
}
filter "files:test4.rule"
testRuleVars {
TestListPropertySeparatorWithSwitch = { "testValue1", "testValue2" }
}
prepare()
test.capture [[
@ -311,10 +336,44 @@ test2.obj: test2.rule
test.obj: test.rule
@echo Rule-ing test.rule
$(SILENT) dorule testValue1\ testValue2 "test.rule"
$(SILENT) dorule testValue1\ testValue2 "test.rule"
test2.obj: test2.rule
@echo Rule-ing test2.rule
$(SILENT) dorule testValue1,testValue2 "test2.rule"
$(SILENT) dorule -StestValue1\ -StestValue2 "test2.rule"
test3.obj: test3.rule
@echo Rule-ing test3.rule
$(SILENT) dorule testValue1,testValue2 "test3.rule"
test4.obj: test4.rule
@echo Rule-ing test4.rule
$(SILENT) dorule -OtestValue1,testValue2 "test4.rule"
]]
end
function suite.customRuleWithPropertyDefinitionEnum()
rules { "TestRule" }
files { "test.rule", "test2.rule" }
testRuleVars {
TestEnumProperty = "V0"
}
filter "files:test2.rule"
testRuleVars {
TestEnumProperty = "V1"
}
prepare()
test.capture [[
# File Rules
# #############################################
test.obj: test.rule
@echo Rule-ing test.rule
$(SILENT) dorule S0 "test.rule"
test2.obj: test2.rule
@echo Rule-ing test2.rule
$(SILENT) dorule S1 "test2.rule"
]]
end

View File

@ -136,21 +136,14 @@
-- @param format
-- The formatting to be used, ie "[%s]".
---
function rule.prepareEnvironment(self, environ, format)
function rule.createEnvironment(self, format)
local environ = {}
for _, def in ipairs(self.propertydefinition) do
environ[def.name] = string.format(format, def.name)
end
end
function rule.createEnvironment(self, format)
local environ = {}
rule.prepareEnvironment(self, environ, format)
return environ
end
---
-- prepare an table of pathVars with the rule properties as global tokens,
-- according to the format specified.
@ -172,3 +165,78 @@
rule.preparePathVars(self, pathVars, format)
return pathVars
end
function rule.prepareEnvironment(self, environ, cfg)
local function path(cfg, value)
cfg = cfg.project or cfg
local dirs = path.translate(project.getrelative(cfg, value))
if type(dirs) == 'table' then
dirs = table.filterempty(dirs)
end
return dirs
end
local function expandRuleString(prop, value)
-- list
if type(value) == "table" then
if #value > 0 then
local switch = prop.switch or ""
if prop.separator then
return switch .. table.concat(value, prop.separator)
else
return switch .. table.concat(value, " " .. switch)
end
else
return nil
end
end
-- bool just emits the switch
if prop.switch and type(value) == "boolean" then
if value then
return prop.switch
else
return nil
end
end
-- enum
if prop.values then
local switch = prop.switch or {}
value = table.findKeyByValue(prop.values, value)
if value == nil then
return nil
end
return switch[value]
end
-- primitive
local switch = prop.switch or ""
value = tostring(value)
if #value > 0 then
return switch .. value
else
return nil
end
end
for _, prop in ipairs(self.propertydefinition) do
local fld = p.rule.getPropertyField(self, prop)
local value = cfg[fld.name]
if value ~= nil then
if fld.kind == "path" then
value = path(cfg, value)
elseif fld.kind == "list:path" then
value = path(cfg, value)
end
value = expandRuleString(prop, value)
if value ~= nil and #value > 0 then
environ[prop.name] = p.esc(value)
end
end
end
end

View File

@ -16,6 +16,7 @@ return {
"base/test_premake_command.lua",
"base/test_table.lua",
"base/test_tree.lua",
"base/test_rule.lua",
"base/test_uuid.lua",
"base/test_versions.lua",
"base/test_http.lua",

85
tests/base/test_rule.lua Normal file
View File

@ -0,0 +1,85 @@
--
-- tests/base/test_rule.lua
-- Automated test suite for custom rule.
-- Copyright (c) 2008-2021 Jason Perkins and the Premake project
--
local suite = test.declare("rule")
local p = premake
function suite.setup()
rule "TestRule"
display "Test Rule"
fileextension ".rule"
propertydefinition {
name = "TestPropertyFalse",
kind = "boolean",
value = false,
switch = "-dummy"
}
propertydefinition {
name = "TestPropertyTrue",
kind = "boolean",
value = false,
switch = "-p"
}
propertydefinition {
name = "TestListProperty",
kind = "list"
}
propertydefinition {
name = "TestListPropertyWithSwitch",
kind = "list",
switch = "-S"
}
propertydefinition {
name = "TestListPropertySeparator",
kind = "list",
separator = ","
}
propertydefinition {
name = "TestListPropertySeparatorWithSwitch",
kind = "list",
separator = ",",
switch = "-O"
}
propertydefinition {
name = "TestEnumProperty",
values = { [0] = "V0", [1] = "V1"},
switch = { [0] = "S0", [1] = "S1"},
value = 0
}
end
--
-- rule tests
--
function suite.prepareEnvironment()
local rule = premake.global.getRule("TestRule")
local environ = {}
local cfg = {
["_rule_TestRule_TestPropertyFalse"] = false,
["_rule_TestRule_TestPropertyTrue"] = true,
["_rule_TestRule_TestListProperty"] = {"a", "b"},
["_rule_TestRule_TestListPropertyWithSwitch"] = {"c", "d"},
["_rule_TestRule_TestListPropertySeparator"] = {"e", "f"},
["_rule_TestRule_TestListPropertySeparatorWithSwitch"] = {"1", "2"},
["_rule_TestRule_TestEnumProperty"] = 'V1'
}
p.rule.prepareEnvironment(rule, environ, cfg)
test.isequal(nil, environ["TestPropertyFalse"])
test.isequal("-p", environ["TestPropertyTrue"])
test.isequal("a b", environ["TestListProperty"])
test.isequal("-Sc -Sd", environ["TestListPropertyWithSwitch"])
test.isequal("e,f", environ["TestListPropertySeparator"])
test.isequal("-O1,2", environ["TestListPropertySeparatorWithSwitch"])
test.isequal("S1", environ["TestEnumProperty"])
end

View File

@ -47,9 +47,8 @@ For enum properties, a key-value table of the possible values of the property, a
The value to be placed into the command line for this property. See the examples below for more information.
#### separator ####
For list properties, this sets the value of the list item separator in the command line.\
gmake2: Default value is ' '. If a switch is set, the separator is ignored and instead the switch is duplicated.\
VS201x: If set, the list is concatenated by the separator and placed behind a single switch. If not set, the switch is duplicated.
For list properties, this sets the value of the list item separator in the command line.
If set, the list is concatenated by the separator and placed behind a single switch. If not set, the switch is duplicated.
#### category ####
Visual Studio only.