Search code examples
c#msbuildcode-generationlempec#

Esharp LeMP #r "path" works only with absolute path in compileTime directive


I am using the latest version 2.8.1

I tried project source and binary relative pathes without success. Here is the code: https://github.com/dadhi/LempTest/blob/strong text9ce17d16a8d7c71fc475921e1414e5bc64d72f9d/LibWithEcs/ServiceRegistrations.ecs.include#L3

Also, I am not sure how to pass the directory via --set:key=value. Should I wrap the key or value in quotes, escape the slashes? And if I succeed in passing, could I #get(key) in compileTime section and somehow use it for #r?

Update - after playing with @Qwertie answer

I have created a simple Test.ecs file to test the --set:key=value output

compileTime 
{
    using C = System.Console;
    var a = #get(a);
    C.WriteLine(a);
}

Here what I found:

  • lemp Test.ecs --set:a="c:" returns "Error: Expected a particle (id, literal, {braces} or (parens))."
  • lemp Test.ecs --set:a=@"c:" returns "c:"
  • lemp Test.ecs --set:a=@"c:\Code" returns the same "c:"
  • lemp Test.ecs --set:a=@"c:\\Code" again returns the same "c:"

and at least

  • lemp Test.ecs --set:a="""c:\\Code""" works and outputs "c:\Code"

Update - passing the Path value into Lemp from MSBuild Exec target

The final puzzle piece for me was passing the $(ProjectDir) as a value of the set:key=value command-line switch. Here is the final (actually not, see Update below) solution:

    <Target Name="LempTransform" BeforeTargets="BeforeBuild">
        <PropertyGroup>
            <ProjectDirEscaped>$(ProjectDir.Replace('\', '___'))</ProjectDirEscaped>
        </PropertyGroup>
        <!-- <Message Text="Project with escaped slashes is $(ProjectDirEscaped)" Importance="high"/> -->
        <Exec WorkingDirectory="$(ProjectDir)" 
            Command="dotnet lemp %(EcsFile.Identity) --outext=.Generated.cs --set:ProjectDir=@&quot;$(ProjectDirEscaped)&quot;" />
    </Target>

The main problem was even using @&quot$(ProjectDir)&quot; was not enough because the ProjectDir outputs the path with single slashes like c:\foo\bar. Then there is no way to escape them inside the string literal. So I've ended up replacing slashes manually with string.Replace and then replacing them back when working with the key in .ecs file:

compileTime 
{
    ##reference(precompute(System.IO.Path.Combine(#get(ProjectDir).Replace("___", "\\"), @"..\AnotherLib\bin\Debug\netstandard2.0\AnotherLib.dll")));

    using AnotherLib;

    //...

Update - a simplier solution by replacing backward with a forward slash per @Qwertie comment

The comment helped a lot, so I don't need to Replace escaped value back in consuming template.

First, I am replacing \ with / instead of ___ which still keeps the path a valid for .NET BCL API:

    <PropertyGroup>
        <ProjectDirEscaped>$(ProjectDir.Replace('\', '/'))</ProjectDirEscaped>
    </PropertyGroup>

Consequently, I don't need to Replace when using the path in ##reference:

    ##reference(precompute(System.IO.Path.Combine(#get(ProjectDir), @"..\AnotherLib\bin\Debug\netstandard2.0\AnotherLib.dll")));

Solution

  • Relative paths in #r are working for me in the Visual Studio extension. The path should be relative to the source file location.

    To find out the input folder where the file "believes" it is, write #get(#inputFolder); at the top of the file and then look at LeMP's output. If the output says _numget(_numinputFolder), something went wrong - the variable is unset, but it would be possible to set its value on the command line: --set:#inputFolder="C:\\Dev\\MyFolder".

    After --set:, key=value is parsed as an assignment expression, so you should wrap the value in quotes and replace backslashes with double-backslashes. (The expression syntax is LES or the current input language if any - --inlang=ecs for Enhanced C#)

    You can use #get(key) inside compileTime; however, the #r directive is parsed in a very dumb way because of the directive's very dumb syntax (which is different from normal C# strings). As a result, macros cannot be used on the #r line. You probably don't need to hack around this limitation, but if you do, you can replace #r "dir\Foo.dll" with its equivalent Loyc tree ##reference(@"""dir\\Foo.dll"""); (the extra quotes are optional) and then you can use macros in the modified line.

    It did not occur to me that one could use precompute inside compileTime, but you can and it works. So for example you can do ##reference(precompute(System.IO.Path.Combine(@"dir", "Foo.dll")));.