Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to use compiler from Visual Studio #86

Open
kccarter76 opened this issue Jul 31, 2020 · 7 comments · May be fixed by #85
Open

Add option to use compiler from Visual Studio #86

kccarter76 opened this issue Jul 31, 2020 · 7 comments · May be fixed by #85
Labels

Comments

@kccarter76
Copy link

How can I go about ensuring that Mono.TextTemplating is using the Roslyn compiler that comes with the currently installed version of visual studio?

@kccarter76 kccarter76 linked a pull request Jul 31, 2020 that will close this issue
@jogibear9988
Copy link

@kccarter76
Copy link
Author

not completely fixed. if your using .net framework < 5 I am pretty sure it is still using the rosyln engine installed there which for my system is still C# 5. however, I have the .net to .net core bridge working via RPC and the T4 Host for .net core is using the roslyn engine installed with .net core 3.1 and that is C# 8.0

@jogibear9988
Copy link

when you look here:

https://github.com/dotnet/roslyn/blob/master/docs/wiki/NuGet-packages.md

As you use 3.6 it should contain at least c#8

@jogibear9988
Copy link

i think if you switch to 3.8, you could use c#9 features

@jogibear9988
Copy link

could you enable issues in your branch?
i've switch now to your nuget from ms TextTemplateing, but my code does not work.
It fails to load my reference cause of this line:
https://github.com/SubSonic-Core/SubSonic.Core.TextTemplating/blob/9b4efc2a604fc426fe95cc1e91b7262593ba6fd8/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs#L377

   Path.IsPathRooted(referencePath)

returns false, but why is it needed?

@kccarter76
Copy link
Author

path rooted location is defined as the c:\***.dll location on a physical drive and anything else is is essential not resolved.

this is an example of a t4 template that I use to generate DAL wrapper.

<#@ parameter type="System.String" name="$connectionKey$" #>
<#@ assembly name="SubSonic.Core.Abstractions" #>
<#@ assembly name="SubSonic.Core.DAL" #>
<#@ assembly name="SubSonic.Core.Extensions" #>
<#@ assembly name="System.Linq.Expressions" #>
<#@ assembly name="System.Linq.Queryable" #>
<#@ assembly name="System.Linq" #>

<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.Common" #>
<#@ import namespace="SubSonic" #>
<#@ import namespace="SubSonic.Core" #>
<#+
public class Settings
{
	public static ITextTemplatingEngineHost Host { get; set; }

	public static IEnumerable<string> ExcludeRelationships
	{
		get
		{
			return new string[] { };
		}
	}

	public static IEnumerable<string> ExcludeTables
	{
		get
		{ 
			return new string[]{
				"sysdiagrams",
				"BuildVersion",
				"__RefactorLog",
				"aspnet_Applications",
				"aspnet_Membership",
				"aspnet_Paths",
				"aspnet_PersonalizationAllUsers",
				"aspnet_PersonalizationPerUser",
				"aspnet_Profile",
				"aspnet_Roles",
				"aspnet_SchemaVersions",
				"aspnet_Users",
				"aspnet_UsersInRoles",
				"aspnet_WebEvent_Events"
				};
		}
	}

	public static string Connection
	{
		get
		{
			if (!string.IsNullOrEmpty($connectionKey$))
			{
				return $connectionKey$;
			}

			throw new InvalidOperationException("Connection string was not injected.");
		}
	}

	public static TService GetService<TService>()
	{
		if(((IServiceProvider)Host).GetService(typeof(TService)) is TService success)
		{
			return success;
		}
		return default;
	}
}
#>

this is also an implementation of a transformation host that you will not find in the mono text templating. but it is derived from a subclass which is found in the code you are linking. The transformation host can use nuget package references to resolve assembly's this is just one piece required and is out of a visual studio 2019 extension that make the mono engine useable inside visual studio and overrides/replaces the stock implementation of the t4 templating service which is out of box with VS 2019. there is quite a lot to deal with when making sure the assemblies are path rooted for the roselyn engine.

using Mono.TextTemplating;
using Mono.TextTemplating.CodeCompilation;
using Mono.VisualStudio.TextTemplating;
using Mono.VisualStudio.TextTemplating.VSHost;
using SubSonic.Core.VisualStudio.Common;
using SubSonic.Core.VisualStudio.Host;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using NuGet.Configuration;
using System.Text.RegularExpressions;

