From efe5f1e292a458e05058cc8cf85592908a2410e8 Mon Sep 17 00:00:00 2001 From: Jason Perkins Date: Tue, 10 Jun 2014 16:38:16 -0400 Subject: [PATCH] Initial implementation of a minimal custom rules API (currently Visual Studio only) --- src/_premake_init.lua | 13 ++ src/actions/vstudio/vs2010_vcxproj.lua | 189 ++++++++++++++---- src/base/api.lua | 45 +++++ src/base/field.lua | 14 ++ src/base/path.lua | 7 +- tests/actions/vstudio/vc2010/test_files.lua | 59 +++++- .../vstudio/vc2010/test_import_extensions.lua | 75 +++++++ tests/premake5.lua | 7 + 8 files changed, 362 insertions(+), 47 deletions(-) create mode 100644 tests/actions/vstudio/vc2010/test_import_extensions.lua diff --git a/src/_premake_init.lua b/src/_premake_init.lua index 793b2db1..d8e5e6cf 100644 --- a/src/_premake_init.lua +++ b/src/_premake_init.lua @@ -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", diff --git a/src/actions/vstudio/vs2010_vcxproj.lua b/src/actions/vstudio/vs2010_vcxproj.lua index e3933bb2..b96df9b6 100644 --- a/src/actions/vstudio/vs2010_vcxproj.lua +++ b/src/actions/vstudio/vs2010_vcxproj.lua @@ -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,'') + 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', 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,'', rule) + else + _p(2,'<%s Include=\"%s\" />', rule, path.translate(file.relpath)) + end + end + _p(1,'') + 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,'') - _p(1,'') - _p(1,'') + p.w('') + p.push('') + table.foreachi(prj.customRules, function(value) + value = path.translate(project.getrelative(prj, value)) + value = path.appendExtension(value, ".props") + p.x('', value) + end) + p.pop('') end diff --git a/src/base/api.lua b/src/base/api.lua index f7ecc7ec..20689f03 100755 --- a/src/base/api.lua +++ b/src/base/api.lua @@ -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 diff --git a/src/base/field.lua b/src/base/field.lua index 19b35b13..961d28d2 100644 --- a/src/base/field.lua +++ b/src/base/field.lua @@ -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. -- diff --git a/src/base/path.lua b/src/base/path.lua index 66b8cc32..3200fbcc 100644 --- a/src/base/path.lua +++ b/src/base/path.lua @@ -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. diff --git a/tests/actions/vstudio/vc2010/test_files.lua b/tests/actions/vstudio/vc2010/test_files.lua index 20ba076d..b1599786 100755 --- a/tests/actions/vstudio/vc2010/test_files.lua +++ b/tests/actions/vstudio/vc2010/test_files.lua @@ -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 @@ ]] end + + +-- +-- Check handling of files using custom rule definitions. +-- + + function suite.correctlyCategorized_onCustomRule() + files { "hello.dae" } + filter "files:**.dae" + customRule "Animation" + prepare() + test.capture [[ + + + + ]] + end + + + function suite.customRule_onLiteralVars() + files { "hello.dae" } + filter "files:**.dae" + customRule "Animation" + customVar { "GenerateDebugInfo", "True" } + prepare() + test.capture [[ + + + True + True + + + ]] + end + + + function suite.customRule_onPerConfigLiteralVars() + files { "hello.dae" } + filter { "files:**.dae" } + customRule "Animation" + filter { "files:**.dae", "configurations:Debug" } + customVar { "GenerateDebugInfo", "True" } + prepare() + test.capture [[ + + + True + + + ]] + end + + diff --git a/tests/actions/vstudio/vc2010/test_import_extensions.lua b/tests/actions/vstudio/vc2010/test_import_extensions.lua new file mode 100644 index 00000000..5f8bdf46 --- /dev/null +++ b/tests/actions/vstudio/vc2010/test_import_extensions.lua @@ -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 [[ + + + + ]] + end + + + +-- +-- Writes entries for each project scoped custom rules path. +-- + + function suite.addsImport_onEachRulesFile() + customRules "MyRules" + customRules "MyOtherRules" + prepare() + test.capture [[ + + + + + + ]] + end + + +-- +-- Rule files use a project relative path. +-- + + function suite.usesProjectRelativePaths() + customRules "path/to/MyRules" + location "build" + prepare() + test.capture [[ + + + + + ]] + end diff --git a/tests/premake5.lua b/tests/premake5.lua index ad44964b..a496e1eb 100644 --- a/tests/premake5.lua +++ b/tests/premake5.lua @@ -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")