diff --git a/Google.Api.Generator.Utils/SourceFileContext.cs b/Google.Api.Generator.Utils/SourceFileContext.cs index 743543e1..33ccdec2 100644 --- a/Google.Api.Generator.Utils/SourceFileContext.cs +++ b/Google.Api.Generator.Utils/SourceFileContext.cs @@ -117,14 +117,16 @@ public Unaliased( IClock clock, IReadOnlyDictionary wellKnownNamespaceAliases, IReadOnlyCollection avoidAliasingNamespaceRegex, + IReadOnlyCollection forcedAliases, IEnumerable 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 _wellKnownNamespaceAliases; private readonly IReadOnlyCollection _avoidAliasingNamespaceRegex; + private readonly IReadOnlyCollection _forcedAliases; private readonly IEnumerable _packageTyps; // Seen namespaces. The associated bool value indicates whether we may skip import or not. private readonly Dictionary _seenNamespaces = new Dictionary(); @@ -198,7 +200,8 @@ public static TypeAliaser Create( IReadOnlyCollection seenTypes, IEnumerable packageTyps, IReadOnlyDictionary wellKnownNamespaceAliases, - IReadOnlyCollection avoidAliasingNamespaceRegex) + IReadOnlyCollection avoidAliasingNamespaceRegex, + IReadOnlyCollection forcedAliases) { // Let's copy some of these collections over so we can make changes. // Also, copy them to more usable collections. @@ -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) @@ -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> nonCollidingAliasedTypes = new Dictionary>(); - // 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 @@ -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))) @@ -496,10 +512,11 @@ public static SourceFileContext CreateUnaliased( IClock clock, IReadOnlyDictionary wellKnownNamespaceAliases, IReadOnlyCollection avoidAliasingNamespaceRegex, + IReadOnlyCollection forcedAliases, // We need these here to take into account in collisions. IEnumerable 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); diff --git a/Google.Api.Generator/CodeGenerator.cs b/Google.Api.Generator/CodeGenerator.cs index f06edd87..779a4f76 100644 --- a/Google.Api.Generator/CodeGenerator.cs +++ b/Google.Api.Generator/CodeGenerator.cs @@ -86,6 +86,18 @@ internal static class CodeGenerator new Regex(@"^System\.?.*", RegexOptions.Compiled | RegexOptions.CultureInvariant), }; + /// + /// 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. + /// + private static readonly IReadOnlyDictionary> ForcedAliasesForLibrary = new Dictionary> + { + { "Google.Cloud.Kms.Inventory.V1", new List { "Google.Cloud.Kms.V1" } } + }; + private static readonly IReadOnlyList AllowedAdditionalServices = new List { IAMPolicy.Descriptor.FullName, @@ -234,6 +246,10 @@ private static IEnumerable GeneratePackage(string ns, HashSet allResourceNameClasses = new HashSet(); HashSet duplicateResourceNameClasses = new HashSet(); IList snippets = new List(); + if (!ForcedAliasesForLibrary.TryGetValue(ns, out IReadOnlyCollection forcedAliases)) + { + forcedAliases = new HashSet(); + } IEnumerable packageTyps = packageFileDescriptors.SelectMany( fileDescriptor => fileDescriptor.Services.Select(serv => Typ.Manual(ns, serv.Name)) @@ -257,7 +273,7 @@ private static IEnumerable 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); @@ -266,7 +282,7 @@ private static IEnumerable 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}";