diff --git a/modules/vstudio/tests/sln2026/test_projects.lua b/modules/vstudio/tests/sln2026/test_projects.lua index 45e845c60..6c5e321cf 100644 --- a/modules/vstudio/tests/sln2026/test_projects.lua +++ b/modules/vstudio/tests/sln2026/test_projects.lua @@ -14,13 +14,16 @@ function suite.setup() p.action.set("vs2026") end +function prepare(wks) + wks = test.getWorkspace(wks) + sln2026.projects(wks) +end function suite.single_project() local wks = workspace "MyWorkspace" local prj = project "MyProject" - uuid "AE61726D-187C-E440-BD07-2556188A6565" - - sln2026.projects(wks) + uuid "AE61726D-187C-E440-BD07-2556188A6565" + prepare(wks) test.capture [[ @@ -38,7 +41,7 @@ function suite.multiple_projects_no_dependencies() local prj2 = project "MyProject2" uuid "BE62726D-187C-E440-BD07-2556188A6565" - sln2026.projects(wks) + prepare(wks) test.capture [[ @@ -64,12 +67,20 @@ function suite.multiple_projects_with_dependency() language "C++" kind "StaticLib" - sln2026.projects(wks) + prepare(wks) test.capture [[ + + + + + + + + ]] @@ -79,22 +90,33 @@ function suite.project_in_groups() local wks = workspace "MyWorkspace" configurations { "Debug", "Release" } - local grp1 = group "Group1" - local prj1 = project "MyProject1" uuid "AE61726D-187C-E440-BD07-2556188A6565" - sln2026.projects(wks) + local grp1 = group "Group1" + local prj2 = project "MyProject2" + uuid "BE61726D-187C-E440-BD07-2556188A6565" + + prepare(wks) test.capture [[ + + + + + + - + + + + + ]] end - function suite.project_with_configmap() local wks = workspace "MyWorkspace" configurations { "Debug", "Release", "SpecialRelease" } @@ -107,13 +129,16 @@ function suite.project_with_configmap() [ "Debug" ] = "Debug", } - sln2026.projects(wks) + prepare(wks) test.capture [[ - - - + + + + + + ]] end @@ -129,11 +154,16 @@ function suite.project_with_single_configmap() [ "SpecialRelease" ] = "Release", } - sln2026.projects(wks) + prepare(wks) test.capture [[ - + + + + + + ]] end @@ -150,11 +180,18 @@ function suite.project_with_platform_configmap() [ "MyPlatform" ] = "x64", } - sln2026.projects(wks) + prepare(wks) test.capture [[ - + + + + + + + + ]] end @@ -171,12 +208,18 @@ function suite.project_with_platform_and_config_configmap() [ { "Debug", "MyPlatform" } ] = { "Debug", "x64" }, } - sln2026.projects(wks) + prepare(wks) test.capture [[ - + + + + + + + ]] end @@ -191,10 +234,14 @@ function suite.project_excluded_from_build() filter "configurations:Release" excludefrombuild "On" - sln2026.projects(wks) + prepare(wks) test.capture [[ + + + + ]] @@ -209,11 +256,16 @@ function suite.project_remove_configuration() uuid "AE61726D-187C-E440-BD07-2556188A6565" removeconfigurations { "Release" } - sln2026.projects(wks) + prepare(wks) test.capture [[ - + + + + + + ]] end diff --git a/modules/vstudio/vs2026_solution.lua b/modules/vstudio/vs2026_solution.lua index d87baefc1..6d27cf700 100644 --- a/modules/vstudio/vs2026_solution.lua +++ b/modules/vstudio/vs2026_solution.lua @@ -26,9 +26,7 @@ end m.elements.project = function(prj) return { - m.projectDependencies, - m.projectConfigMap, - m.projectExclusion, + m.projectDependencies } end @@ -38,6 +36,24 @@ function sln2026.generate(wks) p.pop('') end +function sln2026.allowDeployment(prj, cfg) + + local prjCfg = project.getconfig(prj, cfg.buildcfg, cfg.platform) + + if not prjCfg.excludefrombuild then + + if prjCfg.system == p.UWP and (prjCfg.kind == p.WINDOWEDAPP or prjCfg.kind == p.CONSOLEAPP) then + return true + elseif cfg.system == p.ANDROID and prjCfg.kind == p.PACKAGING then + return true + end + + end + + return false + +end + function m.solution(wks) local action = p.action.current() p.push('', action.vstudio.solutionVersion) @@ -148,6 +164,14 @@ function m.projects(wks) end table.sort(sortedKeys) + -- Sort the solution configurations + local sortedSolutionConfigs = {} + local solutionConfigCount = 0 + for solutionConfig in p.workspace.eachconfig(wks) do + table.insert(sortedSolutionConfigs, solutionConfig); + solutionConfigCount = solutionConfigCount + 1 + end + for _, groupName in ipairs(sortedKeys) do local projects = groups[groupName] table.sort(projects, function(a, b) return a.name:lower() < b.name:lower() end) @@ -155,154 +179,166 @@ function m.projects(wks) if groupName ~= "" then p.push('', groupName) end + for _, prj in ipairs(projects) do + + local typeID = vstudio.tool(prj) + + local projectPath = '' + + -- Open the XML tag, add common properties + projectPath = projectPath .. '<' + projectPath = projectPath .. string.format('Project Path="%s" Id="%s"', m.buildRelativePath(prj), prj.uuid) + + -- Mark startup project if startupProject and startupProject.name == prj.name then - p.push('', m.buildRelativePath(prj), prj.uuid) - else - p.push('', m.buildRelativePath(prj), prj.uuid) + projectPath = projectPath .. ' DefaultStartup="true"' end - p.callArray(m.elements.project, prj) - p.pop('') - end - if groupName ~= "" then - p.pop('') - end - end -end + -- Mark projects with uncommon types + if prj.kind == p.PACKAGING then + projectPath = projectPath .. string.format(' Type="%s"', typeID) + end + -- SharedItems projects don't have any configuration platform entries + if prj.kind == p.SHAREDITEMS then -function m.projectDependencies(prj) - local deps = p.project.getdependencies(prj, 'dependOnly') - if #deps > 0 then - for _, dep in ipairs(deps) do - p.push('', m.buildRelativePath(dep)) - p.pop() - end - end -end + -- Directly close the XML tag + projectPath = projectPath .. '/>' + p.push(projectPath) + p.pop() + else -function m.projectConfigMap(prj) - local cfgmap = prj.configmap or {} - for mi = 1, #cfgmap do - -- Key may be a string (buildcfg only) or a table (buildcfg + platform) - -- Sort the keys to ensure stable output - -- If the key is a table, sort by buildcfg first, then platform - -- On comparison of a string and a table - -- If the string matches the buildcfg portion of the table, the string is "less than" the table - -- If the string does not match the buildcfg portion of the table, sort by buildcfg alphabetically - local slncfgmaps = cfgmap[mi] - local sortedkeys = {} - for slncfg, _ in pairs(slncfgmaps) do - table.insert(sortedkeys, slncfg) - end + -- Close the opening XML tag + projectPath = projectPath .. '>' + p.push(projectPath) - table.sort(sortedkeys, function(a, b) - if type(a) == "string" and type(b) == "string" then - return a:lower() < b:lower() - elseif type(a) == "string" then - return a:lower() < b[1]:lower() - elseif type(b) == "string" then - return false - else - if a[1]:lower() == b[1]:lower() then - return a[2]:lower() < b[2]:lower() - else - return a[1]:lower() < b[1]:lower() - end - end - end) - - -- Iterate over each sorted key - for _, cfg in ipairs(sortedkeys) do - local target = slncfgmaps[cfg] - -- Determine if the target is a platform or a configuration, or both - -- Target may be a length 1 table (buildcfg or platform) or a length 2 table (buildcfg + platform) - - local getplatform = function(tgt) - if type(tgt) == "string" then - if table.contains(prj.workspace.platforms, tgt) then - return tgt - end - elseif type(tgt) == "table" then - for _, plat in ipairs(tgt) do - if table.contains(prj.workspace.platforms, plat) then - return plat - end - end - end - return nil - end + -- Create a context with all the properties we need to populate the relevant fields + -- Many of these are repeated and it's useful to have them in a single place + local sortedContexts = {} + local availableConfigCount = 0 + local excludedConfigCount = 0 + for _, solutionConfig in ipairs(sortedSolutionConfigs) do + local context = {} - local getbuildcfg = function(tgt) - if type(tgt) == "string" then - if table.contains(prj.workspace.configurations, tgt) then - return tgt - end - elseif type(tgt) == "table" then - for _, cfg in ipairs(tgt) do - if table.contains(prj.workspace.configurations, cfg) then - return cfg + local projectConfig = project.getconfig(prj, solutionConfig.buildcfg, solutionConfig.platform) + + if projectConfig then + + context.solutionConfig = solutionConfig + context.solutionPlatform = vstudio.solutionPlatform(solutionConfig) + context.allowDeployment = sln2026.allowDeployment(prj, solutionConfig) + context.descriptor = string.format("%s|%s", solutionConfig.buildcfg, context.solutionPlatform) + + context.projectPlatform = vstudio.projectPlatform(projectConfig) + context.arch = vstudio.archFromConfig(projectConfig, true) + context.excluded = projectConfig.excludefrombuild + + if projectConfig.excludefrombuild then + excludedConfigCount = excludedConfigCount + 1 end + + table.insert(sortedContexts, context) + + availableConfigCount = availableConfigCount + 1 + end end - return nil - end - local platform = getplatform(target) - local buildcfg = getbuildcfg(target) + -- Are all available configs for this project excluded + local allExcluded = excludedConfigCount == availableConfigCount - local output = function(cfg, platform, tag) - if type(cfg) == "string" then - local isplatform = getplatform(cfg) ~= nil - local isbuildcfg = getbuildcfg(cfg) ~= nil - if isplatform then - p.push('<%s Solution="*|%s" Project="%s" />', tag, cfg, platform) + -- Output properties if there are any configs + if next(sortedContexts) ~= nil then + + local closestCfg = project.findClosestMatch(prj, sortedContexts[1].solutionConfig.buildcfg, sortedContexts[1].solutionConfig.platform) + local closestArch = vstudio.archFromConfig(closestCfg, true) + local closestProjectPlatform = vstudio.projectPlatform(closestCfg) + + -- BuildType + for _, context in ipairs(sortedContexts) do + p.push('', context.descriptor, context.projectPlatform) p.pop() - elseif isbuildcfg then - p.push('<%s Solution="%s|*" Project="%s" />', tag, cfg, platform) + end + + -- We might have more, in which case we'll have accounted for all of them above + if availableConfigCount < solutionConfigCount then + p.push('', closestProjectPlatform) p.pop() end - else - local isplatform = getplatform(cfg) ~= nil - local isbuildcfg = getbuildcfg(cfg) ~= nil - - if isplatform and isbuildcfg then - p.push('<%s Solution="%s|%s" Project="%s" />', tag, cfg[1], cfg[2], platform) + + -- Platform to Arch + for _, context in ipairs(sortedContexts) do + p.push('', context.descriptor, context.arch) p.pop() - elseif isplatform then - p.push('<%s Solution="*|%s" Project="%s" />', tag, cfg[2], platform) + end + + if availableConfigCount < solutionConfigCount then + p.push('', closestArch) p.pop() - elseif isbuildcfg then - p.push('<%s Solution="%s|*" Project="%s" />', tag, cfg[1], platform) + end + + -- Build Status + -- If this project has all solution configs (or more, perhaps a per-project platform), set them individually or all to false if we detected it previously + if availableConfigCount >= solutionConfigCount then + -- If they are all excluded, set them all to false + if allExcluded then + p.push('') + p.pop() + else + -- Otherwise set excluded ones to false, as the default is true + for _, context in ipairs(sortedContexts) do + if context.excluded then + p.push('', context.descriptor) + p.pop() + end + end + end + else + -- Set the included ones to true, as we change the default to false (we don't know the rest of the configs) + for _, context in ipairs(sortedContexts) do + if not context.excluded then + p.push('', context.descriptor) + p.pop() + end + end + + p.push('') p.pop() end + + -- Deployment + for _, context in ipairs(sortedContexts) do + if context.allowDeployment then + p.push('', context.descriptor) + p.pop() + end + end + end - end - if platform ~= nil then - output(cfg, platform, "Platform") - end + p.callArray(m.elements.project, prj) + + -- Close the project XML tag + p.pop('') - if buildcfg ~= nil then - output(cfg, buildcfg, "BuildType") end + + end + + if groupName ~= "" then + p.pop('') end end end -function m.projectExclusion(prj) - -- For each configuration + platform in the solution, find the matching project configuration - -- If the project configuration is missing or excluded, add an exclusion entry - local wks = prj.workspace - - for solcfg in p.workspace.eachconfig(wks) do - local prjcfg = project.getconfig(prj, solcfg.buildcfg, solcfg.platform) - if prjcfg == nil or prjcfg.excludefrombuild then - local platform = vstudio.solutionPlatform(solcfg) - p.push('', solcfg.buildcfg, platform or "*") +function m.projectDependencies(prj) + local deps = p.project.getdependencies(prj, 'dependOnly') + if #deps > 0 then + for _, dep in ipairs(deps) do + p.push('', m.buildRelativePath(dep)) p.pop() end end