namespace SubSonic.Core.VisualStudio.Services
{
    [Serializable]
    public class RemoteTransformationHost
        : ProcessEngineHost
        , IServiceProvider
    {
        private static readonly Dictionary<string, string> KnownAssemblyNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            { "System", Path.GetFileName(typeof(String).Assembly.Location) },
            { "System.Data", Path.GetFileName(typeof(System.Data.DataTable).Assembly.Location) },
            { "System.Xml", Path.GetFileName(typeof(System.Xml.XmlAttribute).Assembly.Location) }
        };

        private readonly List<string> includePaths;
        private readonly Dictionary<RuntimeKind, List<string>> referencePaths;
        private readonly Dictionary<string, string> parameters;
        private readonly Dictionary<string, KeyValuePair<string, string>> directiveProcessors;

        [NonSerialized]
        private GetHostOptionEventHandler getHostOptionEventHandler;

        public event GetHostOptionEventHandler GetHostOptionEventHandler
        {
            add
            {
                getHostOptionEventHandler += value;
            }
            remove
            {
                getHostOptionEventHandler -= value;
            }
        }

        [NonSerialized]
        private ResolveAssemblyReferenceEventHandler resolveAssemblyReferenceEventHandler;

        public event ResolveAssemblyReferenceEventHandler ResolveAssemblyReferenceEventHandler
        {
            add
            {
                resolveAssemblyReferenceEventHandler += value;
            }
            remove
            {
                resolveAssemblyReferenceEventHandler -= value;
            }
        }

        [NonSerialized]
        private ExpandAllVariablesEventHandler expandAllVariablesEventHandler;

        public event ExpandAllVariablesEventHandler ExpandAllVariablesEventHandler
        {
            add
            {
                expandAllVariablesEventHandler += value;
            }
            remove
            {
                expandAllVariablesEventHandler -= value;
            }
        }

        [NonSerialized]
        private ResolveParameterValueEventHandler resolveParameterValueEventHandler;

        public event ResolveParameterValueEventHandler ResolveParameterValueEventHandler
        {
            add
            {
                resolveParameterValueEventHandler += value;
            }
            remove
            {
                resolveParameterValueEventHandler -= value;
            }
        }

        [NonSerialized]
        private ResolveDirectiveProcessorEventHandler resolveDirectiveProcessorEventHandler;

        public event ResolveDirectiveProcessorEventHandler ResolveDirectiveProcessorEventHandler
        {
            add
            {
                resolveDirectiveProcessorEventHandler += value;
            }
            remove
            {
                resolveDirectiveProcessorEventHandler -= value;
            }
        }

        public RemoteTransformationHost()
            : base()
        {
            parameters = new Dictionary<string, string>();
            includePaths = new List<string>();
            referencePaths = new Dictionary<RuntimeKind, List<string>>();
            directiveProcessors = new Dictionary<string, KeyValuePair<string, string>>();
        }

        public Dictionary<RuntimeKind, List<string>> ReferencePaths => referencePaths;

        public IList<string> RuntimeReferencePaths
        {
            get
            {
                RuntimeKind runtime = (RuntimeKind)(OnGetHostOption(nameof(TemplateSettings.RuntimeKind)) ?? RuntimeKind.NetCore);

                if (referencePaths.ContainsKey(runtime))
                {
                    return referencePaths[runtime];
                }

                return new List<string>();
            }
        }

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
        public async Task InitializeAsync()
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
        {
            Initialize();
        }

        protected override void Initialize()
        {
            foreach (RuntimeInfo info in RuntimeInfo.GetAllValidRuntimes())
            {
                AddReferencePath(info.Kind, info.RuntimeDirectory);
            }

            base.Initialize();

            if (!StandardAssemblyReferences.Any(x => x == GetType().Assembly.Location))
            {
                StandardAssemblyReferences.Add(GetType().Assembly.Location);
                StandardAssemblyReferences.Add(OnResolveAssemblyReference("System.Data"));
                StandardAssemblyReferences.Add(OnResolveAssemblyReference("System.Data.Common"));
                StandardAssemblyReferences.Add(OnResolveAssemblyReference("System.ComponentModel"));
            }
        }

        /// <summary>
        /// add a reference path for a particular runtime
        /// </summary>
        /// <param name="runtime"></param>
        /// <param name="directoryPath"></param>
        public void AddReferencePath(RuntimeKind runtime, string directoryPath)
        {
            if (!ReferencePaths.ContainsKey(runtime))
            {
                ReferencePaths[runtime] = new List<string>();
            }

            if (!ReferencePaths[runtime].Any(x => x.Equals(directoryPath, StringComparison.OrdinalIgnoreCase)))
            {
                ReferencePaths[runtime].Add(directoryPath);
            }
        }

        public override ITextTemplatingSession CreateSession()
        {
            ITextTemplatingSession session = new TextTemplatingSession
            {
                [nameof(TemplateFile)] = TemplateFile
            };

            return session;
        }

