Skip to content

Commit

Permalink
Added support for installing packages as optional
Browse files Browse the repository at this point in the history
Delivers #2439

Added a new method to IInstaller to support marking some packages as optional.
Added a new property bool IsPartOfAnOptionalWorkload to IInstallUnityDescriptor.
Updated IInstallUnitDescriptorFactory to pipe the additional data during template installation.

Updated all the implementations of the above interfaces to comply with the changes.
Updated the tests that were broken because of the changes.
  • Loading branch information
bekir-ozturk authored Aug 7, 2020
2 parents de8e453 + b7a77cd commit 633dff0
Show file tree
Hide file tree
Showing 16 changed files with 194 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ public interface IInstallUnitDescriptor
string UninstallString { get; }

IReadOnlyList<string> DetailKeysDisplayOrder { get; }

bool IsPartOfAnOptionalWorkload { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ namespace Microsoft.TemplateEngine.Abstractions.TemplateUpdates
public interface IInstallUnitDescriptorFactory : IIdentifiedComponent
{
// for existing descriptors saved in the metadata
bool TryCreateFromDetails(Guid descriptorId, string identifier, Guid mountPointId, IReadOnlyDictionary<string, string> details, out IInstallUnitDescriptor descriptor);
bool TryCreateFromDetails(Guid descriptorId, string identifier, Guid mountPointId, bool isPartOfAnOptionalWorkload,
IReadOnlyDictionary<string, string> details, out IInstallUnitDescriptor descriptor);

// for creating from a mount point
bool TryCreateFromMountPoint(IMountPoint mountPoint, out IReadOnlyList<IInstallUnitDescriptor> descriptorList);
bool TryCreateFromMountPoint(IMountPoint mountPoint, bool isPartOfAnOptionalWorkload, out IReadOnlyList<IInstallUnitDescriptor> descriptorList);
}
}
2 changes: 2 additions & 0 deletions src/Microsoft.TemplateEngine.Cli/IInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ public interface IInstaller : IInstallerBase
void InstallPackages(IEnumerable<string> installationRequests, IList<string> nuGetSources, bool debugAllowDevInstall);

void InstallPackages(IEnumerable<string> installationRequests, IList<string> nuGetSources, bool debugAllowDevInstall, bool interactive);

void InstallPackages(IEnumerable<InstallationRequest> installationRequests, IList<string> nuGetSources = null, bool debugAllowDevInstall = false, bool interactive = false);
}
}
59 changes: 59 additions & 0 deletions src/Microsoft.TemplateEngine.Cli/InstallationRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;

namespace Microsoft.TemplateEngine.Cli
{
public struct InstallationRequest : IEquatable<InstallationRequest>
{
public InstallationRequest(string installString, bool isPartOfAnOptionalWorkload = false)
{
InstallString = installString;
IsPartOfAnOptionalWorkload = isPartOfAnOptionalWorkload;
}

/// <summary>
/// String to identify the package/directory/archive containing the templates
/// </summary>
public string InstallString { get; set; }

/// <summary>
/// Is this package being installed as part of an optional workload?
/// </summary>
public bool IsPartOfAnOptionalWorkload { get; set; }

public bool Equals(InstallationRequest other)
{
return InstallString == other.InstallString &&
IsPartOfAnOptionalWorkload == other.IsPartOfAnOptionalWorkload;
}

public override bool Equals(object obj)
{
if (!(obj is InstallationRequest other))
{
return false;
}

return Equals(other);
}

public override string ToString()
{
return $"{(IsPartOfAnOptionalWorkload ? "[OW]" : string.Empty)}{InstallString}";
}

public override int GetHashCode()
{
return unchecked((InstallString?.GetHashCode() ?? 0) + 13 * IsPartOfAnOptionalWorkload.GetHashCode());
}

public static bool operator ==(InstallationRequest x, InstallationRequest y)
{
return x.Equals(y);
}

public static bool operator !=(InstallationRequest x, InstallationRequest y)
{
return !(x == y);
}
}
}
103 changes: 67 additions & 36 deletions src/Microsoft.TemplateEngine.Cli/Installer.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.Mount;
using Microsoft.TemplateEngine.Abstractions.TemplateUpdates;
using Microsoft.TemplateEngine.Edge;
using Microsoft.TemplateEngine.Edge.Settings;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Microsoft.TemplateEngine.Cli
{
Expand All @@ -25,37 +25,51 @@ public Installer(IEngineEnvironmentSettings environmentSettings)
_paths = new Paths(environmentSettings);
}

public void AddInstallDescriptorForLocation(Guid mountPointId, out IReadOnlyList<IInstallUnitDescriptor> descriptorList)
public void AddInstallDescriptorForLocation(Guid mountPointId, bool isPartOfAnOptionalWorkload, out IReadOnlyList<IInstallUnitDescriptor> descriptorList)
{
((SettingsLoader)(_environmentSettings.SettingsLoader)).InstallUnitDescriptorCache.TryAddDescriptorForLocation(mountPointId, out descriptorList);
((SettingsLoader)(_environmentSettings.SettingsLoader)).InstallUnitDescriptorCache.TryAddDescriptorForLocation(mountPointId, isPartOfAnOptionalWorkload, out descriptorList);
}

public void InstallPackages(IEnumerable<string> installationRequests) => InstallPackages(installationRequests, null, false, false);
public void InstallPackages(IEnumerable<string> installationRequests)
=> InstallPackages(installationRequests, null, false, false);

public void InstallPackages(IEnumerable<string> installationRequests, IList<string> nuGetSources) => InstallPackages(installationRequests, nuGetSources, false, false);
public void InstallPackages(IEnumerable<string> installationRequests, IList<string> nuGetSources)
=> InstallPackages(installationRequests, nuGetSources, false, false);

public void InstallPackages(IEnumerable<string> installationRequests, IList<string> nuGetSources, bool debugAllowDevInstall) => InstallPackages(installationRequests, nuGetSources, debugAllowDevInstall, false);
public void InstallPackages(IEnumerable<string> installationRequests, IList<string> nuGetSources, bool debugAllowDevInstall)
=> InstallPackages(installationRequests, nuGetSources, debugAllowDevInstall, false);

public void InstallPackages(IEnumerable<string> installationRequests, IList<string> nuGetSources, bool debugAllowDevInstall, bool interactive)
=> InstallPackages(installationRequests.Select(x => new InstallationRequest(x)), nuGetSources, debugAllowDevInstall, interactive);

