diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f8ea0a0..da7257e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -53,8 +53,8 @@ jobs:
if: startsWith (matrix.os, 'windows') == false
# dotnet test doesn't support mono so we have to use the xunit console runner
run: |
- nuget install xunit.runner.console -Version 2.4.1 -OutputDirectory testrunner
- mono ./testrunner/xunit.runner.console.2.4.1/tools/net472/xunit.console.exe ./Mono.TextTemplating.Tests/bin/${{ matrix.config }}/net472/Mono.TextTemplating.Tests.dll -parallel none -noshadow -noappdomain
+ nuget install xunit.runner.console -Version 2.4.2 -OutputDirectory testrunner
+ mono ./testrunner/xunit.runner.console.2.4.2/tools/net472/xunit.console.exe ./Mono.TextTemplating.Tests/bin/${{ matrix.config }}/net472/Mono.TextTemplating.Tests.dll -parallel none -noshadow
dotnet test -c ${{ matrix.config }} --no-build -f netcoreapp2.1
dotnet test -c ${{ matrix.config }} --no-build -f netcoreapp3.1
dotnet test -c ${{ matrix.config }} --no-build -f net5.0
diff --git a/.gitignore b/.gitignore
index 2c9dc65..4c64ccc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
packages/
TestResults/
.vs/
+testrunner
# globs
*.DS_Store
diff --git a/Mono.TextTemplating.Roslyn/Mono.TextTemplating.Roslyn.csproj b/Mono.TextTemplating.Roslyn/Mono.TextTemplating.Roslyn.csproj
index 6f03a3c..8cc2c11 100644
--- a/Mono.TextTemplating.Roslyn/Mono.TextTemplating.Roslyn.csproj
+++ b/Mono.TextTemplating.Roslyn/Mono.TextTemplating.Roslyn.csproj
@@ -24,11 +24,6 @@ To enable the in-process C# compiler, use the TemplatingEngine.UseInProcessCompi
snupkg
-
-
-
-
-
diff --git a/Mono.TextTemplating.Tests/AppDomainTests.cs b/Mono.TextTemplating.Tests/AppDomainTests.cs
new file mode 100644
index 0000000..1a55d37
--- /dev/null
+++ b/Mono.TextTemplating.Tests/AppDomainTests.cs
@@ -0,0 +1,126 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#if FEATURE_APPDOMAINS
+
+using System;
+using System.CodeDom.Compiler;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+
+using Xunit;
+
+namespace Mono.TextTemplating.Tests;
+
+public class AppDomainTests : AssemblyLoadTests>
+{
+ protected override TemplateGenerator CreateGenerator ([CallerMemberName] string testName = null) => CreateGeneratorWithAppDomain (testName: testName);
+
+ protected override void CleanupGenerator (TemplateGenerator generator)
+ {
+ // FIXME: app domain unload doesn't seem to work on Mono
+ if (FactExceptOnMonoAttribute.IsRunningOnMono) {
+ return;
+ }
+
+ // verify that the AppDomain is collected
+ var weakRef = ((TestTemplateGeneratorWithAppDomain)generator).ReleaseDomain ();
+ int count = 0;
+ while (weakRef.IsAlive) {
+ GC.Collect ();
+ Assert.InRange (count++, 0, 5);
+ }
+ }
+
+ protected override SnapshotSet GetInitialState () => Snapshot.LoadedAssemblies ();
+
+ protected override void VerifyFinalState (SnapshotSet state)
+ {
+ (var added, var removed) = state.GetChanges ();
+ added = added.Where (a => !ShouldIgnoreAssemblyAdd (a));
+ Assert.Empty (added);
+ Assert.Empty (removed);
+ }
+
+ static bool ShouldIgnoreAssemblyAdd (string name) =>
+ // System.Configuration may cause these to load in the main AppDomain
+ name.StartsWith ("System.Configuration,", StringComparison.Ordinal) ||
+ name.StartsWith ("System.Xml.Linq,", StringComparison.Ordinal) ||
+ name.StartsWith ("System.Data,", StringComparison.Ordinal) ||
+ name.StartsWith ("System.Reflection,", StringComparison.Ordinal);
+
+ [Fact]
+ public async Task BadAppDomain ()
+ {
+ var testDir = TestDataPath.Get (nameof (LoadOpenApiDll));
+ var badGen = CreateGeneratorWithAppDomain (testDir, testDir);
+
+ badGen.ReferencePaths.Add (PackagePath.Microsoft_OpenApi_1_2_3.Combine ("lib", "netstandard2.0"));
+
+ var templateText = await testDir["LoadOpenApiDll.tt"].ReadAllTextNormalizedAsync ();
+
+ await Assert.ThrowsAnyAsync (() => badGen.ProcessTemplateAsync ("LoadOpenApiDll.tt", templateText, null));
+ CleanupGenerator (badGen);
+ }
+
+ [Fact]
+ public async Task RunsInAppDomain ()
+ {
+ var gen = CreateGeneratorWithAppDomain ();
+ var templateText = "<#= System.AppDomain.CurrentDomain.FriendlyName #>";
+ var expectedOutputText = GetAppDomainNameForCurrentTest ();
+
+ var state = GetInitialState ();
+
+ var result = await gen.ProcessTemplateAsync ("TemplateInAppDomain.tt", templateText, null);
+
+ Assert.Null (gen.Errors.OfType ().FirstOrDefault ());
+ Assert.Equal (expectedOutputText, result.content);
+
+ CleanupGenerator (gen);
+ VerifyFinalState (state);
+ }
+
+ static TestTemplateGeneratorWithAppDomain CreateGeneratorWithAppDomain (
+ string basePath = null, string relativeSearchPath = null, bool shadowCopy = false,
+ [CallerMemberName] string testName = null
+ )
+ {
+ if (basePath is null) {
+ // need to be able to resolve Mono.TextTemplating to load CompiledTemplate, which will resolve other assemblies via the host.
+ basePath = AppDomain.CurrentDomain.BaseDirectory;
+ if (relativeSearchPath is not null) {
+ throw new ArgumentException ($"{nameof (relativeSearchPath)} must be null if {nameof (basePath)} is null", nameof (relativeSearchPath));
+ }
+ relativeSearchPath = AppDomain.CurrentDomain.RelativeSearchPath;
+ }
+ return new (AppDomain.CreateDomain (
+ GetAppDomainNameForCurrentTest (testName),
+ null,
+ basePath,
+ relativeSearchPath,
+ shadowCopy)
+ );
+ }
+
+ static string GetAppDomainNameForCurrentTest ([CallerMemberName] string testName = null) => $"Template Test - {testName ?? "(unknown)"}";
+
+ class TestTemplateGeneratorWithAppDomain : TemplateGenerator
+ {
+ AppDomain appDomain;
+ public TestTemplateGeneratorWithAppDomain (AppDomain appDomain) => this.appDomain = appDomain;
+
+ public override AppDomain ProvideTemplatingAppDomain (string content) => appDomain;
+
+ public WeakReference ReleaseDomain ()
+ {
+ AppDomain.Unload (appDomain);
+ var weakRef = new WeakReference (appDomain);
+ appDomain = null;
+ return weakRef;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Mono.TextTemplating.Tests/AssemblyLoadContextTests.cs b/Mono.TextTemplating.Tests/AssemblyLoadContextTests.cs
new file mode 100644
index 0000000..9ca2997
--- /dev/null
+++ b/Mono.TextTemplating.Tests/AssemblyLoadContextTests.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#if NETCOREAPP3_0_OR_GREATER
+
+using System;
+using System.CodeDom.Compiler;
+using System.Linq;
+using System.Runtime.Loader;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Mono.TextTemplating.Tests;
+
+public class AssemblyLoadContextTests : AssemblyLoadTests<(SnapshotSet assembliesInDefaultContext, SnapshotSet allContexts)>
+{
+ protected override (SnapshotSet assembliesInDefaultContext, SnapshotSet allContexts) GetInitialState () => (
+ Snapshot.LoadedAssemblies (),
+ Snapshot.AssemblyLoadContexts ()
+ );
+
+ protected override void VerifyFinalState ((SnapshotSet assembliesInDefaultContext, SnapshotSet allContexts) state)
+ {
+ state.assembliesInDefaultContext.AssertUnchanged ();
+
+ // ensure unloadable contexts are collected
+ for (int i = 0; i < 10; i++) {
+ GC.Collect ();
+ GC.WaitForPendingFinalizers ();
+ }
+
+ state.allContexts.AssertUnchanged ();
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Mono.TextTemplating.Tests/AssemblyLoadTests.cs b/Mono.TextTemplating.Tests/AssemblyLoadTests.cs
new file mode 100644
index 0000000..f5f5933
--- /dev/null
+++ b/Mono.TextTemplating.Tests/AssemblyLoadTests.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.CodeDom.Compiler;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Mono.TextTemplating.Tests;
+
+public abstract class AssemblyLoadTests : StatefulTest
+{
+ protected virtual TemplateGenerator CreateGenerator ([CallerMemberName] string testName = null) => new ();
+ protected virtual void CleanupGenerator (TemplateGenerator generator) { }
+
+ [Fact]
+ public async Task LoadOpenApiDll ()
+ {
+ var testDir = TestDataPath.Get ();
+ var gen = CreateGenerator ();
+ gen.ReferencePaths.Add (PackagePath.Microsoft_OpenApi_1_2_3.Combine ("lib", "netstandard2.0").AssertDirectoryExists ());
+
+ var templatePath = testDir["LoadOpenApiDll.tt"];
+ var templateText = await templatePath.ReadAllTextNormalizedAsync ();
+
+ var expectedOutputPath = testDir["LoadOpenApiDll.yaml"];
+ var expectedOutputText = await expectedOutputPath.ReadAllTextNormalizedAsync ();
+
+ var state = GetInitialState ();
+
+ var result = await gen.ProcessTemplateAsync (templatePath, templateText, null);
+
+ Assert.Null (gen.Errors.OfType ().FirstOrDefault ());
+ Assert.Equal (expectedOutputText, result.content);
+ Assert.Equal (expectedOutputPath, result.fileName);
+
+ CleanupGenerator (gen);
+ VerifyFinalState (state);
+ }
+
+ [Fact]
+ public async Task LoadOpenApiReadersDll ()
+ {
+ var testDir = TestDataPath.Get (nameof (LoadOpenApiDll));
+ var gen = CreateGenerator ();
+ gen.ReferencePaths.Add (PackagePath.Microsoft_OpenApi_1_2_3.Combine ("lib", "netstandard2.0").AssertDirectoryExists ());
+ gen.ReferencePaths.Add (PackagePath.Microsoft_OpenApi_Readers_1_2_3.Combine ("lib", "netstandard2.0").AssertDirectoryExists ());
+ gen.ReferencePaths.Add (PackagePath.SharpYaml_1_6_5.Combine ("lib", "netstandard2.0").AssertDirectoryExists ());
+
+ var templatePath = testDir["LoadOpenApiReaders.tt"];
+ var templateText = await templatePath.ReadAllTextNormalizedAsync ();
+
+ var state = GetInitialState ();
+
+ var result = await gen.ProcessTemplateAsync (templatePath, templateText, null);
+
+ Assert.Null (gen.Errors.OfType ().FirstOrDefault ());
+ Assert.Equal ("Example", result.content);
+
+ CleanupGenerator (gen);
+ VerifyFinalState (state);
+ }
+
+ [FactExceptOnMono ("Mono incorrectly resolves the assembly if it has been loaded in a different AppDomain")]
+ public async Task MissingTransitiveReference ()
+ {
+ var gen = CreateGenerator ();
+ gen.ReferencePaths.Add (PackagePath.Microsoft_OpenApi_1_2_3.Combine ("lib", "netstandard2.0").AssertDirectoryExists ());
+ gen.ReferencePaths.Add (PackagePath.Microsoft_OpenApi_Readers_1_2_3.Combine ("lib", "netstandard2.0").AssertDirectoryExists ());
+
+ var testDir = TestDataPath.Get (nameof (LoadOpenApiDll));
+ var templatePath = testDir["LoadOpenApiReaders.tt"];
+ var templateText = await templatePath.ReadAllTextNormalizedAsync ();
+
+ await gen.ProcessTemplateAsync (templatePath, templateText, null);
+
+ var firstError = gen.Errors.OfType ().FirstOrDefault ()?.ErrorText;
+ Assert.Contains ("FileNotFoundException: Could not load file or assembly 'SharpYaml, Version=1.6.5.0", firstError);
+
+ CleanupGenerator (gen);
+ }
+}
+
+public class FactExceptOnMonoAttribute : FactAttribute
+{
+ public FactExceptOnMonoAttribute (string reason)
+ {
+ if (IsRunningOnMono) {
+ Skip = reason;
+ }
+ }
+
+ public static bool IsRunningOnMono { get; } = System.Type.GetType ("Mono.Runtime") != null;
+}
\ No newline at end of file
diff --git a/Mono.TextTemplating.Tests/GlobalSuppressions.cs b/Mono.TextTemplating.Tests/GlobalSuppressions.cs
new file mode 100644
index 0000000..278ed3c
--- /dev/null
+++ b/Mono.TextTemplating.Tests/GlobalSuppressions.cs
@@ -0,0 +1,7 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+
+using System.Diagnostics.CodeAnalysis;
+
+[assembly: SuppressMessage ("Design", "CA1034:Nested types should not be visible",
+ Justification = "Many nested classes are test specific but need to be accessible from template code")]
diff --git a/Mono.TextTemplating.Tests/LoadedAssembliesSnapshot.cs b/Mono.TextTemplating.Tests/LoadedAssembliesSnapshot.cs
new file mode 100644
index 0000000..3635843
--- /dev/null
+++ b/Mono.TextTemplating.Tests/LoadedAssembliesSnapshot.cs
@@ -0,0 +1,85 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+
+#if NETCOREAPP3_0_OR_GREATER
+using System.Runtime.Loader;
+#endif
+
+namespace Mono.TextTemplating.Tests;
+
+public abstract class Snapshot
+{
+ public abstract void AssertUnchanged ();
+
+#if FEATURE_APPDOMAINS
+ public static SnapshotSet LoadedAssemblies (AppDomain context = null) => new (() => GetNames ((context ?? AppDomain.CurrentDomain).GetAssemblies ()));
+#elif NETCOREAPP3_0_OR_GREATER
+ public static SnapshotSet LoadedAssemblies (AssemblyLoadContext context = null) => new (() => GetNames ((context ?? AssemblyLoadContext.Default).Assemblies));
+ public static SnapshotSet AssemblyLoadContexts () => new (() => AssemblyLoadContext.All);
+#endif
+
+ static IEnumerable GetNames (IEnumerable assemblies)
+ {
+ var names = assemblies.Select (a => a.FullName);
+ if (!System.Diagnostics.Debugger.IsAttached) {
+ return names;
+ }
+ return names.Where (a => !a.StartsWith ("Microsoft.VisualStudio.Debugger", StringComparison.Ordinal));
+ }
+}
+public class SnapshotSet : Snapshot
+{
+ readonly Func> getCurrent;
+ readonly HashSet initial;
+
+ public SnapshotSet (Func> getCurrent)
+ {
+ this.getCurrent = getCurrent;
+ initial = getCurrent ().ToHashSet ();
+ }
+
+ public override void AssertUnchanged ()
+ {
+ (var added, var removed) = GetChanges ();
+ Assert.Empty (added);
+ Assert.Empty (removed);
+ }
+
+ public (IEnumerable added, IEnumerable removed) GetChanges ()
+ {
+ var current = getCurrent ().ToHashSet ();
+ return (
+ current.Except (initial),
+ initial.Except (current)
+ );
+ }
+}
+
+public class AggregateSnapshot : Snapshot
+{
+ readonly Snapshot[] snapshots;
+ public AggregateSnapshot (params Snapshot[] snapshots) => this.snapshots = snapshots;
+ public override void AssertUnchanged ()
+ {
+ foreach (var snapshot in snapshots) {
+ snapshot.AssertUnchanged ();
+ }
+ }
+}
+
+// these test process state so cannot be run in parallel with other tests
+[CollectionDefinition (nameof (StatefulTests), DisableParallelization = true)]
+public class StatefulTests { }
+
+[Collection (nameof (StatefulTests))]
+public abstract class StatefulTest
+{
+ protected abstract T GetInitialState ();
+ protected abstract void VerifyFinalState (T state);
+}
\ No newline at end of file
diff --git a/Mono.TextTemplating.Tests/Mono.TextTemplating.Tests.csproj b/Mono.TextTemplating.Tests/Mono.TextTemplating.Tests.csproj
index 68b1988..2810314 100644
--- a/Mono.TextTemplating.Tests/Mono.TextTemplating.Tests.csproj
+++ b/Mono.TextTemplating.Tests/Mono.TextTemplating.Tests.csproj
@@ -1,4 +1,4 @@
-
+
net472;netcoreapp2.1;netcoreapp3.1;net5.0
true
@@ -21,7 +21,47 @@
-
+
+
+
+
+
+
+
+
+
+
+
+ <_PackageDownloadConstsPathFile>$(IntermediateOutputPath)PackageDownloadPathConstants.cs
+ <_EscapedNuGetPackageRoot>$(NuGetPackageRoot.Replace('\','\\'))
+
+
+
+ <_PackageDownloadWithVersion Include="@(PackageDownload)" Version="$([System.String]::Copy('%(Version)').Replace('[','').Replace(']',''))" />
+ <_PackageDownloadWithPath Include="@(_PackageDownloadWithVersion)" Path="$(NuGetPackageRoot)$([System.String]::Copy('%(Identity)').ToLower())\%(Version)" />
+ <_PackageDownloadVars
+ Include="@(_PackageDownloadWithPath)"
+ VarName="$([System.String]::Copy('%(Identity)_%(Version)').Replace('.','_'))"
+ EscapedValue="$([System.String]::Copy('%(Path)').Replace('\','\\'))"
+ />
+ <_PackageDownloadPathConstLines Include="namespace $(ProjectName) {" />
+ <_PackageDownloadPathConstLines Include="partial class PackagePath {" />
+ <_PackageDownloadPathConstLines Include="@(_PackageDownloadVars->' public static TestDataPath %(VarName) => new("%(EscapedValue)");')" />
+ <_PackageDownloadPathConstLines Include="}}" />
+
+
+
+
+
+ <_PackageDownloadWithVersion Remove="@(_PackageDownloadWithVersion)" />
+ <_PackageDownloadWithPath Remove="@(_PackageDownloadWithPath)" />
+ <_PackageDownloadVars Remove="@(_PackageDownloadVars)" />
+ <_PackageDownloadPathConstLines Remove="@(_PackageDownloadPathConstLines)" />
+
+
+
+
+
diff --git a/Mono.TextTemplating.Tests/ParsingTests.cs b/Mono.TextTemplating.Tests/ParsingTests.cs
index 9864610..ba26be4 100644
--- a/Mono.TextTemplating.Tests/ParsingTests.cs
+++ b/Mono.TextTemplating.Tests/ParsingTests.cs
@@ -35,12 +35,6 @@ namespace Mono.TextTemplating.Tests
{
public class ParsingTests
{
- public static string GetTestFile (string filename, [CallerMemberName] string testDir = null)
- {
- var asmDir = Environment.CurrentDirectory;
- return Path.Combine (asmDir, "TestCases", testDir, filename);
- }
-
public const string ParseSample1 =
@"<#@ template language=""C#v3.5"" #>
Line One
@@ -240,7 +234,7 @@ public void IncludeOnceTest ()
[Fact]
public void RelativeInclude ()
{
- var testFile = GetTestFile ("RelativeInclude.tt");
+ var testFile = TestDataPath.Get ().Combine ("RelativeInclude.tt");
var host = new TemplateGenerator ();
var pt = host.ParseTemplate (testFile, File.ReadAllText (testFile));
Assert.Collection (pt.Content, c => Assert.Equal("Hello", c.Text));
diff --git a/Mono.TextTemplating.Tests/TestCases/LoadOpenApiDll/LoadOpenApiDll.tt b/Mono.TextTemplating.Tests/TestCases/LoadOpenApiDll/LoadOpenApiDll.tt
new file mode 100644
index 0000000..54d26db
--- /dev/null
+++ b/Mono.TextTemplating.Tests/TestCases/LoadOpenApiDll/LoadOpenApiDll.tt
@@ -0,0 +1,23 @@
+<#@ output extension=".yaml" #>
+<#@ assembly name="Microsoft.OpenApi.dll" #>
+<#@ import namespace="Microsoft.OpenApi" #>
+<#@ import namespace="Microsoft.OpenApi.Models" #>
+<#@ import namespace="Microsoft.OpenApi.Extensions" #>
+<#@ import namespace="System.Collections.Generic" #>
+<#
+
+var document = new OpenApiDocument
+{
+ Info = new OpenApiInfo
+ {
+ Version = "1.0.0",
+ Title = "Example",
+ },
+ Servers = new List
+ {
+ new OpenApiServer { Url = "https://example.com/api" }
+ }
+};
+
+#>
+<#= document.Serialize(OpenApiSpecVersion.OpenApi2_0, OpenApiFormat.Yaml) #>
\ No newline at end of file
diff --git a/Mono.TextTemplating.Tests/TestCases/LoadOpenApiDll/LoadOpenApiDll.yaml b/Mono.TextTemplating.Tests/TestCases/LoadOpenApiDll/LoadOpenApiDll.yaml
new file mode 100644
index 0000000..6697114
--- /dev/null
+++ b/Mono.TextTemplating.Tests/TestCases/LoadOpenApiDll/LoadOpenApiDll.yaml
@@ -0,0 +1,9 @@
+swagger: '2.0'
+info:
+ title: Example
+ version: 1.0.0
+host: example.com
+basePath: /api
+schemes:
+ - https
+paths: { }
\ No newline at end of file
diff --git a/Mono.TextTemplating.Tests/TestCases/LoadOpenApiDll/LoadOpenApiReaders.tt b/Mono.TextTemplating.Tests/TestCases/LoadOpenApiDll/LoadOpenApiReaders.tt
new file mode 100644
index 0000000..b44f2e5
--- /dev/null
+++ b/Mono.TextTemplating.Tests/TestCases/LoadOpenApiDll/LoadOpenApiReaders.tt
@@ -0,0 +1,17 @@
+<#@ template hostspecific="true" #>
+<#@ output extension=".txt" #>
+<#@ assembly name="Microsoft.OpenApi.dll" #>
+<#@ assembly name="Microsoft.OpenApi.Readers.dll" #>
+<#@ import namespace="System.IO" #>
+<#@ import namespace="Microsoft.OpenApi.Models" #>
+<#@ import namespace="Microsoft.OpenApi.Readers" #>
+<#
+OpenApiDocument openApiDocument;
+OpenApiDiagnostic openApiDiagnostic;
+var yamlFile = Host.ResolvePath("LoadOpenApiDll.yaml");
+using (var fileStream = new FileStream(yamlFile, FileMode.Open, FileAccess.Read)) {
+var reader = new OpenApiStreamReader();
+openApiDocument = reader.Read(fileStream, out openApiDiagnostic);
+}
+#>
+<#= openApiDocument.Info.Title #>
\ No newline at end of file
diff --git a/Mono.TextTemplating.Tests/TestDataPath.cs b/Mono.TextTemplating.Tests/TestDataPath.cs
new file mode 100644
index 0000000..729c40a
--- /dev/null
+++ b/Mono.TextTemplating.Tests/TestDataPath.cs
@@ -0,0 +1,67 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Mono.TextTemplating.Tests;
+
+struct TestDataPath
+{
+ readonly string path;
+ public TestDataPath (string path) => this.path = TrimEndingDirectorySeparator (path);
+
+ public static TestDataPath Get ([CallerMemberName] string testName = null)
+ => new (Path.Combine (Environment.CurrentDirectory, "TestCases", testName));
+
+ public TestDataPath Combine (string path) => new (Path.Combine (this.path, path));
+ public TestDataPath Combine (string path1, string path2) => new (Path.Combine (path, path1, path2));
+ public TestDataPath Combine (string path1, string path2, string path3) => new (Path.Combine (path, path1, path2, path3));
+ public TestDataPath Combine (params string[] paths) => new (Path.Combine (path, Path.Combine (paths)));
+
+ public TestDataPath this[string path] => Combine (path);
+ public TestDataPath this[string path1, string path2] => Combine (path1, path2);
+ public TestDataPath this[string path1, string path2, string path3] => Combine (path1, path2, path3);
+ public TestDataPath this[params string[] paths] => Combine (paths);
+
+ public static implicit operator string (TestDataPath path) => path.path;
+
+#if NETCOREAPP2_0_OR_GREATER
+ public Task ReadAllTextAsync () => File.ReadAllTextAsync (path);
+ public async Task ReadAllTextNormalizedAsync () => (await ReadAllTextAsync ().ConfigureAwait (false)).NormalizeNewlines ();
+#else
+ public Task ReadAllTextAsync () => Task.FromResult (ReadAllText ());
+ public Task ReadAllTextNormalizedAsync () => Task.FromResult (ReadAllText ().NormalizeNewlines ());
+#endif
+
+ public string ReadAllText () => File.ReadAllText (path);
+ public string ReadAllTextNormalized () => File.ReadAllText (path).NormalizeNewlines ();
+
+ public TestDataPath AssertFileExists ()
+ {
+ Assert.True (File.Exists (path), $"File '{path}' does not exist");
+ return this;
+ }
+
+ public TestDataPath AssertDirectoryExists ()
+ {
+ Assert.True (Directory.Exists (path), $"Directory '{path}' does not exist");
+ return this;
+ }
+
+ static string TrimEndingDirectorySeparator (string path)
+#if NETCOREAPP3_0_OR_GREATER
+ => Path.TrimEndingDirectorySeparator (path);
+#else
+ {
+ var trimmed = path.TrimEnd (Path.DirectorySeparatorChar);
+ if (trimmed.Length == path.Length && Path.AltDirectorySeparatorChar != Path.DirectorySeparatorChar) {
+ return trimmed.TrimEnd (Path.DirectorySeparatorChar);
+ }
+ return trimmed;
+ }
+#endif
+}
diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CompiledAssemblyData.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CompiledAssemblyData.cs
index 91ab720..98f2445 100644
--- a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CompiledAssemblyData.cs
+++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CompiledAssemblyData.cs
@@ -11,6 +11,9 @@
namespace Mono.TextTemplating.CodeCompilation
{
+#if FEATURE_APPDOMAINS
+ [Serializable]
+#endif
class CompiledAssemblyData
{
public byte[] Assembly { get; }
@@ -18,10 +21,14 @@ class CompiledAssemblyData
public CompiledAssemblyData (byte[] assembly, byte[] debugSymbols)
{
- Assembly = assembly ?? throw new System.ArgumentNullException (nameof (assembly));
+ Assembly = assembly ?? throw new ArgumentNullException (nameof (assembly));
DebugSymbols = debugSymbols;
}
+#if FEATURE_APPDOMAINS
+ CompiledAssemblyData () { }
+#endif
+
#if FEATURE_ASSEMBLY_LOAD_CONTEXT
public Assembly LoadInAssemblyLoadContext (AssemblyLoadContext loadContext)
{
diff --git a/Mono.TextTemplating/Mono.TextTemplating.csproj b/Mono.TextTemplating/Mono.TextTemplating.csproj
index 398247c..205f2f7 100644
--- a/Mono.TextTemplating/Mono.TextTemplating.csproj
+++ b/Mono.TextTemplating/Mono.TextTemplating.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;netcoreapp2.1;net472
+ netstandard2.0;netcoreapp2.1;netcoreapp3.1;net472
true
..\TextTemplating.snk
true
@@ -24,10 +24,6 @@ This package allows embedding the T4 engine in an application.
snupkg
-
-
-
-
diff --git a/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateAssemblyContext.cs b/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateAssemblyContext.cs
new file mode 100644
index 0000000..d2e697b
--- /dev/null
+++ b/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateAssemblyContext.cs
@@ -0,0 +1,29 @@
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Reflection;
+
+using Microsoft.VisualStudio.TextTemplating;
+using Mono.TextTemplating.CodeCompilation;
+
+namespace Mono.TextTemplating;
+
+partial class CompiledTemplate
+{
+ sealed class TemplateAssemblyContext : IDisposable
+ {
+#if FEATURE_ASSEMBLY_LOAD_CONTEXT
+ readonly TemplateAssemblyLoadContext templateContext;
+ public TemplateAssemblyContext (ITextTemplatingEngineHost host, string[] referenceAssemblyFiles) => templateContext = new (referenceAssemblyFiles, host);
+ public Assembly LoadAssemblyFile (string assemblyPath) => templateContext.LoadFromAssemblyPath (assemblyPath);
+ public Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData) => assemblyData.LoadInAssemblyLoadContext (templateContext);
+ public void Dispose () { }
+#else
+ readonly CurrentDomainAssemblyResolver assemblyResolver;
+ public TemplateAssemblyContext (ITextTemplatingEngineHost host, string[] referenceAssemblyFiles) => assemblyResolver = new (referenceAssemblyFiles, host.ResolveAssemblyReference);
+ public Assembly LoadAssemblyFile (string assemblyPath) => Assembly.LoadFile (assemblyPath);
+ public Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData) => assemblyData.LoadInCurrentAppDomain ();
+ public void Dispose () => assemblyResolver.Dispose ();
+#endif
+ }
+}
diff --git a/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateExecutor.cs b/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateExecutor.cs
new file mode 100644
index 0000000..d9cd4ee
--- /dev/null
+++ b/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateExecutor.cs
@@ -0,0 +1,88 @@
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.CodeDom.Compiler;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Reflection;
+
+using Microsoft.VisualStudio.TextTemplating;
+using Mono.TextTemplating.CodeCompilation;
+
+namespace Mono.TextTemplating;
+
+partial class CompiledTemplate
+{
+ class TemplateProcessor : MarshalByRefObject
+ {
+ public string CreateAndProcess (ITextTemplatingEngineHost host, CompiledAssemblyData templateAssemblyData, string templateAssemblyFile, string fullName, CultureInfo culture, string[] referencedAssemblyFiles)
+ {
+ using var context = new TemplateAssemblyContext (host, referencedAssemblyFiles);
+
+ Assembly assembly = templateAssemblyData is not null
+ ? context.LoadInMemoryAssembly (templateAssemblyData)
+ : context.LoadAssemblyFile (templateAssemblyFile);
+
+ // MS Templating Engine does not care about the type itself
+ // it only requires the expected members to be on the compiled type
+ // so we don't try to cast it, we invoke via reflection instead
+ // TODO: could we use additional codegen to collapse all the init work to a single method?
+ Type transformType = assembly.GetType (fullName);
+ object textTransformation = Activator.CreateInstance (transformType);
+
+ //set the host property if it exists
+ Type hostType = null;
+ if (host is TemplateGenerator gen) {
+ hostType = gen.SpecificHostType;
+ }
+ var hostProp = transformType.GetProperty ("Host", hostType ?? typeof (ITextTemplatingEngineHost));
+ if (hostProp != null && hostProp.CanWrite)
+ hostProp.SetValue (textTransformation, host, null);
+
+ if (host is ITextTemplatingSessionHost sessionHost) {
+ //FIXME: should we create a session if it's null?
+ var sessionProp = transformType.GetProperty ("Session", typeof (IDictionary));
+ sessionProp.SetValue (textTransformation, sessionHost.Session, null);
+ }
+
+ var errorProp = transformType.GetProperty ("Errors", BindingFlags.Instance | BindingFlags.NonPublic);
+ if (errorProp == null)
+ throw new ArgumentException ("Template must have 'Errors' property");
+ var errorMethod = transformType.GetMethod ("Error", new Type[] { typeof (string) });
+ if (errorMethod == null) {
+ throw new ArgumentException ("Template must have 'Error(string message)' method");
+ }
+
+ var errors = (CompilerErrorCollection)errorProp.GetValue (textTransformation, null);
+ errors.Clear ();
+
+ //set the culture
+ if (culture != null)
+ ToStringHelper.FormatProvider = culture;
+ else
+ ToStringHelper.FormatProvider = CultureInfo.InvariantCulture;
+
+ string output = null;
+
+ var initMethod = transformType.GetMethod ("Initialize");
+ var transformMethod = transformType.GetMethod ("TransformText");
+
+ if (initMethod == null) {
+ errorMethod.Invoke (textTransformation, new object[] { "Error running transform: no method Initialize()" });
+ } else if (transformMethod == null) {
+ errorMethod.Invoke (textTransformation, new object[] { "Error running transform: no method TransformText()" });
+ } else try {
+ initMethod.Invoke (textTransformation, null);
+ output = (string)transformMethod.Invoke (textTransformation, null);
+ }
+ catch (Exception ex) {
+ errorMethod.Invoke (textTransformation, new object[] { "Error running transform: " + ex });
+ }
+
+ host.LogErrors (errors);
+
+ ToStringHelper.FormatProvider = CultureInfo.InvariantCulture;
+ return output;
+ }
+ }
+}
diff --git a/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.cs b/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.cs
index 1992993..b0711cf 100644
--- a/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.cs
+++ b/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.cs
@@ -26,149 +26,92 @@
using System;
using System.CodeDom.Compiler;
-using System.Collections.Generic;
using System.Globalization;
-using System.Reflection;
-using System.IO;
using Microsoft.VisualStudio.TextTemplating;
using Mono.TextTemplating.CodeCompilation;
namespace Mono.TextTemplating
{
- public sealed class CompiledTemplate :
+ public sealed partial class CompiledTemplate :
#if FEATURE_APPDOMAINS
MarshalByRefObject,
#endif
IDisposable
{
- ITextTemplatingEngineHost host;
- object textTransformation;
+ readonly ITextTemplatingEngineHost host;
readonly CultureInfo culture;
+ readonly string templateClassFullName;
-#if FEATURE_ASSEMBLY_LOAD_CONTEXT
- readonly TemplateAssemblyLoadContext templateContext;
-#else
- readonly CurrentDomainAssemblyResolver assemblyResolver;
-#endif
+ readonly CompiledAssemblyData templateAssemblyData;
+ readonly string templateAssemblyFile;
- [Obsolete ("Should not have been public")]
- public CompiledTemplate (ITextTemplatingEngineHost host, CompilerResults results, string fullName, CultureInfo culture, string[] assemblyFiles)
- : this (host, culture, assemblyFiles)
- {
- Assembly assembly = results.PathToAssembly != null
- ? LoadAssemblyFile (results.PathToAssembly)
- : results.CompiledAssembly;
- InitializeTemplate (assembly, fullName);
- }
+ internal string[] ReferencedAssemblyFiles { get; }
+ [Obsolete ("Should not have been public")]
+ public CompiledTemplate (ITextTemplatingEngineHost host, CompilerResults results, string fullName, CultureInfo culture, string[] assemblyFiles) => throw new NotSupportedException ();
+ [Obsolete ("Should not have been public")]
public CompiledTemplate (ITextTemplatingEngineHost host, string templateAssemblyFile, string fullName, CultureInfo culture, string[] referenceAssemblyFiles)
- : this (host, culture, referenceAssemblyFiles)
+ : this (fullName, host, culture, referenceAssemblyFiles)
{
- var assembly = LoadAssemblyFile (templateAssemblyFile);
- InitializeTemplate (assembly, fullName);
+ this.templateAssemblyFile = templateAssemblyFile;
}
- internal CompiledTemplate (ITextTemplatingEngineHost host, CompiledAssemblyData compiledAssembly, string fullName, CultureInfo culture, string[] referenceAssemblyFiles)
- : this (host, culture, referenceAssemblyFiles)
+ internal CompiledTemplate (ITextTemplatingEngineHost host, CompiledAssemblyData templateAssemblyData, string fullName, CultureInfo culture, string[] referencedAssemblyFiles)
+ : this (fullName, host, culture, referencedAssemblyFiles)
{
- var assembly = LoadInMemoryAssembly (compiledAssembly);
- InitializeTemplate (assembly, fullName);
+ this.templateAssemblyData = templateAssemblyData;
}
- CompiledTemplate (ITextTemplatingEngineHost host, CultureInfo culture, string[] referenceAssemblyFiles)
+ CompiledTemplate (string templateClassFullName, ITextTemplatingEngineHost host, CultureInfo culture, string[] referencedAssemblyFiles)
{
+ this.templateClassFullName = templateClassFullName;
this.host = host;
this.culture = culture;
-
-#if FEATURE_ASSEMBLY_LOAD_CONTEXT
- templateContext = new TemplateAssemblyLoadContext (referenceAssemblyFiles, host);
-#else
- assemblyResolver = new (host, referenceAssemblyFiles);
-#endif
+ this.ReferencedAssemblyFiles = referencedAssemblyFiles;
}
-#if FEATURE_ASSEMBLY_LOAD_CONTEXT
- Assembly LoadAssemblyFile (string assemblyPath) => templateContext.LoadFromAssemblyPath (assemblyPath);
- Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData) => assemblyData.LoadInAssemblyLoadContext (templateContext);
-#else
- static Assembly LoadAssemblyFile (string assemblyPath) => Assembly.LoadFile (assemblyPath);
- static Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData) => assemblyData.LoadInCurrentAppDomain ();
-#endif
-
- void InitializeTemplate (Assembly assembly, string fullName)
+#if FEATURE_APPDOMAINS
+ string templateContentForAppDomain;
+ internal void SetTemplateContentForAppDomain (string content)
{
- //MS Templating Engine does not care about the type itself
- //it only requires the expected members to be on the compiled type
- Type transformType = assembly.GetType (fullName);
- textTransformation = Activator.CreateInstance (transformType);
+ templateContentForAppDomain = content;
+ }
- //set the host property if it exists
- Type hostType = null;
- if (host is TemplateGenerator gen) {
- hostType = gen.SpecificHostType;
+ TemplateProcessor CreateTemplateProcessor ()
+ {
+ var domain = host.ProvideTemplatingAppDomain (templateContentForAppDomain);
+ if (domain == null) {
+ return new TemplateProcessor ();
}
- var hostProp = transformType.GetProperty ("Host", hostType ?? typeof (ITextTemplatingEngineHost));
- if (hostProp != null && hostProp.CanWrite)
- hostProp.SetValue (textTransformation, host, null);
- if (host is ITextTemplatingSessionHost sessionHost) {
- //FIXME: should we create a session if it's null?
- var sessionProp = transformType.GetProperty ("Session", typeof (IDictionary));
- sessionProp.SetValue (textTransformation, sessionHost.Session, null);
+ var templateProcessorType = typeof (TemplateProcessor);
+
+ try {
+ var obj = domain.CreateInstanceAndUnwrap (templateProcessorType.Assembly.FullName, templateProcessorType.FullName);
+ return (TemplateProcessor)obj;
+ }
+ catch (Exception ex) when (ex is MissingMethodException || ex is System.IO.FileNotFoundException || ex is System.Runtime.Serialization.SerializationException) {
+ throw new TemplatingEngineException (
+ $"Could not instantiate type {templateProcessorType.FullName} in templating AppDomain '{domain.FriendlyName ?? "(no name)"}'. " +
+ $"The assembly '{templateProcessorType.Assembly.FullName}' must be resolvable in the domain. " +
+ $"The AppDomain's base directory may be incorrect: '{domain.BaseDirectory}'",
+ ex);
}
}
+#else
+ static TemplateProcessor CreateTemplateProcessor () => new ();
+#endif
public string Process ()
{
- var ttType = textTransformation.GetType ();
-
- var errorProp = ttType.GetProperty ("Errors", BindingFlags.Instance | BindingFlags.NonPublic);
- if (errorProp == null)
- throw new ArgumentException ("Template must have 'Errors' property");
- var errorMethod = ttType.GetMethod ("Error", new Type [] { typeof (string) });
- if (errorMethod == null) {
- throw new ArgumentException ("Template must have 'Error(string message)' method");
- }
-
- var errors = (CompilerErrorCollection)errorProp.GetValue (textTransformation, null);
- errors.Clear ();
-
- //set the culture
- if (culture != null)
- ToStringHelper.FormatProvider = culture;
- else
- ToStringHelper.FormatProvider = CultureInfo.InvariantCulture;
-
- string output = null;
-
- var initMethod = ttType.GetMethod ("Initialize");
- var transformMethod = ttType.GetMethod ("TransformText");
-
- if (initMethod == null) {
- errorMethod.Invoke (textTransformation, new object [] { "Error running transform: no method Initialize()" });
- } else if (transformMethod == null) {
- errorMethod.Invoke (textTransformation, new object [] { "Error running transform: no method TransformText()" });
- } else try {
- initMethod.Invoke (textTransformation, null);
- output = (string)transformMethod.Invoke (textTransformation, null);
- } catch (Exception ex) {
- errorMethod.Invoke (textTransformation, new object [] { "Error running transform: " + ex });
- }
-
- host.LogErrors (errors);
-
- ToStringHelper.FormatProvider = CultureInfo.InvariantCulture;
- return output;
+ TemplateProcessor processor = CreateTemplateProcessor ();
+ return processor.CreateAndProcess (host, templateAssemblyData, templateAssemblyFile, templateClassFullName, culture, ReferencedAssemblyFiles);
}
public void Dispose ()
{
- host = null;
-#if !FEATURE_ASSEMBLY_LOAD_CONTEXT
- assemblyResolver.Dispose ();
-#endif
}
}
}
diff --git a/Mono.TextTemplating/Mono.TextTemplating/CurrentDomainAssemblyResolver.cs b/Mono.TextTemplating/Mono.TextTemplating/CurrentDomainAssemblyResolver.cs
index 28964fd..2d79a8a 100644
--- a/Mono.TextTemplating/Mono.TextTemplating/CurrentDomainAssemblyResolver.cs
+++ b/Mono.TextTemplating/Mono.TextTemplating/CurrentDomainAssemblyResolver.cs
@@ -6,42 +6,40 @@
using System;
using System.IO;
using System.Reflection;
-using Microsoft.VisualStudio.TextTemplating;
-
namespace Mono.TextTemplating
{
class CurrentDomainAssemblyResolver : IDisposable
{
- readonly ITextTemplatingEngineHost host;
+ readonly Func resolveAssemblyReference;
readonly string[] assemblyFiles;
bool disposed;
- public CurrentDomainAssemblyResolver (ITextTemplatingEngineHost host, string[] assemblyFiles)
+ public CurrentDomainAssemblyResolver (string[] assemblyFiles, Func resolveAssemblyReference)
{
- this.host = host;
+ this.resolveAssemblyReference = resolveAssemblyReference;
this.assemblyFiles = assemblyFiles;
+
+ AppDomain.CurrentDomain.AssemblyResolve += ResolveReferencedAssemblies;
}
Assembly ResolveReferencedAssemblies (object sender, ResolveEventArgs args)
{
- AssemblyName asmName = new AssemblyName (args.Name);
+ var asmName = new AssemblyName (args.Name);
+
foreach (var asmFile in assemblyFiles) {
- if (asmName.Name == Path.GetFileNameWithoutExtension (asmFile))
+ if (asmName.Name == Path.GetFileNameWithoutExtension (asmFile)) {
return Assembly.LoadFrom (asmFile);
+ }
}
- var path = host.ResolveAssemblyReference (asmName.Name + ".dll");
- if (File.Exists (path))
+ var path = resolveAssemblyReference (asmName.Name + ".dll");
+ if (File.Exists (path)) {
return Assembly.LoadFrom (path);
+ }
return null;
}
- public void RegisterForCurrentDomain ()
- {
- AppDomain.CurrentDomain.AssemblyResolve += ResolveReferencedAssemblies;
- }
-
public void Dispose ()
{
if (!disposed) {
diff --git a/Mono.TextTemplating/Mono.TextTemplating/TemplateAssemblyLoadContext.cs b/Mono.TextTemplating/Mono.TextTemplating/TemplateAssemblyLoadContext.cs
index cf9ee0d..59bb59b 100644
--- a/Mono.TextTemplating/Mono.TextTemplating/TemplateAssemblyLoadContext.cs
+++ b/Mono.TextTemplating/Mono.TextTemplating/TemplateAssemblyLoadContext.cs
@@ -23,6 +23,9 @@ class TemplateAssemblyLoadContext : AssemblyLoadContext
readonly AssemblyName hostAsmName;
public TemplateAssemblyLoadContext (string[] templateAssemblyFiles, ITextTemplatingEngineHost host)
+#if NETCOREAPP3_0_OR_GREATER
+ : base (isCollectible: true)
+#endif
{
this.templateAssemblyFiles = templateAssemblyFiles;
this.host = host;
diff --git a/Mono.TextTemplating/Mono.TextTemplating/TemplateGenerator.cs b/Mono.TextTemplating/Mono.TextTemplating/TemplateGenerator.cs
index b0c6819..1b65322 100644
--- a/Mono.TextTemplating/Mono.TextTemplating/TemplateGenerator.cs
+++ b/Mono.TextTemplating/Mono.TextTemplating/TemplateGenerator.cs
@@ -90,9 +90,7 @@ public Task CompileTemplateAsync (string content, Cancellation
if (string.IsNullOrEmpty (content))
throw new ArgumentNullException (nameof (content));
- Errors.Clear ();
- encoding = Utf8.BomlessEncoding;
-
+ InitializeForRun ();
return Engine.CompileTemplateAsync (content, this, token);
}
@@ -104,6 +102,19 @@ protected internal TemplatingEngine Engine {
}
}
+ void InitializeForRun (string inputFileName = null, string outputFileName = null, Encoding encoding = null)
+ {
+ Errors.Clear ();
+ this.encoding = encoding ?? Utf8.BomlessEncoding;
+
+ if (outputFileName is null && inputFileName is not null) {
+ outputFileName = Path.ChangeExtension (inputFileName, ".txt");
+ }
+
+ TemplateFile = inputFileName;
+ OutputFile = outputFileName;
+ }
+
[Obsolete("Use ProcessTemplateAsync")]
public bool ProcessTemplate (string inputFile, string outputFile)
=> ProcessTemplateAsync (inputFile, outputFile, CancellationToken.None).Result;
@@ -115,6 +126,8 @@ public async Task ProcessTemplateAsync (string inputFile, string outputFil
if (string.IsNullOrEmpty (outputFile))
throw new ArgumentNullException (nameof (outputFile));
+ InitializeForRun (inputFile, outputFile);
+
string content;
try {
#if NETCOREAPP2_1_OR_GREATER
@@ -124,7 +137,6 @@ public async Task ProcessTemplateAsync (string inputFile, string outputFil
#endif
}
catch (IOException ex) {
- Errors.Clear ();
AddError ("Could not read input file '" + inputFile + "':\n" + ex);
return false;
}
@@ -155,15 +167,11 @@ public bool ProcessTemplate (string inputFileName, string inputContent, ref stri
public async Task<(string fileName, string content, bool success)> ProcessTemplateAsync (string inputFileName, string inputContent, string outputFileName, CancellationToken token = default)
{
- Errors.Clear ();
- encoding = Utf8.BomlessEncoding;
+ InitializeForRun (inputFileName, outputFileName);
- OutputFile = outputFileName;
- TemplateFile = inputFileName;
var outputContent = await Engine.ProcessTemplateAsync (inputContent, this, token).ConfigureAwait (false);
- outputFileName = OutputFile;
- return (outputFileName, outputContent, !Errors.HasErrors);
+ return (OutputFile, outputContent, !Errors.HasErrors);
}
public bool PreprocessTemplate (string inputFile, string className, string classNamespace,
@@ -177,11 +185,12 @@ public bool PreprocessTemplate (string inputFile, string className, string class
if (string.IsNullOrEmpty (outputFile))
throw new ArgumentNullException (nameof (outputFile));
+ InitializeForRun (inputFile, outputFile, encoding);
+
string content;
try {
content = File.ReadAllText (inputFile);
} catch (IOException ex) {
- Errors.Clear ();
AddError ("Could not read input file '" + inputFile + "':\n" + ex);
return false;
}
@@ -201,10 +210,8 @@ public bool PreprocessTemplate (string inputFile, string className, string class
public bool PreprocessTemplate (string inputFileName, string className, string classNamespace, string inputContent,
out string language, out string[] references, out string outputContent)
{
- Errors.Clear ();
- encoding = Utf8.BomlessEncoding;
+ InitializeForRun (null, inputFileName);
- TemplateFile = inputFileName;
outputContent = Engine.PreprocessTemplate (inputContent, this, className, classNamespace, out language, out references);
return !Errors.HasErrors;
@@ -231,7 +238,7 @@ public string PreprocessTemplate (
out string language,
out string[] references)
{
- TemplateFile = inputFile;
+ InitializeForRun (inputFileName: inputFile);
return Engine.PreprocessTemplate (pt, inputContent, settings, this, out language, out references);
}
@@ -243,13 +250,9 @@ public string PreprocessTemplate (
TemplateSettings settings,
CancellationToken token = default)
{
- Errors.Clear ();
- encoding = Utf8.BomlessEncoding;
+ InitializeForRun (inputFileName, outputFileName);
- OutputFile = outputFileName;
- TemplateFile = inputFileName;
var outputContent = await Engine.ProcessTemplateAsync (pt, inputContent, settings, this, token).ConfigureAwait (false);
- outputFileName = OutputFile;
return (outputFileName, outputContent);
}
@@ -269,13 +272,13 @@ public virtual AppDomain ProvideTemplatingAppDomain (string content)
protected virtual string ResolveAssemblyReference (string assemblyReference)
{
- if (System.IO.Path.IsPathRooted (assemblyReference))
- return assemblyReference;
- foreach (string referencePath in ReferencePaths) {
- var path = System.IO.Path.Combine (referencePath, assemblyReference);
- if (System.IO.File.Exists (path))
- return path;
- }
+ if (Path.IsPathRooted (assemblyReference))
+ return assemblyReference;
+ foreach (string referencePath in ReferencePaths) {
+ var path = Path.Combine (referencePath, assemblyReference);
+ if (File.Exists (path))
+ return path;
+ }
var assemblyName = new AssemblyName(assemblyReference);
if (assemblyName.Version != null)//Load via GAC and return full path
@@ -476,11 +479,13 @@ string ITextTemplatingEngineHost.ResolvePath (string path)
void ITextTemplatingEngineHost.SetFileExtension (string extension)
{
- extension = extension.TrimStart ('.');
- if (Path.HasExtension (OutputFile)) {
- OutputFile = Path.ChangeExtension (OutputFile, extension);
- } else {
- OutputFile = OutputFile + "." + extension;
+ if (OutputFile is not null) {
+ extension = extension.TrimStart ('.');
+ if (Path.HasExtension (OutputFile)) {
+ OutputFile = Path.ChangeExtension (OutputFile, extension);
+ } else {
+ OutputFile = OutputFile + "." + extension;
+ }
}
}
diff --git a/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs b/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs
index 3828d38..eb3b33c 100644
--- a/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs
+++ b/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs
@@ -275,20 +275,11 @@ CancellationToken token
return null;
}
+ var compiledTemplate = new CompiledTemplate (host, assembly, settings.GetFullName (), settings.Culture, references);
#if FEATURE_APPDOMAINS
- var domain = host.ProvideTemplatingAppDomain (content);
- var templateClassFullName = string.Concat(settings.Namespace, ".", settings.Name);
- if (domain != null) {
- var type = typeof(CompiledTemplate);
- var obj = domain.CreateInstanceFromAndUnwrap (type.Assembly.Location,
- type.FullName,
- new object[] { host, results, templateClassFullName, settings.Culture, references.ToArray () });
-
- return ((CompiledTemplate)obj, references);
- }
+ compiledTemplate.SetTemplateContentForAppDomain (content);
#endif
-
- return (new CompiledTemplate (host, assembly, settings.GetFullName (), settings.Culture, references), references);
+ return (compiledTemplate, references);
}
async Task<(CompilerResults, CompiledAssemblyData)> CompileCode (IEnumerable references, TemplateSettings settings, CodeCompileUnit ccu, CancellationToken token)
diff --git a/TextTransform/TextTransform.csproj b/TextTransform/TextTransform.csproj
index 703fd32..5974756 100644
--- a/TextTransform/TextTransform.csproj
+++ b/TextTransform/TextTransform.csproj
@@ -15,10 +15,6 @@
snupkg
-
-
-
-
diff --git a/dotnet-t4-project-tool/dotnet-t4-project-tool.csproj b/dotnet-t4-project-tool/dotnet-t4-project-tool.csproj
index fd89441..6a83abb 100644
--- a/dotnet-t4-project-tool/dotnet-t4-project-tool.csproj
+++ b/dotnet-t4-project-tool/dotnet-t4-project-tool.csproj
@@ -19,11 +19,6 @@ This package can be installed into a project using `DotNetCliToolReference`.
true
-
-
-
-
-
diff --git a/dotnet-t4/dotnet-t4.csproj b/dotnet-t4/dotnet-t4.csproj
index 9e4de61..6b641b9 100644
--- a/dotnet-t4/dotnet-t4.csproj
+++ b/dotnet-t4/dotnet-t4.csproj
@@ -22,11 +22,6 @@ This package can be installed as a dotnet global or local tool.
true
-
-
-
-
-
Project
test.tt