|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Build.Evaluation;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using NuGet.Versioning;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
namespace Microsoft.DotNet.Arcade.Sdk
{
#if NET472
[LoadInSeparateAppDomain]
public class InstallDotNetCore : AppDomainIsolatedTask
{
static InstallDotNetCore() => AssemblyResolution.Initialize();
#else
public class InstallDotNetCore : Microsoft.Build.Utilities.Task
{
#endif
private static readonly char[] s_keyTrimChars = [ '$', '(', ')' ];
public string VersionsPropsPath { get; set; }
[Required]
public string DotNetInstallScript { get; set; }
[Required]
public string GlobalJsonPath { get; set; }
[Required]
public string Platform { get; set; }
public string RuntimeSourceFeed { get; set; }
public string RuntimeSourceFeedKey { get; set; }
public override bool Execute()
{
if (!File.Exists(GlobalJsonPath))
{
Log.LogWarning($"Unable to find global.json file '{GlobalJsonPath} exiting");
return true;
}
if (!File.Exists(DotNetInstallScript))
{
Log.LogError($"Unable to find dotnet install script '{DotNetInstallScript} exiting");
return !Log.HasLoggedErrors;
}
var jsonContent = File.ReadAllText(GlobalJsonPath);
var bytes = Encoding.UTF8.GetBytes(jsonContent);
using (JsonDocument jsonDocument = JsonDocument.Parse(bytes))
{
if (jsonDocument.RootElement.TryGetProperty("tools", out JsonElement toolsElement))
{
if (toolsElement.TryGetProperty("runtimes", out JsonElement dotnetLocalElement))
{
var runtimeItems = new Dictionary<string, IEnumerable<KeyValuePair<string, string>>>();
foreach (var runtime in dotnetLocalElement.EnumerateObject())
{
var items = GetItemsFromJsonElementArray(runtime, out string runtimeName);
if (runtimeItems.ContainsKey(runtimeName))
{
runtimeItems[runtimeName] = runtimeItems[runtimeName].Concat(items);
}
else
{
runtimeItems.Add(runtimeName, items);
}
}
if (runtimeItems.Count > 0)
{
System.Linq.ILookup<string, ProjectProperty> properties = null;
// Only load Versions.props if there's a need to look for a version identifier (ie, there's a value listed that's not a parsable version).
if (runtimeItems.SelectMany(r => r.Value).Select(r => r.Key).FirstOrDefault(f => !SemanticVersion.TryParse(f, out SemanticVersion version)) != null)
{
if (!File.Exists(VersionsPropsPath))
{
Log.LogError($"Unable to find translation file {VersionsPropsPath}");
return !Log.HasLoggedErrors;
}
else
{
var proj = Project.FromFile(VersionsPropsPath, new Build.Definition.ProjectOptions() { ProjectCollection = new ProjectCollection() });
properties = proj.AllEvaluatedProperties.ToLookup(p => p.Name, StringComparer.OrdinalIgnoreCase);
}
}
foreach (var runtimeItem in runtimeItems)
{
foreach (var item in runtimeItem.Value)
{
string architecture = GetArchitecture(item.Value);
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && string.Equals("x86", architecture, StringComparison.OrdinalIgnoreCase))
{
Log.LogMessage(MessageImportance.Low, "Skipping installing x86 runtimes because this is a non-Windows platform and .NET Core x86 is not currently supported on any non-Windows platform.");
continue;
}
SemanticVersion version = null;
// Try to parse version
if (!SemanticVersion.TryParse(item.Key, out version))
{
var propertyName = item.Key.Trim(s_keyTrimChars);
// Unable to parse version, try to find the corresponding identifier from the MSBuild loaded MSBuild properties
ProjectProperty property = properties[propertyName].FirstOrDefault();
if (property == null)
{
Log.LogError($"Unable to find '{item.Key}' in properties defined in '{VersionsPropsPath}'");
}
else if (!SemanticVersion.TryParse(property.EvaluatedValue, out version))
{
Log.LogError($"Unable to parse '{item.Key}' from properties defined in '{VersionsPropsPath}'");
}
}
if (version != null)
{
string arguments = $"-runtime \"{runtimeItem.Key}\" -version \"{version.ToNormalizedString()}\"";
if (!string.IsNullOrEmpty(architecture))
{
arguments += $" -architecture {architecture}";
}
if (!string.IsNullOrWhiteSpace(RuntimeSourceFeed))
{
arguments += $" -runtimeSourceFeed {RuntimeSourceFeed}";
}
// The default RuntimeSourceFeed doesn't need a key
if (!string.IsNullOrWhiteSpace(RuntimeSourceFeed) && !string.IsNullOrWhiteSpace(RuntimeSourceFeedKey))
{
arguments += $" -runtimeSourceFeedKey {RuntimeSourceFeedKey}";
}
Log.LogMessage(MessageImportance.Low, $"Executing: {DotNetInstallScript} {arguments}");
var process = Process.Start(new ProcessStartInfo()
{
FileName = DotNetInstallScript,
Arguments = arguments,
UseShellExecute = false,
// Redirect to stdout/err. Addressing https://github.com/dotnet/msbuild/issues/7913
// Without it script execution was failing on Linux when run from
RedirectStandardOutput = true,
RedirectStandardError = true,
});
process.OutputDataReceived += (sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
Console.WriteLine(e.Data);
}
};
process.ErrorDataReceived += (sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
Console.Error.WriteLine(e.Data);
}
};
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
if (process.ExitCode != 0)
{
Log.LogError("dotnet-install failed");
}
}
}
}
}
}
}
}
return !Log.HasLoggedErrors;
}
private string GetArchitecture(string architecture)
{
if (!string.IsNullOrWhiteSpace(architecture))
{
return architecture;
}
else if (!string.IsNullOrWhiteSpace(Platform) && !string.Equals(Platform, "AnyCpu", StringComparison.OrdinalIgnoreCase))
{
return Platform;
}
else if (RuntimeInformation.OSArchitecture == Architecture.X86 ||
RuntimeInformation.OSArchitecture == Architecture.X64)
{
return "x64";
}
// let dotnet-install.sh/ps1 infer a default arch
return null;
}
/*
* Parses a json token of this format
* { (runtime): [(version), ..., (version)] }
* or this format
* { (runtime/architecture): [(version), ..., (version)] }
*/
private IEnumerable<KeyValuePair<string, string>> GetItemsFromJsonElementArray(JsonProperty token, out string runtime)
{
var items = new List<KeyValuePair<string, string>>();
runtime = token.Name;
string architecture = string.Empty;
if (runtime.Contains('/'))
{
var parts = runtime.Split(new char[] { '/' }, 2);
runtime = parts[0];
architecture = parts[1];
}
foreach (var version in token.Value.EnumerateArray())
{
items.Add(new KeyValuePair<string, string>(version.GetString(), architecture));
}
return items.ToArray();
}
}
}
|