public void InstallPackages(IEnumerable<InstallationRequest> installationRequests, IList<string> nuGetSources = null, bool debugAllowDevInstall = false, bool interactive = false)
{
List<string> localSources = new List<string>();
List<InstallationRequest> localSources = new List<InstallationRequest>();
List<Package> packages = new List<Package>();
List<Package> packagesFromOptionalWorkloads = new List<Package>();

foreach (string request in installationRequests)
foreach (InstallationRequest request in installationRequests)
{
string req = request;
string req = request.InstallString;

//If the request string doesn't have any wild cards or probable path indicators,
// and doesn't have a "::" delimiter either, try to convert it to "latest package"
// form
if (OriginalRequestIsImplicitPackageVersionSyntax(request))
if (OriginalRequestIsImplicitPackageVersionSyntax(request.InstallString))
{
req += "::*";
}

if (Package.TryParse(req, out Package package))
{
packages.Add(package);
if (request.IsPartOfAnOptionalWorkload)
{
packagesFromOptionalWorkloads.Add(package);
}
else
{
packages.Add(package);
}
}
else
{
Expand All @@ -68,10 +82,19 @@ public void InstallPackages(IEnumerable<string> installationRequests, IList<stri
InstallLocalPackages(localSources, debugAllowDevInstall);
}

if (packages.Count > 0)
if (packages.Count + packagesFromOptionalWorkloads.Count > 0)
{
IList<string> additionalDotNetArguments = (interactive) ? new string[] { "--interactive" } : null;
InstallRemotePackages(packages, nuGetSources, additionalDotNetArguments);
IList<string> additionalDotNetArguments = interactive ? new string[] { "--interactive" } : null;

if (packages.Count > 0)
{
InstallRemotePackages(packages, nuGetSources, additionalDotNetArguments, false);
}

if (packagesFromOptionalWorkloads.Count > 0)
{
InstallRemotePackages(packagesFromOptionalWorkloads, nuGetSources, additionalDotNetArguments, true);
}
}

_environmentSettings.SettingsLoader.Save();
Expand Down Expand Up @@ -197,7 +220,15 @@ private IReadOnlyCollection<Guid> GetDescendantMountPointsFromParent(Guid parent
return mountPoints;
}

private void InstallRemotePackages(List<Package> packages, IList<string> nuGetSources, IList<string> additionalDotNetArguments)
/// <summary>
/// Acquires given nuget packages with 'dotnet restore' and installs them.
/// </summary>
/// <param name="packages">Packages to be installed.</param>
/// <param name="nuGetSources">Nuget sources to look for the packages.</param>
/// <param name="additionalDotNetArguments">Additional arguments to be passed to 'dotnet restore'.</param>
/// <param name="isPartOfAnOptionalWorkload">Specifies if given packages are part of an optional workload. This should be correct for
/// each of the specified packages (do not mix optional and non-optional packages when calling this method).</param>
private void InstallRemotePackages(List<Package> packages, IList<string> nuGetSources, IList<string> additionalDotNetArguments, bool isPartOfAnOptionalWorkload)
{
const string packageRef = @" <PackageReference Include=""{0}"" Version=""{1}"" />";
const string projectFile = @"<Project ToolsVersion=""15.0"" Sdk=""Microsoft.NET.Sdk"">
Expand Down Expand Up @@ -250,40 +281,40 @@ private void InstallRemotePackages(List<Package> packages, IList<string> nuGetSo
string stagingDir = Path.Combine(_paths.User.ScratchDir, "Staging");
_paths.CreateDirectory(stagingDir);

List<string> newLocalPackages = new List<string>();
List<InstallationRequest> newLocalPackages = new List<InstallationRequest>();
foreach (string packagePath in _paths.EnumerateFiles(restored, "*.nupkg", SearchOption.AllDirectories))
{
string stagingPathForPackage = Path.Combine(stagingDir, Path.GetFileName(packagePath));
_paths.Copy(packagePath, stagingPathForPackage);
newLocalPackages.Add(stagingPathForPackage);
newLocalPackages.Add(new InstallationRequest(stagingPathForPackage, isPartOfAnOptionalWorkload));
}

InstallLocalPackages(newLocalPackages, false);
_paths.DeleteDirectory(_paths.User.ScratchDir);
}

private void InstallLocalPackages(IReadOnlyList<string> packageNames, bool debugAllowDevInstall)
private void InstallLocalPackages(IEnumerable<InstallationRequest> localInstallationRequests, bool debugAllowDevInstall)
{
foreach (string package in packageNames)
foreach (InstallationRequest localInstallRequest in localInstallationRequests)
{
if (package == null)
if (string.IsNullOrWhiteSpace(localInstallRequest.InstallString))
{
continue;
}

string pkg = package.Trim();
pkg = _environmentSettings.Environment.ExpandEnvironmentVariables(pkg);
string req = localInstallRequest.InstallString.Trim();
req = _environmentSettings.Environment.ExpandEnvironmentVariables(req);
string pattern = null;

int wildcardIndex = pkg.IndexOfAny(new[] { '*', '?' });
int wildcardIndex = req.IndexOfAny(new[] { '*', '?' });

if (wildcardIndex > -1)
{
int lastSlashBeforeWildcard = pkg.LastIndexOfAny(new[] { '\\', '/' });
int lastSlashBeforeWildcard = req.LastIndexOfAny(new[] { '\\', '/' });
if (lastSlashBeforeWildcard >= 0)
{
pattern = pkg.Substring(lastSlashBeforeWildcard + 1);
pkg = pkg.Substring(0, lastSlashBeforeWildcard);
pattern = req.Substring(lastSlashBeforeWildcard + 1);
req = req.Substring(0, lastSlashBeforeWildcard);
}
}

Expand All @@ -293,16 +324,16 @@ private void InstallLocalPackages(IReadOnlyList<string> packageNames, bool debug

if (pattern != null)
{
string fullDirectory = new DirectoryInfo(pkg).FullName;
string fullDirectory = new DirectoryInfo(req).FullName;
installString = Path.Combine(fullDirectory, pattern);
}
else if (_environmentSettings.Host.FileSystem.DirectoryExists(pkg) || _environmentSettings.Host.FileSystem.FileExists(pkg))
else if (_environmentSettings.Host.FileSystem.DirectoryExists(req) || _environmentSettings.Host.FileSystem.FileExists(req))
{
installString = new DirectoryInfo(pkg).FullName;
installString = new DirectoryInfo(req).FullName;
}
else
{
_environmentSettings.Host.OnNonCriticalError("InvalidPackageSpecification", string.Format(LocalizableStrings.CouldNotFindItemToInstall, pkg), null, 0);
_environmentSettings.Host.OnNonCriticalError("InvalidPackageSpecification", string.Format(LocalizableStrings.CouldNotFindItemToInstall, req), null, 0);
}

if (installString != null)
Expand All @@ -311,7 +342,7 @@ private void InstallLocalPackages(IReadOnlyList<string> packageNames, bool debug

foreach (Guid mountPointId in contentMountPointIds)
{
AddInstallDescriptorForLocation(mountPointId, out IReadOnlyList<IInstallUnitDescriptor> descriptorList);
AddInstallDescriptorForLocation(mountPointId, localInstallRequest.IsPartOfAnOptionalWorkload, out IReadOnlyList<IInstallUnitDescriptor> descriptorList);

foreach (IInstallUnitDescriptor descriptor in descriptorList)
{
Expand All @@ -322,7 +353,7 @@ private void InstallLocalPackages(IReadOnlyList<string> packageNames, bool debug
}
catch (Exception ex)
{
_environmentSettings.Host.OnNonCriticalError("InvalidPackageSpecification", string.Format(LocalizableStrings.BadPackageSpec, pkg), null, 0);
_environmentSettings.Host.OnNonCriticalError("InvalidPackageSpecification", string.Format(LocalizableStrings.BadPackageSpec, req), null, 0);

if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_NEW_DEBUG")))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public bool TryGetDescriptorForTemplate(ITemplateInfo templateInfo, out IInstall
[JsonProperty]
public IReadOnlyDictionary<Guid, IInstallUnitDescriptor> Descriptors => _cache;

public bool TryAddDescriptorForLocation(Guid mountPointId, out IReadOnlyList<IInstallUnitDescriptor> descriptorList)
public bool TryAddDescriptorForLocation(Guid mountPointId, bool isPartOfAnOptionalWorkload, out IReadOnlyList<IInstallUnitDescriptor> descriptorList)
{
IMountPoint mountPoint = null;

Expand All @@ -53,7 +53,7 @@ public bool TryAddDescriptorForLocation(Guid mountPointId, out IReadOnlyList<IIn
return false;
}

if (InstallUnitDescriptorFactory.TryCreateFromMountPoint(_environmentSettings, mountPoint, out descriptorList))
if (InstallUnitDescriptorFactory.TryCreateFromMountPoint(_environmentSettings, mountPoint, isPartOfAnOptionalWorkload, out descriptorList))
{
foreach (IInstallUnitDescriptor descriptor in descriptorList)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ namespace Microsoft.TemplateEngine.Edge.TemplateUpdates
{
public sealed class DefaultInstallUnitDescriptor : IInstallUnitDescriptor
{
public DefaultInstallUnitDescriptor(Guid descriptorId, Guid mountPointId, string identifier)
public DefaultInstallUnitDescriptor(Guid descriptorId, Guid mountPointId, string identifier, bool isPartOfAnOptionalWorkload)
{
DescriptorId = descriptorId;
MountPointId = mountPointId;
Identifier = identifier;
Details = _details;
DetailKeysDisplayOrder = Empty<string>.List.Value;
IsPartOfAnOptionalWorkload = isPartOfAnOptionalWorkload;
}

private static readonly IReadOnlyDictionary<string, string> _details = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
Expand All @@ -31,5 +32,7 @@ public DefaultInstallUnitDescriptor(Guid descriptorId, Guid mountPointId, string
public string UninstallString => Identifier;

public IReadOnlyList<string> DetailKeysDisplayOrder { get; }

public bool IsPartOfAnOptionalWorkload { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ public class DefaultInstallUnitDescriptorFactory : IInstallUnitDescriptorFactory

public Guid Id => FactoryId;

public bool TryCreateFromDetails(Guid descriptorId, string identifier, Guid mountPointId, IReadOnlyDictionary<string, string> details, out IInstallUnitDescriptor descriptor)
public bool TryCreateFromDetails(Guid descriptorId, string identifier, Guid mountPointId, bool isPartOfAnOptionalWorkload,
IReadOnlyDictionary<string, string> details, out IInstallUnitDescriptor descriptor)
{
descriptor = new DefaultInstallUnitDescriptor(descriptorId, mountPointId, identifier);
descriptor = new DefaultInstallUnitDescriptor(descriptorId, mountPointId, identifier, isPartOfAnOptionalWorkload);
return true;
}

public bool TryCreateFromMountPoint(IMountPoint mountPoint, out IReadOnlyList<IInstallUnitDescriptor> descriptorList)
public bool TryCreateFromMountPoint(IMountPoint mountPoint, bool isPartOfAnOptionalWorkload, out IReadOnlyList<IInstallUnitDescriptor> descriptorList)
{
descriptorList = new List<IInstallUnitDescriptor>()
{
new DefaultInstallUnitDescriptor(Guid.NewGuid(), mountPoint.Info.MountPointId, mountPoint.Info.Place),
new DefaultInstallUnitDescriptor(Guid.NewGuid(), mountPoint.Info.MountPointId, mountPoint.Info.Place, isPartOfAnOptionalWorkload),
};

return true;
Expand Down
Loading

0 comments on commit 633dff0

Please sign in to comment.