Support linking of managed system assemblies in Visual Studio managed C++ projects

This commit is contained in:
Jason Perkins 2013-03-04 11:45:27 -05:00
parent 1773146d4d
commit d3ec20d1c3
11 changed files with 488 additions and 79 deletions

View File

@ -61,6 +61,7 @@
_p(1,'</Configurations>')
_p(1,'<References>')
vc200x.assemblyReferences(prj)
vc200x.projectReferences(prj)
_p(1,'</References>')
@ -672,6 +673,22 @@
end
--
-- For a managed C++ project, write out any linked system assemblies.
--
function vc200x.assemblyReferences(prj)
-- Visual Studio doesn't support per-config references
local cfg = project.getfirstconfig(prj)
local refs = config.getlinks(cfg, "system", "fullpath", "managed")
table.foreachi(refs, function(value)
_p(2,'<AssemblyReference')
_x(3,'RelativePath="%s"', value)
_p(2,'/>')
end)
end
--
-- Write out the list of project references.
--

View File

@ -54,6 +54,7 @@
_p(1,'</ItemDefinitionGroup>')
end
vc2010.assemblyReferences(prj)
vc2010.files(prj)
vc2010.projectReferences(prj)
@ -299,6 +300,24 @@
end
--
-- Reference any managed assemblies listed in the links()
--
function vc2010.assemblyReferences(prj)
-- Visual Studio doesn't support per-config references
local cfg = project.getfirstconfig(prj)
local refs = config.getlinks(cfg, "system", "fullpath", "managed")
if #refs > 0 then
_p(1,'<ItemGroup>')
table.foreachi(refs, function(value)
_x(2,'<Reference Include="%s" />', path.getbasename(value))
end)
_p(1,'</ItemGroup>')
end
end
--
-- Write out the list of source code files, and any associated configuration.
--
@ -481,9 +500,6 @@
if toolset then
links = toolset.getlinks(cfg, not explicit)
else
-- VS always tries to link against project dependencies, even when those
-- projects are excluded from the build. To work around, linking dependent
-- projects is disabled, and sibling projects link explicitly
local scope = iif(explicit, "all", "system")
links = config.getlinks(cfg, scope, "fullpath")
end

View File

@ -11,6 +11,11 @@
--
function path.appendextension(p, ext)
-- if the extension is nil or empty, do nothing
if not ext or ext == "" then
return p
end
-- if the path ends with a quote, pull it off
local endquote
if p:endswith('"') then

View File

