Skip to content

Commit

Permalink
feat: Support unusual dependencies in generated libraries for namespa…
Browse files Browse the repository at this point in the history
…ce aliasing.
  • Loading branch information
amanda-tarafa committed Feb 27, 2023
1 parent 5433e48 commit 737c3d1
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 19 deletions.
51 changes: 34 additions & 17 deletions Google.Api.Generator.Utils/SourceFileContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,16 @@ public Unaliased(
IClock clock,
IReadOnlyDictionary<string, string> wellKnownNamespaceAliases,
IReadOnlyCollection<Regex> avoidAliasingNamespaceRegex,
IReadOnlyCollection<string> forcedAliases,
IEnumerable<Typ> packageTyps,
bool maySkipOwnNamespaceImport) : base(clock) =>
(_wellKnownNamespaceAliases, _avoidAliasingNamespaceRegex, _packageTyps, _maySkipOwnNamespaceImport) =
(wellKnownNamespaceAliases, avoidAliasingNamespaceRegex, packageTyps, maySkipOwnNamespaceImport);
(_wellKnownNamespaceAliases, _avoidAliasingNamespaceRegex, _forcedAliases, _packageTyps, _maySkipOwnNamespaceImport) =
(wellKnownNamespaceAliases, avoidAliasingNamespaceRegex, forcedAliases, packageTyps, maySkipOwnNamespaceImport);

