Skip to content

Commit bf4223b

Browse files
authored
Add file-based program API for use by IDE (#48749)
1 parent 1b8a94f commit bf4223b

File tree

9 files changed

+562
-54
lines changed

9 files changed

+562
-54
lines changed

src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public override int Execute()
3030

3131
// Find directives (this can fail, so do this before creating the target directory).
3232
var sourceFile = VirtualProjectBuildingCommand.LoadSourceFile(file);
33-
var directives = VirtualProjectBuildingCommand.FindDirectivesForConversion(sourceFile, force: _force);
33+
var directives = VirtualProjectBuildingCommand.FindDirectives(sourceFile, reportAllErrors: !_force, errors: null);
3434

3535
Directory.CreateDirectory(targetDirectory);
3636

@@ -50,7 +50,7 @@ public override int Execute()
5050
string projectFile = Path.Join(targetDirectory, Path.GetFileNameWithoutExtension(file) + ".csproj");
5151
using var stream = File.Open(projectFile, FileMode.Create, FileAccess.Write);
5252
using var writer = new StreamWriter(stream, Encoding.UTF8);
53-
VirtualProjectBuildingCommand.WriteProjectFile(writer, directives);
53+
VirtualProjectBuildingCommand.WriteProjectFile(writer, directives, isVirtualProject: false);
5454

5555
return 0;
5656
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Immutable;
5+
using System.CommandLine;
6+
using System.Text.Json;
7+
using System.Text.Json.Serialization;
8+
9+
namespace Microsoft.DotNet.Cli.Commands.Run.Api;
10+
11+
/// <summary>
12+
/// Takes JSON from stdin lines, produces JSON on stdout lines, doesn't perform any changes.
13+
/// Can be used by IDEs to see the project file behind a file-based program.
14+
/// </summary>
15+
internal sealed class RunApiCommand(ParseResult parseResult) : CommandBase(parseResult)
16+
{
17+
public override int Execute()
18+
{
19+
for (string? line; (line = Console.ReadLine()) != null;)
20+
{
21+
if (string.IsNullOrWhiteSpace(line))
22+
{
23+
continue;
24+
}
25+
26+
try
27+
{
28+
RunApiInput input = JsonSerializer.Deserialize(line, RunFileApiJsonSerializerContext.Default.RunApiInput)!;
29+
RunApiOutput output = input.Execute();
30+
Respond(output);
31+
}
32+
catch (Exception ex)
33+
{
34+
Respond(new RunApiOutput.Error { Message = ex.Message, Details = ex.ToString() });
35+
}
36+
}
37+
38+
return 0;
39+
40+
static void Respond(RunApiOutput message)
41+
{
42+
string json = JsonSerializer.Serialize(message, RunFileApiJsonSerializerContext.Default.RunApiOutput);
43+
Console.WriteLine(json);
44+
}
45+
}
46+
}
47+
48+
[JsonDerivedType(typeof(GetProject), nameof(GetProject))]
49+
internal abstract class RunApiInput
50+
{
51+
private RunApiInput() { }
52+
53+
public abstract RunApiOutput Execute();
54+
55+
public sealed class GetProject : RunApiInput
56+
{
57+
public string? ArtifactsPath { get; init; }
58+
public required string EntryPointFileFullPath { get; init; }
59+
60+
public override RunApiOutput Execute()
61+
{
62+
var sourceFile = VirtualProjectBuildingCommand.LoadSourceFile(EntryPointFileFullPath);
63+
var errors = ImmutableArray.CreateBuilder<SimpleDiagnostic>();
64+
var directives = VirtualProjectBuildingCommand.FindDirectives(sourceFile, reportAllErrors: true, errors);
65+
string artifactsPath = ArtifactsPath ?? VirtualProjectBuildingCommand.GetArtifactsPath(EntryPointFileFullPath);
66+
67+
var csprojWriter = new StringWriter();
68+
VirtualProjectBuildingCommand.WriteProjectFile(csprojWriter, directives, isVirtualProject: true, targetFilePath: EntryPointFileFullPath, artifactsPath: artifactsPath);
69+
70+
return new RunApiOutput.Project
71+
{
72+
Content = csprojWriter.ToString(),
73+
Diagnostics = errors.ToImmutableArray(),
74+
};
75+
}
76+
}
77+
}
78+
79+
[JsonDerivedType(typeof(Error), nameof(Error))]
80+
[JsonDerivedType(typeof(Project), nameof(Project))]
81+
internal abstract class RunApiOutput
82+
{
83+
private RunApiOutput() { }
84+
85+
/// <summary>
86+
/// When the API shape or behavior changes, this should be incremented so the callers (IDEs) can act accordingly
87+
/// (e.g., show an error message when an incompatible SDK version is being used).
88+
/// </summary>
89+
[JsonPropertyOrder(-1)]
90+
public int Version { get; } = 1;
91+
92+
public sealed class Error : RunApiOutput
93+
{
94+
public required string Message { get; init; }
95+
public required string Details { get; init; }
96+
}
97+
98+
public sealed class Project : RunApiOutput
99+
{
100+
public required string Content { get; init; }
101+
public required ImmutableArray<SimpleDiagnostic> Diagnostics { get; init; }
102+
}
103+
}
104+
105+
[JsonSerializable(typeof(RunApiInput))]
106+
[JsonSerializable(typeof(RunApiOutput))]
107+
internal partial class RunFileApiJsonSerializerContext : JsonSerializerContext;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.CommandLine;
5+
6+
namespace Microsoft.DotNet.Cli.Commands.Run.Api;
7+
8+
internal sealed class RunApiCommandParser
9+
{
10+
public static Command GetCommand()
11+
{
12+
Command command = new("run-api")
13+
{
14+
Hidden = true,
15+
};
16+
17+
command.SetAction((parseResult) => new RunApiCommand(parseResult).Execute());
18+
return command;
19+
}
20+
}

0 commit comments

Comments
 (0)