|
// 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 System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace Microsoft.Tools.ServiceModel.Svcutil
{
internal class SvcutilBootstrapper : IDisposable
{
private const string ProjectName = "SvcutilBootstrapper.csproj";
private static readonly string s_bootstrapperParamsFileName = $"{Tool.ToolName}-bootstrapper.params.json";
private MSBuildProj MSBuildProj { get; set; }
internal SvcutilOptions Options { get; set; }
public SvcutilBootstrapper(SvcutilOptions options)
{
this.Options = options ?? throw new ArgumentNullException(nameof(options));
this.Options.ProviderId = $"{Tool.ToolName}-bootstrap";
this.Options.Version = Tool.PackageVersion;
// reset options that don't apply to the bootstrapper and that prevent bootstrapping recursion.
this.Options.NoBootstrapping = true;
this.Options.NoLogo = true;
this.Options.NoProjectUpdates = true;
// the operational context has some control over what messages to display on the UI.
if (this.Options.ToolContext.HasValue && this.Options.ToolContext.Value <= OperationalContext.Global)
{
this.Options.ToolContext = OperationalContext.Bootstrapper;
}
ProjectDependency.RemoveRedundantReferences(this.Options.References);
}
internal static bool RequiresBootstrapping(FrameworkInfo targetFramework, IEnumerable<ProjectDependency> references)
{
// Bootstrapping is required for type reuse when targetting a supported .NET Core platform and when there are project references
// different form the .NET Core and WCF ones.
return targetFramework.IsDnx && references.Where(r => !r.IsFramework).Except(TargetFrameworkHelper.ServiceModelPackages).Count() > 0;
}
internal async Task<ProcessRunner.ProcessResult> BoostrapSvcutilAsync(bool keepBootstrapperDir, ILogger logger, CancellationToken cancellationToken)
{
bool redirectOutput = false;
if (this.Options.ToolContext == OperationalContext.Infrastructure)
{
redirectOutput = true;
}
ProcessRunner.ProcessResult result = null;
using (await SafeLogger.WriteStartOperationAsync(logger, "Bootstrapping svcutil ...").ConfigureAwait(false))
{
// guard against bootstrapping recursion.
if (this.Options.NoBootstrapping != true)
{
Debug.Fail($"The NoBootstrapping property is not set, this would cause infinite bootstrapping recursion!");
return null;
}
if (this.Options.Project != null && StringComparer.OrdinalIgnoreCase.Compare(this.Options.Project.FileName, SvcutilBootstrapper.ProjectName) == 0)
{
Debug.Fail("Bootstrapping is enabled for the bootstrapper! This would cause an infinite bootstrapping recursion!");
return null;
}
// When in Infrastructure mode (WCF CS) it is assumed the initial progress message is to be presented by the calling tool.
ToolConsole.WriteLineIf(ToolConsole.ToolModeLevel != OperationalContext.Infrastructure, Resource.BootstrappingApplicationMsg);
await GenerateProjectAsync(keepBootstrapperDir, logger, cancellationToken).ConfigureAwait(false);
await GenerateProgramFileAsync(logger, cancellationToken).ConfigureAwait(false);
var paramsFilePath = await GenerateParamsFileAsync(logger, cancellationToken).ConfigureAwait(false);
await BuildBootstrapProjectAsync(logger, cancellationToken).ConfigureAwait(false);
ToolConsole.WriteLineIf(ToolConsole.Verbosity >= Verbosity.Verbose, Resource.InvokingProjectMsg);
result = await ProcessRunner.RunAsync("dotnet", $"run \"{paramsFilePath}\"", this.MSBuildProj.DirectoryPath, redirectOutput, logger, cancellationToken).ConfigureAwait(false);
MarkupTelemetryHelper.TelemetryPostOperation(result.ExitCode == 0, "Invoke svcutil bootstrapper");
}
return result;
}
internal async Task GenerateProjectAsync(bool keepBootstrapperDir, ILogger logger, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var projectFullPath = Path.Combine(this.Options.BootstrapPath.FullName, nameof(SvcutilBootstrapper), SvcutilBootstrapper.ProjectName);
if (keepBootstrapperDir)
{
// creating the directory prevents the bootstrapper project from deleting it as it doesn't own it, files will not be deleted either.
Directory.CreateDirectory(Path.GetDirectoryName(projectFullPath));
}
// If the target framework was provided, it was validated already when processing params.
// Bootstrapping is enabled only for netcoreapp or netstandard TFM, let's check.
bool isSupportedTFM = TargetFrameworkHelper.IsSupportedFramework(this.Options.TargetFramework.FullName, out var frameworkInfo);
Debug.Assert(frameworkInfo.IsDnx, "The bootstrapper has been enabled for a non-DNX platform!");
ToolConsole.WriteLineIf(ToolConsole.Verbosity >= Verbosity.Verbose, Resource.CreatingProjectMsg);
using (await SafeLogger.WriteStartOperationAsync(logger, $"Creating project file: \"{projectFullPath}\"").ConfigureAwait(false))
{
var svcutilPkgRef = ProjectDependency.FromAssembly(Path.Combine(Path.GetDirectoryName(Tool.FullPath), Tool.AssemblyName + ".dll"));
if (Options.ToolContext == OperationalContext.Infrastructure)
{
svcutilPkgRef = ProjectDependency.FromPackage(Tool.AssemblyName, Tool.PackageVersion);
}
this.MSBuildProj = await MSBuildProj.DotNetNewAsync(projectFullPath, logger, cancellationToken).ConfigureAwait(false);
this.MSBuildProj.AddDependency(svcutilPkgRef, true);
var targetFramework = frameworkInfo.FullName;
if (isSupportedTFM && frameworkInfo.Name != FrameworkInfo.Netstandard && frameworkInfo.Version.CompareTo(new Version(6, 0)) >= 0)
{
this.MSBuildProj.TargetFramework = targetFramework;
if(targetFramework.ToLowerInvariant().Contains("net7.0-windows10"))
{
this.MSBuildProj.SetEnableMsixTooling();
}
}
// else
// The TFM is Netstandard or version lower than 6.0 or unknown: either, it was not provided or it is a version not yet known to the tool,
// we will use the default TF from the generated project.
}
foreach (ProjectDependency dependency in this.Options.References)
{
this.MSBuildProj.AddDependency(dependency);
}
if (!string.IsNullOrEmpty(this.Options.RuntimeIdentifier))
{
this.MSBuildProj.RuntimeIdentifier = this.Options.RuntimeIdentifier;
}
// Don't treat warnings as errors so the bootstrapper will succeed as often as possible.
this.MSBuildProj.ClearWarningsAsErrors();
await this.MSBuildProj.SaveAsync(logger, cancellationToken).ConfigureAwait(false);
}
private static readonly string s_programClass =
@"using System;
namespace SvcutilBootstrap {
public class Program {
public static int Main(string[] args) {
return Microsoft.Tools.ServiceModel.Svcutil.Tool.Main(args);
}
}
}";
internal async Task GenerateProgramFileAsync(ILogger logger, CancellationToken cancellationToken)
{
using (var safeLogger = await SafeLogger.WriteStartOperationAsync(logger, "Generating Program.cs ...").ConfigureAwait(false))
{
string programFilePath = Path.Combine(this.MSBuildProj.DirectoryPath, "Program.cs");
File.WriteAllText(programFilePath, s_programClass);
}
}
internal async Task<string> GenerateParamsFileAsync(ILogger logger, CancellationToken cancellationToken)
{
var paramsFilePath = Path.Combine(this.MSBuildProj.DirectoryPath, s_bootstrapperParamsFileName);
using (await SafeLogger.WriteStartOperationAsync(logger, $"Generating {paramsFilePath} params file ...").ConfigureAwait(false))
{
await AsyncHelper.RunAsync(() => this.Options.Save(paramsFilePath), cancellationToken).ConfigureAwait(false);
return paramsFilePath;
}
}
internal async Task BuildBootstrapProjectAsync(ILogger logger, CancellationToken cancellationToken)
{
string outputText;
string errorMessage = null;
try
{
ToolConsole.WriteLineIf(ToolConsole.Verbosity >= Verbosity.Verbose, Resource.RestoringNuGetPackagesMsg);
var restoreResult = await this.MSBuildProj.RestoreAsync(logger, cancellationToken).ConfigureAwait(false);
MarkupTelemetryHelper.TelemetryPostOperation(restoreResult.ExitCode == 0, "Restore bootstrapper");
if (restoreResult.ExitCode != 0)
{
ToolConsole.WriteWarning(restoreResult.OutputText);
}
ToolConsole.WriteLineIf(ToolConsole.Verbosity >= Verbosity.Verbose, Resource.BuildingProjectMsg);
var buildResult = await this.MSBuildProj.BuildAsync(logger, cancellationToken).ConfigureAwait(false);
MarkupTelemetryHelper.TelemetryPostOperation(buildResult.ExitCode == 0, "Build bootstrapper");
}
catch (ProcessRunner.ProcessException exception)
{
throw new BootstrapException(string.Format(CultureInfo.CurrentCulture, "{0}{1}{2}", exception.Message, Environment.NewLine, Resource.BootstrapErrorDisableReferences));
}
}
#region IDisposable Support
private bool _disposedValue; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
this.MSBuildProj?.Dispose();
}
_disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
}
}
|