private readonly bool _maySkipOwnNamespaceImport;
private readonly IReadOnlyDictionary<string, string> _wellKnownNamespaceAliases;
private readonly IReadOnlyCollection<Regex> _avoidAliasingNamespaceRegex;
private readonly IReadOnlyCollection<string> _forcedAliases;
private readonly IEnumerable<Typ> _packageTyps;
// Seen namespaces. The associated bool value indicates whether we may skip import or not.
private readonly Dictionary<string, bool> _seenNamespaces = new Dictionary<string, bool>();
Expand Down Expand Up @@ -198,7 +200,8 @@ public static TypeAliaser Create(
IReadOnlyCollection<Typ> seenTypes,
IEnumerable<Typ> packageTyps,
IReadOnlyDictionary<string, string> wellKnownNamespaceAliases,
IReadOnlyCollection<Regex> avoidAliasingNamespaceRegex)
IReadOnlyCollection<Regex> avoidAliasingNamespaceRegex,
IReadOnlyCollection<string> forcedAliases)
{
// Let's copy some of these collections over so we can make changes.
// Also, copy them to more usable collections.
Expand All @@ -207,8 +210,9 @@ public static TypeAliaser Create(
// imported unaliased. We don't look at skippable namespaces because those are only imported aliased, if imported.
// We can safely exclude generic types because we don't generate any generic types and we know there
// are no clashes between generic types in the non generated dependencies.
// This mechanism isn't fool-proof, but will work fine in most cases because this generator
// depends on all assemblies that a generated library will depend on, and on the protobuf generated code.
// This mechanism is now fool-proof, because this generator depends on most assemblies that a generated library
// will depend on, knows protobuf generated code and has a list of known aliases to force per library for the
// sporadic dependencies that the library being generated may have that this generator hasn't.
// Type name -> namespaces through which it could be imported.
var couldBeImportedTypes = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic)
Expand Down Expand Up @@ -247,28 +251,40 @@ public static TypeAliaser Create(
// from other types. These types may not be aliased if used in the context of their own namespace. Type -> namespaces.
Dictionary<string, HashSet<string>> nonCollidingAliasedTypes = new Dictionary<string, HashSet<string>>();

// Let's alias namespaces until we have no collisions.
// Let's alias namespaces that we are forced to alias.
foreach (string ns in forcedAliases.Where(fns => mustImportNs.Contains(fns)))
{
Alias(ns);
}

// Now let's alias remaining namespaces until we have no collisions.
while (GetTopCollidingNamespace() is string topCollidingNamespace)
{
Alias(topCollidingNamespace);
}

// We have no collisions now. We can safely import remaining namespaces.
unaliasedNamespaces.UnionWith(mustImportNs);

return new TypeAliaser(aliasedNamespaces, unaliasedNamespaces, collidingAliasedTypes, nonCollidingAliasedTypes);

// Alias a namespace and all types from it.
void Alias(string ns)
{
// First alias the namespace.
var alias = ImportAliased(topCollidingNamespace);
var alias = ImportAliased(ns);
// Now alias all seen types from that namespace, colliding or not.
foreach (var t in seenTypes.Where(t => t.Namespace == topCollidingNamespace))
foreach (var t in seenTypes.Where(t => t.Namespace == ns))
{
AliasType(t.Name, topCollidingNamespace);
AliasType(t.Name, ns);
}
// If there are still colliding types from this namespace, we can safely remove them.
// They are not seen types that we need to alias, they are types that would have
// been imported if the namespace were to be imported unaliased, but we just aliased
// the namespace.
RemoveNamespaceFromColliding(topCollidingNamespace);
RemoveNamespaceFromColliding(ns);
}

// We have no collisions now. We can safely import remaining namespaces.
unaliasedNamespaces.UnionWith(mustImportNs);

return new TypeAliaser(aliasedNamespaces, unaliasedNamespaces, collidingAliasedTypes, nonCollidingAliasedTypes);

// Find the next namespace to be aliased.
string GetTopCollidingNamespace() =>
collidingTypes
Expand Down Expand Up @@ -419,7 +435,7 @@ public override CompilationUnitSyntax CreateCompilationUnit(NamespaceDeclaration
{
// Rewrite as fully-aliased any types for which there are name collisions.
// This has to be done post-generation, as we don't know the complete set of types & imports until generation is complete.
var typeAliaser = TypeAliaser.Create(_seenNamespaces, _seenTypes, _packageTyps, _wellKnownNamespaceAliases, _avoidAliasingNamespaceRegex);
var typeAliaser = TypeAliaser.Create(_seenNamespaces, _seenTypes, _packageTyps, _wellKnownNamespaceAliases, _avoidAliasingNamespaceRegex, _forcedAliases);
ns = (NamespaceDeclarationSyntax) typeAliaser.Visit(ns);
// Add using statements for standard imports, and for fully-qualifying name collisions.
var usings = typeAliaser.UnaliasedNamespaces.OrderBy(x => x).Select(x => UsingDirective(IdentifierName(x)))
Expand Down Expand Up @@ -496,10 +512,11 @@ public static SourceFileContext CreateUnaliased(
IClock clock,
IReadOnlyDictionary<string, string> wellKnownNamespaceAliases,
IReadOnlyCollection<Regex> avoidAliasingNamespaceRegex,
IReadOnlyCollection<string> forcedAliases,
// We need these here to take into account in collisions.
IEnumerable<Typ> packageTyps,
bool maySkipOwnNamespaceImport) =>
new Unaliased(clock, wellKnownNamespaceAliases, avoidAliasingNamespaceRegex, packageTyps, maySkipOwnNamespaceImport);
new Unaliased(clock, wellKnownNamespaceAliases, avoidAliasingNamespaceRegex, forcedAliases, packageTyps, maySkipOwnNamespaceImport);

public static SourceFileContext CreateFullyQualified(IClock clock) => new FullyQualified(clock);

Expand Down
20 changes: 18 additions & 2 deletions Google.Api.Generator/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ internal static class CodeGenerator
new Regex(@"^System\.?.*", RegexOptions.Compiled | RegexOptions.CultureInvariant),
};

/// <summary>
/// For unaliased source file context, forces the alias on namespaces listed on "value"
/// when generating the package in "key".
/// This is useful when a package depends on another package
/// (usually on another library, but not a support library like GAX or a mixin library)
/// and we know there are name collisions.
/// </summary>
private static readonly IReadOnlyDictionary<string, IReadOnlyCollection<string>> ForcedAliasesForLibrary = new Dictionary<string, IReadOnlyCollection<string>>
{
{ "Google.Cloud.Kms.Inventory.V1", new List<string> { "Google.Cloud.Kms.V1" } }
};

private static readonly IReadOnlyList<string> AllowedAdditionalServices = new List<string>
{
IAMPolicy.Descriptor.FullName,
Expand Down Expand Up @@ -234,6 +246,10 @@ private static IEnumerable<ResultFile> GeneratePackage(string ns,
HashSet<string> allResourceNameClasses = new HashSet<string>();
HashSet<string> duplicateResourceNameClasses = new HashSet<string>();
IList<Snippet> snippets = new List<Snippet>();
if (!ForcedAliasesForLibrary.TryGetValue(ns, out IReadOnlyCollection<string> forcedAliases))
{
forcedAliases = new HashSet<string>();
}

IEnumerable<Typ> packageTyps = packageFileDescriptors.SelectMany(
fileDescriptor => fileDescriptor.Services.Select(serv => Typ.Manual(ns, serv.Name))
Expand All @@ -257,7 +273,7 @@ private static IEnumerable<ResultFile> GeneratePackage(string ns,
// TODO: Consider removing this once we have integrated the snippet-per-file snippets
// with docs generation.
var serviceSnippetsCtx = SourceFileContext.CreateUnaliased(
clock, WellknownNamespaceAliases, AvoidAliasingNamespaceRegex, packageTyps, maySkipOwnNamespaceImport: true);
clock, WellknownNamespaceAliases, AvoidAliasingNamespaceRegex, forcedAliases, packageTyps, maySkipOwnNamespaceImport: true);
var serviceSnippetsCode = SnippetCodeGenerator.Generate(serviceSnippetsCtx, serviceDetails);
var serviceSnippetsFilename = $"{serviceSnippetsPathPrefix}{serviceDetails.ClientAbstractTyp.Name}Snippets.g.cs";
yield return new ResultFile(serviceSnippetsFilename, serviceSnippetsCode);
Expand All @@ -266,7 +282,7 @@ private static IEnumerable<ResultFile> GeneratePackage(string ns,
foreach (var snippetGenerator in SnippetCodeGenerator.SnippetsGenerators(serviceDetails))
{
var snippetCtx = SourceFileContext.CreateUnaliased(
clock, WellknownNamespaceAliases, AvoidAliasingNamespaceRegex, packageTyps, maySkipOwnNamespaceImport: false);
clock, WellknownNamespaceAliases, AvoidAliasingNamespaceRegex, forcedAliases, packageTyps, maySkipOwnNamespaceImport: false);
var (snippetCode, snippetMetadata) = snippetGenerator.Generate(snippetCtx);
snippetMetadata.File = $"{serviceDetails.ClientAbstractTyp.Name}.{snippetGenerator.SnippetMethodName}Snippet.g.cs";
var snippetFile = $"{snippetsPathPrefix}{snippetMetadata.File}";
Expand Down

0 comments on commit 737c3d1

Please sign in to comment.