Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for VS Build Tools and VS Test Agent #46

Merged
merged 11 commits into from
Nov 4, 2020
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Usage: vs install [options]
|-|-|
| `pre\|preview` | install preview version |
| `int\|internal` | install internal (aka 'dogfood') version |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional` or `c\|com\|community` |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional`, `c\|com\|community` `b\|build\|buildtools` or `t\|test\|testagent` |
| `filter:` | An expression to filter VS instances. E.g. `x => x.InstanceId = '123'` |
| `add:` | A workload ID |

Expand Down Expand Up @@ -73,7 +73,7 @@ Usage: vs run [options]
|-|-|
| `pre\|preview` | run preview version |
| `int\|internal` | run internal (aka 'dogfood') version |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional` or `c\|com\|community` |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional`, `c\|com\|community` `b\|build\|buildtools` or `t\|test\|testagent` |
| `filter:` | An expression to filter VS instances. E.g. `x => x.InstanceId = '123'` |
| `exp\|experimental` | run experimental instance instead of regular. |
| `id:` | Run a specific instance by its ID |
Expand Down Expand Up @@ -119,7 +119,7 @@ Usage: vs where [options]
|-|-|
| `pre\|preview` | show preview version |
| `int\|internal` | show internal (aka 'dogfood') version |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional` or `c\|com\|community` |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional`, `c\|com\|community` `b\|build\|buildtools` or `t\|test\|testagent` |
| `filter:` | An expression to filter VS instances. E.g. `x => x.InstanceId = '123'` |
| `all` | show all instances. |
| `prop\|property:` | The name of a property to return |
Expand Down Expand Up @@ -187,7 +187,7 @@ Usage: vs update [options]
|-|-|
| `pre\|preview` | Update preview version |
| `int\|internal` | Update internal (aka 'dogfood') version |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional` or `c\|com\|community` |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional`, `c\|com\|community` `b\|build\|buildtools` or `t\|test\|testagent` |
| `filter:` | An expression to filter VS instances. E.g. `x => x.InstanceId = '123'` |
'123'` |

Expand All @@ -209,7 +209,7 @@ Usage: vs modify [options]
|-|-|
| `pre\|preview` | modify preview version |
| `int\|internal` | modify internal (aka 'dogfood') version |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional` or `c\|com\|community` |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional`, `c\|com\|community` `b\|build\|buildtools` or `t\|test\|testagent` |
| `filter:` | An expression to filter VS instances. E.g. `x => x.InstanceId = '123'` |
| `add:` | A workload ID |
| `remove:` | A workload ID |
Expand All @@ -232,7 +232,7 @@ Usage: vs config [options]
|-|-|
| `pre\|preview` | open preview version |
| `int\|internal` | open internal (aka 'dogfood') version |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional` or `c\|com\|community` |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional`, `c\|com\|community` `b\|build\|buildtools` or `t\|test\|testagent` |
| `filter:` | An expression to filter VS instances. E.g. `x => x.InstanceId = '123'` |
| `exp\|experimental` | open experimental instance instead of regular. |

Expand All @@ -254,7 +254,7 @@ Usage: vs log [options]
|-|-|
| `pre\|preview` | open preview version |
| `int\|internal` | open internal (aka 'dogfood') version |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional` or `c\|com\|community` |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional`, `c\|com\|community` `b\|build\|buildtools` or `t\|test\|testagent` |
| `filter:` | An expression to filter VS instances. E.g. `x => x.InstanceId = '123'` |
| `exp\|experimental` | open experimental instance instead of regular. |

Expand All @@ -276,7 +276,7 @@ Usage: vs kill [options]
|-|-|
| `pre\|preview` | kill preview version |
| `int\|internal` | kill internal (aka 'dogfood') version |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional` or `c\|com\|community` |
| `sku:` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional`, `c\|com\|community` `b\|build\|buildtools` or `t\|test\|testagent` |
| `filter:` | An expression to filter VS instances. E.g. `x => x.InstanceId = '123'` |
| `exp\|experimental` | kill experimental instance instead of regular. |
| `all` | kill all instances. |
Expand Down
4 changes: 3 additions & 1 deletion VisualStudio.Tests/VisualStudioInstanceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ static class VisualStudioInstanceExtensions
{
{ Sku.Enterprise, "Microsoft.VisualStudio.Product.Enterprise" },
{ Sku.Professional, "Microsoft.VisualStudio.Product.Professional" },
{ Sku.Community, "Microsoft.VisualStudio.Product.Community" }
{ Sku.Community, "Microsoft.VisualStudio.Product.Community" },
{ Sku.BuildTools, "Microsoft.VisualStudio.Product.BuildTools" },
{ Sku.TestAgent, "Microsoft.VisualStudio.Product.TestAgent" }
};

static readonly Dictionary<Channel, string> productIdByChannel = new Dictionary<Channel, string>
Expand Down
21 changes: 21 additions & 0 deletions VisualStudio.Tests/VisualStudioOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,25 @@ public void when_parsing_channel_argument_then_channel_is_set(string argument, C
[InlineData("--sku=com", Sku.Community)]
[InlineData("--sku=community", Sku.Community)]
[InlineData("--sku=Community", Sku.Community)]
[InlineData("b", Sku.BuildTools)]
[InlineData("build", Sku.BuildTools)]
[InlineData("buildtools", Sku.BuildTools)]
[InlineData("Build", Sku.BuildTools)]
[InlineData("BuildTools", Sku.BuildTools)]
[InlineData("--sku=b", Sku.BuildTools)]
[InlineData("--sku=build", Sku.BuildTools)]
[InlineData("--sku=buildtools", Sku.BuildTools)]
[InlineData("--sku=BuildTools", Sku.BuildTools)]
[InlineData("t", Sku.TestAgent)]
[InlineData("test", Sku.TestAgent)]
[InlineData("testagent", Sku.TestAgent)]
[InlineData("Test", Sku.TestAgent)]
[InlineData("TestAgent", Sku.TestAgent)]
[InlineData("--sku=t", Sku.TestAgent)]
[InlineData("--sku=test", Sku.TestAgent)]
[InlineData("--sku=testagent", Sku.TestAgent)]
[InlineData("--sku=TestAgent", Sku.TestAgent)]

public void when_parsing_sku_argument_then_sku_is_set(string argument, Sku? expectedValue)
{
var options = VisualStudioOptions.Empty().WithSku();
Expand Down Expand Up @@ -137,6 +156,8 @@ public void when_parsing_all_argument_then_all_is_set(string argument, bool expe
(new [] { "ent", "main" }, x => x.Sku == Sku.Enterprise && x.Channel == Channel.Main),
(new [] { "main", "x => x.InstanceId == '123'" }, x => x.Channel == Channel.Main && x.Expression == "x => x.InstanceId == \"123\""),
(new [] { "pro" , "release", "--nick=foo" }, x => x.Sku == Sku.Professional && x.Channel == Channel.Release && x.Nickname == "foo"),
(new [] { "build", "release" }, x => x.Sku == Sku.BuildTools && x.Channel == Channel.Release),
(new [] { "test", "release" }, x => x.Sku == Sku.TestAgent && x.Channel == Channel.Release)
};

// Hack to use typed func and avoid to make VisualStudioOptions type public
Expand Down
2 changes: 2 additions & 0 deletions VisualStudio.Tests/VisualStudioPredicateBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public async Task when_evaluating_sku_then_predicate_matches_configured_sku()
Assert.True(predicate(new vswhere.VisualStudioInstance().WithSku(Sku.Enterprise)));
Assert.False(predicate(new vswhere.VisualStudioInstance().WithSku(Sku.Professional)));
Assert.False(predicate(new vswhere.VisualStudioInstance().WithSku(Sku.Community)));
Assert.False(predicate(new vswhere.VisualStudioInstance().WithSku(Sku.BuildTools)));
Assert.False(predicate(new vswhere.VisualStudioInstance().WithSku(Sku.TestAgent)));
}

[Fact]
Expand Down
4 changes: 3 additions & 1 deletion VisualStudio/Commands/ClientCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ public ClientCommand(ClientCommandDescriptor descriptor, WhereService whereServi
public override async Task ExecuteAsync(TextWriter output) =>
await ExecuteAsync(new Chooser().Choose(await whereService.GetAllInstancesAsync(Descriptor.Options), output), output);

public async Task ExecuteAsync(DevEnv devenv, TextWriter output)
public Task ExecuteAsync(DevEnv devenv, TextWriter output)
{
if (string.IsNullOrEmpty(Descriptor.WorkspaceId))
StartServerAndClient(devenv, output);
else
StartClient(devenv, Descriptor.WorkspaceId, output);

return Task.CompletedTask;
}

void StartClient(DevEnv devenv, string workspaceId, TextWriter output)
Expand Down
78 changes: 37 additions & 41 deletions VisualStudio/InstallerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,9 @@ public Task ModifyAsync(Channel? channel, Sku? sku, IEnumerable<string> args, Te
async Task RunAsync(string command, Channel? channel, Sku? sku, IEnumerable<string> args, TextWriter output)
{
var uri = new StringBuilder("https://aka.ms/vs/16/");
if (channel == Channel.Preview)
uri = uri.Append("pre/");
else if (channel == Channel.IntPreview)
uri = uri.Append("intpreview/");
else if (channel == Channel.Main)
uri = uri.Append("int.main/");
else
uri = uri.Append("release/");

uri = uri.Append("vs_");
switch (sku)
{
case Sku.Professional:
uri = uri.Append("professional");
break;
case Sku.Enterprise:
uri = uri.Append("enterprise");
break;
case Sku.Community:
default:
uri = uri.Append("community");
break;
}
uri = uri.Append(MapChannel(channel));
uri = uri.Append("/vs_");
uri = uri.Append(MapSku(sku));
uri = uri.Append(".exe");

var bootstrapper = await DownloadAsync(uri.ToString(), output);
Expand All @@ -72,26 +52,42 @@ async Task RunAsync(string command, Channel? channel, Sku? sku, IEnumerable<stri
process.WaitForExit();
}

string MapChannel(Channel? channel)
=> channel switch
{
Channel.Preview => "pre",
Channel.IntPreview => "intpreview",
Channel.Main => "int.main",
_ => "release"
};

string MapSku(Sku? sku)
=> sku switch
{
Sku.Professional => "professional",
Sku.Enterprise => "enterprise",
Sku.BuildTools => "buildtools",
Sku.TestAgent => "testagent",
_ => "community"
};

async Task<string> DownloadAsync(string bootstrapperUrl, TextWriter output)
{
using (var client = new HttpClient())
{
output.WriteLine($"Downloading {bootstrapperUrl}...");
var request = new HttpRequestMessage(HttpMethod.Get, bootstrapperUrl);
var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
var filePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(), new Uri(bootstrapperUrl).Segments.Last());
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
using (var httpStream = await response.Content.ReadAsStreamAsync())
{
using (var fileStream = File.Create(filePath))
{
await httpStream.CopyToAsync(fileStream, 8 * 1024);
}
}

return filePath;
}
using var client = new HttpClient();
output.WriteLine($"Downloading {bootstrapperUrl}...");
using var request = new HttpRequestMessage(HttpMethod.Get, bootstrapperUrl);
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

response.EnsureSuccessStatusCode();

var filePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(), new Uri(bootstrapperUrl).Segments.Last());
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
using var httpStream = await response.Content.ReadAsStreamAsync();

using var fileStream = File.Create(filePath);
await httpStream.CopyToAsync(fileStream, 8 * 1024);

return filePath;
}
}
}
14 changes: 12 additions & 2 deletions VisualStudio/Options/SkuOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ namespace VisualStudio
{
class SkuOption : OptionSet<Sku?>
{
readonly static string[] shortcuts = new[] { "e", "ent", "enterprise", "p", "pro", "professional", "c", "com", "community" };
readonly static string[] shortcuts = new[]
{
"e", "ent", "enterprise",
"p", "pro", "professional",
"c", "com", "community",
"b", "build", "buildtools",
"t", "test", "testagent" };

public SkuOption(Sku? defaultValue = default) : base(defaultValue)
{
Value = defaultValue;

Add("sku:", "Edition, one of [e|ent|enterprise], [p|pro|professional] or [c|com|community]", s => Value = ParseSku(s));
Add("sku:", "Edition, one of [e|ent|enterprise], [p|pro|professional], [c|com|community], [b|bld|buildtools] or [t|tsa|testagent]", s => Value = ParseSku(s));
}

protected override bool Parse(string argument, OptionContext c)
Expand All @@ -31,6 +37,10 @@ Sku ParseSku(string sku)
return VisualStudio.Sku.Professional;
else if (sku.StartsWith("c", StringComparison.OrdinalIgnoreCase))
return VisualStudio.Sku.Community;
else if (sku.StartsWith("b", StringComparison.OrdinalIgnoreCase))
return VisualStudio.Sku.BuildTools;
else if (sku.StartsWith("t", StringComparison.OrdinalIgnoreCase))
return VisualStudio.Sku.TestAgent;
else
throw new OptionException($"Invalid SKU {sku}. Must be one of {string.Join(", ", Enum.GetNames(typeof(Sku)).Select(x => x.ToLowerInvariant()))}.", "sku");
}
Expand Down
38 changes: 18 additions & 20 deletions VisualStudio/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ namespace VisualStudio
{
class Program
{
CommandFactory commandFactory;
TextWriter output;
string[] args;
readonly CommandFactory commandFactory;
readonly TextWriter output;
readonly string[] args;
Command executingCommand;

static Task<int> Main(string[] args)
Expand Down Expand Up @@ -108,25 +108,23 @@ protected virtual void ShowExamples(string commandName)
{
if (Assembly.GetExecutingAssembly().GetManifestResourceStream("VisualStudio.Docs." + commandName + ".md") is Stream stream)
{
using (var reader = new StreamReader(stream))
using var reader = new StreamReader(stream);
var showLine = false;
var line = default(string);
while ((line = reader.ReadLine()) != null && !line.StartsWith("<!-- EXAMPLES_END"))
{
var showLine = false;
var line = default(string);
while ((line = reader.ReadLine()) != null && !line.StartsWith("<!-- EXAMPLES_END"))
if (line.StartsWith("<!-- EXAMPLES_BEGIN"))
{
if (line.StartsWith("<!-- EXAMPLES_BEGIN"))
{
// It means that we found the first comment and the file contains examples to be shown
output.WriteLine();
output.WriteLine("Examples:");
output.WriteLine();

showLine = true;
}
else if (showLine)
{
output.WriteLine(line);
}
// It means that we found the first comment and the file contains examples to be shown
output.WriteLine();
output.WriteLine("Examples:");
output.WriteLine();

showLine = true;
}
else if (showLine)
{
output.WriteLine(line);
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions VisualStudio/Sku.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ public enum Sku
Community,
Professional,
Enterprise,
BuildTools,
TestAgent
}
}
44 changes: 15 additions & 29 deletions VisualStudio/VisualStudioInstanceExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,30 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using VisualStudio;

namespace vswhere
{
static class VisualStudioInstanceExtensions
{
public static Sku GetSku(this VisualStudioInstance vsInstance)
{
switch (vsInstance.ProductId)
=> vsInstance.ProductId switch
{
case "Microsoft.VisualStudio.Product.Enterprise":
return Sku.Enterprise;
case "Microsoft.VisualStudio.Product.Professional":
return Sku.Professional;
case "Microsoft.VisualStudio.Product.Community":
return Sku.Community;
}

throw new ArgumentException($"Invalid SKU {vsInstance.ProductId}. Must be one of {string.Join(", ", Enum.GetNames(typeof(Sku)).Select(x => x.ToLowerInvariant()))}.", "sku");
}
"Microsoft.VisualStudio.Product.Enterprise" => Sku.Enterprise,
"Microsoft.VisualStudio.Product.Professional" => Sku.Professional,
"Microsoft.VisualStudio.Product.Community" => Sku.Community,
"Microsoft.VisualStudio.Product.BuildTools" => Sku.BuildTools,
"Microsoft.VisualStudio.Product.TestAgent" => Sku.TestAgent,
_ => throw new ArgumentException($"Invalid SKU {vsInstance.ProductId}. Must be one of {string.Join(", ", Enum.GetNames(typeof(Sku)).Select(x => x.ToLowerInvariant()))}.", "sku"),
};

public static Channel GetChannel(this VisualStudioInstance vsInstance)
{
switch (vsInstance.ChannelId)
=> vsInstance.ChannelId switch
{
case "VisualStudio.16.Release":
return Channel.Release;
case "VisualStudio.16.Preview":
return Channel.Preview;
case "VisualStudio.16.IntPreview":
return Channel.IntPreview;
case "VisualStudio.16.int.main":
return Channel.Main;
}

throw new ArgumentException($"Invalid ChannelId {vsInstance.ChannelId}. Must be one of {string.Join(", ", Enum.GetNames(typeof(Channel)).Select(x => x.ToLowerInvariant()))}.", "sku");
}
"VisualStudio.16.Release" => Channel.Release,
"VisualStudio.16.Preview" => Channel.Preview,
"VisualStudio.16.IntPreview" => Channel.IntPreview,
"VisualStudio.16.int.main" => Channel.Main,
_ => throw new ArgumentException($"Invalid ChannelId {vsInstance.ChannelId}. Must be one of {string.Join(", ", Enum.GetNames(typeof(Channel)).Select(x => x.ToLowerInvariant()))}.", "sku"),
};
}
}
Loading