Skip to content

Commit

Permalink
Force generated assemblies to reference jsii-only dependencies. (#216)
Browse files Browse the repository at this point in the history
  • Loading branch information
mpiroc committed Sep 7, 2018
1 parent 060784d commit cf62773
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ static string GetProjectFilePath(string dotnetPackage, string dotnetAssembly)
return $"{Path.Combine(GetPackageOutputRoot(dotnetPackage), dotnetAssembly)}.csproj";
}

static string GetAnchorFilePath(string dotnetPackage, string dotnetNamespace)
{
string path = GetPackageOutputRoot(dotnetPackage);

foreach (string token in dotnetNamespace.Split('.')) {
path = Path.Combine(path, token);
}

return Path.Combine(path, "Internal", "DependencyResolution", "Anchor.cs");
}

static string GetTypeFilePath(string dotnetPackage, string dotnetNamespace, string dotnetType)
{
string directory = Path.Combine(GetPackageOutputRoot(dotnetPackage), Path.Combine(dotnetNamespace.Split('.')));
Expand Down Expand Up @@ -352,6 +363,115 @@ public void CreatesProjectFileWithDependencies()
file.Received().WriteAllText(projectFilePath, Arg.Do<string>(actual => PlatformIndependentEqual(expected, actual)));
}

[Fact(DisplayName = Prefix + nameof(CreatesAnchorFile))]
public void CreatesAnchorFile()
{
string json =
@"{
""name"": ""jsii$aws_cdk$"",
""description"": """",
""homepage"": """",
""repository"": {
""type"": """",
""url"": """"
},
""author"": {
""name"": """",
""roles"": []
},
""fingerprint"": """",
""license"": """",
""targets"": {
""dotnet"": {
""namespace"": ""Aws.CdkNamespace"",
""packageId"": ""Aws.CdkPackageId""
}
},
""version"": ""1.2.3"",
""types"": {},
""dependencies"": {
""jsii$aws_cdk_cx_api$"": {
""package"": ""aws-cdk-cx-api"",
""version"": """",
""targets"": {
""dotnet"": {
""namespace"": ""Aws.Cdk.CxApi"",
""packageId"": ""Aws.Cdk.CxApi""
}
}
}
}
}";
string cxJson =
@"{
""name"": ""jsii$aws_cdk_cx_api$"",
""description"": """",
""homepage"": """",
""repository"": {
""type"": """",
""url"": """"
},
""author"": {
""name"": """",
""roles"": []
},
""fingerprint"": """",
""license"": """",
""version"": """",
""targets"": {
""dotnet"": {
""namespace"": ""Aws.Cdk.CxApiNamespace"",
""packageId"": ""Aws.Cdk.CxApiPackageId""
}
},
""types"": {}
}";

string jsonPath = GetJsonPath("aws-cdk");
string cxJsonPath = Path.Combine(Path.GetDirectoryName(jsonPath), "node_modules", "jsii$aws_cdk_cx_api$");
string anchorFilePath = GetAnchorFilePath("Aws.CdkPackageId", "Aws.CdkNamespace");

IFile file = Substitute.For<IFile>();
file.ReadAllText(jsonPath).Returns(json);
file.ReadAllText(Path.Combine(cxJsonPath, ".jsii")).Returns(cxJson);

IDirectory directory = Substitute.For<IDirectory>();
directory.Exists(cxJsonPath).Returns(true);

IFileSystem fileSystem = Substitute.For<IFileSystem>();
fileSystem.Directory.Returns(directory);
fileSystem.File.Returns(file);

Symbols.MapTypeToPackage("aws-cdk", "Aws.CdkPackageId");
Symbols.MapTypeToPackage("aws-cdk-cx-api", "Aws.Cdk.CxApiNamespace");
Symbols.MapAssemblyName("jsii$aws_cdk_cx_api$", "Aws.Cdk.CxApiPackageId");

AssemblyGenerator generator = new AssemblyGenerator
(
OutputRoot,
fileSystem
);
generator.Generate
(
Path.Combine(InputRoot, "aws-cdk", "dist", Constants.SPEC_FILE_NAME),
Path.Combine(InputRoot, "aws-cdk", "aws-cdk-1.2.3.4.tgz"),
Symbols
);

string expected =
@"namespace Aws.CdkNamespace.Internal.DependencyResolution
{
public class Anchor
{
public Anchor()
{
new Aws.Cdk.CxApiNamespace.Internal.DependencyResolution.Anchor();
}
}
}";
file.Received().WriteAllText(anchorFilePath, Arg.Do<string>(actual => PlatformIndependentEqual(expected, actual)));
}

[Fact(DisplayName = Prefix + nameof(CreatesAssemblyInfo))]
public void CreatesAssemblyInfo()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ public class AssemblyGenerator
readonly string _outputRoot;
readonly IFileSystem _fileSystem;

