NeVeSpl / NTypewriter

File/code generator using Scriban text templates populated with C# code metadata from Roslyn API.
https://nevespl.github.io/NTypewriter/
MIT License
122 stars 25 forks source link

Code works on Windows but not Linux #72

Closed MelDommer closed 1 year ago

MelDommer commented 1 year ago

I have been using the Nuget to write my own custom CLI to run NTypewriter. The CLI works great when running on Windows it compiles the csproj and when I render the script it does everything perfect. However when I run on Linux I get an error thrown.

I have a custom attribute:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
internal sealed class ExportToTypescriptAttribute : Attribute
{
    public string OutputPath { get; }

    public ExportToTypescriptAttribute(string OutputPath)
    {
        this.OutputPath = OutputPath;
    }
}

I have a class with this attribute on it: a string with a relative path that should be returned when looking at the arguments is passed in on the custom attribute.

  [ExportToTypescript(@"relative\path\")]
  public class DatabaseBackupStatus
  {
      public bool IsBackupConfigured { get; set; }

      public bool IsSucceed { get; set; }

      public string BackupAddress { get; set; } = string.Empty;

      public string ErrorMessage { get; set; } = string.Empty;

  }

My .nt script that is rendered is this: note the Custom.GetOutputPath this is where the trouble happens on linux

{{ ## Generate Interfaces from Classes ## }}
{{- for class in data.Classes | Symbols.ThatHaveAttribute "ExportToTypescript"}}
{{- capture output -}}
{{- $path = class | Custom.GetOutputPath-}}
{{- class | Custom.CopyrightHeader}}
{{~class | Custom.GetImports~}}
export interface {{class.Name}}{{-if class.HasBaseClass}} extends {{class.BaseClass.Name; end}} {
{{-for prop in class.Properties | Symbols.ThatArePublic}}
{{}}  {{prop.Name}}: {{prop | Custom.ToTypeScriptType}}{{if for.length > 1 }};{{end}}
  {{-if for.last}}
}
  {{~ end -}}
{{- end -}}
{{- end -}}
{{- output ?! Save output $path-}}
{{- end -}}

This is the GetOutputPath function on windows it works fine but on linux the "generateTo" is null and it throws the ArgumentNullException.

public static string GetOutputPath(ISymbolBase symbol)
{
    string appendPath = "";

    // Get the ExportToTypeScript attribute and check for the OutputPath
    IAttribute attr = GetExportToTypescriptAttribute(symbol);
    object? generateTo = attr.Arguments.FirstOrDefault(a => a.Name == "OutputPath")?.Value ?? null;

    if (generateTo is null)
    {
        throw new ArgumentNullException($"{symbol.Name}'s ExportToTypescript had no OutputPath this should not be possible!");
    }

    appendPath = (string)generateTo; // returns the path based on what was passed in the arguments of the type.

    // return the path concatenated together into one string
    return GetPathToRepository() + appendPath + generatedSuffix + (symbol is IEnum ? enumSuffix : "") + symbol.BareName + ".ts";
}

This is the GetExportToTypescriptAttribute method that gets the attribute from the symbol. It is not throwing so I know it is at least getting and returning the IAttribute.

private static IAttribute GetExportToTypescriptAttribute(ISymbolBase symbol)
{
    try
    {
        IAttribute attr = symbol.Attributes.First(a => a.Name == "ExportToTypescript");
        return attr;
    }
    catch (ArgumentNullException e) // Should never happen since the script should only pass symbols that have this attribute. Throw if it does
    {
        // Make sure you script is using -> | Symbols.ThatHaveAttribute "ExportToTypescript" to filter the code model.
        throw new ArgumentNullException($"The symbol {symbol.BareName} does not contain the ExportToTypescript attribute.", e.InnerException);
    }
}

What I don't know is why on Windows the following code here works and on Linux it does not.

    IAttribute attr = GetExportToTypescriptAttribute(symbol);
    object? generateTo = attr.Arguments.FirstOrDefault(a => a.Name == "OutputPath")?.Value ?? null;

The attr.Arguments does not seem to find the one named "OutputPath" or the Value of it is null if it does find it. I've not got far enough yet to figure out as I didn't have Linux environment setup to debug this on.

I'm currently in the process of setting up a linux machine that I can try and debug this on but I would love to see if someone else knows why this is happening.

MelDommer commented 1 year ago

I was able to get a Linux machine setup and run this and debug it.

I found that in fact IAttribute.Arguments is 0 on Linux and when I run on windows it is populated Debug on Windows: image

Debug on Linux: image

NeVeSpl commented 1 year ago

That is interesting, first of all, you should try using unix paths, linux does not understand paths in windows format, I had a problem with that when I was preparing linux workflow: https://github.com/NeVeSpl/NTypewriter.SourceGenerator.Examples/commit/724f53fc42aeb0671772aa295a4e2bed32ce5082

MelDommer commented 1 year ago

Right but this part is just a string. The CLI is finding the csproj files and compiling them just fine. I'm not getting any issues with paths and things not found. This is in the CodeAnalysis where the property in my custom attribute is not showing up as an argument on a Linux machine that runs the CLI. It does show up on Windows though.

I do know that paths are case sensitive though and it is on my list to fix the path separators so they are correct when run on Linux. I don't see this as being a factor in where this error is happening though. Do you? For the purposes where this code is it should be an Argument with the name "OutputPath" and the value is just a string. It doesn't matter what is in the string is what I mean.

MelDommer commented 1 year ago

I did just replace all the strings passed into the constructor for the Attribute with / but that had no effect. I didn't think it would.

MelDommer commented 1 year ago

I wonder since my csproj is targeting .net6 if this is a problem because the CodeAnalysis.CSharp is 3.11 which I think is .NET5 I don't know if that would be a problem but it seems odd that the arguments are not found on Linux but are on Windows. I tried looking around a bit in the Ntypewriter.CodeModel.Rosyln area but didn't come up with anything other than there are newer versions of it and that my projects it is compiling are .NET6

MelDommer commented 1 year ago

The only other thing I can think of is maybe it's the Lazy stuff. I was reading up on it https://learn.microsoft.com/en-us/dotnet/api/system.lazy-1?view=net-5.0

I know threads on Windows and Linux work differently as far as how work is queued. Maybe there is something with that. I'm kind of at a loss. I will probably just running this on Linux.

NeVeSpl commented 1 year ago

The problem is located rather outside of NTypewriter code. I have added your attr to NTypewriter.SourceGenerator.Examples:

https://github.com/NeVeSpl/NTypewriter.SourceGenerator.Examples/commit/f06050de342c0baf614a35941becde257577f79e

and it works as expected on ubuntu-latest with .net 6.0.x

https://github.com/NeVeSpl/NTypewriter.SourceGenerator.Examples/actions/runs/4551265596

Your CLI is probably using Buildalyzer to build a project, or you are doing compilation by hand, and I expect that attribute data is not present in Compilation.

MelDommer commented 1 year ago

You are correct it is using Buildalyzer. So Buildalyzer doesn't work on ubuntu is what you're saying? I'm not sure I can switch to SourceGenerator at the moment but thanks for clarifying.

NeVeSpl commented 1 year ago

It may be that case, but usually, a problem is in setting up Buildalyzer incorrectly. Doing compilation is not an easy thing to do, even with the help of Buildalyzer not everything works out of the box and some fine-tuning per project usually is needed. This is a reason why NTypewriter does not have an official CLI.

MelDommer commented 1 year ago

Ok thanks, yeah I'm not sure what I can configure differently since it works on windows but not linux. I do know the csproj files when they are referencing other items, folders, projects, etc. they are using ..\otherproject.csproj but that's how the IDE set it up. I could manual change them to ../ to see but I don't think that's the issue as the compilations is at least finding all the classes with my custom attribute just doesn't have the arguments of that attribute.