|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.DotNet.Build.Tasks;
using Microsoft.Cci;
using Microsoft.Cci.Extensions;
using Microsoft.Cci.Extensions.CSharp;
using Microsoft.Cci.Filters;
using Microsoft.Cci.Writers;
using Microsoft.Cci.Writers.CSharp;
using Microsoft.Cci.Writers.Syntax;
namespace Microsoft.DotNet.GenAPI
{
public class GenAPITask : BuildTask
{
private const string InternalsVisibleTypeName = "System.Runtime.CompilerServices.InternalsVisibleToAttribute";
private const string DefaultFileHeader =
"//------------------------------------------------------------------------------\r\n" +
"// <auto-generated>\r\n" +
"// This code was generated by a tool.\r\n" +
"// GenAPI Version: {0}\r\n" +
"//\r\n" +
"// Changes to this file may cause incorrect behavior and will be lost if\r\n" +
"// the code is regenerated.\r\n" +
"// </auto-generated>\r\n" +
"//------------------------------------------------------------------------------\r\n";
private WriterType _writerType;
private SyntaxWriterType _syntaxWriterType;
private DocIdKinds _docIdKinds = Cci.Writers.DocIdKinds.All;
/// <summary>
/// Path for an specific assembly or a directory to get all assemblies.
/// </summary>
[Required]
public string Assembly { get; set; }
/// <summary>
/// Delimited (',' or ';') set of paths to use for resolving assembly references.
/// </summary>
public string LibPath { get; set; }
/// <summary>
/// Specify a api list in the DocId format of which APIs to include.
/// </summary>
public string ApiList { get; set; }
/// <summary>
/// Output path. Default is the console. Can specify an existing directory as well and
/// then a file will be created for each assembly with the matching name of the assembly.
/// </summary>
public string OutputPath { get; set; }
/// <summary>
/// Specify a file with an alternate header content to prepend to output.
/// </summary>
public string HeaderFile { get; set; }
/// <summary>
/// Specify the writer type to use. Legal values: CSDecl, DocIds, TypeForwards, TypeList. Default is CSDecl.
/// </summary>
public string WriterType
{
get => _writerType.ToString();
set => _writerType = string.IsNullOrWhiteSpace(value) ? default : (WriterType) Enum.Parse(typeof(WriterType), value, true);
}
/// <summary>
/// Specific the syntax writer type. Only used if the writer is CSDecl. Legal values: Text, Html, Xml. Default is Text.
/// </summary>
public string SyntaxWriterType
{
get => _syntaxWriterType.ToString();
set => _syntaxWriterType = string.IsNullOrWhiteSpace(value) ? default : (SyntaxWriterType)Enum.Parse(typeof(SyntaxWriterType), value, true);
}
/// <summary>
/// Only include API of the specified kinds. Legal values: A, Assembly, Namespace, N, T, Type, Field, F, P, Property, Method, M, Event, E, All. Default is All.
/// </summary>
public string DocIdKinds
{
get => _docIdKinds.ToString();
set => _docIdKinds = string.IsNullOrWhiteSpace(value) ? Cci.Writers.DocIdKinds.All : (DocIdKinds)Enum.Parse(typeof(DocIdKinds), value, true);
}
/// <summary>
/// Method bodies should throw PlatformNotSupportedException.
/// </summary>
public string ExceptionMessage { get; set; }
/// <summary>
/// Include global prefix for compilation.
/// </summary>
public bool GlobalPrefix { get; set; }
/// <summary>
/// Specify a api list in the DocId format of which APIs to exclude.
/// </summary>
public string ExcludeApiList { get; set; }
/// <summary>
/// Specify a list in the DocId format of which attributes should be excluded from being applied on apis.
/// </summary>
public string ExcludeAttributesList { get; set; }
/// <summary>
/// [CSDecl] Resolve type forwards and include its members.
/// </summary>
public bool FollowTypeForwards { get; set; }
/// <summary>
/// [CSDecl] Include only API's not CS code that compiles.
/// </summary>
public bool ApiOnly { get; set; }
/// <summary>
/// Include all API's not just public APIs. Default is public only.
/// </summary>
public bool All { get; set; }
/// <summary>
/// Include both internal and public APIs if assembly contains an InternalsVisibleTo attribute. Otherwise, include only public APIs.
/// </summary>
public bool RespectInternals { get; set; }
/// <summary>
/// Exclude APIs marked with a CompilerGenerated attribute.
/// </summary>
public bool ExcludeCompilerGenerated { get; set; }
/// <summary>
/// [CSDecl] Include member headings for each type of member.
/// </summary>
public bool MemberHeadings { get; set; }
/// <summary>
/// [CSDecl] Highlight overridden base members.
/// </summary>
public bool HighlightBaseMembers { get; set; }
/// <summary>
/// [CSDecl] Highlight interface implementation members.
/// </summary>
public bool HighlightInterfaceMembers { get; set; }
/// <summary>
/// [CSDecl] Include base types, interfaces, and attributes, even when those types are filtered.
/// </summary>
public bool AlwaysIncludeBase { get; set; }
/// <summary>
/// [CSDecl] Specify one or more namespace+type list(s) in the DocId format of types that should be wrapped inside an #if with the symbol specified in the <c>Symbol</c> metadata item.
/// If the <c>Symbol</c> metadata item is empty the types won't be wrapped but will still be output after all other types, this can be used in combination with <see cref="DefaultCondition" /> to wrap all other types.
/// </summary>
public ITaskItem[] ConditionalTypeLists { get; set; }
/// <summary>
/// [CSDecl] #if condition to apply to all types not included in <see cref="ConditionalTypeLists" />.
/// </summary>
public string DefaultCondition { get; set; }
/// <summary>
/// Exclude members when return value or parameter types are excluded.
/// </summary>
public bool ExcludeMembers { get; set; }
/// <summary>
/// [CSDecl] Language Version to target.
/// </summary>
public string LangVersion { get; set; }
public override bool Execute()
{
HostEnvironment host = new();
host.UnableToResolve += (sender, e) =>
Log.LogWarning("Unable to resolve assembly '{0}' referenced by '{1}'.", e.Unresolved.ToString(), e.Referrer.ToString());
host.UnifyToLibPath = true;
if (!string.IsNullOrWhiteSpace(LibPath))
{
host.AddLibPaths(HostEnvironment.SplitPaths(LibPath));
}
IEnumerable<IAssembly> assemblies = host.LoadAssemblies(HostEnvironment.SplitPaths(Assembly));
if (!assemblies.Any())
{
Log.LogError("ERROR: Failed to load any assemblies from '{0}'", Assembly);
return false;
}
string headerText = GetHeaderText(HeaderFile, _writerType, _syntaxWriterType);
bool loopPerAssembly = Directory.Exists(OutputPath);
if (loopPerAssembly)
{
foreach (IAssembly assembly in assemblies)
{
using (TextWriter output = GetOutput(OutputPath, GetFilename(assembly, _writerType, _syntaxWriterType)))
using (IStyleSyntaxWriter syntaxWriter = GetSyntaxWriter(output, _writerType, _syntaxWriterType))
{
ICciWriter writer = null;
try
{
if (headerText != null)
{
output.Write(headerText);
}
bool includeInternals = RespectInternals &&
assembly.Attributes.HasAttributeOfType(InternalsVisibleTypeName);
writer = GetWriter(output, syntaxWriter, includeInternals);
writer.WriteAssemblies(new IAssembly[] { assembly });
}
finally
{
if (writer is CSharpWriter csWriter)
{
csWriter.Dispose();
}
}
}
}
}
else
{
using (TextWriter output = GetOutput(OutputPath))
using (IStyleSyntaxWriter syntaxWriter = GetSyntaxWriter(output, _writerType, _syntaxWriterType))
{
ICciWriter writer = null;
try
{
if (headerText != null)
{
output.Write(headerText);
}
bool includeInternals = RespectInternals &&
assemblies.Any(assembly => assembly.Attributes.HasAttributeOfType(InternalsVisibleTypeName));
writer = GetWriter(output, syntaxWriter, includeInternals);
writer.WriteAssemblies(assemblies);
}
finally
{
if (writer is CSharpWriter csWriter)
{
csWriter.Dispose();
}
}
}
}
return !Log.HasLoggedErrors;
}
private static string GetHeaderText(string headerFile, WriterType writerType, SyntaxWriterType syntaxWriterType)
{
if (!string.IsNullOrEmpty(headerFile))
{
return File.ReadAllText(headerFile);
}
string defaultHeader = string.Empty;
// This header is for CS source only
if ((writerType == GenAPI.WriterType.CSDecl || writerType == GenAPI.WriterType.TypeForwards) &&
syntaxWriterType == GenAPI.SyntaxWriterType.Text)
{
// Write default header (culture-invariant, so that the generated file will not be language-dependent)
defaultHeader = string.Format(CultureInfo.InvariantCulture,
DefaultFileHeader, typeof(GenAPITask).Assembly.GetName().Version.ToString());
}
return defaultHeader;
}
private static TextWriter GetOutput(string outFilePath, string filename = "")
{
// If this is a null, empty, whitespace, or a directory use console
if (string.IsNullOrWhiteSpace(outFilePath))
return Console.Out;
if (Directory.Exists(outFilePath) && !string.IsNullOrEmpty(filename))
{
return File.CreateText(Path.Combine(outFilePath, filename));
}
return File.CreateText(outFilePath);
}
private static string GetFilename(IAssembly assembly, WriterType writer, SyntaxWriterType syntax)
{
string name = assembly.Name.Value;
return writer switch
{
GenAPI.WriterType.DocIds or GenAPI.WriterType.TypeForwards => name + ".txt",
_ => syntax switch
{
GenAPI.SyntaxWriterType.Xml => name + ".xml",
GenAPI.SyntaxWriterType.Html => name + ".html",
_ => name + ".cs",
},
};
}
private static ICciFilter GetFilter(
string apiList,
bool all,
bool includeInternals,
bool apiOnly,
bool excludeCompilerGenerated,
string excludeApiList,
bool excludeMembers,
string excludeAttributesList,
bool includeForwardedTypes)
{
ICciFilter includeFilter;
if (!string.IsNullOrWhiteSpace(apiList))
{
includeFilter = new DocIdIncludeListFilter(apiList);
}
else if (all)
{
includeFilter = new IncludeAllFilter();
}
else if (includeInternals)
{
includeFilter = new InternalsAndPublicCciFilter(excludeAttributes: apiOnly, includeForwardedTypes);
}
else
{
includeFilter = new PublicOnlyCciFilter(excludeAttributes: apiOnly, includeForwardedTypes);
}
if (excludeCompilerGenerated)
{
includeFilter = new IntersectionFilter(includeFilter, new ExcludeCompilerGeneratedCciFilter());
}
if (!string.IsNullOrWhiteSpace(excludeApiList))
{
includeFilter = new IntersectionFilter(includeFilter, new DocIdExcludeListFilter(excludeApiList, excludeMembers) { IncludeForwardedTypes = includeForwardedTypes });
}
if (!string.IsNullOrWhiteSpace(excludeAttributesList))
{
includeFilter = new IntersectionFilter(includeFilter, new ExcludeAttributesFilter(excludeAttributesList));
}
return includeFilter;
}
private static IStyleSyntaxWriter GetSyntaxWriter(TextWriter output, WriterType writer, SyntaxWriterType syntax)
{
if (writer != GenAPI.WriterType.CSDecl && writer != GenAPI.WriterType.TypeList)
return null;
return syntax switch
{
GenAPI.SyntaxWriterType.Xml => new OpenXmlSyntaxWriter(output),
GenAPI.SyntaxWriterType.Html => new HtmlSyntaxWriter(output),
_ => new TextSyntaxWriter(output) { SpacesInIndent = 4 },
};
}
private ICciWriter GetWriter(TextWriter output, ISyntaxWriter syntaxWriter, bool includeInternals)
{
ICciFilter filter = GetFilter(
ApiList,
All,
includeInternals,
ApiOnly,
ExcludeCompilerGenerated,
ExcludeApiList,
ExcludeMembers,
ExcludeAttributesList,
FollowTypeForwards);
switch (_writerType)
{
case GenAPI.WriterType.DocIds:
return new DocumentIdWriter(output, filter, _docIdKinds);
case GenAPI.WriterType.TypeForwards:
return new TypeForwardWriter(output, filter)
{
IncludeForwardedTypes = true
};
case GenAPI.WriterType.TypeList:
return new TypeListWriter(syntaxWriter, filter);
case GenAPI.WriterType.CSDecl:
default:
{
CSharpWriter writer = new(syntaxWriter, filter, ApiOnly);
writer.IncludeSpaceBetweenMemberGroups = writer.IncludeMemberGroupHeadings = MemberHeadings;
writer.HighlightBaseMembers = HighlightBaseMembers;
writer.HighlightInterfaceMembers = HighlightInterfaceMembers;
writer.PutBraceOnNewLine = true;
writer.PlatformNotSupportedExceptionMessage = ExceptionMessage;
writer.IncludeGlobalPrefixForCompilation = GlobalPrefix;
writer.AlwaysIncludeBase = AlwaysIncludeBase;
writer.LangVersion = GetLangVersion(LangVersion);
writer.IncludeForwardedTypes = FollowTypeForwards;
writer.DefaultCondition = DefaultCondition;
if (ConditionalTypeLists != null)
{
writer.ConditionalTypeLists = ConditionalTypeLists.Select(c =>
new CSharpWriter.ConditionalTypeList
{
Symbol = c.GetMetadata("Symbol"),
Filter = new DocIdIncludeListFilter(c.ItemSpec) { IncludeForwardedTypes = FollowTypeForwards }
});
}
return writer;
}
}
}
private static Version GetLangVersion(string langVersion)
{
string langVersionValue = langVersion ?? string.Empty;
if (langVersionValue.Equals("default", StringComparison.OrdinalIgnoreCase))
{
return CSDeclarationWriter.LangVersionDefault;
}
else if (langVersionValue.Equals("latest", StringComparison.OrdinalIgnoreCase))
{
return CSDeclarationWriter.LangVersionLatest;
}
else if (langVersionValue.Equals("preview", StringComparison.OrdinalIgnoreCase))
{
return CSDeclarationWriter.LangVersionPreview;
}
else if (Version.TryParse(langVersionValue, out Version parsedVersion))
{
return parsedVersion;
}
else
{
return CSDeclarationWriter.LangVersionDefault;
}
}
}
}
|