Skip to content
This repository has been archived by the owner on Mar 13, 2018. It is now read-only.

Commit

Permalink
Enable install specific version
Browse files Browse the repository at this point in the history
Closes #3
  • Loading branch information
giggio committed Sep 13, 2016
1 parent 4f6e7d1 commit 7dd909b
Show file tree
Hide file tree
Showing 22 changed files with 192 additions and 44 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,18 @@ Simply run `dotnet commands` to see the options, which are similar to this:

````
.NET Commands
Usage:
dotnet commands install <command> [--force] [--pre] [--verbose]
dotnet commands install <command>[@<version>] [--force] [--pre] [--verbose]
dotnet commands uninstall <command> [ --verbose]
dotnet commands update (<command> | all) [--pre] [--verbose]
dotnet commands list [--verbose]
dotnet commands ls [--verbose]
dotnet commands (list|ls) [--verbose]
dotnet commands --help
dotnet commands --version
Options:
--force Installs even if package was already installed. Optional.
--pre Include pre-release versions. Optional.
--pre Include pre-release versions. Ignored if version is supplied. Optional.
--verbose Verbose. Optional.
--help -h Show this screen.
--version -v Show version.
Expand Down
5 changes: 3 additions & 2 deletions src/dotnet-commands/CommandDirectory.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using NuGet.Versioning;
using System.IO;

namespace DotNetCommands
Expand All @@ -23,10 +24,10 @@ public CommandDirectory(string baseDir)
Directory.CreateDirectory(binDir);
}

public string GetDirectoryForPackage(string packageName, string packageVersion = null) =>
public string GetDirectoryForPackage(string packageName, SemanticVersion packageVersion = null) =>
packageVersion == null
? Path.Combine(PackagesDir, packageName)
: Path.Combine(PackagesDir, packageName, packageVersion);
: Path.Combine(PackagesDir, packageName, packageVersion.ToString());

public string GetBinFile(string fileName) =>
Path.Combine(binDir, fileName.Replace('/', Path.DirectorySeparatorChar));
Expand Down
5 changes: 3 additions & 2 deletions src/dotnet-commands/Installer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using NuGet.Versioning;

namespace DotNetCommands
{
Expand All @@ -17,15 +18,15 @@ public Installer(CommandDirectory commandDirectory)
this.commandDirectory = commandDirectory;
}

