-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow live debugging tasks with actual sources (#19978)
This change makes it much easier to troubleshoot problems with tasks or develop new features. It makes it possible to connect to a specific task which is being executed by the agent and debug this in Visual Studio Code.
- Loading branch information
Showing
17 changed files
with
298 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
namespace BuildConfigGen.Debugging | ||
{ | ||
internal interface IDebugConfigGenerator | ||
{ | ||
void WriteTypescriptConfig(string taskOutput); | ||
|
||
void AddForTask(string taskConfigPath); | ||
|
||
void WriteLaunchConfigurations(); | ||
} | ||
|
||
sealed internal class NoDebugConfigGenerator : IDebugConfigGenerator | ||
{ | ||
public void AddForTask(string taskConfigPath) | ||
{ | ||
// noop | ||
} | ||
|
||
public void WriteLaunchConfigurations() | ||
{ | ||
// noop | ||
} | ||
|
||
public void WriteTypescriptConfig(string taskOutput) | ||
{ | ||
// noop | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
using System.Text.Json; | ||
using System.Text.Json.Nodes; | ||
|
||
namespace BuildConfigGen.Debugging | ||
{ | ||
internal class VsCodeLaunchConfigGenerator : IDebugConfigGenerator | ||
{ | ||
private string GitRootPath { get; } | ||
|
||
private string AgentPath { get; } | ||
|
||
private string LaunchConfigPath => Path.Combine(GitRootPath, ".vscode", "launch.json"); | ||
|
||
private VsCodeLaunchConfiguration LaunchConfig { get; } | ||
|
||
public VsCodeLaunchConfigGenerator(string gitRootPath, string agentPath) | ||
{ | ||
ArgumentException.ThrowIfNullOrEmpty(agentPath, nameof(agentPath)); | ||
ArgumentException.ThrowIfNullOrEmpty(gitRootPath, nameof(gitRootPath)); | ||
|
||
if (!Directory.Exists(agentPath)) | ||
{ | ||
throw new ArgumentException($"Agent directory used for debugging could not be found at {Path.GetFullPath(agentPath)}!"); | ||
} | ||
|
||
AgentPath = agentPath; | ||
GitRootPath = gitRootPath; | ||
LaunchConfig = VsCodeLaunchConfiguration.ReadFromFileIfPresentOrDefault(LaunchConfigPath); | ||
} | ||
|
||
public void AddForTask(string taskConfigPath) | ||
{ | ||
if (!File.Exists(taskConfigPath)) | ||
{ | ||
throw new ArgumentException($"Task configuration (task.json) does not exist at path {taskConfigPath}!"); | ||
} | ||
|
||
var taskContent = File.ReadAllText(taskConfigPath); | ||
var taskConfig = JsonNode.Parse(taskContent)!; | ||
|
||
JsonNode versionNode = taskConfig["version"]!; | ||
int major = versionNode["Major"]!.GetValue<int>(); | ||
int minor = versionNode["Minor"]!.GetValue<int>(); | ||
int patch = versionNode["Patch"]!.GetValue<int>(); | ||
|
||
var version = new TaskVersion(major, minor, patch); | ||
|
||
LaunchConfig.AddConfigForTask( | ||
taskId: taskConfig["id"]!.GetValue<string>(), | ||
taskName: taskConfig["name"]!.GetValue<string>(), | ||
taskVersion: version.ToString(), | ||
agentPath: AgentPath | ||
); | ||
} | ||
|
||
public void WriteLaunchConfigurations() | ||
{ | ||
var launchConfigString = LaunchConfig.ToJsonString(); | ||
File.WriteAllText(LaunchConfigPath, launchConfigString); | ||
} | ||
|
||
public void WriteTypescriptConfig(string taskOutput) | ||
{ | ||
var tsconfigPath = Path.Combine(taskOutput, "tsconfig.json"); | ||
if (!File.Exists(tsconfigPath)) | ||
{ | ||
return; | ||
} | ||
|
||
var tsConfigContent = File.ReadAllText(tsconfigPath); | ||
var tsConfigObject = JsonNode.Parse(tsConfigContent)?.AsObject(); | ||
|
||
if (tsConfigObject == null) | ||
{ | ||
return; | ||
} | ||
|
||
var compilerOptionsObject = tsConfigObject["compilerOptions"]?.AsObject(); | ||
compilerOptionsObject?.Add("inlineSourceMap", true); | ||
compilerOptionsObject?.Add("inlineSources", true); | ||
|
||
JsonSerializerOptions options = new() { WriteIndented = true }; | ||
var outputTsConfigString = JsonSerializer.Serialize(tsConfigObject, options); | ||
File.WriteAllText(tsconfigPath, outputTsConfigString); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
using System.Text.Json; | ||
using System.Text.Json.Nodes; | ||
using System.Text.RegularExpressions; | ||
|
||
namespace BuildConfigGen | ||
{ | ||
internal partial class VsCodeLaunchConfiguration | ||
{ | ||
private JsonObject LaunchConfiguration { get; } | ||
|
||
private JsonArray ConfigurationsList => _configurationsList.Value; | ||
|
||
private readonly Lazy<JsonArray> _configurationsList; | ||
|
||
public VsCodeLaunchConfiguration(JsonObject launchConfiguration) | ||
{ | ||
ArgumentNullException.ThrowIfNull(launchConfiguration); | ||
LaunchConfiguration = launchConfiguration; | ||
|
||
_configurationsList = new(() => | ||
{ | ||
if (!LaunchConfiguration.TryGetPropertyValue("configurations", out JsonNode? configurationsNode)) | ||
{ | ||
configurationsNode = new JsonArray(); | ||
LaunchConfiguration["configurations"] = configurationsNode; | ||
} | ||
return configurationsNode!.AsArray(); | ||
}); | ||
} | ||
|
||
public static VsCodeLaunchConfiguration ReadFromFileIfPresentOrDefault(string configPath) | ||
{ | ||
ArgumentException.ThrowIfNullOrEmpty(configPath); | ||
|
||
JsonObject launchConfiguration; | ||
if (File.Exists(configPath)) | ||
{ | ||
var rawConfigurationsString = File.ReadAllText(configPath); | ||
var safeConfigurationsString = RemoveJsonComments(rawConfigurationsString); | ||
|
||
launchConfiguration = JsonNode.Parse(safeConfigurationsString)?.AsObject() ?? throw new ArgumentException($"Provided configuration file at {Path.GetFullPath(configPath)} is not a valid JSON file!"); | ||
} else | ||
{ | ||
launchConfiguration = new JsonObject | ||
{ | ||
["version"] = "0.2.0", | ||
["configurations"] = new JsonArray() | ||
}; | ||
} | ||
|
||
return new VsCodeLaunchConfiguration(launchConfiguration); | ||
} | ||
|
||
public void AddConfigForTask( | ||
string taskName, | ||
string taskVersion, | ||
string taskId, | ||
string agentPath) | ||
{ | ||
ArgumentException.ThrowIfNullOrEmpty(taskName); | ||
ArgumentException.ThrowIfNullOrEmpty(taskVersion); | ||
ArgumentException.ThrowIfNullOrEmpty(taskId); | ||
ArgumentException.ThrowIfNullOrEmpty(agentPath); | ||
|
||
var launchConfigName = GetLaunchConfigurationName(taskName, taskVersion); | ||
|
||
var existingLaunchConfig = ConfigurationsList.FirstOrDefault(x => | ||
{ | ||
var name = x?[c_taskName]?.GetValue<string>(); | ||
|
||
return string.Equals(name, launchConfigName, StringComparison.OrdinalIgnoreCase); | ||
}); | ||
|
||
ConfigurationsList.Remove(existingLaunchConfig); | ||
|
||
var launchConfig = new JsonObject | ||
{ | ||
[c_taskName] = launchConfigName, | ||
["type"] = "node", | ||
["request"] = "attach", | ||
["address"] = "localhost", | ||
["port"] = 9229, | ||
["autoAttachChildProcesses"] = true, | ||
["skipFiles"] = new JsonArray("<node_internals>/**"), | ||
["sourceMaps"] = true, | ||
["remoteRoot"] = GetRemoteSourcesPath(taskName, taskVersion, taskId, agentPath) | ||
}; | ||
|
||
ConfigurationsList.Add(launchConfig); | ||
} | ||
|
||
public string ToJsonString() | ||
{ | ||
var options = new JsonSerializerOptions { WriteIndented = true }; | ||
return JsonSerializer.Serialize(LaunchConfiguration, options); | ||
} | ||
|
||
private static string GetLaunchConfigurationName(string task, string version) => | ||
$"Attach to {task} ({version})"; | ||
|
||
private static string GetRemoteSourcesPath(string taskName, string taskVersion, string taskId, string agentPath) => | ||
@$"{agentPath}\_work\_tasks\{taskName}_{taskId.ToLower()}\{taskVersion}"; | ||
|
||
private static string RemoveJsonComments(string jsonString) | ||
{ | ||
jsonString = SingleLineCommentsRegex().Replace(jsonString, string.Empty); | ||
jsonString = MultiLineCommentsRegex().Replace(jsonString, string.Empty); | ||
return jsonString; | ||
} | ||
|
||
[GeneratedRegex(@"//.*(?=\r?\n|$)")] | ||
private static partial Regex SingleLineCommentsRegex(); | ||
|
||
[GeneratedRegex(@"/\*.*?\*/", RegexOptions.Singleline)] | ||
private static partial Regex MultiLineCommentsRegex(); | ||
|
||
private const string c_taskName = "name"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.