@ -84,6 +84,115 @@
end
--
-- Determine whether the given configuration can meaningfully link
-- against the target object.
--
-- @param cfg
-- The configuration to be tested.
-- @param target
-- The object to test against. This can be a library file name, or a
-- configuration from another project.
-- @param linkage
-- Optional. For languages or environments that support different kinds of
-- linking (i.e. Managed/CLR C++, which can link both managed and unmanaged
-- libs), which one to return. One of "unmanaged", "managed". If not
-- specified, the default for the configuration will be used.
-- @return
-- True if linking the target into the configuration makes sense.
--
function config.canlink(cfg, target, linkage)
-- Have I got a project configuration? If so, I've got some checks
-- I can do with the extra information
if type(target) ~= "string" then
-- Can't link against executables
if target.kind ~= "SharedLib" and target.kind ~= "StaticLib" then
return false
end
-- Can't link managed and unmanaged projects
local cfgManaged = premake.isdotnetproject(cfg.project) or (cfg.flags.Managed ~= nil)
local tgtManaged = premake.isdotnetproject(target.project) or (target.flags.Managed ~= nil)
return (cfgManaged == tgtManaged)
end
-- For now, I assume that everything listed in a .NET project can be
-- linked; unmanaged code is simply not supported
if premake.isdotnetproject(cfg.project) then
return true
end
-- In C++ projects, managed dependencies must explicitly include
-- the ".dll" extension, to distinguish from unmanaged libraries
local isManaged = (path.getextension(target) == ".dll")
-- Unmanaged projects can never link managed assemblies
if isManaged and not cfg.flags.Managed then
return false
end
-- Only allow this link it matches the requested linkage
return (isManaged) == (linkage == "managed")
end
--
-- Given a raw link target filename, properly format it for the given
-- configuration. Adds file decorations, and handles relative path
-- conversions.
--
-- @param cfg
-- The configuration that is linking.
-- @param target
-- The file name of the library being linked.
-- @param linkage
-- Optional. For languages or environments that support different kinds of
-- linking (i.e. Managed/CLR C++, which can link both managed and unmanaged
-- libs), which one to return. One of "unmanaged", "managed". If not
-- specified, the default for the configuration will be used.
-- @return
-- The decorated library file name.
--
function config.decoratelink(cfg, target, linkage)
-- Determine if a file extension is required, and append if so
local ext
if cfg.system == premake.WINDOWS then
if premake.isdotnetproject(cfg.project) or linkage == "managed" then
ext = ".dll"
elseif premake.iscppproject(cfg.project) then
ext = ".lib"
end
end
target = path.appendextension(target, ext)
-- if the target is listed via an explicit path (i.e. not a
-- system library or assembly), make it project-relative
if target:find("/", nil, true) then
target = project.getrelative(cfg.project, target)
end
return target
end
--
-- Check a configuration for a source code file with the specified
-- extension. Used for locating special files, such as Windows
@ -221,95 +330,92 @@
-- directory - just the directory, no name
-- fullpath - full path with decorated name
-- object - return the project object of the dependency
-- @param linkage
-- Optional. For languages or environments that support different kinds of
-- linking (i.e. Managed/CLR C++, which can link both managed and unmanaged
-- libs), which one to return. One of "unmanaged", "managed". If not
-- specified, the default for the configuration will be used.
-- @return
-- An array containing the requested link target information.
--
function config.getlinks(cfg, kind, part)
function config.getlinks(cfg, kind, part, linkage)
local result = {}
-- if I'm building a list of link directories, include libdirs
-- If I'm building a list of link directories, include libdirs
if part == "directory" and kind == "all" then
for _, dir in ipairs(cfg.libdirs) do
table.foreachi(cfg.libdirs, function(dir)
table.insert(result, project.getrelative(cfg.project, dir))
end
end)
end
local function canlink(source, target)
-- can't link executables
if (target.kind ~= "SharedLib" and target.kind ~= "StaticLib") then
return false
end
-- can't link managed and unmanaged projects
if premake.iscppproject(source.project) then
return premake.iscppproject(target.project)
elseif premake.isdotnetproject(source.project) then
return premake.isdotnetproject(target.project)
end
end
-- Iterate all of the links listed in the configuration and boil
-- them down to the requested data set
for _, link in ipairs(cfg.links) do
table.foreachi(cfg.links, function(link)
local item
-- is this a sibling project?
-- Sort the links into "sibling" (is another project in this same
-- solution) and "system" (is not part of this solution) libraries.
local prj = premake.solution.findproject(cfg.solution, link)
if prj and kind ~= "system" then
-- Sibling; is there a matching configuration in this project that
-- is compatible with linking to me?
local prjcfg = project.getconfig(prj, cfg.buildcfg, cfg.platform)
if prjcfg and (kind == "dependencies" or canlink(cfg, prjcfg)) then
-- if the caller wants the whole project object, then okay
if prjcfg and (kind == "dependencies" or config.canlink(cfg, prjcfg)) then
-- Yes; does the caller want the whole project config or only part?
if part == "object" then
item = prjcfg
-- if this is an external project reference, I can't return
-- any kind of path info, because I don't know the target name
-- Just some part of the path. Grab the whole thing now, split it up
-- below. Skip external projects, because I have no way to know their
-- target file (without parsing the project, which I'm not doing)
elseif not prj.external then
if part == "basename" then
item = prjcfg.linktarget.basename
else
item = path.rebase(prjcfg.linktarget.fullpath,
project.getlocation(prjcfg.project),
project.getlocation(cfg.project))
if part == "directory" then
item = path.getdirectory(item)
end
end
item = project.getrelative(cfg.project, prjcfg.linktarget.fullpath)
end
end
elseif not prj and (kind == "system" or kind == "all") then
if part == "directory" then
local dir = path.getdirectory(link)
if dir ~= "." then
item = dir
end
elseif part == "fullpath" then
item = link
if cfg.system == premake.WINDOWS then
if premake.iscppproject(cfg.project) then
item = path.appendextension(item, ".lib")
elseif premake.isdotnetproject(cfg.project) then
item = path.appendextension(item, ".dll")
end
end
if item:find("/", nil, true) then
item = project.getrelative(cfg.project, item)
end
elseif part == "name" then
item = path.getname(link)
elseif part == "basename" then
item = path.getbasename(link)
else
item = link
-- Make sure this library makes sense for the requested linkage; don't
-- link managed .DLLs into unmanaged code, etc.
if config.canlink(cfg, link, linkage) then
item = config.decoratelink(cfg, link, linkage)
end
end
-- If this is something I can link against, pull out the requested part
if item then
if part == "directory" then
item = path.getdirectory(item)
if item == "." then
item = nil
end
elseif part == "name" then
item = path.getname(item)
elseif part == "basename" then
item = path.getbasename(item)
end
end
-- Add it to the list, skipping duplicates
if item and not table.contains(result, item) then
table.insert(result, item)
end
end
end)
return result
end

View File

@ -322,10 +322,6 @@
--
-- Returns an iterator function for the configuration objects contained by
-- the project. Each configuration corresponds to a build configuration/
-- platform pair (i.e. "Debug|x32") as specified in the solution.

View File

@ -0,0 +1,69 @@
--
-- tests/actions/vstudio/vc200x/test_assembly_refs.lua
-- Validate managed assembly references in Visual Studio 2010 C/C++ projects.
-- Copyright (c) 2013 Jason Perkins and the Premake project
--
local suite = test.declare("vs200x_assembly_refs")
local vc200x = premake.vstudio.vc200x
--
-- Setup
--
local sln, prj
function suite.setup()
sln = test.createsolution()
flags { "Managed" }
end
local function prepare(platform)
prj = premake.solution.getproject_ng(sln, 1)
vc200x.assemblyReferences(prj)
end
--
-- If there are no managed assemblies listed in links, output nothing.
--
function suite.noOutput_onNoAssemblies()
prepare()
test.isemptycapture()
end
--
-- To distinguish between managed and unmanaged libraries, the ".dll"
-- extension must be explicitly supplied.
--
function suite.listsAssemblies()
links { "System.dll", "System.Data.dll" }
prepare()
test.capture [[
<AssemblyReference
RelativePath="System.dll"
/>
<AssemblyReference
RelativePath="System.Data.dll"
/>
]]
end
--
-- Any unmanaged libraries included in the list should be ignored.
--
function suite.ignoresUnmanagedLibraries()
links { "m", "System.dll" }
prepare()
test.capture [[
<AssemblyReference
RelativePath="System.dll"
/>
]]
end

View File

@ -106,7 +106,7 @@
--
-- Verify the handling of the Symbols flag.
-- Verify the handling of the Symbols flag.
--
function suite.onSymbolsFlag()
@ -165,7 +165,7 @@
--
-- Verify handling of the NoIncrementalLink flag.
--
function suite.onNoIncrementalLink()
flags { "NoIncrementalLink" }
prepare()
@ -264,3 +264,18 @@
AdditionalDependencies="&quot;My Lib.lib&quot;"
]]
end
--
-- Managed assembly references should not be listed in additional dependencies.
--
function suite.ignoresAssemblyReferences()
links { "kernel32", "System.dll", "System.Data.dll" }
prepare()
test.capture [[
<Tool
Name="VCLinkerTool"
AdditionalDependencies="kernel32.lib"
]]
end