public async Task<bool> InstallAsync(string packageName, bool force, bool includePreRelease)
public async Task<bool> InstallAsync(string packageName, SemanticVersion packageVersion, bool force, bool includePreRelease)
{
WriteLineIfVerbose($"Installing {packageName}...");
PackageInfo packageInfo;
try
{
using (var downloader = new NugetDownloader(commandDirectory))
{
packageInfo = await downloader.DownloadAndExtractNugetAsync(packageName, force, includePreRelease);
packageInfo = await downloader.DownloadAndExtractNugetAsync(packageName, packageVersion, force, includePreRelease);
if (packageInfo == null) return false;
}
}
Expand Down
79 changes: 68 additions & 11 deletions src/dotnet-commands/NugetDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace DotNetCommands
public sealed class NugetDownloader : IDisposable
{
private readonly CommandDirectory commandDirectory;
private IList<SourceInfo> sourceInfos;
private readonly IList<SourceInfo> sourceInfos;

public NugetDownloader(CommandDirectory commandDirectory)
{
Expand Down Expand Up @@ -59,6 +59,39 @@ public async Task<string> GetLatestVersionAsync(string packageName, bool include
return (await GetLatestVersionAndSourceInfoAsync(packageName, includePreRelease))?.Version.ToString();
}

private async Task<NugetVersion> GetSpecificVersionAndSourceInfoAsync(string packageName, SemanticVersion packageVersion)
{
foreach (var sourceInfo in sourceInfos)
{
WriteLineIfVerbose($"Searching for '{packageName}@{packageVersion.ToString()}' on '{sourceInfo.Source.Name}'...");
NugetVersion latestNugetVersion;
try
{
latestNugetVersion = await sourceInfo.GetPackageAsync(packageName, packageVersion, true);
}
catch (Exception ex)
{
WriteLine($"Error when getting '{packageName}@{packageVersion.ToString()}'. Source used: '{sourceInfo.Source.Name}'.");
WriteLineIfVerbose($"Error details: {ex.ToString()}'.");
return null;
}
if (latestNugetVersion == null)
{
WriteLineIfVerbose($"Could not get a version for '{packageName}@{packageVersion.ToString()}' on '{sourceInfo.Source.Name}'.");
continue;
}
else
{
WriteLineIfVerbose($"Found version '{latestNugetVersion.Version}' for '{packageName}@{packageVersion.ToString()}' on '{sourceInfo.Source.Name}'.");
return latestNugetVersion;
}
}
WriteLine($"Package '{packageName}' not found. Sources used:");
foreach (var source in sourceInfos.Select(p => p.Source))
WriteLine($" - {source.Name}: {source.Source}");
return null;
}

private async Task<NugetVersion> GetLatestVersionAndSourceInfoAsync(string packageName, bool includePreRelease)
{
NugetVersion currentNugetVersion = null;
Expand All @@ -68,7 +101,7 @@ private async Task<NugetVersion> GetLatestVersionAndSourceInfoAsync(string packa
NugetVersion latestNugetVersion;
try
{
latestNugetVersion = await sourceInfo.GetLatestVersionAsync(packageName, includePreRelease);
latestNugetVersion = await sourceInfo.GetPackageAsync(packageName, null, includePreRelease);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -103,17 +136,20 @@ private async Task<NugetVersion> GetLatestVersionAndSourceInfoAsync(string packa
/// Downloads the specified nupkg and extracts it to a directory
/// </summary>
/// <param name="packageName">The command to download, i.e. "dotnet-foo".</param>
/// <param name="packageVersion">The version to install. If null, then latest will be used.</param>
/// <param name="force">Force the download be made again if it was already downloaded earlier.</param>
/// <param name="includePreRelease">Allow pre-release versions.</param>
/// <returns>The directory where it is extracted</returns>
public async Task<PackageInfo> DownloadAndExtractNugetAsync(string packageName, bool force, bool includePreRelease)
public async Task<PackageInfo> DownloadAndExtractNugetAsync(string packageName, SemanticVersion packageVersion, bool force, bool includePreRelease)
{
if (!sourceInfos.Any())
{
WriteLine("No NuGet sources found.");
return null;
}
var nugetVersion = await GetLatestVersionAndSourceInfoAsync(packageName, includePreRelease);
var nugetVersion = packageVersion == null
? await GetLatestVersionAndSourceInfoAsync(packageName, includePreRelease)
: await GetSpecificVersionAndSourceInfoAsync(packageName, packageVersion);
if (nugetVersion == null)
{
WriteLineIfVerbose($"Could not get latest version for package '{packageName}'.");
Expand All @@ -134,7 +170,7 @@ public async Task<PackageInfo> DownloadAndExtractNugetAsync(string packageName,
WriteLineIfVerbose($"Saving to '{tempFilePath}'.");
using (var tempFileStream = File.OpenWrite(tempFilePath))
await nupkgResponse.Content.CopyToAsync(tempFileStream);
var destinationDir = commandDirectory.GetDirectoryForPackage(nugetVersion.PackageName, nugetVersion.Version.ToString());
var destinationDir = commandDirectory.GetDirectoryForPackage(nugetVersion.PackageName, nugetVersion.Version);
if (force)
Directory.Delete(destinationDir, true);
var shouldExtract = force || !Directory.Exists(destinationDir);
Expand Down Expand Up @@ -185,6 +221,14 @@ private class ServiceData
public string Id { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
[JsonProperty("versions")]
public IList<ServiceDataVersion> Versions { get; set; }
}

private class ServiceDataVersion
{
[JsonProperty("version")]
public string Version { get; set; }
}

private class SourceInfo : IDisposable
Expand Down Expand Up @@ -213,7 +257,7 @@ public async Task<Feed> GetFeedAsync()
return feed;
}

public async Task<NugetVersion> GetLatestVersionAsync(string packageName, bool includePreRelease)
public async Task<NugetVersion> GetPackageAsync(string packageName, SemanticVersion packageVersion, bool includePreRelease)
{
var currentFeed = await GetFeedAsync();
if (currentFeed == null)
Expand Down Expand Up @@ -254,14 +298,27 @@ public async Task<NugetVersion> GetLatestVersionAsync(string packageName, bool i
var serviceData = supportsQueryById
? service.Data.FirstOrDefault()
: service.Data.FirstOrDefault(sd => string.Compare(sd.Id, packageName, true) == 0);
var version = serviceData?.Version;
if (version == null)
var latest = serviceData?.Version;
if (latest == null)
{
WriteLineIfVerbose($"There was no version info for '{packageName}' on '{Source.Name}'.");
WriteLineIfVerbose($"There was no package info for '{packageName}' on '{Source.Name}'.");
return null;
}
WriteLineIfVerbose($"Found version {version}.");
var currentSemanticVersion = SemanticVersion.Parse(version);
WriteLineIfVerbose($"Found package '{packageName}' with latest version {latest}.");
var currentSemanticVersion = SemanticVersion.Parse(latest);
if (packageVersion != null)
{
var versionExists = serviceData.Versions.Any(v => v.Version == packageVersion.ToString());
if (versionExists)
{
currentSemanticVersion = packageVersion;
}
else
{
WriteLineIfVerbose($"Version '{packageVersion.ToString()}' was not found for '{packageName}' on '{Source.Name}'.");
return null;
}
}
var nugetVersion = new NugetVersion(currentSemanticVersion, this, serviceData.Id);
return nugetVersion;
}
Expand Down
34 changes: 28 additions & 6 deletions src/dotnet-commands/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private async static Task<int> RunAsync(string[] args)
const string usage = @".NET Commands
Usage:
dotnet commands install <command> [--force] [--pre] [--verbose]
dotnet commands install <command>[@<version>] [--force] [--pre] [--verbose]
dotnet commands uninstall <command> [ --verbose]
dotnet commands update (<command> | all) [--pre] [--verbose]
dotnet commands (list|ls) [--verbose]
Expand All @@ -27,7 +27,7 @@ dotnet commands --version
Options:
--force Installs even if package was already installed. Optional.
--pre Include pre-release versions. Optional.
--pre Include pre-release versions. Ignored if version is supplied. Optional.
--verbose Verbose. Optional.
--help -h Show this screen.
--version -v Show version.
Expand All @@ -38,7 +38,7 @@ dotnet commands --version
if (args.Length == 1 && args[0] == "bootstrap")
{
var installer = new Installer(commandDirectory);
var success = await installer.InstallAsync("dotnet-commands", force: true, includePreRelease: true);
var success = await installer.InstallAsync("dotnet-commands", null, force: true, includePreRelease: true);
return (int)(success ? ExitCodes.Success : ExitCodes.BootstrapFailed);
}
var argsWithRun = args;
Expand All @@ -59,16 +59,37 @@ dotnet commands --version
{
WriteLineIfVerbose("Request to update .NET Commands.");
var updater = new Updater(commandDirectory);
var updateNeeded = await updater.IsUpdateNeeded(command, arguments["--pre"].IsTrue);
var updateNeeded = await updater.IsUpdateNeededAsync(command, arguments["--pre"].IsTrue);
var exitCode = updateNeeded == Updater.UpdateNeeded.Yes ? ExitCodes.StartUpdate : ExitCodes.Success;
WriteLineIfVerbose($"Should update .NET Commands: {updateNeeded}, exit code is going to be {exitCode} ({(int)exitCode}).");
return (int)exitCode;
}
if (arguments["install"].IsTrue)
{

var commandParts = command.Split('@');
NuGet.Versioning.SemanticVersion packageVersion = null;
switch (commandParts.Length)
{
case 1:
break;
case 2:
command = commandParts[0];
try
{
packageVersion = NuGet.Versioning.SemanticVersion.Parse(commandParts[0]);
}
catch (ArgumentException)
{
Console.WriteLine($"Invalid version.\n{usage}");
return (int)ExitCodes.InvalidVersion;
}
break;
default:
Console.WriteLine($"Invalid version.\n{usage}");
return (int)ExitCodes.InvalidVersion;
}
var installer = new Installer(commandDirectory);
var success = await installer.InstallAsync(command, arguments["--force"].IsTrue, arguments["--pre"].IsTrue);
var success = await installer.InstallAsync(command, packageVersion, arguments["--force"].IsTrue, arguments["--pre"].IsTrue);
return (int)(success ? ExitCodes.Success : ExitCodes.InstallFailed);
}
if (arguments["uninstall"].IsTrue)
Expand Down Expand Up @@ -119,6 +140,7 @@ enum ExitCodes : byte // 0 and from 64-113 according to http://tldp.org/LDP/abs/
InstallFailed = 69,
UninstallFailed = 70,
CantUninstallDotNetCommands = 71,
InvalidVersion = 72,
StartUpdate = 113
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/dotnet-commands/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"profiles": {
"dotnet-commands": {
"commandName": "Project",
"commandLineArgs": "install dotnet-foo"
"commandLineArgs": "install dotnet-foo@1.0.1"
}
}
}
6 changes: 3 additions & 3 deletions src/dotnet-commands/Updater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ public Updater(CommandDirectory commandDirectory)

public async Task<UpdateResult> UpdateAsync(string packageName, bool force, bool includePreRelease)
{
var updateNeeded = await IsUpdateNeeded(packageName, includePreRelease);
var updateNeeded = await IsUpdateNeededAsync(packageName, includePreRelease);
if (updateNeeded == UpdateNeeded.No) return UpdateResult.NotNeeded;
if (updateNeeded == UpdateNeeded.PackageNotFound) return UpdateResult.PackageNotFound;
var uninstaller = new Uninstaller(commandDirectory);
var uninstalled = await uninstaller.UninstallAsync(packageName);
if (!uninstalled) return UpdateResult.CouldntUninstall;
var installer = new Installer(commandDirectory);
var installed = await installer.InstallAsync(packageName, force, includePreRelease);
var installed = await installer.InstallAsync(packageName, null, force, includePreRelease);
return installed ? UpdateResult.Success : UpdateResult.UninstalledAndNotReinstalled;
}

public async Task<UpdateNeeded> IsUpdateNeeded(string packageName, bool includePreRelease)
public async Task<UpdateNeeded> IsUpdateNeededAsync(string packageName, bool includePreRelease)
{
SemanticVersion largestAvailableVersion;
try
Expand Down
2 changes: 1 addition & 1 deletion src/dotnet-commands/project.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.4.2",
"version": "0.5.0",
"description": "A .NET CLI Commands library.",
"dependencies": {
"Microsoft.NETCore.App": {
Expand Down
2 changes: 1 addition & 1 deletion src/dotnet-foo/project.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.0.1-*",
"version": "1.0.2-*",
"description": "A sample package only to validate that the tools installer works.",
"dependencies": {
"Microsoft.NETCore": "5.0.2"
Expand Down
3 changes: 2 additions & 1 deletion test/IntegrationTests/CommandDirectoryTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using DotNetCommands;
using FluentAssertions;
using NuGet.Versioning;
using NUnit.Framework;
using System.IO;

Expand Down Expand Up @@ -28,7 +29,7 @@ public void ClassInitialize()
public void GetBinFile() => commandDirectory.GetBinFile("foo.cmd").Should().Be(Path.Combine(baseDir, "bin", "foo.cmd"));

[Test]
public void GetDirectoryForPackage() => commandDirectory.GetDirectoryForPackage("foo", "1.2.3").Should().Be(Path.Combine(baseDir, "packages", "foo", "1.2.3"));
public void GetDirectoryForPackage() => commandDirectory.GetDirectoryForPackage("foo", SemanticVersion.Parse("1.2.3")).Should().Be(Path.Combine(baseDir, "packages", "foo", "1.2.3"));

[Test]
public void MakeRelativeToBaseDir() => commandDirectory.MakeRelativeToBaseDir(Path.Combine(baseDir, "foo", "bar")).Should().Be(Path.Combine("..", "foo", "bar"));
Expand Down
2 changes: 1 addition & 1 deletion test/IntegrationTests/DownloadNugetTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void ClassCleanup()
[Test, Retry]
public async Task DownloadDotNetFooAsync()
{
var packageInfo = await downloader.DownloadAndExtractNugetAsync("dotnet-foo", force: false, includePreRelease: false);
var packageInfo = await downloader.DownloadAndExtractNugetAsync("dotnet-foo", null, force: false, includePreRelease: false);
Directory.Exists(packageInfo.PackageDir).Should().BeTrue();
var cmd = Directory.EnumerateFiles(packageInfo.PackageDir, "dotnet-foo.cmd", SearchOption.AllDirectories).FirstOrDefault();
cmd.Should().NotBeNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void ClassCleanup()
[Test, Retry]
public async Task DownloadDotNetFooWithoutPackageSourcesAsync()
{
var packageInfo = await downloader.DownloadAndExtractNugetAsync("dotnet-foo", force: false, includePreRelease: false);
var packageInfo = await downloader.DownloadAndExtractNugetAsync("dotnet-foo", null, force: false, includePreRelease: false);
Directory.Exists(packageInfo.PackageDir).Should().BeTrue();
var cmd = Directory.EnumerateFiles(packageInfo.PackageDir, "dotnet-foo.cmd", SearchOption.AllDirectories).FirstOrDefault();
cmd.Should().NotBeNull();
Expand Down
1 change: 1 addition & 0 deletions test/IntegrationTests/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "CC0061:Async method can be terminating with 'Async' name.", Justification = "Tests don't need to end in Async.")]
2 changes: 1 addition & 1 deletion test/IntegrationTests/InstallerTestForDotNetTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public async Task SetupAsync()
commandDirectoryCleanup = new CommandDirectoryCleanup();
baseDir = commandDirectoryCleanup.CommandDirectory.BaseDir;
installer = new Installer(commandDirectoryCleanup.CommandDirectory);
installed = await installer.InstallAsync(packageName, force: false, includePreRelease: true);
installed = await installer.InstallAsync(packageName, null, force: false, includePreRelease: true);
installed.Should().BeTrue();
}

Expand Down
Loading

0 comments on commit 7dd909b

Please sign in to comment.