diff --git a/Mono.TextTemplating.Build.Tests/Mono.TextTemplating.Build.Tests.csproj b/Mono.TextTemplating.Build.Tests/Mono.TextTemplating.Build.Tests.csproj
index 9f81497..d309a71 100644
--- a/Mono.TextTemplating.Build.Tests/Mono.TextTemplating.Build.Tests.csproj
+++ b/Mono.TextTemplating.Build.Tests/Mono.TextTemplating.Build.Tests.csproj
@@ -21,7 +21,7 @@
and loads first, thereby breaking loading of MSBuild assemblies. Force-upgrade it.
-->
-
+
diff --git a/Mono.TextTemplating.Build/BuildSpecificCodeCompilationContext.cs b/Mono.TextTemplating.Build/BuildSpecificCodeCompilationContext.cs
new file mode 100644
index 0000000..d73d5fd
--- /dev/null
+++ b/Mono.TextTemplating.Build/BuildSpecificCodeCompilationContext.cs
@@ -0,0 +1,16 @@
+using Mono.TextTemplating.CodeCompilation;
+
+namespace Mono.TextTemplating.Build
+{
+ public sealed class BuildSpecificCodeCompilationContext : ICodeCompilationContext
+ {
+
+ readonly string _compilerSearchPath;
+ public BuildSpecificCodeCompilationContext (string compilerSearchPath)
+ {
+ _compilerSearchPath = compilerSearchPath;
+ }
+
+ public string CompilerSearchPath => _compilerSearchPath;
+ }
+}
\ No newline at end of file
diff --git a/Mono.TextTemplating.Build/Mono.TextTemplating.Build.csproj b/Mono.TextTemplating.Build/Mono.TextTemplating.Build.csproj
index 81b90d2..4c81069 100644
--- a/Mono.TextTemplating.Build/Mono.TextTemplating.Build.csproj
+++ b/Mono.TextTemplating.Build/Mono.TextTemplating.Build.csproj
@@ -34,7 +34,7 @@
-
+
diff --git a/Mono.TextTemplating.Build/T4.BuildTools.targets b/Mono.TextTemplating.Build/T4.BuildTools.targets
index 3839b1b..b6bcfc9 100644
--- a/Mono.TextTemplating.Build/T4.BuildTools.targets
+++ b/Mono.TextTemplating.Build/T4.BuildTools.targets
@@ -84,6 +84,7 @@
IncludePaths="@(T4IncludePath)"
ParameterValues="@(T4Argument)"
ReferencePaths="@(T4ReferencePath)"
+ RoslynTargetsPath="$(RoslynTargetsPath)"
TransformTemplates="@(T4Transform)"
PreprocessTemplates="@(T4Preprocess)"
AssemblyReferences="@(T4AssemblyReference)"
diff --git a/Mono.TextTemplating.Build/TextTransform.cs b/Mono.TextTemplating.Build/TextTransform.cs
index 79a4e02..4afd1a3 100644
--- a/Mono.TextTemplating.Build/TextTransform.cs
+++ b/Mono.TextTemplating.Build/TextTransform.cs
@@ -10,6 +10,7 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
+using Mono.TextTemplating.CodeCompilation;
namespace Mono.TextTemplating.Build
{
@@ -37,6 +38,11 @@ public TextTransform () : base (Messages.ResourceManager) { }
public string PreprocessTargetRuntimeIdentifier { get; set; }
+ ///
+ /// The path to roslyn which will be used for this build.
+ ///
+ public string RoslynTargetsPath { get; set; }
+
[Required]
public string IntermediateDirectory { get; set; }
@@ -130,7 +136,9 @@ public override bool Execute ()
}
}
- TextTransformProcessor.Process (Log, previousBuildState, buildState, PreprocessOnly);
+ ICodeCompilationContext context = RoslynTargetsPath != null ? new BuildSpecificCodeCompilationContext(RoslynTargetsPath) : new DefaultCodeCompilationContext ();
+
+ TextTransformProcessor.Process (Log, previousBuildState, buildState, PreprocessOnly, context);
if (buildState.TransformTemplates != null) {
TransformTemplateOutput = new ITaskItem[buildState.TransformTemplates.Count];
diff --git a/Mono.TextTemplating.Build/TextTransformProcessor.cs b/Mono.TextTemplating.Build/TextTransformProcessor.cs
index 3854cd2..2c68126 100644
--- a/Mono.TextTemplating.Build/TextTransformProcessor.cs
+++ b/Mono.TextTemplating.Build/TextTransformProcessor.cs
@@ -12,12 +12,13 @@
using Microsoft.Build.Utilities;
using Microsoft.VisualStudio.TextTemplating;
+using Mono.TextTemplating.CodeCompilation;
namespace Mono.TextTemplating.Build
{
static class TextTransformProcessor
{
- public static bool Process (TaskLoggingHelper taskLog, TemplateBuildState previousBuildState, TemplateBuildState buildState, bool preprocessOnly)
+ public static bool Process (TaskLoggingHelper taskLog, TemplateBuildState previousBuildState, TemplateBuildState buildState, bool preprocessOnly, ICodeCompilationContext context)
{
(var transforms, var preprocessed) = buildState.GetStaleAndNewTemplates (previousBuildState, preprocessOnly, new WriteTimeCache ().GetWriteTime, taskLog);
@@ -50,7 +51,7 @@ public static bool Process (TaskLoggingHelper taskLog, TemplateBuildState previo
}
string outputContent;
- (outputFile, outputContent) = generator.ProcessTemplateAsync (pt, inputFile, inputContent, outputFile, settings).Result;
+ (outputFile, outputContent) = generator.ProcessTemplateAsync (pt, inputFile, inputContent, outputFile, settings, context).Result;
if (generator.Errors.HasErrors) {
return;
diff --git a/Mono.TextTemplating.Tests/ProcessingTests.cs b/Mono.TextTemplating.Tests/ProcessingTests.cs
index 2cb8b75..223c41a 100644
--- a/Mono.TextTemplating.Tests/ProcessingTests.cs
+++ b/Mono.TextTemplating.Tests/ProcessingTests.cs
@@ -29,7 +29,7 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
-
+using Mono.TextTemplating.CodeCompilation;
using Xunit;
namespace Mono.TextTemplating.Tests
@@ -150,7 +150,7 @@ public async Task SetOutputExtension ()
var gen = new TemplateGenerator ();
var pt = gen.ParseTemplate (inputFile, inputContent);
TemplateSettings settings = TemplatingEngine.GetSettings (gen, pt);
- (outputName, _) = await gen.ProcessTemplateAsync (pt, inputFile, inputContent, outputName, settings);
+ (outputName, _) = await gen.ProcessTemplateAsync (pt, inputFile, inputContent, outputName, settings, new DefaultCodeCompilationContext());
Assert.Equal ("hello.cfg", outputName);
}
diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs
index 4235e06..da96513 100644
--- a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs
+++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs
@@ -45,6 +45,37 @@ public static bool IsLangVersionArg (string arg) =>
return $"-langversion:{ToString (runtime.RuntimeLangVersion)}";
}
+ public static CSharpLangVersion ParseLangVersionOutput(string stdOut){
+ // first remove any lines which are not numbers, and handle the default of something similar to "13.0 (default)"
+ var lines = stdOut.Split(new[] { Environment.NewLine, " " }, StringSplitOptions.RemoveEmptyEntries);
+ // now find any numbers remaining - these are the versions and 1-5 are ints and greater than 6.0 are floats
+ var version = lines.Where(l => float.TryParse(l, out _))
+ .Select(float.Parse)
+ .ToArray();
+
+ // if we have any numbers, return the highest one
+ if(version.Length > 0){
+ return version.Max() switch {
+ 1 => CSharpLangVersion.v5_0,
+ 2 => CSharpLangVersion.v6_0,
+ 3 => CSharpLangVersion.v7_0,
+ 4 => CSharpLangVersion.v7_1,
+ 5 => CSharpLangVersion.v7_2,
+ 6 => CSharpLangVersion.v7_3,
+ 7 => CSharpLangVersion.v8_0,
+ 8 => CSharpLangVersion.v9_0,
+ 9 => CSharpLangVersion.v10_0,
+ 10 => CSharpLangVersion.v11_0,
+ 11 => CSharpLangVersion.v12_0,
+ 12 => CSharpLangVersion.v13_0,
+ _ => CSharpLangVersion.Latest
+ };
+ }
+
+ // we didn't find any numbers, so return the latest version
+ return CSharpLangVersion.Latest;
+ }
+
//https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history
public static CSharpLangVersion FromNetCoreSdkVersion (SemVersion sdkVersion)
=> sdkVersion switch {
diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/DefaultCodeCompilationContext.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/DefaultCodeCompilationContext.cs
new file mode 100644
index 0000000..07c1ae0
--- /dev/null
+++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/DefaultCodeCompilationContext.cs
@@ -0,0 +1,11 @@
+using System;
+using System.IO;
+
+namespace Mono.TextTemplating.CodeCompilation
+{
+ public class DefaultCodeCompilationContext : ICodeCompilationContext
+ {
+ public string CompilerSearchPath { get; } = Path.GetDirectoryName (typeof (object).Assembly.Location);
+
+ }
+}
\ No newline at end of file
diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/ICodeCompilationContext.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/ICodeCompilationContext.cs
new file mode 100644
index 0000000..116db07
--- /dev/null
+++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/ICodeCompilationContext.cs
@@ -0,0 +1,10 @@
+namespace Mono.TextTemplating.CodeCompilation
+{
+ public interface ICodeCompilationContext
+ {
+ ///
+ /// The directory to search for the compiler in.
+ ///
+ string CompilerSearchPath { get; }
+ }
+}
\ No newline at end of file
diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs
index 3a00fac..9307950 100644
--- a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs
+++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs
@@ -85,7 +85,9 @@ class RuntimeInfo
public string RefAssembliesDir { get; }
public string RuntimeFacadesDir { get; }
- public static RuntimeInfo GetRuntime ()
+ public static RuntimeInfo GetRuntime () => GetRuntime (new DefaultCodeCompilationContext ());
+
+ public static RuntimeInfo GetRuntime (ICodeCompilationContext context)
{
if (Type.GetType ("Mono.Runtime") != null)
{
@@ -93,7 +95,7 @@ public static RuntimeInfo GetRuntime ()
}
else if (RuntimeInformation.FrameworkDescription.StartsWith (".NET Framework", StringComparison.OrdinalIgnoreCase))
{
- return GetNetFrameworkRuntime ();
+ return GetNetFrameworkRuntime (context);
}
else
{
@@ -123,24 +125,56 @@ static RuntimeInfo GetMonoRuntime ()
);
}
- static RuntimeInfo GetNetFrameworkRuntime ()
+ static RuntimeInfo GetNetFrameworkRuntime (ICodeCompilationContext context)
{
- var runtimeDir = Path.GetDirectoryName (typeof (object).Assembly.Location);
- var csc = Path.Combine (runtimeDir, "csc.exe");
+ var csc = Path.Combine (context.CompilerSearchPath, "csc.exe");
if (!File.Exists (csc)) {
return FromError (RuntimeKind.NetFramework, "Could not find csc in host .NET Framework installation");
}
- return new RuntimeInfo (
- RuntimeKind.NetFramework,
- runtimeDir: runtimeDir,
- // we don't really care about the version if it's not .net core
- runtimeVersion: new Version ("4.7.2"),
- refAssembliesDir: null,
- runtimeFacadesDir: runtimeDir,
- cscPath: csc,
- cscMaxLangVersion: CSharpLangVersion.v5_0,
- runtimeLangVersion: CSharpLangVersion.v5_0
- );
+
+ // now, determine the csc max lang version, just run csc -langversion:? and parse the output
+ var psi = new System.Diagnostics.ProcessStartInfo (csc, "-langversion:?") {
+ RedirectStandardOutput = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ psi.RedirectStandardOutput = true;
+ using (var p = System.Diagnostics.Process.Start (psi)) {
+ p.WaitForExit ();
+ var output = p.StandardOutput.ReadToEnd ();
+ if (output.Contains ("error CS2008")) {
+ // then we have a basic csc that doesn't support -langversion, probably net4
+ return new RuntimeInfo (
+ RuntimeKind.NetFramework,
+ runtimeDir: context.CompilerSearchPath,
+ // we don't really care about the version if it's not .net core
+ runtimeVersion: new Version ("4.7.2"),
+ refAssembliesDir: null,
+ runtimeFacadesDir: context.CompilerSearchPath,
+ cscPath: csc,
+ cscMaxLangVersion: CSharpLangVersion.v5_0,
+ runtimeLangVersion: CSharpLangVersion.v5_0
+ );
+ }
+
+ var maxLangVersion = CSharpLangVersionHelper.ParseLangVersionOutput (output);
+
+ // now that we know the max lang version, we need to find the runtime dir, which if it's msbuild, it's gonna be one dir up from CompilerSearchPath
+ // C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\Roslyn -> C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin
+
+ return new RuntimeInfo (
+ RuntimeKind.NetFramework,
+ runtimeDir: Directory.GetParent (context.CompilerSearchPath).FullName,
+ // we don't really care about the version if it's not .net core
+ runtimeVersion: new Version ("4.7.2"),
+ refAssembliesDir: null,
+ runtimeFacadesDir: context.CompilerSearchPath,
+ cscPath: csc,
+ cscMaxLangVersion: maxLangVersion,
+ runtimeLangVersion: maxLangVersion
+ );
+ }
}
static RuntimeInfo GetDotNetCoreSdk ()
@@ -193,8 +227,6 @@ static RuntimeInfo GetDotNetCoreSdk ()
out _
);
-
-
return new RuntimeInfo (
RuntimeKind.NetCore,
runtimeDir: runtimeDir,
diff --git a/Mono.TextTemplating/Mono.TextTemplating/TemplateGenerator.cs b/Mono.TextTemplating/Mono.TextTemplating/TemplateGenerator.cs
index 9cd4839..33ff3e4 100644
--- a/Mono.TextTemplating/Mono.TextTemplating/TemplateGenerator.cs
+++ b/Mono.TextTemplating/Mono.TextTemplating/TemplateGenerator.cs
@@ -33,6 +33,7 @@
using Microsoft.VisualStudio.TextTemplating;
using System.Threading;
using System.Threading.Tasks;
+using Mono.TextTemplating.CodeCompilation;
namespace Mono.TextTemplating
{
@@ -119,7 +120,22 @@ void InitializeForRun (string inputFileName = null, string outputFileName = null
public bool ProcessTemplate (string inputFile, string outputFile)
=> ProcessTemplateAsync (inputFile, outputFile, CancellationToken.None).Result;
- public async Task ProcessTemplateAsync (string inputFile, string outputFile, CancellationToken token = default)
+ public async Task ProcessTemplateAsync (string inputFile, string outputFile)
+ => await ProcessTemplateAsync (inputFile, outputFile, CancellationToken.None).ConfigureAwait (false);
+
+ public async Task ProcessTemplateAsync (string inputFile, string outputFile, CancellationToken token)
+ => await ProcessTemplateAsync (inputFile, outputFile, new DefaultCodeCompilationContext(), token).ConfigureAwait (false);
+
+ internal async Task<(string outputFile, string outputContent)> ProcessTemplateAsync (ParsedTemplate pt, string inputFile, string inputContent, string outputFile, TemplateSettings settings)
+ {
+ InitializeForRun (inputFileName: inputFile, outputFileName: outputFile);
+
+ var outputContent = await Engine.ProcessTemplateAsync (pt, inputContent, settings, this, new DefaultCodeCompilationContext()).ConfigureAwait (false);
+
+ return (OutputFile, outputContent);
+ }
+
+ public async Task ProcessTemplateAsync (string inputFile, string outputFile, ICodeCompilationContext context, CancellationToken token)
{
if (string.IsNullOrEmpty (inputFile))
throw new ArgumentNullException (nameof (inputFile));
@@ -263,10 +279,20 @@ public string PreprocessTemplate (
string outputFileName,
TemplateSettings settings,
CancellationToken token = default)
+ => await ProcessTemplateAsync (pt, inputFileName, inputContent, outputFileName, settings, new DefaultCodeCompilationContext (), token).ConfigureAwait (false);
+
+ public async Task<(string fileName, string content)> ProcessTemplateAsync (
+ ParsedTemplate pt,
+ string inputFileName,
+ string inputContent,
+ string outputFileName,
+ TemplateSettings settings,
+ ICodeCompilationContext context,
+ CancellationToken token = default)
{
InitializeForRun (inputFileName, outputFileName);
- var outputContent = await Engine.ProcessTemplateAsync (pt, inputContent, settings, this, token).ConfigureAwait (false);
+ var outputContent = await Engine.ProcessTemplateAsync (pt, inputContent, settings, this, context, token).ConfigureAwait (false);
return (OutputFile, outputContent);
}
diff --git a/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs b/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs
index 7a00f75..e448f04 100644
--- a/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs
+++ b/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs
@@ -59,10 +59,10 @@ internal void SetCompilerFunc (Func cr
createCompilerFunc = createCompiler;
}
- CodeCompilation.CodeCompiler GetOrCreateCompiler ()
+ CodeCompilation.CodeCompiler GetOrCreateCompiler (ICodeCompilationContext context)
{
if (cachedCompiler == null) {
- var runtime = RuntimeInfo.GetRuntime ();
+ var runtime = RuntimeInfo.GetRuntime (context);
if (runtime.Error != null) {
throw new TemplatingEngineException (runtime.Error);
}
@@ -79,13 +79,13 @@ public string ProcessTemplate (string content, ITextTemplatingEngineHost host)
public async Task ProcessTemplateAsync (string content, ITextTemplatingEngineHost host, CancellationToken token = default)
{
- using var tpl = await CompileTemplateAsync (content, host, token).ConfigureAwait (false);
+ using var tpl = await CompileTemplateAsync (content, host, new DefaultCodeCompilationContext(), token).ConfigureAwait (false);
return tpl?.Process ();
}
- public async Task ProcessTemplateAsync (ParsedTemplate pt, string content, TemplateSettings settings, ITextTemplatingEngineHost host, CancellationToken token = default)
+ public async Task ProcessTemplateAsync (ParsedTemplate pt, string content, TemplateSettings settings, ITextTemplatingEngineHost host, ICodeCompilationContext context, CancellationToken token = default)
{
- var tpl = await CompileTemplateAsync (pt, content, host, settings, token).ConfigureAwait (false);
+ var tpl = await CompileTemplateAsync (pt, content, host, settings, context, token).ConfigureAwait (false);
using (tpl?.template) {
return tpl?.template.Process ();
}
@@ -189,9 +189,12 @@ internal static string PreprocessTemplateInternal (ParsedTemplate pt, string con
[Obsolete("Use CompileTemplateAsync")]
public CompiledTemplate CompileTemplate (string content, ITextTemplatingEngineHost host)
- => CompileTemplateAsync (content, host, CancellationToken.None).Result;
+ => CompileTemplateAsync (content, host, new DefaultCodeCompilationContext(), CancellationToken.None).Result;
public async Task CompileTemplateAsync (string content, ITextTemplatingEngineHost host, CancellationToken token)
+ => await CompileTemplateAsync (content, host, new DefaultCodeCompilationContext(), token).ConfigureAwait (false);
+
+ public async Task CompileTemplateAsync (string content, ITextTemplatingEngineHost host, ICodeCompilationContext context, CancellationToken token)
{
if (content == null)
throw new ArgumentNullException (nameof (content));
@@ -204,7 +207,7 @@ public async Task CompileTemplateAsync (string content, ITextT
return null;
}
- var tpl = await CompileTemplateInternal (pt, content, host, null, token).ConfigureAwait (false);
+ var tpl = await CompileTemplateInternal (pt, content, host, null, context, token).ConfigureAwait (false);
return tpl?.template;
}
@@ -224,7 +227,7 @@ public CompiledTemplate CompileTemplate (
out string[] references,
TemplateSettings settings = null)
{
- var result = CompileTemplateAsync (pt, content, host, settings, CancellationToken.None).Result;
+ var result = CompileTemplateAsync (pt, content, host, settings, new DefaultCodeCompilationContext(), CancellationToken.None).Result;
references = result?.references;
return result?.template;
}
@@ -234,14 +237,17 @@ public CompiledTemplate CompileTemplate (
string content,
ITextTemplatingEngineHost host,
TemplateSettings settings = null,
+ ICodeCompilationContext context = null,
CancellationToken token = default)
{
if (pt == null)
throw new ArgumentNullException (nameof (pt));
if (host == null)
throw new ArgumentNullException (nameof (host));
+ if (content == null)
+ context = new DefaultCodeCompilationContext();
- return CompileTemplateInternal (pt, content, host, settings, token);
+ return CompileTemplateInternal (pt, content, host, settings,context, token);
}
async Task<(CompiledTemplate template, string[] references)?> CompileTemplateInternal (
@@ -249,6 +255,7 @@ public CompiledTemplate CompileTemplate (
string content,
ITextTemplatingEngineHost host,
TemplateSettings settings,
+ ICodeCompilationContext context,
CancellationToken token
)
{
@@ -274,7 +281,7 @@ CancellationToken token
return null;
}
- (var results, var assembly) = await CompileCode (references, settings, ccu, token).ConfigureAwait (false);
+ (var results, var assembly) = await CompileCode (references, settings, ccu, context, token).ConfigureAwait (false);
if (results.Errors.HasErrors) {
host.LogErrors (pt.Errors);
host.LogErrors (results.Errors);
@@ -288,7 +295,7 @@ CancellationToken token
return (compiledTemplate, references);
}
- async Task<(CompilerResults, CompiledAssemblyData)> CompileCode (IEnumerable references, TemplateSettings settings, CodeCompileUnit ccu, CancellationToken token)
+ async Task<(CompilerResults, CompiledAssemblyData)> CompileCode (IEnumerable references, TemplateSettings settings, CodeCompileUnit ccu, ICodeCompilationContext context, CancellationToken token)
{
string sourceText;
var genOptions = new CodeGeneratorOptions ();
@@ -300,7 +307,7 @@ CancellationToken token
CompiledAssemblyData compiledAssembly = null;
// this may throw, so do it before writing source files
- var compiler = GetOrCreateCompiler ();
+ var compiler = GetOrCreateCompiler (context);
// GetTempFileName guarantees that the returned file name is unique, but
// there are no equivalent for directories, so we create a directory