Drizin / CodegenCS

C# Toolkit for Code Generation (T4 alternative!)
MIT License
223 stars 30 forks source link

Unable to run if reference are added #23

Closed Seuleuzeuh closed 3 days ago

Seuleuzeuh commented 8 months ago

Hi @Drizin !

I've try to replace an old T4 template by a CodegenCS generation. Our previous T4 query the database to generate some files. I've try do to the same with CodegenCS but this crash on run command. Is it possible to do that with CodegenCS ? I'm missing something ?

Have a great day !

dotnet-codegencs.exe version 3.1.5.0 (CodegenCS.Core.dll version 3.3.1.0) Building 'EnumGenerator.cs'... Errors: CS0234: Line 3 Le nom de type ou d'espace de noms 'Data' n'existe pas dans l'espace de noms 'Microsoft' (vous manque-t-il une référence d'assembly ?) CS0234: Line 4 Le nom de type ou d'espace de noms 'Generator' n'existe pas dans l'espace de noms 'Leo.Entities.Enum' (vous manque-t-il une référence d'assembly ?) CS0246: Line 148 Le nom de type ou d'espace de noms 'TableEnumInfo' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 164 Le nom de type ou d'espace de noms 'EnumValueItem' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 164 Le nom de type ou d'espace de noms 'TableEnumInfo' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 174 Le nom de type ou d'espace de noms 'TableEnumInfo' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 186 Le nom de type ou d'espace de noms 'TableEnumInfo' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 17 Le nom de type ou d'espace de noms 'TableEnumInfo' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 17 Le nom de type ou d'espace de noms 'TableEnumInfo' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 18 Le nom de type ou d'espace de noms 'SqlConnection' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 18 Le nom de type ou d'espace de noms 'SqlConnection' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 21 Le nom de type ou d'espace de noms 'SqlCommand' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 21 Le nom de type ou d'espace de noms 'SqlCommand' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 24 Le nom de type ou d'espace de noms 'SqlDataReader' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 36 Le nom de type ou d'espace de noms 'TableEnumInfo' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 64 Le nom de type ou d'espace de noms 'SqlDataReader' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) CS0246: Line 68 Le nom de type ou d'espace de noms 'EnumValueItem' est introuvable (vous manque-t-il une directive using ou une référence d'assembly ?) Error while building 'EnumGenerator.cs'. TemplateBuilder ('dotnet-codegencs template build') Failed. ERROR: Could not load or build template: EnumGenerator.cs

Seuleuzeuh commented 8 months ago

Maybe it's a duclicate of #19 ? But reading this, i don't understand how @JanK-Qics make it work.

Drizin commented 8 months ago

@Seuleuzeuh as explained in #19, dotnet-codegencs does not allow directives like #r <assembly> in the templates. My suggestion there (and here) is to create a regular CSPROJ where you can add the references that you need (including Microsoft.Data.SqlClient or whatever else you need), and use CodegenCS nuget.

You have two options here:

  1. You can build an EXE, so you write a regular entrypoint named Main() (you write the whole program).
  2. You can build a DLL, and you write a Main() entrypoint compatible with dotnet-codegencs. That means you can use dotnet-codegencs template run <yourdll> [model]. It can be useful because there are some smart defaults: you don't need to explicitly save the output files, the command-line tool can be used to define output folders, input models, etc. Code gets much shorter.
Seuleuzeuh commented 8 months ago

Thanks @Drizin.

I've tried the second one, but it does not work. I've made a small repro project. I've try the command line with the .cs and the .dll, both not working.

jano-kucera commented 8 months ago

Maybe it's a duclicate of #19 ? But reading this, i don't understand how @JanK-Qics make it work.

In main I load all assemblies (Assembly.LoadFrom) which I need and then I go over all the types which are compatible. Some of our assemblies are in Framework 4.8, so some types cannot be properly loaded to the .NET 7 CodegenCS project this is in, that's why I filter them out and work only with what doesn't fail.

   public static void LoadAssembliesWithDocumentations()
   {
       foreach (var path in AssembliesPaths)
       {
           var assembly = Assembly.LoadFrom(path);
           Assemblies.Add(assembly);

           var xmlPath = path.Replace(".dll", ".xml", StringComparison.InvariantCulture);
           var xml = XDocument.Load(xmlPath);
           AssembliesDocumentations.Add(assembly, xml);
       }

       LoadAssemblyTypes();
   }

   /// <summary>
   /// Loads types from assemblies which provide safe access to their CustomAttributes
   /// </summary>
   private static void LoadAssemblyTypes()
   {
       foreach (var assembly in Assemblies)
       {
           IEnumerable<Type> types = Array.Empty<Type>();

           // load all loadable types
           try
           {
               types = assembly.GetTypes();
           }
           catch (ReflectionTypeLoadException ex)
           {
               types = ex.Types
                   .Where(t => t != null && !string.IsNullOrEmpty(t.FullName) && t.FullName.StartsWith("QicsUnity", StringComparison.InvariantCulture))
                   .Select(t => t!);
           }
           finally
           {
               // filter out types which don't provide safe access to CustomAttributes
               foreach (var t in types)
               {
                   try
                   {
                       // get only types on which the GetCustomAttributes call does not fail
                       t.GetCustomAttributes(false);
                       AssemblyTypes.Add(t);
                   }
                   catch (FileNotFoundException)
                   {
                   }
               }
           }
       }
   }

Here's Main() - here I trigger CodegenCS template runs, which are basically .WriteLine($$""" XY""") calls over the ICodegenContext created as new CodegenContext(). Each template finishes with .SaveToFolder on the context.

 public static void Main()
 {
     // display login prompt if user is not authenticated yet
     TfsUtility.Authenticate().GetAwaiter().GetResult();

     // load the assemblies before generation starts
     AssembliesUtility.LoadAssembliesWithDocumentations();

     Console.WriteLine($"{"Generator",-40}{"Status",20}");
     Console.WriteLine(new string('-', 55));

     // Setup template runs
     var templates = new CodegenCSTemplate[]
     {
         new ConstantsTemplate(),
         new CqrsServicesTemplate(),
         new DatePropertiesTemplate(),
         new EnumsTemplate(),
         new LocalizationTemplate(),
         new ModelsTemplate(),
         new ObjectsToInstancesTemplate(),
         new ResourceStringsClassTemplate(),
         new ResourceStringsJsonTemplate(),
         new ResourceStringsResxTemplate()
     };
     ...
     }
Drizin commented 4 months ago

@Seuleuzeuh I'm sorry for the delay - I finally found some time to check your repository. I could make it work by making TemplateLauncher.cs pre-load all DLLs in the same folder as the entrypoint type:

var baseFolder = new FileInfo(_entryPointClass.Assembly.Location).Directory;
var dlls = baseFolder.GetFiles("*.dll");
foreach (var dll in dlls)
{
    try
    {
        Assembly.LoadFrom(dll.FullName);
    }
    catch (Exception ex) { }
}

Not the best solution, and probably we should make that parametrizable (dynamically pass the assembly references to load), but I'm open to other suggestions.

Then I was getting PlatformNotSupportedException: Microsoft.Data.SqlClient is not supported on this platform., which I could fix by adding <RuntimeIdentifier>win7-x64</RuntimeIdentifier> to your csproj.

Please let me know your thoughts, and if this works.

Drizin commented 3 weeks ago

Issue https://github.com/Drizin/CodegenCS/issues/30 is about the same problem, so let me recap/resume this topic:

The underlying problem here (feature gap) is that during compilation we're passing a small number of DLL references but currently there is no way to add more references (the using namespaces doesn't help with that), so basically System.Xml.dll is not available.

Ideally, we would want some way of specifying references during the compilation phase (TemplateBuilder/RoslynCompiler) - that can also be used during execution (TemplateLauncher, when we invoke the built "template.dll").

In order to specify references in the command-line we could use something like -r:somepath.dll, and that could also be used during execution (or maybe we can just save the references paths during build and load them automatically during execution - not sure if paths are already inside dll or if we can somehow store them as metadata).

But for people using VS Extension instead of CLI ideally we want to read some directives directly from source code (something like #r "somepath.dll", which is used for CSX scripts).

I'll try to draft it on this weekend. Feel free to share your thoughts and suggestions.

Drizin commented 1 week ago

I've just republished new dotnet-codegencs 3.5.0 and VSExtension 3.7.2. CLI now has the option to specify -r:assembly.dll, and both CLI/VSExtension allow the use of #r "assembly.dll" in the templates. Please check changes new unit tests and/or documentation updates. Looks like it's working fine for framework assemblies, but probably need some runtime adjustments to make it work with third-party assemblies. Let me know how it goes.