public AssemblyGenerator
(
public AssemblyGenerator(
string outputRoot,
IFileSystem fileSystem = null
)
Expand Down Expand Up @@ -102,6 +101,7 @@ void Save(string packageOutputRoot, ISymbolMap symbols, Assembly assembly, strin

SaveProjectFile();
SaveAssemblyInfo(assembly.Name, assembly.Version, tarballFileName);
SaveDependencyAnchorFile();

foreach (Type type in assembly.Types?.Values ?? Enumerable.Empty<Type>())
{
Expand Down Expand Up @@ -185,6 +185,64 @@ void SaveProjectFile()
}
}

void SaveDependencyAnchorFile()
{
string anchorNamespace = $"{assembly.GetNativeNamespace()}.Internal.DependencyResolution";
var syntaxTree = SF.SyntaxTree(
SF.CompilationUnit(
SF.List<ExternAliasDirectiveSyntax>(),
SF.List<UsingDirectiveSyntax>(),
SF.List<AttributeListSyntax>(),
SF.List<MemberDeclarationSyntax>(new[] {
SF.NamespaceDeclaration(
SF.IdentifierName(anchorNamespace),
SF.List<ExternAliasDirectiveSyntax>(),
SF.List<UsingDirectiveSyntax>(),
SF.List(new MemberDeclarationSyntax[] { GenerateDependencyAnchor() })
)
})
).NormalizeWhitespace(elasticTrivia: true)
);

string directory = GetNamespaceDirectory(packageOutputRoot, @anchorNamespace);
SaveSyntaxTree(directory, "Anchor.cs", syntaxTree);

ClassDeclarationSyntax GenerateDependencyAnchor() {
return SF.ClassDeclaration(
SF.List<AttributeListSyntax>(),
SF.TokenList(SF.Token(SyntaxKind.PublicKeyword)),
SF.Identifier("Anchor"),
null,
null,
SF.List<TypeParameterConstraintClauseSyntax>(),
SF.List(new MemberDeclarationSyntax[] {
SF.ConstructorDeclaration(
SF.List<AttributeListSyntax>(),
SF.TokenList(SF.Token(SyntaxKind.PublicKeyword)),
SF.Identifier("Anchor"),
SF.ParameterList(SF.SeparatedList<ParameterSyntax>()),
null,
SF.Block(SF.List(GenerateAnchorReferences())),
null
)
})
);

IEnumerable<StatementSyntax> GenerateAnchorReferences()
{
return assembly.Dependencies?.Keys
.Select(k => assembly.GetNativeNamespace(k))
.Select(n => $"{n}.Internal.DependencyResolution.Anchor")
.Select(t => SF.ExpressionStatement(SF.ObjectCreationExpression(
SF.Token(SyntaxKind.NewKeyword),
SF.ParseTypeName(t),
SF.ArgumentList(SF.SeparatedList<ArgumentSyntax>()),
null
))) ?? Enumerable.Empty<StatementSyntax>();
}
}
}

void SaveAssemblyInfo(string name, string version, string tarball)
{
SyntaxTree assemblyInfo = SF.SyntaxTree(
Expand Down Expand Up @@ -217,14 +275,8 @@ void SaveAssemblyInfo(string name, string version, string tarball)

void SaveType(Type type)
{
string packageName = Path.GetFileName(packageOutputRoot);
string @namespace = symbols.GetNamespace(type);
if (@namespace.StartsWith(packageName))
{
@namespace = @namespace.Substring(packageName.Length).TrimStart('.');
}

string directory = Path.Combine(packageOutputRoot, Path.Combine(@namespace.Split('.')));
string directory = GetNamespaceDirectory(packageOutputRoot, @namespace);

switch (type.Kind)
{
Expand Down Expand Up @@ -252,17 +304,37 @@ void SaveType(Type type)

void SaveTypeFile(string filename, SyntaxTree syntaxTree)
{
if (!_fileSystem.Directory.Exists(directory))
{
_fileSystem.Directory.CreateDirectory(directory);
}
SaveSyntaxTree(directory, filename, syntaxTree);
}
}

_fileSystem.File.WriteAllText(
Path.Combine(directory, filename),
syntaxTree.ToString()
);
void SaveSyntaxTree(string directory, string filename, SyntaxTree syntaxTree)
{
if (!_fileSystem.Directory.Exists(directory))
{
_fileSystem.Directory.CreateDirectory(directory);
}

_fileSystem.File.WriteAllText(
Path.Combine(directory, filename),
syntaxTree.ToString()
);
}
}

string GetNamespaceDirectory(string packageOutputRoot, string @namespace)
{
string packageName = Path.GetFileName(packageOutputRoot);

// packageOutputRoot is typically a directory like My.Root.Namespace/
// We don't want to create redundant directories, i.e.
// My.Root.Namespace/My/Root/Namespace/Sub/Namespace. We just
// want My.Root.Namespace/Sub/Namespace.
if (@namespace.StartsWith(packageName)) {
@namespace = @namespace.Substring(packageName.Length).TrimStart('.');
}

return Path.Combine(packageOutputRoot, Path.Combine(@namespace.Split('.')));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace.Internal.DependencyResolution
{
public class Anchor
{
public Anchor()
{
new Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace.Internal.DependencyResolution.Anchor();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Amazon.JSII.Tests.CalculatorNamespace.LibNamespace.Internal.DependencyResolution
{
public class Anchor
{
public Anchor()
{
new Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace.Internal.DependencyResolution.Anchor();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Amazon.JSII.Tests.CalculatorNamespace.Internal.DependencyResolution
{
public class Anchor
{
public Anchor()
{
new Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace.Internal.DependencyResolution.Anchor();
new Amazon.JSII.Tests.CalculatorNamespace.LibNamespace.Internal.DependencyResolution.Anchor();
}
}
}
Loading

0 comments on commit cf62773

Please sign in to comment.