|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#if !RUNTIME_TYPE_NETCORE
using System;
using System.IO;
using System.Diagnostics;
#endif
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
#if !RUNTIME_TYPE_NETCORE
using Microsoft.Build.Shared.FileSystem;
using Microsoft.Build.Utilities;
#endif
#nullable disable
namespace Microsoft.Build.Tasks
{
/// <summary>
/// An interface containing public SGen task properties to make sure that all versions of the task have the same public surface.
/// </summary>
internal interface ISGenTaskContract
{
#region Properties
// Input files
[Required]
string BuildAssemblyName { get; set; }
[Required]
string BuildAssemblyPath { get; set; }
[Required]
bool ShouldGenerateSerializer { get; set; }
[Required]
bool UseProxyTypes { get; set; }
bool UseKeep { get; set; }
string[] References { get; set; }
string KeyContainer { get; set; }
string KeyFile { get; set; }
bool DelaySign { get; set; }
[Output]
ITaskItem[] SerializationAssembly { get; set; }
string SerializationAssemblyName { get; }
string SdkToolsPath { get; set; }
/// <summary>
/// Gets or Sets the Compiler Platform used by SGen to generate the output assembly.
/// </summary>
string Platform { get; set; }
/// <summary>
/// Gets or Sets a list of specific Types to generate serialization code for, SGen will generate serialization code only for those types.
/// </summary>
string[] Types { get; set; }
#endregion
}
#if RUNTIME_TYPE_NETCORE
public class SGen : ToolTaskExtension, ISGenTaskContract
{
#pragma warning disable format // region formatting is different in net7.0 and net472, and cannot be fixed for both
#region Properties
[Required]
public string BuildAssemblyName { get; set; }
[Required]
public string BuildAssemblyPath { get; set; }
[Required]
public bool ShouldGenerateSerializer { get; set; }
[Required]
public bool UseProxyTypes { get; set; }
public bool UseKeep { get; set; }
public string[] References { get; set; }
public string KeyContainer { get; set; }
public string KeyFile { get; set; }
public bool DelaySign { get; set; }
[Output]
public ITaskItem[] SerializationAssembly { get; set; }
public string SerializationAssemblyName { get; }
public string SdkToolsPath { get; set; }
public string Platform { get; set; }
public string[] Types { get; set; }
#endregion
#region Tool Members
protected override string ToolName
{
get
{
ErrorUtilities.ThrowInternalErrorUnreachable();
return null;
}
}
protected override string GenerateFullPathToTool()
{
ErrorUtilities.ThrowInternalErrorUnreachable();
return null;
}
public override bool Execute()
{
Log.LogErrorWithCodeFromResources("SGen.TaskNotSupported", nameof(SGen));
return false;
}
#endregion
#pragma warning restore format
}
#else
/// <summary>
/// Genererates a serialization assembly containing XML serializers for the input assembly.
/// </summary>
public class SGen : ToolTaskExtension, ISGenTaskContract
{
private string _buildAssemblyPath;
#pragma warning disable format // region formatting is different in net7.0 and net472, and cannot be fixed for both
#region Properties
// Input files
[Required]
public string BuildAssemblyName
{
set
{
ErrorUtilities.VerifyThrowArgumentNull(value, nameof(BuildAssemblyName));
Bag[nameof(BuildAssemblyName)] = value;
}
get => (string)Bag[nameof(BuildAssemblyName)];
}
[Required]
public string BuildAssemblyPath
{
set
{
ErrorUtilities.VerifyThrowArgumentNull(value, nameof(BuildAssemblyPath));
_buildAssemblyPath = value;
}
get
{
string thisPath;
try
{
thisPath = Path.GetFullPath(_buildAssemblyPath);
}
catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
{
// If it is an Expected Exception log the error
Log.LogErrorWithCodeFromResources("SGen.InvalidPath", "BuildAssemblyPath", e.Message);
throw;
}
return thisPath;
}
}
[Required]
public bool ShouldGenerateSerializer
{
set => Bag[nameof(ShouldGenerateSerializer)] = value;
get => GetBoolParameterWithDefault(nameof(ShouldGenerateSerializer), false);
}
[Required]
public bool UseProxyTypes
{
set => Bag[nameof(UseProxyTypes)] = value;
get => GetBoolParameterWithDefault(nameof(UseProxyTypes), false);
}
public bool UseKeep
{
set => Bag[nameof(UseKeep)] = value;
get => GetBoolParameterWithDefault(nameof(UseKeep), false);
}
public string[] References
{
set => Bag[nameof(References)] = value;
get => (string[])Bag[nameof(References)];
}
public string KeyContainer
{
set => Bag[nameof(KeyContainer)] = value;
get => (string)Bag[nameof(KeyContainer)];
}
public string KeyFile
{
set => Bag[nameof(KeyFile)] = value;
get => (string)Bag[nameof(KeyFile)];
}
public bool DelaySign
{
set => Bag[nameof(DelaySign)] = value;
get => GetBoolParameterWithDefault(nameof(DelaySign), false);
}
[Output]
public ITaskItem[] SerializationAssembly
{
set => Bag[nameof(SerializationAssembly)] = value;
get => (ITaskItem[])Bag[nameof(SerializationAssembly)];
}
public string SerializationAssemblyName
{
get
{
Debug.Assert(BuildAssemblyName.Length > 0, "Build assembly name is blank");
string prunedAssemblyName;
try
{
prunedAssemblyName = Path.GetFileNameWithoutExtension(BuildAssemblyName);
}
catch (ArgumentException e)
{
Log.LogErrorWithCodeFromResources("SGen.InvalidPath", "BuildAssemblyName", e.Message);
throw;
}
prunedAssemblyName += ".XmlSerializers.dll";
return prunedAssemblyName;
}
}
private string SerializationAssemblyPath
{
get
{
Debug.Assert(BuildAssemblyPath.Length > 0, "Build assembly path is blank");
return Path.Combine(BuildAssemblyPath, SerializationAssemblyName);
}
}
private string AssemblyFullPath => Path.Combine(BuildAssemblyPath, BuildAssemblyName);
public string SdkToolsPath
{
set => Bag[nameof(SdkToolsPath)] = value;
get => (string)Bag[nameof(SdkToolsPath)];
}
/// <summary>
/// Gets or Sets the Compiler Platform used by SGen to generate the output assembly.
/// </summary>
public string Platform
{
set => Bag[nameof(Platform)] = value;
get => (string)Bag[nameof(Platform)];
}
/// <summary>
/// Gets or Sets a list of specific Types to generate serialization code for, SGen will generate serialization code only for those types.
/// </summary>
public string[] Types
{
set => Bag[nameof(Types)] = value;
get => (string[])Bag[nameof(Types)];
}
#endregion
#region Tool Members
/// <summary>
/// The name of the tool to execute.
/// </summary>
protected override string ToolName => "sgen.exe";
/// <summary>
/// The full path of the tool to execute.
/// </summary>
protected override string GenerateFullPathToTool()
{
string pathToTool = null;
// If COMPLUS_InstallRoot\COMPLUS_Version are set (the dogfood world), we want to find it there, instead of
// the SDK, which may or may not be installed. The following will look there.
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("COMPLUS_InstallRoot")) || !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("COMPLUS_Version")))
{
pathToTool = ToolLocationHelper.GetPathToDotNetFrameworkFile(ToolExe, TargetDotNetFrameworkVersion.Latest);
}
if (String.IsNullOrEmpty(pathToTool) || !FileSystems.Default.FileExists(pathToTool))
{
pathToTool = SdkToolsPathUtility.GeneratePathToTool(SdkToolsPathUtility.FileInfoExists, ProcessorArchitecture.CurrentProcessArchitecture, SdkToolsPath, ToolExe, Log, true);
}
return pathToTool;
}
/// <summary>
/// Validate parameters, log errors and warnings and return true if Execute should proceed.
/// </summary>
protected override bool ValidateParameters()
{
// Ensure the references exist before passing them to SGen.exe
if (References != null)
{
foreach (string reference in References)
{
if (!FileSystems.Default.FileExists(reference))
{
Log.LogErrorWithCodeFromResources("SGen.ResourceNotFound", reference);
return false;
}
}
}
return true;
}
/// <summary>
/// Returns true if task execution is not necessary. Executed after ValidateParameters
/// </summary>
/// <returns></returns>
protected override bool SkipTaskExecution()
{
return SerializationAssembly == null && !ShouldGenerateSerializer;
}
/// <summary>
/// Returns a string with those switches and other information that can't go into a response file and
/// must go directly onto the command line.
/// Called after ValidateParameters and SkipTaskExecution
/// </summary>
protected override string GenerateCommandLineCommands()
{
var commandLineBuilder = new CommandLineBuilderExtension();
bool serializationAssemblyPathExists = false;
try
{
if (SerializationAssembly == null)
{
Debug.Assert(ShouldGenerateSerializer, "GenerateCommandLineCommands() should not be called if ShouldGenerateSerializer is true and SerializationAssembly is null.");
SerializationAssembly = [new TaskItem(SerializationAssemblyPath)];
}
// Add the assembly switch
commandLineBuilder.AppendSwitchIfNotNull("/assembly:", AssemblyFullPath);
commandLineBuilder.AppendWhenTrue("/proxytypes", Bag, "UseProxyTypes");
// add the keep switch
commandLineBuilder.AppendWhenTrue("/keep", Bag, "UseKeep");
// Append the references, if any.
if (References != null)
{
if (References.Length > 0)
{
// Use a single comma-delimited argument rather than multiple /reference:file
// arguments to save 11 characters per reference (" /reference:" versus ","),
// which can help squeak under the command-line length limit in the
// many-references case.
commandLineBuilder.AppendSwitchIfNotNull("/reference:", string.Join(",", References));
}
}
// Append the Types to the command line, if any.
if (Types != null)
{
foreach (string type in Types)
{
commandLineBuilder.AppendSwitchIfNotNull("/type:", type);
}
}
// The arguments to the "/compiler" switch are themselves switches to be passed to
// the compiler when generating the serialization assembly.
// Add the compiler command switches for strong naming on the serialization assembly
if (KeyFile != null)
{
commandLineBuilder.AppendNestedSwitch("/compiler:", "/keyfile:", KeyFile);
}
else if (KeyContainer != null)
{
commandLineBuilder.AppendNestedSwitch("/compiler:", "/keycontainer:", KeyContainer);
}
commandLineBuilder.AppendPlusOrMinusSwitch("/compiler:/delaysign", Bag, "DelaySign");
// Add the Platform switch to the compiler.
if (Platform != null)
{
commandLineBuilder.AppendNestedSwitch("/compiler:", "/platform:", Platform);
}
serializationAssemblyPathExists = FileSystems.Default.FileExists(SerializationAssemblyPath);
}
catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
{
// Ignore the expected exceptions because they have already been logged
}
// Delete the assembly if it already exists.
if (serializationAssemblyPathExists)
{
try
{
File.Delete(SerializationAssemblyPath);
}
// Of all of the exceptions that can be thrown on a File.Delete, the only ones we need to
// be immediately concerned with are the UnauthorizedAccessException and the IOException
// (file is in use exception). We need to make sure that the assembly is gone before we
// try to produce a new one because it is possible that after some changes were made to the
// base assembly, there will, in fact, not be a serialization assembly produced. We cannot
// leave the earlier produced assembly around to be propagated by later processes.
catch (UnauthorizedAccessException e)
{
Log.LogErrorWithCodeFromResources("SGen.CouldNotDeleteSerializer", SerializationAssemblyPath, e.Message);
}
catch (IOException e)
{
Log.LogErrorWithCodeFromResources("SGen.CouldNotDeleteSerializer", SerializationAssemblyPath, e.Message);
}
// The DirectoryNotFoundException is safely ignorable since that means that there is no
// existing serialization assembly. This would be extremely unlikely anyway because we
// found the serializer just a couple of milliseconds ago.
}
return commandLineBuilder.ToString();
}
#endregion
#pragma warning restore format
}
#endif
}
|