Skip to content

Commit 28cea40

Browse files
CopilotArcturusZhangCopilot
authored
[http-client-csharp] Switch scaffolding to generate .slnx instead of .sln (#10234)
Fixes #4065 Fixes #10233 Per latest .NET guidelines, new projects should use the XML-based `.slnx` solution format rather than the legacy `.sln` format. ### Changes - **`NewProjectScaffolding.cs`**: Replace `.sln` generation with `.slnx`. The verbose GUIDs-and-config `.sln` content is replaced with the minimal XML format. Cleanup in `Execute()` now handles both `.sln` (backward compat) and `.slnx`. - **`CommandLineOptions.cs`**: Updated help text to reference `slnx`. - **Test projects**: Regenerated all 72 test project solution files as `.slnx`. Generated `.slnx` output: ```xml <Solution> <Project Path="src\PackageName.csproj" /> </Solution> ``` --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> Co-authored-by: Dapeng Zhang <dapzhang@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6d7d7da commit 28cea40

148 files changed

Lines changed: 362 additions & 3513 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cspell.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ words:
247247
- sfixed
248248
- shiki
249249
- sint
250+
- slnx
250251
- snakeyaml
251252
- srnagar
252253
- ssdlrs

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Primitives/NewProjectScaffolding.cs

Lines changed: 22 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@
99

1010
namespace Microsoft.TypeSpec.Generator.Primitives
1111
{
12-
//TODO Need to define the rest of the extensibility points https://github.com/microsoft/typespec/issues/4065
1312
public class NewProjectScaffolding
1413
{
1514
public async Task<bool> Execute()
1615
{
17-
//clean up old sln and csproj files
16+
//clean up old sln, slnx and csproj files
1817
foreach (var file in Directory.GetFiles(CodeModelGenerator.Instance.Configuration.OutputDirectory, "*.csproj", SearchOption.AllDirectories))
1918
{
2019
File.Delete(file);
@@ -23,14 +22,29 @@ public async Task<bool> Execute()
2322
{
2423
File.Delete(file);
2524
}
25+
foreach (var file in Directory.GetFiles(CodeModelGenerator.Instance.Configuration.OutputDirectory, "*.slnx", SearchOption.TopDirectoryOnly))
26+
{
27+
File.Delete(file);
28+
}
2629

2730
await WriteSolutionFiles();
2831

2932
await WriteProjectFiles();
3033

34+
await WriteAdditionalFiles();
35+
3136
return true;
3237
}
3338

39+
/// <summary>
40+
/// Override this method to write additional files during new project scaffolding.
41+
/// This is called after the solution and project files have been written.
42+
/// </summary>
43+
protected virtual Task WriteAdditionalFiles()
44+
{
45+
return Task.CompletedTask;
46+
}
47+
3448
private async Task WriteProjectFiles()
3549
{
3650
await File.WriteAllBytesAsync(
@@ -46,7 +60,7 @@ private string NormalizeLineEndings(string content)
4660
private async Task WriteSolutionFiles()
4761
{
4862
await File.WriteAllBytesAsync(
49-
Path.Combine(CodeModelGenerator.Instance.Configuration.OutputDirectory, $"{CodeModelGenerator.Instance.Configuration.PackageName}.sln"),
63+
Path.Combine(CodeModelGenerator.Instance.Configuration.OutputDirectory, $"{CodeModelGenerator.Instance.Configuration.PackageName}.slnx"),
5064
Encoding.UTF8.GetBytes(NormalizeLineEndings(GetSolutionFileContent())));
5165
}
5266

@@ -95,59 +109,11 @@ protected virtual string GetSourceProjectFileContent()
95109

96110
protected virtual string GetSolutionFileContent()
97111
{
98-
string slnContent = @"Microsoft Visual Studio Solution File, Format Version 12.00
99-
# Visual Studio Version 17
100-
VisualStudioVersion = 17.0.31903.59
101-
MinimumVisualStudioVersion = 10.0.40219.1
102-
";
103-
slnContent += @"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""{0}"", ""src\{0}.csproj"", ""{{28FF4005-4467-4E36-92E7-DEA27DEB1519}}""
104-
EndProject
105-
";
106-
slnContent += @"Global
107-
GlobalSection(SolutionConfigurationPlatforms) = preSolution
108-
Debug|Any CPU = Debug|Any CPU
109-
Release|Any CPU = Release|Any CPU
110-
EndGlobalSection
111-
GlobalSection(ProjectConfigurationPlatforms) = postSolution
112-
{{B0C276D1-2930-4887-B29A-D1A33E7009A2}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
113-
{{B0C276D1-2930-4887-B29A-D1A33E7009A2}}.Debug|Any CPU.Build.0 = Debug|Any CPU
114-
{{B0C276D1-2930-4887-B29A-D1A33E7009A2}}.Release|Any CPU.ActiveCfg = Release|Any CPU
115-
{{B0C276D1-2930-4887-B29A-D1A33E7009A2}}.Release|Any CPU.Build.0 = Release|Any CPU
116-
{{8E9A77AC-792A-4432-8320-ACFD46730401}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
117-
{{8E9A77AC-792A-4432-8320-ACFD46730401}}.Debug|Any CPU.Build.0 = Debug|Any CPU
118-
{{8E9A77AC-792A-4432-8320-ACFD46730401}}.Release|Any CPU.ActiveCfg = Release|Any CPU
119-
{{8E9A77AC-792A-4432-8320-ACFD46730401}}.Release|Any CPU.Build.0 = Release|Any CPU
120-
";
121-
slnContent += @" {{A4241C1F-A53D-474C-9E4E-075054407E74}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
122-
{{A4241C1F-A53D-474C-9E4E-075054407E74}}.Debug|Any CPU.Build.0 = Debug|Any CPU
123-
{{A4241C1F-A53D-474C-9E4E-075054407E74}}.Release|Any CPU.ActiveCfg = Release|Any CPU
124-
{{A4241C1F-A53D-474C-9E4E-075054407E74}}.Release|Any CPU.Build.0 = Release|Any CPU
125-
{{FA8BD3F1-8616-47B6-974C-7576CDF4717E}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
126-
{{FA8BD3F1-8616-47B6-974C-7576CDF4717E}}.Debug|Any CPU.Build.0 = Debug|Any CPU
127-
{{FA8BD3F1-8616-47B6-974C-7576CDF4717E}}.Release|Any CPU.ActiveCfg = Release|Any CPU
128-
{{FA8BD3F1-8616-47B6-974C-7576CDF4717E}}.Release|Any CPU.Build.0 = Release|Any CPU
129-
{{85677AD3-C214-42FA-AE6E-49B956CAC8DC}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
130-
{{85677AD3-C214-42FA-AE6E-49B956CAC8DC}}.Debug|Any CPU.Build.0 = Debug|Any CPU
131-
{{85677AD3-C214-42FA-AE6E-49B956CAC8DC}}.Release|Any CPU.ActiveCfg = Release|Any CPU
132-
{{85677AD3-C214-42FA-AE6E-49B956CAC8DC}}.Release|Any CPU.Build.0 = Release|Any CPU
133-
{{28FF4005-4467-4E36-92E7-DEA27DEB1519}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
134-
{{28FF4005-4467-4E36-92E7-DEA27DEB1519}}.Debug|Any CPU.Build.0 = Debug|Any CPU
135-
{{28FF4005-4467-4E36-92E7-DEA27DEB1519}}.Release|Any CPU.ActiveCfg = Release|Any CPU
136-
{{28FF4005-4467-4E36-92E7-DEA27DEB1519}}.Release|Any CPU.Build.0 = Release|Any CPU
137-
{{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
138-
{{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}}.Debug|Any CPU.Build.0 = Debug|Any CPU
139-
{{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}}.Release|Any CPU.ActiveCfg = Release|Any CPU
140-
{{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}}.Release|Any CPU.Build.0 = Release|Any CPU
141-
EndGlobalSection
142-
GlobalSection(SolutionProperties) = preSolution
143-
HideSolutionNode = FALSE
144-
EndGlobalSection
145-
GlobalSection(ExtensibilityGlobals) = postSolution
146-
SolutionGuid = {{A97F4B90-2591-4689-B1F8-5F21FE6D6CAE}}
147-
EndGlobalSection
148-
EndGlobal
149-
";
150-
return string.Format(slnContent, CodeModelGenerator.Instance.Configuration.PackageName);
112+
return string.Format(
113+
@"<Solution>
114+
<Project Path=""src\{0}.csproj"" />
115+
</Solution>
116+
", CodeModelGenerator.Instance.Configuration.PackageName);
151117
}
152118
}
153119
}

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/StartUp/CommandLineOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ internal class CommandLineOptions
1818
private const string CmdLineOutputDirectoryOptionHelpText = "The path to the directory containing the input files to the generator including the code model file and the configuration file for the generator.";
1919
private const string CmdLineDebugOptionHelpText = "Attempt to attach the debugger on execute.";
2020
private const string CmdLineGeneratorOptionHelpText = "The name of the generator to execute.";
21-
private const string CmdLineNewProjectOptionHelpText = "Indicates if the generator should create the project files such as csproj, sln, etc.";
21+
private const string CmdLineNewProjectOptionHelpText = "Indicates if the generator should create the project files such as csproj, slnx, etc.";
2222

2323
/// <summary>
2424
/// The command line option to specify the path to the directory containing the input files to the generator.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.IO;
5+
using System.Threading.Tasks;
6+
using Microsoft.TypeSpec.Generator.Primitives;
7+
using NUnit.Framework;
8+
9+
namespace Microsoft.TypeSpec.Generator.Tests.Primitives
10+
{
11+
internal class NewProjectScaffoldingTests
12+
{
13+
private string _outputDir = null!;
14+
15+
[SetUp]
16+
public void SetUp()
17+
{
18+
_outputDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
19+
Directory.CreateDirectory(_outputDir);
20+
Directory.CreateDirectory(Path.Combine(_outputDir, "src"));
21+
22+
MockHelpers.LoadMockGenerator(
23+
outputPath: _outputDir,
24+
configuration: "{\"package-name\": \"TestPackage\"}");
25+
}
26+
27+
[TearDown]
28+
public void TearDown()
29+
{
30+
if (Directory.Exists(_outputDir))
31+
{
32+
Directory.Delete(_outputDir, true);
33+
}
34+
}
35+
36+
[Test]
37+
public async Task Execute_WritesSlnxAndCsprojFiles()
38+
{
39+
var scaffolding = new NewProjectScaffolding();
40+
var result = await scaffolding.Execute();
41+
42+
Assert.IsTrue(result);
43+
Assert.IsTrue(File.Exists(Path.Combine(_outputDir, "TestPackage.slnx")));
44+
Assert.IsTrue(File.Exists(Path.Combine(_outputDir, "src", "TestPackage.csproj")));
45+
}
46+
47+
[Test]
48+
public async Task Execute_CleansUpOldSlnFiles()
49+
{
50+
// Create old .sln file
51+
var oldSlnPath = Path.Combine(_outputDir, "OldProject.sln");
52+
await File.WriteAllTextAsync(oldSlnPath, "old content");
53+
54+
var scaffolding = new NewProjectScaffolding();
55+
await scaffolding.Execute();
56+
57+
Assert.IsFalse(File.Exists(oldSlnPath));
58+
}
59+
60+
[Test]
61+
public async Task Execute_CleansUpOldSlnxFiles()
62+
{
63+
// Create old .slnx file
64+
var oldSlnxPath = Path.Combine(_outputDir, "OldProject.slnx");
65+
await File.WriteAllTextAsync(oldSlnxPath, "old content");
66+
67+
var scaffolding = new NewProjectScaffolding();
68+
await scaffolding.Execute();
69+
70+
Assert.IsFalse(File.Exists(oldSlnxPath));
71+
}
72+
73+
[Test]
74+
public async Task Execute_CallsWriteAdditionalFiles()
75+
{
76+
var scaffolding = new TestScaffolding();
77+
var result = await scaffolding.Execute();
78+
79+
Assert.IsTrue(result);
80+
Assert.IsTrue(scaffolding.WriteAdditionalFilesCalled);
81+
}
82+
83+
[Test]
84+
public async Task WriteAdditionalFiles_CanEmitCustomFiles()
85+
{
86+
var ciYmlPath = Path.Combine(_outputDir, "ci.yml");
87+
var scaffolding = new FileWritingScaffolding(ciYmlPath, "name: CI");
88+
await scaffolding.Execute();
89+
90+
Assert.IsTrue(File.Exists(ciYmlPath));
91+
Assert.AreEqual("name: CI", await File.ReadAllTextAsync(ciYmlPath));
92+
}
93+
94+
private class TestScaffolding : NewProjectScaffolding
95+
{
96+
public bool WriteAdditionalFilesCalled { get; private set; }
97+
98+
protected override Task WriteAdditionalFiles()
99+
{
100+
WriteAdditionalFilesCalled = true;
101+
return Task.CompletedTask;
102+
}
103+
}
104+
105+
private class FileWritingScaffolding : NewProjectScaffolding
106+
{
107+
private readonly string _filePath;
108+
private readonly string _content;
109+
110+
public FileWritingScaffolding(string filePath, string content)
111+
{
112+
_filePath = filePath;
113+
_content = content;
114+
}
115+
116+
protected override async Task WriteAdditionalFiles()
117+
{
118+
await File.WriteAllTextAsync(_filePath, _content);
119+
}
120+
}
121+
}
122+
}

packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/SampleTypeSpec.sln

Lines changed: 0 additions & 48 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<Solution>
2+
<Project Path="src\SampleTypeSpec.csproj" />
3+
</Solution>

packages/http-client-csharp/generator/TestProjects/Spector/http/authentication/api-key/Authentication.ApiKey.sln

Lines changed: 0 additions & 48 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<Solution>
2+
<Project Path="src\Authentication.ApiKey.csproj" />
3+
</Solution>

0 commit comments

Comments
 (0)