View File

@ -0,0 +1,67 @@
--
-- tests/actions/vstudio/vc2010/test_assembly_refs.lua
-- Validate managed assembly references in Visual Studio 2010 C/C++ projects.
-- Copyright (c) 2013 Jason Perkins and the Premake project
--
local suite = test.declare("vs2010_assembly_refs")
local vc2010 = premake.vstudio.vc2010
--
-- Setup
--
local sln, prj
function suite.setup()
sln = test.createsolution()
flags { "Managed" }
end
local function prepare(platform)
prj = premake.solution.getproject_ng(sln, 1)
vc2010.assemblyReferences(prj)
end
--
-- If there are no managed assemblies listed in links, output nothing.
--
function suite.noOutput_onNoAssemblies()
prepare()
test.isemptycapture()
end
--
-- To distinguish between managed and unmanaged libraries, the ".dll"
-- extension must be explicitly supplied.
--
function suite.listsAssemblies()
links { "System.dll", "System.Data.dll" }
prepare()
test.capture [[
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
</ItemGroup>
]]
end
--
-- Any unmanaged libraries included in the list should be ignored.
--
function suite.ignoresUnmanagedLibraries()
links { "m", "System.dll" }
prepare()
test.capture [[
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
]]
end

View File

@ -1,11 +1,10 @@
--
-- tests/actions/vstudio/vc2010/test_link.lua
-- Validate linking and project references in Visual Studio 2010 C/C++ projects.
-- Copyright (c) 2011 Jason Perkins and the Premake project
-- Copyright (c) 2011-2013 Jason Perkins and the Premake project
--
T.vstudio_vs2010_link = { }
local suite = T.vstudio_vs2010_link
local suite = test.declare("vs2010_link")
local vc2010 = premake.vstudio.vc2010
local project = premake5.project
@ -344,3 +343,19 @@
</Link>
]]
end
--
-- Managed assembly references should not be listed in additional dependencies.
--
function suite.ignoresAssemblyReferences()
links { "kernel32", "System.dll", "System.Data.dll" }
prepare()
test.capture [[
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>false</GenerateDebugInformation>
<AdditionalDependencies>kernel32.lib;%(AdditionalDependencies)</AdditionalDependencies>
]]
end

