From ed51ba719bf7f3a57c065539d6b35b3d4ad58be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= Date: Wed, 15 Apr 2026 21:37:14 +0100 Subject: [PATCH 1/6] Initial version improving the slnx format output --- modules/vstudio/vs2026_solution.lua | 124 +++++++++++++++++++++++----- 1 file changed, 104 insertions(+), 20 deletions(-) diff --git a/modules/vstudio/vs2026_solution.lua b/modules/vstudio/vs2026_solution.lua index d87baefc1a..2a23777f6e 100644 --- a/modules/vstudio/vs2026_solution.lua +++ b/modules/vstudio/vs2026_solution.lua @@ -28,7 +28,6 @@ m.elements.project = function(prj) return { m.projectDependencies, m.projectConfigMap, - m.projectExclusion, } end @@ -38,6 +37,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 +165,13 @@ function m.projects(wks) end table.sort(sortedKeys) + -- Sort the solution configurations + local sortedSolCfgs = {} + for solcfg in p.workspace.eachconfig(wks) do + table.insert(sortedSolCfgs, solcfg); + end + table.sort(sortedSolCfgs, function(a, b) return a.name:lower() < b.name:lower() 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) @@ -156,10 +180,84 @@ function m.projects(wks) p.push('', groupName) end for _, prj in ipairs(projects) do + + local typeID = vstudio.tool(prj) + + local projectPathStrings = {} + + -- Open the XML tag, add common properties + table.insert(projectPathStrings, '<') + table.insert(projectPathStrings, 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) + table.insert(projectPathStrings, ' DefaultStartup="true"') + end + + -- Mark projects with uncommon types + if prj.kind == p.PACKAGING then + table.insert(projectPathStrings, string.format(' Type="%s"', typeID)) + end + + -- Close the XML tag + table.insert(projectPathStrings, '>') + + -- Concatenate all the parts of the project description + p.push(table.concat(projectPathStrings)) + + -- Get a list of contexts, a set of precomputed properties we can reference later + local sortedSolutionContexts = {} + + -- Precompute the project configs and architectures + for _, solcfg in ipairs(sortedSolCfgs) do + + local context = {} + context.solCfg = solcfg + context.prjCfg = project.getconfig(prj, solcfg.buildcfg, solcfg.platform) + context.excluded = context.prjCfg == nil or context.prjCfg.excludefrombuild + + if context.prjCfg == nil then + context.prjCfg = project.findClosestMatch(prj, solcfg.buildcfg, solcfg.platform) + end + + context.arch = vstudio.archFromConfig(context.prjCfg, true) + context.buildType = context.solCfg.buildcfg.." "..context.prjCfg.platform + + table.insert(sortedSolutionContexts, context) + + end + + -- BuildType + for _, context in ipairs(sortedSolutionContexts) do + p.push('', context.solCfg.buildcfg, context.solCfg.platform, context.buildType) + p.pop() + end + + -- Map Platform to Arch + for _, context in ipairs(sortedSolutionContexts) do + p.push('', context.solCfg.buildcfg, context.solCfg.platform, context.arch) + p.pop() + end + + -- Exclude from build if either config doesn't exist or manually specified + for _, context in ipairs(sortedSolutionContexts) do + p.push('', context.solCfg.buildcfg, context.solCfg.platform) + p.pop() + end + + -- Get a list of sorted local configs + local sortedConfigs = {} + for cfg in project.eachconfig(prj) do + table.insert(sortedConfigs, cfg) + end + table.sort(sortedConfigs, function(a, b) return a.name:lower() < b.name:lower() end) + + -- Deployment. Only mark available solutions as deployable + for _, cfg in ipairs(sortedConfigs) do + if sln2026.allowDeployment(prj, cfg) then + p.push('', cfg.buildcfg, cfg.platform) + p.pop() + end end p.callArray(m.elements.project, prj) @@ -184,7 +282,9 @@ end 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 @@ -291,19 +391,3 @@ function m.projectConfigMap(prj) 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 "*") - p.pop() - end - end -end From 20f87cab80d6e4af83c4f28d5cb9a90bf4fa4ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= Date: Wed, 15 Apr 2026 22:01:42 +0100 Subject: [PATCH 2/6] Forgot the excluded bit from the deduplication removal --- modules/vstudio/vs2026_solution.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/vstudio/vs2026_solution.lua b/modules/vstudio/vs2026_solution.lua index 2a23777f6e..0e40d0684a 100644 --- a/modules/vstudio/vs2026_solution.lua +++ b/modules/vstudio/vs2026_solution.lua @@ -241,8 +241,10 @@ function m.projects(wks) -- Exclude from build if either config doesn't exist or manually specified for _, context in ipairs(sortedSolutionContexts) do - p.push('', context.solCfg.buildcfg, context.solCfg.platform) - p.pop() + if context.excludefrombuild then + p.push('', context.solCfg.buildcfg, context.solCfg.platform) + p.pop() + end end -- Get a list of sorted local configs From a4893dd8f45a5354c4fb642e7c14bb9a9a3b04c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= Date: Wed, 15 Apr 2026 22:03:21 +0100 Subject: [PATCH 3/6] Typo --- modules/vstudio/vs2026_solution.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/vstudio/vs2026_solution.lua b/modules/vstudio/vs2026_solution.lua index 0e40d0684a..98a293b497 100644 --- a/modules/vstudio/vs2026_solution.lua +++ b/modules/vstudio/vs2026_solution.lua @@ -241,7 +241,7 @@ function m.projects(wks) -- Exclude from build if either config doesn't exist or manually specified for _, context in ipairs(sortedSolutionContexts) do - if context.excludefrombuild then + if context.excluded then p.push('', context.solCfg.buildcfg, context.solCfg.platform) p.pop() end From f54ad146cbc481f4d76718c5c9093a9bf140a008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= Date: Fri, 1 May 2026 00:04:53 +0100 Subject: [PATCH 4/6] Greatly improve the slnx solution generator Update unit tests so they all pass correctly I have taken a different approach altogether to this, as the new format allows for default properties to be set. Therefore what we do is detect what is relevant for a given project from what's available in the solution, and everything else is set as a default, as all projects need to specify what happens to configurations in the solution but outside their purview. By not adding configs the project doesn't care about, we make the slnx more understandable and concise --- .../vstudio/tests/sln2026/test_projects.lua | 84 +++-- modules/vstudio/vs2026_solution.lua | 318 ++++++++---------- 2 files changed, 201 insertions(+), 201 deletions(-) diff --git a/modules/vstudio/tests/sln2026/test_projects.lua b/modules/vstudio/tests/sln2026/test_projects.lua index 45e845c60e..93a863345d 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 [[ + + + + + + + + ]] @@ -84,17 +95,20 @@ function suite.project_in_groups() local prj1 = project "MyProject1" uuid "AE61726D-187C-E440-BD07-2556188A6565" - sln2026.projects(wks) + prepare(wks) test.capture [[ + + + + ]] end - function suite.project_with_configmap() local wks = workspace "MyWorkspace" configurations { "Debug", "Release", "SpecialRelease" } @@ -107,13 +121,16 @@ function suite.project_with_configmap() [ "Debug" ] = "Debug", } - sln2026.projects(wks) + prepare(wks) test.capture [[ - - - + + + + + + ]] end @@ -129,11 +146,16 @@ function suite.project_with_single_configmap() [ "SpecialRelease" ] = "Release", } - sln2026.projects(wks) + prepare(wks) test.capture [[ - + + + + + + ]] end @@ -150,11 +172,18 @@ function suite.project_with_platform_configmap() [ "MyPlatform" ] = "x64", } - sln2026.projects(wks) + prepare(wks) test.capture [[ - + + + + + + + + ]] end @@ -171,12 +200,18 @@ function suite.project_with_platform_and_config_configmap() [ { "Debug", "MyPlatform" } ] = { "Debug", "x64" }, } - sln2026.projects(wks) + prepare(wks) test.capture [[ - + + + + + + + ]] end @@ -191,10 +226,14 @@ function suite.project_excluded_from_build() filter "configurations:Release" excludefrombuild "On" - sln2026.projects(wks) + prepare(wks) test.capture [[ + + + + ]] @@ -209,11 +248,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 98a293b497..78fbd88e1e 100644 --- a/modules/vstudio/vs2026_solution.lua +++ b/modules/vstudio/vs2026_solution.lua @@ -26,8 +26,7 @@ end m.elements.project = function(prj) return { - m.projectDependencies, - m.projectConfigMap, + m.projectDependencies } end @@ -166,11 +165,12 @@ function m.projects(wks) table.sort(sortedKeys) -- Sort the solution configurations - local sortedSolCfgs = {} - for solcfg in p.workspace.eachconfig(wks) do - table.insert(sortedSolCfgs, solcfg); + local sortedSolutionConfigs = {} + local solutionConfigCount = 0 + for solutionConfig in p.workspace.eachconfig(wks) do + table.insert(sortedSolutionConfigs, solutionConfig); + solutionConfigCount = solutionConfigCount + 1 end - table.sort(sortedSolCfgs, function(a, b) return a.name:lower() < b.name:lower() end) for _, groupName in ipairs(sortedKeys) do local projects = groups[groupName] @@ -179,217 +179,173 @@ function m.projects(wks) if groupName ~= "" then p.push('', groupName) end - for _, prj in ipairs(projects) do - local typeID = vstudio.tool(prj) + local tr = p.workspace.grouptree(wks) + tree.traverse(tr, { + onleaf = function(n) + local prj = n.project - local projectPathStrings = {} + local typeID = vstudio.tool(prj) - -- Open the XML tag, add common properties - table.insert(projectPathStrings, '<') - table.insert(projectPathStrings, string.format('Project Path="%s" Id="%s"', m.buildRelativePath(prj), prj.uuid)) + local projectPath = '' - -- Mark startup project - if startupProject and startupProject.name == prj.name then - table.insert(projectPathStrings, ' DefaultStartup="true"') - end + -- Open the XML tag, add common properties + projectPath = projectPath .. '<' + projectPath = projectPath .. string.format('Project Path="%s" Id="%s"', m.buildRelativePath(prj), prj.uuid) - -- Mark projects with uncommon types - if prj.kind == p.PACKAGING then - table.insert(projectPathStrings, string.format(' Type="%s"', typeID)) - end + -- Mark startup project + if startupProject and startupProject.name == prj.name then + projectPath = projectPath .. ' DefaultStartup="true"' + end - -- Close the XML tag - table.insert(projectPathStrings, '>') + -- Mark projects with uncommon types + if prj.kind == p.PACKAGING then + projectPath = projectPath .. string.format(' Type="%s"', typeID) + end - -- Concatenate all the parts of the project description - p.push(table.concat(projectPathStrings)) + - -- Get a list of contexts, a set of precomputed properties we can reference later - local sortedSolutionContexts = {} + -- SharedItems projects don't have any configuration platform entries + if prj.kind == p.SHAREDITEMS then - -- Precompute the project configs and architectures - for _, solcfg in ipairs(sortedSolCfgs) do + -- Directly close the XML tag + projectPath = projectPath .. '/>' + p.push(projectPath) + p.pop() - local context = {} - context.solCfg = solcfg - context.prjCfg = project.getconfig(prj, solcfg.buildcfg, solcfg.platform) - context.excluded = context.prjCfg == nil or context.prjCfg.excludefrombuild + else - if context.prjCfg == nil then - context.prjCfg = project.findClosestMatch(prj, solcfg.buildcfg, solcfg.platform) - end + -- Close the opening XML tag + projectPath = projectPath .. '>' + p.push(projectPath) - context.arch = vstudio.archFromConfig(context.prjCfg, true) - context.buildType = context.solCfg.buildcfg.." "..context.prjCfg.platform + -- 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 = {} - table.insert(sortedSolutionContexts, context) - - end + local projectConfig = project.getconfig(prj, solutionConfig.buildcfg, solutionConfig.platform) - -- BuildType - for _, context in ipairs(sortedSolutionContexts) do - p.push('', context.solCfg.buildcfg, context.solCfg.platform, context.buildType) - p.pop() - end + if projectConfig then - -- Map Platform to Arch - for _, context in ipairs(sortedSolutionContexts) do - p.push('', context.solCfg.buildcfg, context.solCfg.platform, context.arch) - p.pop() - end + context.solutionConfig = solutionConfig + context.solutionPlatform = vstudio.solutionPlatform(solutionConfig) + context.allowDeployment = sln2026.allowDeployment(prj, solutionConfig) + context.descriptor = string.format("%s|%s", solutionConfig.buildcfg, context.solutionPlatform) - -- Exclude from build if either config doesn't exist or manually specified - for _, context in ipairs(sortedSolutionContexts) do - if context.excluded then - p.push('', context.solCfg.buildcfg, context.solCfg.platform) - p.pop() - end - end + context.projectPlatform = vstudio.projectPlatform(projectConfig) + context.arch = vstudio.archFromConfig(projectConfig, true) + context.excluded = projectConfig.excludefrombuild - -- Get a list of sorted local configs - local sortedConfigs = {} - for cfg in project.eachconfig(prj) do - table.insert(sortedConfigs, cfg) - end - table.sort(sortedConfigs, function(a, b) return a.name:lower() < b.name:lower() end) + if projectConfig.excludefrombuild then + excludedConfigCount = excludedConfigCount + 1 + end - -- Deployment. Only mark available solutions as deployable - for _, cfg in ipairs(sortedConfigs) do - if sln2026.allowDeployment(prj, cfg) then - p.push('', cfg.buildcfg, cfg.platform) - p.pop() - end - end + table.insert(sortedContexts, context) - p.callArray(m.elements.project, prj) - p.pop('') - end - if groupName ~= "" then - p.pop('') - end - end -end + availableConfigCount = availableConfigCount + 1 + end + end -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 + -- Are all available configs for this project excluded + local allExcluded = excludedConfigCount == availableConfigCount + -- Output properties if there are any configs + if next(sortedContexts) ~= nil then -function m.projectConfigMap(prj) + local closestCfg = project.findClosestMatch(prj, sortedContexts[1].solutionConfig.buildcfg, sortedContexts[1].solutionConfig.platform) + local closestArch = vstudio.archFromConfig(closestCfg, true) + local closestProjectPlatform = vstudio.projectPlatform(closestCfg) - local cfgmap = prj.configmap or {} + -- BuildType + for _, context in ipairs(sortedContexts) do + p.push('', context.descriptor, context.projectPlatform) + p.pop() + end - 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 + -- 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 - 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 + -- Platform to Arch + for _, context in ipairs(sortedContexts) do + p.push('', context.descriptor, context.arch) + p.pop() end - end - end - return nil - end - 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 + if availableConfigCount < solutionConfigCount then + p.push('', closestArch) + p.pop() end - end - end - return nil - end - local platform = getplatform(target) - local buildcfg = getbuildcfg(target) - - 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) - p.pop() - elseif isbuildcfg then - p.push('<%s Solution="%s|*" Project="%s" />', tag, cfg, platform) - p.pop() - end - else - local isplatform = getplatform(cfg) ~= nil - local isbuildcfg = getbuildcfg(cfg) ~= nil + -- 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 - if isplatform and isbuildcfg then - p.push('<%s Solution="%s|%s" Project="%s" />', tag, cfg[1], cfg[2], platform) - p.pop() - elseif isplatform then - p.push('<%s Solution="*|%s" Project="%s" />', tag, cfg[2], platform) - p.pop() - elseif isbuildcfg then - p.push('<%s Solution="%s|*" Project="%s" />', tag, cfg[1], platform) - p.pop() + 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 + + p.callArray(m.elements.project, prj) + + -- Close the project XML tag + p.pop('') + end - end - if platform ~= nil then - output(cfg, platform, "Platform") end + }) - if buildcfg ~= nil then - output(cfg, buildcfg, "BuildType") - end + if groupName ~= "" then + p.pop('') + end + end +end + + +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 From 2088b62abcc29f29df4332ad3b5e90be534ff7cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= Date: Fri, 1 May 2026 19:18:53 +0100 Subject: [PATCH 5/6] With the issue dealing with groups --- modules/vstudio/vs2026_solution.lua | 210 ++++++++++++++-------------- 1 file changed, 102 insertions(+), 108 deletions(-) diff --git a/modules/vstudio/vs2026_solution.lua b/modules/vstudio/vs2026_solution.lua index 78fbd88e1e..6d27cf7002 100644 --- a/modules/vstudio/vs2026_solution.lua +++ b/modules/vstudio/vs2026_solution.lua @@ -180,158 +180,152 @@ function m.projects(wks) p.push('', groupName) end - local tr = p.workspace.grouptree(wks) - tree.traverse(tr, { - onleaf = function(n) - local prj = n.project + for _, prj in ipairs(projects) do - local typeID = vstudio.tool(prj) + local typeID = vstudio.tool(prj) - local projectPath = '' + 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) + -- 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 - projectPath = projectPath .. ' DefaultStartup="true"' - end - - -- Mark projects with uncommon types - if prj.kind == p.PACKAGING then - projectPath = projectPath .. string.format(' Type="%s"', typeID) - end + -- Mark startup project + if startupProject and startupProject.name == prj.name then + projectPath = projectPath .. ' DefaultStartup="true"' + 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 + -- SharedItems projects don't have any configuration platform entries + if prj.kind == p.SHAREDITEMS then - -- Directly close the XML tag - projectPath = projectPath .. '/>' - p.push(projectPath) - p.pop() + -- Directly close the XML tag + projectPath = projectPath .. '/>' + p.push(projectPath) + p.pop() - else + else - -- Close the opening XML tag - projectPath = projectPath .. '>' - p.push(projectPath) + -- Close the opening XML tag + projectPath = projectPath .. '>' + p.push(projectPath) - -- 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 = {} + -- 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 projectConfig = project.getconfig(prj, solutionConfig.buildcfg, solutionConfig.platform) + local projectConfig = project.getconfig(prj, solutionConfig.buildcfg, solutionConfig.platform) - if projectConfig then + 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.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 + context.projectPlatform = vstudio.projectPlatform(projectConfig) + context.arch = vstudio.archFromConfig(projectConfig, true) + context.excluded = projectConfig.excludefrombuild - if projectConfig.excludefrombuild then - excludedConfigCount = excludedConfigCount + 1 - end + if projectConfig.excludefrombuild then + excludedConfigCount = excludedConfigCount + 1 + end - table.insert(sortedContexts, context) + table.insert(sortedContexts, context) - availableConfigCount = availableConfigCount + 1 + availableConfigCount = availableConfigCount + 1 - end end + end - -- Are all available configs for this project excluded - local allExcluded = excludedConfigCount == availableConfigCount + -- Are all available configs for this project excluded + local allExcluded = excludedConfigCount == availableConfigCount - -- Output properties if there are any configs - if next(sortedContexts) ~= nil then + -- 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) + 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() - end + -- BuildType + for _, context in ipairs(sortedContexts) do + p.push('', context.descriptor, context.projectPlatform) + p.pop() + 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 + -- 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 - -- Platform to Arch - for _, context in ipairs(sortedContexts) do - p.push('', context.descriptor, context.arch) - p.pop() - end + -- Platform to Arch + for _, context in ipairs(sortedContexts) do + p.push('', context.descriptor, context.arch) + p.pop() + end - if availableConfigCount < solutionConfigCount then - p.push('', closestArch) - p.pop() - end + if availableConfigCount < solutionConfigCount then + p.push('', closestArch) + p.pop() + 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 + -- 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 - -- Set the included ones to true, as we change the default to false (we don't know the rest of the configs) + -- Otherwise set excluded ones to false, as the default is true for _, context in ipairs(sortedContexts) do - if not context.excluded then - p.push('', context.descriptor) + if context.excluded then + p.push('', context.descriptor) p.pop() end end - - p.push('') - p.pop() end - - -- Deployment + 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 context.allowDeployment then - p.push('', context.descriptor) + 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 - p.callArray(m.elements.project, prj) + end - -- Close the project XML tag - p.pop('') + p.callArray(m.elements.project, prj) - end + -- Close the project XML tag + p.pop('') end - }) + + end if groupName ~= "" then p.pop('') From 6555e5d53a50f6649f022e9bf2842904574e5b4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= Date: Fri, 1 May 2026 19:35:01 +0100 Subject: [PATCH 6/6] Improve unit test to avoid group and project mismatches --- modules/vstudio/tests/sln2026/test_projects.lua | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/vstudio/tests/sln2026/test_projects.lua b/modules/vstudio/tests/sln2026/test_projects.lua index 93a863345d..6c5e321cf9 100644 --- a/modules/vstudio/tests/sln2026/test_projects.lua +++ b/modules/vstudio/tests/sln2026/test_projects.lua @@ -90,16 +90,24 @@ 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" + local grp1 = group "Group1" + local prj2 = project "MyProject2" + uuid "BE61726D-187C-E440-BD07-2556188A6565" + prepare(wks) test.capture [[ + + + + + + - +