        private object OnGetHostOption(string optionName)
        {
            if (getHostOptionEventHandler != null)
            {
                return getHostOptionEventHandler(this, new GetHostOptionEventArgs(optionName));
            }
            return default;
        }

        public override object GetHostOption(string optionName)
        {
            return OnGetHostOption(optionName);
        }

        public void AddDirective(string key, KeyValuePair<string, string> processor)
        {
            directiveProcessors.Add(key, processor);
        }

        public void AddParameter(string key, string value)
        {
            parameters.Add(key, value);
        }

        public override bool LoadIncludeText(string requestFileName, out string content, out string location)
        {
            bool success = false;

            content = "";
            location = ResolvePath(requestFileName);

            if (location == null || !File.Exists(location))
            {
                foreach (string path in includePaths)
                {
                    string f = Path.Combine(path, requestFileName);

                    if (File.Exists(f))
                    {
                        location = f;
                        break;
                    }
                }
            }

            if (location.IsNullOrEmpty())
            {
                return success;
            }

            try
            {
                content = File.ReadAllText(location);

                success = true;
            }
            catch (IOException ex)
            {
                _ = LogErrorAsync($"Could not read included file '{location}':\n{ex}");
            }

            return success;
        }

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
        public async Task LogErrorsAsync(TemplateErrorCollection errors)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
        {
            LogErrors(errors);
        }



#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
        public async Task LogErrorAsync(string message, Location location, bool isWarning = default)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
        {
            LogError(message, location, isWarning);
        }

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
        public async Task LogErrorAsync(string message, bool isWarning = default)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
        {
            LogError(message, isWarning);
        }

#if NETFRAMEWORK
        public override AppDomain ProvideTemplatingAppDomain(string content)
        {
            return null;    // not supported in a framework to netcore solution
        }
#endif

        public override string ResolveAssemblyReference(string assemblyReference)
        {
            return OnResolveAssemblyReference(assemblyReference);
        }

        private string OnResolveAssemblyReference(string assemblyReference)
        {
            if (!Path.IsPathRooted(assemblyReference))
            {
                KnownAssemblyNames.TryGetValue(assemblyReference, out string fileName);

                foreach (string referencePath in RuntimeReferencePaths)
                {
                    string xpath = Path.Combine(referencePath, fileName ?? assemblyReference);
                    if (File.Exists(xpath))
                    {
                        return xpath;
                    }
                    else if (File.Exists($"{xpath}.dll"))
                    {
                        return $"{xpath}.dll";
                    }
                }

                if (TryResolveAssemblyReferenceByNuget(assemblyReference, out string assemblyPath))
                {
                    return assemblyPath;
                }

                if (resolveAssemblyReferenceEventHandler != null)
                {
                    return resolveAssemblyReferenceEventHandler(this, new ResolveAssemblyReferenceEventArgs(assemblyReference));
                }
            }

            return assemblyReference;
        }

        public bool TryResolveAssemblyReferenceByNuget(string assemblyReference, out string assemblyPath)
        {
            var settings = Settings.LoadDefaultSettings(null);
            string 
                NugetPackageRoot = SettingsUtility.GetGlobalPackagesFolder(settings),
                FrameworkSegment = null;

            if (Directory.Exists(Path.Combine(NugetPackageRoot, assemblyReference)))
            {
                assemblyPath = Utilities.FindHighestVersionedDirectory(Path.Combine(NugetPackageRoot, assemblyReference), path =>
                {
                    bool success = false;
                    string[] framework = new[] { "lib", "netcoreapp3.1" };
                    decimal netStandardVer = GetNetStandardVersion(framework[1]);

                    while (true)
                    {
                        FrameworkSegment = string.Join("\\", framework);
                        success = File.Exists(Path.Combine(path, FrameworkSegment, $"{assemblyReference}.dll"));

                        if (!success && netStandardVer >= 1.0M)
                        {
                            framework[1] = $"netstandard{netStandardVer}";
                            netStandardVer -= 0.1M;
                            continue;
                        }

                        break;
                    }

                    return success;
                }) ?? assemblyReference;

                assemblyPath = Path.Combine(assemblyPath, FrameworkSegment, $"{assemblyReference}.dll");
            }
            else
            {
                assemblyPath = assemblyReference;
            }

            return Path.IsPathRooted(assemblyPath);
        }