View File

@ -6,7 +6,6 @@
T.config_links = { }
local suite = T.config_links
local project = premake5.project
local config = premake5.config
@ -18,13 +17,13 @@
function suite.setup()
_ACTION = "test"
_OS = "windows"
sln, prj = test.createsolution()
system "macosx"
end
local function prepare(kind, part)
cfg = project.getconfig(prj, "Debug")
return config.getlinks(cfg, kind, part)
local function prepare(kind, part, linkage)
cfg = premake5.project.getconfig(prj, "Debug")
return config.getlinks(cfg, kind, part, linkage)
end
@ -46,7 +45,7 @@
location "build"
links { "../libs/z" }
local r = prepare("all", "fullpath")
test.isequal({ "../../libs/z" }, r)
test.isequal({ "../../libs/z.lib" }, r)
end
@ -55,13 +54,18 @@
--
function suite.libAdded_onWindowsSystemLibs()
system "windows"
links { "user32" }
local r = prepare("all", "fullpath")
test.isequal({ "user32.lib" }, r)
end
function suite.libAdded_onWindowsSystemLibs()
--
-- Handle the case where the library extension has been explicitly
-- included in the link statement.
--
function suite.skipsExtension_onExplicitExtension()
system "windows"
links { "user32.lib" }
local r = prepare("all", "fullpath")
@ -109,12 +113,110 @@
function suite.skipsExternalProjectRefs()
links { "MyProject2" }
external "MyProject2"
kind "StaticLib"
language "C++"
local r = prepare("all", "fullpath")
test.isequal({}, r)
end
--
-- Managed C++ projects should ignore links to managed assemblies, which
-- are designated with an explicit ".dll" extension.
--
function suite.skipsAssemblies_onManagedCpp()
system "windows"
flags { "Managed" }
links { "user32", "System.dll" }
local r = prepare("all", "fullpath")
test.isequal({ "user32.lib" }, r)
end
--
-- When explicitly requesting managed links, any unmanaged items in the
-- list should be ignored.
--
function suite.skipsUnmanagedLibs_onManagedLinkage()
system "windows"
flags { "Managed" }
links { "user32", "System.dll" }
local r = prepare("all", "fullpath", "managed")
test.isequal({ "System.dll" }, r)
end
--
-- Managed projects can link to other managed projects, and unmanaged
-- projects can link to unmanaged projects.
--
function suite.canLink_CppAndCpp()
links { "MyProject2" }
project "MyProject2"
kind "StaticLib"
language "C++"
local r = prepare("all", "fullpath")
test.isequal({ "MyProject2.lib" }, r)
end
function suite.canLink_CsAndCs()
language "C#"
links { "MyProject2" }
project "MyProject2"
kind "SharedLib"
language "C#"
local r = prepare("all", "fullpath")
test.isequal({ "MyProject2.dll" }, r)
end
function suite.canLink_ManagedCppAndManagedCpp()
flags { "Managed" }
links { "MyProject2" }
project "MyProject2"
kind "StaticLib"
language "C++"
flags { "Managed" }
local r = prepare("all", "fullpath")
test.isequal({ "MyProject2.lib" }, r)
end
function suite.canLink_ManagedCppAndCs()
flags { "Managed" }
links { "MyProject2" }
project "MyProject2"
kind "SharedLib"
language "C#"
local r = prepare("all", "fullpath")
test.isequal({ "MyProject2.dll" }, r)
end
--
-- Managed and unmanaged projects can not link to each other.
--
function suite.ignoreLink_CppAndCs()
links { "MyProject2" }
project "MyProject2"
kind "SharedLib"
language "C#"
local r = prepare("all", "fullpath")
test.isequal({}, r)
end

View File

@ -113,7 +113,7 @@
dofile("actions/vstudio/sln2005/test_platforms.lua")
-- Visual Studio 2002-2008 C/C++ projects
dofile("actions/vstudio/vc200x/test_compiler_block.lua")
dofile("actions/vstudio/vc200x/test_assembly_refs.lua")
dofile("actions/vstudio/vc200x/test_configuration.lua")
dofile("actions/vstudio/vc200x/test_debug_settings.lua")
dofile("actions/vstudio/vc200x/test_excluded_configs.lua")
@ -128,6 +128,7 @@
dofile("actions/vstudio/vc200x/test_resource_compiler.lua")
-- Visual Studio 2010 C/C++ projects
dofile("actions/vstudio/vc2010/test_assembly_refs.lua")
dofile("actions/vstudio/vc2010/test_compile_settings.lua")
dofile("actions/vstudio/vc2010/test_config_props.lua")
dofile("actions/vstudio/vc2010/test_debug_settings.lua")