Skip to content

Commit cf62773

Browse files
authored
Force generated assemblies to reference jsii-only dependencies. (#216)
1 parent 060784d commit cf62773

File tree

6 files changed

+270
-28
lines changed
  • packages
    • jsii-dotnet-generator/src
    • jsii-pacmak/test
      • expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/Internal/DependencyResolution
      • expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/Amazon/JSII/Tests/CalculatorNamespace/BaseNamespace/Internal/DependencyResolution
      • expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/Amazon/JSII/Tests/CalculatorNamespace/LibNamespace/Internal/DependencyResolution
    • jsii-runtime

6 files changed

+270
-28
lines changed

Diff for: packages/jsii-dotnet-generator/src/Amazon.JSII.Generator.UnitTests/AssemblyGeneratorTests.cs

+120
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ static string GetProjectFilePath(string dotnetPackage, string dotnetAssembly)
2929
return $"{Path.Combine(GetPackageOutputRoot(dotnetPackage), dotnetAssembly)}.csproj";
3030
}
3131

32+
static string GetAnchorFilePath(string dotnetPackage, string dotnetNamespace)
33+
{
34+
string path = GetPackageOutputRoot(dotnetPackage);
35+
36+
foreach (string token in dotnetNamespace.Split('.')) {
37+
path = Path.Combine(path, token);
38+
}
39+
40+
return Path.Combine(path, "Internal", "DependencyResolution", "Anchor.cs");
41+
}
42+
3243
static string GetTypeFilePath(string dotnetPackage, string dotnetNamespace, string dotnetType)
3344
{
3445
string directory = Path.Combine(GetPackageOutputRoot(dotnetPackage), Path.Combine(dotnetNamespace.Split('.')));
@@ -352,6 +363,115 @@ public void CreatesProjectFileWithDependencies()
352363
file.Received().WriteAllText(projectFilePath, Arg.Do<string>(actual => PlatformIndependentEqual(expected, actual)));
353364
}
354365

366+
[Fact(DisplayName = Prefix + nameof(CreatesAnchorFile))]
367+
public void CreatesAnchorFile()
368+
{
369+
string json =
370+
@"{
371+
""name"": ""jsii$aws_cdk$"",
372+
""description"": """",
373+
""homepage"": """",
374+
""repository"": {
375+
""type"": """",
376+
""url"": """"
377+
},
378+
""author"": {
379+
""name"": """",
380+
""roles"": []
381+
},
382+
""fingerprint"": """",
383+
""license"": """",
384+
""targets"": {
385+
""dotnet"": {
386+
""namespace"": ""Aws.CdkNamespace"",
387+
""packageId"": ""Aws.CdkPackageId""
388+
}
389+
},
390+
""version"": ""1.2.3"",
391+
""types"": {},
392+
""dependencies"": {
393+
""jsii$aws_cdk_cx_api$"": {
394+
""package"": ""aws-cdk-cx-api"",
395+
""version"": """",
396+
""targets"": {
397+
""dotnet"": {
398+
""namespace"": ""Aws.Cdk.CxApi"",
399+
""packageId"": ""Aws.Cdk.CxApi""
400+
}
401+
}
402+
}
403+
}
404+
}";
405+
string cxJson =
406+
@"{
407+
""name"": ""jsii$aws_cdk_cx_api$"",
408+
""description"": """",
409+
""homepage"": """",
410+
""repository"": {
411+
""type"": """",
412+
""url"": """"
413+
},
414+
""author"": {
415+
""name"": """",
416+
""roles"": []
417+
},
418+
""fingerprint"": """",
419+
""license"": """",
420+
""version"": """",
421+
""targets"": {
422+
""dotnet"": {
423+
""namespace"": ""Aws.Cdk.CxApiNamespace"",
424+
""packageId"": ""Aws.Cdk.CxApiPackageId""
425+
}
426+
},
427+
""types"": {}
428+
}";
429+
430+
string jsonPath = GetJsonPath("aws-cdk");
431+
string cxJsonPath = Path.Combine(Path.GetDirectoryName(jsonPath), "node_modules", "jsii$aws_cdk_cx_api$");
432+
string anchorFilePath = GetAnchorFilePath("Aws.CdkPackageId", "Aws.CdkNamespace");
433+
434+
IFile file = Substitute.For<IFile>();
435+
file.ReadAllText(jsonPath).Returns(json);
436+
file.ReadAllText(Path.Combine(cxJsonPath, ".jsii")).Returns(cxJson);
437+
438+
IDirectory directory = Substitute.For<IDirectory>();
439+
directory.Exists(cxJsonPath).Returns(true);
440+
441+
IFileSystem fileSystem = Substitute.For<IFileSystem>();
442+
fileSystem.Directory.Returns(directory);
443+
fileSystem.File.Returns(file);
444+
445+
Symbols.MapTypeToPackage("aws-cdk", "Aws.CdkPackageId");
446+
Symbols.MapTypeToPackage("aws-cdk-cx-api", "Aws.Cdk.CxApiNamespace");
447+
Symbols.MapAssemblyName("jsii$aws_cdk_cx_api$", "Aws.Cdk.CxApiPackageId");
448+
449+
AssemblyGenerator generator = new AssemblyGenerator
450+
(
451+
OutputRoot,
452+
fileSystem
453+
);
454+
generator.Generate
455+
(
456+
Path.Combine(InputRoot, "aws-cdk", "dist", Constants.SPEC_FILE_NAME),
457+
Path.Combine(InputRoot, "aws-cdk", "aws-cdk-1.2.3.4.tgz"),
458+
Symbols
459+
);
460+
461+
string expected =
462+
@"namespace Aws.CdkNamespace.Internal.DependencyResolution
463+
{
464+
public class Anchor
465+
{
466+
public Anchor()
467+
{
468+
new Aws.Cdk.CxApiNamespace.Internal.DependencyResolution.Anchor();
469+
}
470+
}
471+
}";
472+
file.Received().WriteAllText(anchorFilePath, Arg.Do<string>(actual => PlatformIndependentEqual(expected, actual)));
473+
}
474+
355475
[Fact(DisplayName = Prefix + nameof(CreatesAssemblyInfo))]
356476
public void CreatesAssemblyInfo()
357477
{

Diff for: packages/jsii-dotnet-generator/src/Amazon.JSII.Generator/AssemblyGenerator.cs

+89-17
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ public class AssemblyGenerator
2525
readonly string _outputRoot;
2626
readonly IFileSystem _fileSystem;
2727

28-
public AssemblyGenerator
29-
(
28+
public AssemblyGenerator(
3029
string outputRoot,
3130
IFileSystem fileSystem = null
3231
)
@@ -102,6 +101,7 @@ void Save(string packageOutputRoot, ISymbolMap symbols, Assembly assembly, strin
102101

103102
SaveProjectFile();
104103
SaveAssemblyInfo(assembly.Name, assembly.Version, tarballFileName);
104+
SaveDependencyAnchorFile();
105105

106106
foreach (Type type in assembly.Types?.Values ?? Enumerable.Empty<Type>())
107107
{
@@ -185,6 +185,64 @@ IEnumerable<KeyValuePair<string, PackageVersion>> GetDependenciesCore(Dependency
185185
}
186186
}
187187

188+
void SaveDependencyAnchorFile()
189+
{
190+
string anchorNamespace = $"{assembly.GetNativeNamespace()}.Internal.DependencyResolution";
191+
var syntaxTree = SF.SyntaxTree(
192+
SF.CompilationUnit(
193+
SF.List<ExternAliasDirectiveSyntax>(),
194+
SF.List<UsingDirectiveSyntax>(),
195+
SF.List<AttributeListSyntax>(),
196+
SF.List<MemberDeclarationSyntax>(new[] {
197+
SF.NamespaceDeclaration(
198+
SF.IdentifierName(anchorNamespace),
199+
SF.List<ExternAliasDirectiveSyntax>(),
200+
SF.List<UsingDirectiveSyntax>(),
201+
SF.List(new MemberDeclarationSyntax[] { GenerateDependencyAnchor() })
202+
)
203+
})
204+
).NormalizeWhitespace(elasticTrivia: true)
205+
);
206+
207+
string directory = GetNamespaceDirectory(packageOutputRoot, @anchorNamespace);
208+
SaveSyntaxTree(directory, "Anchor.cs", syntaxTree);
209+
210+
ClassDeclarationSyntax GenerateDependencyAnchor() {
211+
return SF.ClassDeclaration(
212+
SF.List<AttributeListSyntax>(),
213+
SF.TokenList(SF.Token(SyntaxKind.PublicKeyword)),
214+
SF.Identifier("Anchor"),
215+
null,
216+
null,
217+
SF.List<TypeParameterConstraintClauseSyntax>(),
218+
SF.List(new MemberDeclarationSyntax[] {
219+
SF.ConstructorDeclaration(
220+
SF.List<AttributeListSyntax>(),
221+
SF.TokenList(SF.Token(SyntaxKind.PublicKeyword)),
222+
SF.Identifier("Anchor"),
223+
SF.ParameterList(SF.SeparatedList<ParameterSyntax>()),
224+
null,
225+
SF.Block(SF.List(GenerateAnchorReferences())),
226+
null
227+
)
228+
})
229+
);
230+
231+
IEnumerable<StatementSyntax> GenerateAnchorReferences()
232+
{
233+
return assembly.Dependencies?.Keys
234+
.Select(k => assembly.GetNativeNamespace(k))
235+
.Select(n => $"{n}.Internal.DependencyResolution.Anchor")
236+
.Select(t => SF.ExpressionStatement(SF.ObjectCreationExpression(
237+
SF.Token(SyntaxKind.NewKeyword),
238+
SF.ParseTypeName(t),
239+
SF.ArgumentList(SF.SeparatedList<ArgumentSyntax>()),
240+
null
241+
))) ?? Enumerable.Empty<StatementSyntax>();
242+
}
243+
}
244+
}
245+
188246
void SaveAssemblyInfo(string name, string version, string tarball)
189247
{
190248
SyntaxTree assemblyInfo = SF.SyntaxTree(
@@ -217,14 +275,8 @@ void SaveAssemblyInfo(string name, string version, string tarball)
217275

218276
void SaveType(Type type)
219277
{
220-
string packageName = Path.GetFileName(packageOutputRoot);
221278
string @namespace = symbols.GetNamespace(type);
222-
if (@namespace.StartsWith(packageName))
223-
{
224-
@namespace = @namespace.Substring(packageName.Length).TrimStart('.');
225-
}
226-
227-
string directory = Path.Combine(packageOutputRoot, Path.Combine(@namespace.Split('.')));
279+
string directory = GetNamespaceDirectory(packageOutputRoot, @namespace);
228280

229281
switch (type.Kind)
230282
{
@@ -252,17 +304,37 @@ void SaveType(Type type)
252304

253305
void SaveTypeFile(string filename, SyntaxTree syntaxTree)
254306
{
255-
if (!_fileSystem.Directory.Exists(directory))
256-
{
257-
_fileSystem.Directory.CreateDirectory(directory);
258-
}
307+
SaveSyntaxTree(directory, filename, syntaxTree);
308+
}
309+
}
259310

260-
_fileSystem.File.WriteAllText(
261-
Path.Combine(directory, filename),
262-
syntaxTree.ToString()
263-
);
311+
void SaveSyntaxTree(string directory, string filename, SyntaxTree syntaxTree)
312+
{
313+
if (!_fileSystem.Directory.Exists(directory))
314+
{
315+
_fileSystem.Directory.CreateDirectory(directory);
264316
}
317+
318+
_fileSystem.File.WriteAllText(
319+
Path.Combine(directory, filename),
320+
syntaxTree.ToString()
321+
);
322+
}
323+
}
324+
325+
string GetNamespaceDirectory(string packageOutputRoot, string @namespace)
326+
{
327+
string packageName = Path.GetFileName(packageOutputRoot);
328+
329+
// packageOutputRoot is typically a directory like My.Root.Namespace/
330+
// We don't want to create redundant directories, i.e.
331+
// My.Root.Namespace/My/Root/Namespace/Sub/Namespace. We just
332+
// want My.Root.Namespace/Sub/Namespace.
333+
if (@namespace.StartsWith(packageName)) {
334+
@namespace = @namespace.Substring(packageName.Length).TrimStart('.');
265335
}
336+
337+
return Path.Combine(packageOutputRoot, Path.Combine(@namespace.Split('.')));
266338
}
267339
}
268340
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace.Internal.DependencyResolution
2+
{
3+
public class Anchor
4+
{
5+
public Anchor()
6+
{
7+
new Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace.Internal.DependencyResolution.Anchor();
8+
}
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Amazon.JSII.Tests.CalculatorNamespace.LibNamespace.Internal.DependencyResolution
2+
{
3+
public class Anchor
4+
{
5+
public Anchor()
6+
{
7+
new Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace.Internal.DependencyResolution.Anchor();
8+
}
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace Amazon.JSII.Tests.CalculatorNamespace.Internal.DependencyResolution
2+
{
3+
public class Anchor
4+
{
5+
public Anchor()
6+
{
7+
new Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace.Internal.DependencyResolution.Anchor();
8+
new Amazon.JSII.Tests.CalculatorNamespace.LibNamespace.Internal.DependencyResolution.Anchor();
9+
}
10+
}
11+
}

0 commit comments

Comments
 (0)