200 lines
7.4 KiB
C#
200 lines
7.4 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using Microsoft.Build.Framework;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
|
|
namespace Microsoft.DotNet.UnifiedBuild.Tasks
|
|
{
|
|
/// <summary>
|
|
/// Extends the SDK to handle "SOURCE_BUILT_SDK_*" override environment variables. Each override
|
|
/// should provide a set of 3 environment variables:
|
|
///
|
|
/// SOURCE_BUILT_SDK_ID_EXAMPLE=Your.Sdk.Example
|
|
/// ID of the SDK nuget package to override.
|
|
///
|
|
/// SOURCE_BUILT_SDK_DIR_EXAMPLE=/git/repo/bin/extracted/Your.Sdk.Example/
|
|
/// Directory where the sdk/Sdk.props and/or sdk/Sdk.targets files are located. This should be
|
|
/// the directory where the override SDK package is extracted.
|
|
///
|
|
/// SOURCE_BUILT_SDK_VERSION_EXAMPLE=1.0.0-source-built
|
|
/// (Optional) Version of the SDK package to use. This is informational.
|
|
/// </summary>
|
|
public class UnifiedBuildSdkResolver : SdkResolver
|
|
{
|
|
public override string Name => nameof(UnifiedBuildSdkResolver);
|
|
|
|
public override int Priority => 0;
|
|
|
|
public override SdkResult Resolve(
|
|
SdkReference sdkReference,
|
|
SdkResolverContext resolverContext,
|
|
SdkResultFactory factory)
|
|
{
|
|
string sdkDescription = sdkReference.Name;
|
|
if (!string.IsNullOrEmpty(sdkReference.Version))
|
|
{
|
|
sdkDescription += $" {sdkReference.Version}";
|
|
}
|
|
if (!string.IsNullOrEmpty(sdkReference.MinimumVersion))
|
|
{
|
|
sdkDescription += $" (>= {sdkReference.MinimumVersion})";
|
|
}
|
|
|
|
SourceBuiltSdkOverride[] overrides = Environment.GetEnvironmentVariables()
|
|
.Cast<DictionaryEntry>()
|
|
.Select(SourceBuiltSdkOverride.Create)
|
|
.Where(o => o != null)
|
|
.ToArray();
|
|
|
|
void LogMessage(string message)
|
|
{
|
|
resolverContext.Logger.LogMessage($"[{Name}] {message}", MessageImportance.Low);
|
|
}
|
|
|
|
if (overrides.Any())
|
|
{
|
|
string separator = overrides.Length == 1 ? " " : Environment.NewLine;
|
|
|
|
LogMessage(
|
|
$"Looking for SDK {sdkDescription}. Detected config(s) in env:{separator}" +
|
|
string.Join(Environment.NewLine, overrides.Select(o => o.ToString())));
|
|
}
|
|
|
|
SourceBuiltSdkOverride[] matches = overrides
|
|
.Where(o => sdkReference.Name.Equals(o?.Id, StringComparison.OrdinalIgnoreCase))
|
|
.ToArray();
|
|
|
|
var unresolvableReasons = new List<string>();
|
|
|
|
if (matches.Length != 1)
|
|
{
|
|
unresolvableReasons.Add(
|
|
$"{matches.Length} overrides found for '{sdkReference.Name}'");
|
|
}
|
|
else
|
|
{
|
|
SourceBuiltSdkOverride match = matches[0];
|
|
string[] matchProblems = match.GetValidationErrors().ToArray();
|
|
|
|
if (matchProblems.Any())
|
|
{
|
|
unresolvableReasons.Add($"Found match '{match.Group}' with problems:");
|
|
unresolvableReasons.AddRange(matchProblems);
|
|
}
|
|
else
|
|
{
|
|
LogMessage($"Overriding {sdkDescription} with '{match.Group}'");
|
|
|
|
return factory.IndicateSuccess(match.SdkDir, match.Version);
|
|
}
|
|
}
|
|
|
|
return factory.IndicateFailure(unresolvableReasons.Select(r => $"[{Name}] {r}"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes a directory path (not ending in a separator) and determines if it is the "sdk"
|
|
/// directory inside a SDK package with a case-insensitive comparison.
|
|
/// </summary>
|
|
private static bool IsSdkDirectory(string path)
|
|
{
|
|
return Path.GetFileName(path).Equals("sdk", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private class SourceBuiltSdkOverride
|
|
{
|
|
private const string EnvPrefix = "SOURCE_BUILT_SDK_";
|
|
private const string EnvId = EnvPrefix + "ID_";
|
|
private const string EnvVersion = EnvPrefix + "VERSION_";
|
|
private const string EnvDir = EnvPrefix + "DIR_";
|
|
|
|
public static SourceBuiltSdkOverride Create(DictionaryEntry entry)
|
|
{
|
|
if (entry.Key is string key && key.StartsWith(EnvId))
|
|
{
|
|
// E.g. "ARCADE" from "SOURCE_BUILD_SDK_ID_ARCADE=Microsoft.DotNet.Arcade.Sdk".
|
|
string group = key.Substring(EnvId.Length);
|
|
|
|
if (string.IsNullOrEmpty(group))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
string id = entry.Value as string;
|
|
string version = Environment.GetEnvironmentVariable(EnvVersion + group);
|
|
string dir = Environment.GetEnvironmentVariable(EnvDir + group);
|
|
|
|
if (string.IsNullOrEmpty(version))
|
|
{
|
|
version = "1.0.0-source-built";
|
|
}
|
|
|
|
string sdkDir = null;
|
|
if (!string.IsNullOrEmpty(dir))
|
|
{
|
|
sdkDir = Directory.EnumerateDirectories(dir).FirstOrDefault(IsSdkDirectory);
|
|
}
|
|
|
|
return new SourceBuiltSdkOverride
|
|
{
|
|
Group = group,
|
|
Id = id,
|
|
Version = version,
|
|
Dir = dir,
|
|
SdkDir = sdkDir,
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Name of the environment variable group, used to associate the multiple env vars.
|
|
/// </summary>
|
|
public string Group { get; set; }
|
|
|
|
/// <summary>
|
|
/// ID of the SDK package.
|
|
/// </summary>
|
|
public string Id { get; set; }
|
|
|
|
/// <summary>
|
|
/// Version of the source-built SDK package to use instead.
|
|
/// </summary>
|
|
public string Version { get; set; }
|
|
|
|
/// <summary>
|
|
/// Directory where the source-built SDK files are found, in extracted form.
|
|
/// </summary>
|
|
public string Dir { get; set; }
|
|
|
|
/// <summary>
|
|
/// Directory where the Sdk.props/Sdk.targets files are found, inside Dir.
|
|
/// </summary>
|
|
public string SdkDir { get; set; }
|
|
|
|
public override string ToString() => $"'{Group}' for '{Id}/{Version}' at '{Dir}'";
|
|
|
|
public IEnumerable<string> GetValidationErrors()
|
|
{
|
|
if (string.IsNullOrEmpty(Id))
|
|
{
|
|
yield return $"'{EnvId}{Group}' not specified.";
|
|
}
|
|
if (string.IsNullOrEmpty(Dir))
|
|
{
|
|
yield return $"'{EnvDir}{Group}' not specified.";
|
|
}
|
|
else if (string.IsNullOrEmpty(SdkDir))
|
|
{
|
|
yield return $"Didn't find any 'sdk' directory in SDK package dir '{Dir}'";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|