        private decimal GetNetStandardVersion(string framework)
        {
            Regex netcoreapp = new Regex("(?<framework>[a-z]*)(?<major>[0-9]*).(?<minor>[0-9]*)");

            var match = netcoreapp.Match(framework);

            if (match.Groups["framework"].Value == "netcoreapp")
            {
                int.TryParse(match.Groups["major"].Value, out int major);

                if (major >= 3)
                {
                    return 2.1M;
                }
            }
            return 2.0M;
        }

        private string OnExpandAllVariables(string filePath)
        {
            if (expandAllVariablesEventHandler != null)
            {
                return expandAllVariablesEventHandler(this, new ExpandAllVariablesEventArgs(filePath));
            }
            return filePath;
        }

        public override string ResolvePath(string path)
        {
            if (path.IsNotNullOrEmpty())
            {
                path = OnExpandAllVariables(path);

                if (Path.IsPathRooted(path))
                {
                    return path;
                }
            }

            string directory;
            if (TemplateFile.IsNullOrEmpty())
            {
                directory = Environment.CurrentDirectory;
            }
            else
            {
                directory = Path.GetDirectoryName(Path.GetFullPath(TemplateFile));
            }

            if (path.IsNullOrEmpty())
            {
                return directory;
            }

            string test = Path.Combine(directory, path);
            if (File.Exists(test) || Directory.Exists(test))
            {
                return test;
            }

            return path;
        }        

        public void OnResolveDirectiveProcessor(string processorName)
        {
            if (resolveDirectiveProcessorEventHandler != null && !directiveProcessors.ContainsKey(processorName))
            {
                Type processorType = resolveDirectiveProcessorEventHandler(this, new ResolveDirectiveProcessorEventArgs(processorName));

                if (processorType != null)
                {
                    AddDirective(processorName, new KeyValuePair<string, string>(processorType.Assembly.Location, processorType.AssemblyQualifiedName));
                }
            }
        }

        public override Type ResolveDirectiveProcessor(string processorName)
        {
            OnResolveDirectiveProcessor(processorName);

            if (!directiveProcessors.TryGetValue(processorName, out KeyValuePair<string, string> value))
            {
                throw new Exception(string.Format("No directive processor registered as '{0}'", processorName));
            }

            var assemblyPath = ResolveAssemblyReference(value.Value);

            if (!Path.IsPathRooted(assemblyPath))
            {
                throw new Exception(string.Format("Could not resolve assembly '{0}' for directive processor '{1}'", value.Value, processorName));
            }

            var assembly = Assembly.LoadFrom(assemblyPath);

            return assembly.GetType(value.Key, true);
        }

        private void OnResolveParameterValue(ParameterKey parameterKey)
        {
            if (resolveParameterValueEventHandler != null && !parameters.ContainsKey(parameterKey.ParameterName))
            {
                string value = resolveParameterValueEventHandler(this, new ResolveParameterValueEventArgs(parameterKey));

                if (value.IsNotNullOrEmpty())
                {
                    AddParameter(parameterKey.ParameterName, value);
                }
            }
        }

        public override string ResolveParameterValue(string directiveId, string processorName, string parameterName)
        {
            var key = new ParameterKey(directiveId, processorName, parameterName);

            OnResolveParameterValue(key);

            if (parameters.TryGetValue(key.ParameterName, out var value))
            {
                return value;
            }
            if (processorName != null || directiveId != null)
            {
                return ResolveParameterValue(null, null, parameterName);
            }
            return null;
        }

        public object GetService(Type serviceType)
        {
            if (serviceType.IsAssignableFrom(GetType()))
            {
                return this;
            }
            else if (Session.ContainsKey(serviceType.Name))
            {
                return Session[serviceType.Name];
            }
            return default;
        }
    }
}

@mhutch
Copy link
Contributor

mhutch commented Nov 30, 2020

By design it uses the compiler that comes with the framework that's hosting it. If you use the global tool version, which is currently the primary use case, then it will use the compiler shipped with .NET Core.

If you're hosting the library yourself, then you can choose to also reference the Mono.TextTemplating.Roslyn NuGet and call the UseInProcessCompiler () extension method on the generator. This will bundle a more recent C# compiler with your app and use that.

By design it does not search for the compiler shipped with Visual Studio, as this would mean that apps that use the library would depend on Visual Studio, which is not a good default behavior as it could cause apps to run correctly on developer machines and fail on other machines.

However, I would be open patches that add a method to pass in a custom compiler path or to enable use of the Visual Studio compiler e.g. bool TryUseVisualStudioCompiler(string visualStudioPath=null) or SetCompilerPath(string path).

@mhutch mhutch changed the title Mono.TextTemplating Framework is using a C# 5 compiler Add option to use compiler from Visual Studio Nov 30, 2020
@mhutch mhutch added the feature label Dec 3, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants