Combining paths is a common operation in a build system. Now that MSBuild is cross-platform, it's even more fraught with danger due to the vagaries of slash directions.
Most MSBuild projects just use $(Path1)\$(Path2). This works and is concise but has some downsides:
Path1 has a trailing slash)The most-obviously-correct approach is $([System.IO.Path]::Combine('$(Path1)', '$(Path2)')). That doesn't have any of those downsides, but is verbose and hard to read (and type).
It would be great if we could have a concise syntax that called the cross-platform supported system API, but wasn't unreadable. Ideas welcome!
One thought that I had is to make a new function similar to Exists, that takes two strings and returns a string. It could be named PathCombine. (Or even just Combine and it could imply that it is combining paths. This is similar to Exists, since Exists implies files/directories.)
<MyPath>PathCombine('$(Path1)', '$(Path2)')</MyPath>
Currently, we don't allow bare functions like that in property expansions. Exists() is valid only in Condition attributes--which allows it to have the more concise form.
We could change that, but it strikes me as risky and potentially hard to model when reading the project. Having something stuffed inside a $() property expansion would be more consistent with the current approach.
We need to document my new instrisic function NormalizeDirectory.
<PropertyGroup>
<MyPath1>$([MSBuild]::NormalizeDirectory('foo', 'bar', 'baz'))</MyPath1>
<MyPath2>$([MSBuild]::NormalizeDirectory($(Path1), $(Path2))</MyPath2>
<MyPath3>$([MSBuild]::NormalizeDirectory($(Path1), $(Path2), 'foo')</MyPath3>
</PropertyGroup>
It takes a params string[], calls Path.Combine(), Path.GetFullPath() and ensures the result has a trailing slash (where all directory separators are correct for the platform).
Functions like Exists() only work in conditions as far as I know because of special parsing logic. Property functions must be in the form $([]::) so the shortest is intrinsic functions exposed via $([MSBuild]::).
Oh, NormalizeDirectory! I thought I remembered something like that going in but couldn't dig it up. So yes, we need documentation :)
To make things more concise, is there a way you could you drop the $([MSBuild]::). part and if there is no prefix/class imply [MSBuild]?
Say: $(::NormalizeDirectory($(Path1), $(Path2))
I love that idea. I hoped it would produce a syntax error in today's MSBuild but instead it's . . . silently eaten? Which is not good. But I wouldn't feel too bad about making it do something meaningful.
<Message Importance="High" Text="Yo: $(::NormalizeDirectory('foo', 'bar', 'baz'))" />
Microsoft (R) Build Engine version 15.1.545.13942
Copyright (C) Microsoft Corporation. All rights reserved.
Build started 2/24/2017 12:55:17 PM.
Project "s:\work\bxc-52673\expansion.targets" on node 1 (default targets).
Build:
Yo:
Done Building Project "s:\work\bxc-52673\expansion.targets" (default targets).
@eerhardt anything is possible but I share Rainer's concern about breaking back compat. Properties are in the form $(foo) and you're asking for something like $(NormalizeDirectory('foo')). I'm not familiar enough with the parsing code to know how invasive it would be.
Properties are in the form $(foo) and you're asking for something like $(NormalizeDirectory('foo'))
I think you missed the :: part. Or at least, some sort of way to mark it as "this is a built-in function".
I saw it but I think if it would make sense to go for $(NormalizeDirectory('foo')) instead, do you agree? The :: looks weird to me....
If you know the full property function syntax, that would be a one-line update to your knowledge: "If no class is specified, the [MSBuild] class is implied." It doesn't have quite the same meaning as a leading 驻注诪讬讬诐 谞拽讜讚转讬讬诐 does in C++, but it still serves as a property-function marker, visible to the eye and to the parser. Strikes me as a great compromise between readability and compatibility.
Okay so "implicit intrinsic functions"! I'm fine with it.
Is there any resolution for "extension methods" for properties? If so, one could make a method similar to how string instance methods can be used:
$(PublishDir.Sub('pkg').Sub('stage0'))
Is there any resolution for "extension methods" for properties?
At the moment we use reflection on members of the current type. Is there a way to search for static extensions methods that apply to the current type?
I bet we could add logic to treat "I have a string and they're asking for Combine" specially, replacing it with a method of our choice. That's interesting, too. Could be something like $(Path1.Combine($(Path2), $(Path3))) . . .
+1 to the new extension method.
We could also introduce a special property like $(/): foo$(/)bar$(/)zar. Though the extension method looks easier to the eye. We could even add a Normalize method: $(Path1.Sub('Path2').Sub('Path2').Normalize()).
Most helpful comment
Is there any resolution for "extension methods" for properties? If so, one could make a method similar to how string instance methods can be used: