Using MSBuild PropertyGroup outside of Target block - msbuild

I have a project files collection:
<ItemGroup>
<ApplicationToDeploy
Include="Frontend.WebSite.csproj;11.WebServices.csproj;22.WebServices.csproj"/>
<ApplicationToDeploy
Include="33.WebServices.csproj;44.WebServices.csproj;Workflow55Svc.csproj"/>
</ItemGroup>
I'm trying to get collection of .config-files of these projects:
<Target Name="111">
<PropertyGroup>
<Cfgs>#(ApplicationToDeploy->'%(RootDir)%(Directory)*.config')</Cfgs>
</PropertyGroup>
<ItemGroup>
<InputConfigs Include="$(Cfgs)" />
</ItemGroup>
<Message Text="Cfgs: #(InputConfigs)"/>
</Target>
Inside the Target block all works fine (I see collection of Web.Configs, App.Configs, Log4net.Configs etc.):
Cfgs: C:\Sources\WebServices\11\WebServices\11.WebServices\Web.config;C:\Sources\WebServices\22\WebServices\22.WebServices\web.log4net.config;C:\Sources\WebServices\33\WebServices\33.WebServices\web.environment.config
But I want to initialize this ItemGroup outside of the Target block. Like this:
<PropertyGroup>
<Cfgs>#(ApplicationToDeploy->'%(RootDir)%(Directory)*.config')</Cfgs>
</PropertyGroup>
<ItemGroup>
<InputConfigs Include="$(Cfgs)" />
</ItemGroup>
<Target Name="111">
<Message Text="Cfgs: #(InputConfigs)"/>
</Target>
And when I do this outside of the Target block I get this:
Cfgs: C:\Sources\WebServices\11\WebServices\11.WebServices\*.config;C:\Sources\WebServices\22\WebServices\22.WebServices\*.config;C:\Sources\WebServices\33\WebServices\33.WebServices\*.config
I don't understand what's happens. Is it possible to get the same result outside Target block?

I don't understand what's happens.
This behavior is an effect of the MSBuild evaluation order:
During the evaluation phase of a build:
Properties are defined and modified in the order in which they
appear. Property functions are executed. Property values in the form
$(PropertyName) are expanded within expressions. The property value
is set to the expanded expression.
Item definitions are defined and modified in the order in which they appear. Property functions have already been expanded within expressions. Metadata values are set to the expanded expressions.
Item types are defined and modified in the order in which they appear. Item values in the form #(ItemType) are expanded. Item transformations are also expanded. Property functions and values have already been expanded within expressions. The item list and metadata values are set to the expanded expressions.
During the execution phase of a build:
Properties and items that are defined within targets
are evaluated together in the order in which they appear. Property
functions are executed and property values are expanded within
expressions. Item values and item transformations are also expanded.
The property values, item type values, and metadata values are set to
the expanded expressions."
There's another key point on that link "(...) The string expansion is dependent on the build phase.".
You're using the property 'Cfgs' to recursively map your projects' folders AND to define a wildcard to config files (*.config). When you define 'Cfgs' INSIDE the target, the InputConfigs receives the expanded value of Cfgs (semicolon-separated string list of folders), and just resolve the wildcards. On the other hand, when you define 'Cfgs' OUTSIDE the target, the InputConfigs receives the unexpanded value of Cfgs (#(ApplicationToDeploy->'%(RootDir)%(Directory)*.cs'). When the InputConfigs expands it, it results on the semicolon-separated string list of folders, but it doesn't resolve the wildcards (*.config).
Is it possible to get the same result outside Target block?
I think that InputConfigs should always receive the expanded list of directories. The expansion is made during the execution phase of the build. During this phase, only
properties and items defined within targets are evaluated. So, I would keep all the initialization inside an 'Initialization' Target block. I don't mean it is impossible to do it outside a Target block, but for the reasons mentioned it does not seems logical. =]
Hope this helps,

Related

NLog Conditional Variable Value

In NLog, is there a way to have a variable with a conditional value? I've seen this: https://github.com/NLog/NLog/wiki/When-Layout-Renderer and tried the following:
<variable name="EnvironmentString" value="${when:when='${IsProd}' == 'true':Prod:else:Stage}"/>
but the value is just returned as a literal; the logic is not being processed.
Thanks,
1) Change the syntax to this:
${when:when='${var:IsProd}'=='true':inner=Prod:else=Stage}
Note:
:else= rather than :else:
:inner=
var:IsProd (assuming IsProd is another variable)
2) Move the whole conditional to the final Layout
<target ... layout="other stuff|${when:when='${var:IsProd}'=='true':inner=Prod:else=Stage}|other stuff" ... />
I've not managed to get conditionals working in variables. Perhaps someone else could say why.
This should work.
But it depends how the variable is used.
If you use:
${EnvironmentString}
Then it's evaluated when loading the configuration, and so you could use it for all parameters.
To evaluate it dynamically, use
${var:EnvironmentString}
But please note that ${var} only works if the parameter of the Target/Layout is of type Layout

Could xsl:variable be defined twice with same name

Could xsl:variable be defined twice with same name in same scope.
For code similar to the following:
<xsl:template match="\">
<table>
<tr><td>
<xsl:variable name="status" select="normal"/>
</td></tr>
<tr><td>
<xsl:variable name="status" select="failed"/>
</td></tr>
</table>
</xsl:template>
Maybe it depends on browse's type. What is the standard?
First, two variables never have the same scope. Even if they are both global, the scope of the variable excludes its own select expression, so the scope of the two variables is different.
Second, in your example the scope of the two variables isn't even overlapping. Each variable is confined to its own containing td element.
For two global variables, the rule is that you can have two variables with the same name provided they have different import precedence, in which case all references are treated as references to the one with higher precedence.
If one variable is local and the other is global, then the local variable wins if it is in scope.
If you have two local variables with overlapping scope, this is an error in XSLT 1.0, but is permitted in XSLT 2.0; within the overlap area, the variable with smaller scope wins.
It is an error if two or more top-level variables (outside any template) have the same name.
Similarly it is an error if two or more variables within the same template have the same name.
But it is allowable for a variable within a template to shadow a variable of the same name at the top level.
There is no clash between variables unless their scopes (the parts of the stylesheet where the variables are visible) overlap. The scope of a variable inside a template includes its following sibling elements and their descendants. The scope of a top-level variable is everywhere in the stylesheet after that variable.
I have found the key.
MSXML 3.0 supports only XDR schemas, it does not support XSD schemas. MSXML 4.0, MSXML 5.0, and MSXML 6.0 support XSD schemas.

Using Item functions on metadata values

Background: I manage a fairly large solution. Every so often, people add a DLL reference to a project in the solution where they should've added a project reference. I want to issue a warning in such case. I want to do it by finding all reference with 'bin\debug' in their HintPath*. I know that references are Items in ItemGroup, with metadata "HintPath".
I expected something like this to work:
<Warning Text="Reference %(Reference.Identity) should be a project reference. HintPath: %(Reference.HintPath)"
Condition="%(Reference.HintPath).IndexOf('bin\debug') != -1"/>
However, Seems like I can't use string function IndexOf like that. I tried many permutations of the above, without success.
Edit: I know this check is not full-proof, but I just want to reduce honest mistakes.
Using MSBuild 4.0 Property Functions it is possible to do string comparisons:
<Target Name="AfterBuild">
<Message Text="Checking reference... '%(Reference.HintPath)'" Importance="high" />
<Warning Text="Reference %(Reference.Identity) should be a project reference. HintPath: %(Reference.HintPath)"
Condition="$([System.String]::new('%(Reference.HintPath)').Contains('\bin\$(Configuration)'))" />
</Target>
First not that your syntax is not correct for invoking functions, it would need to be:
%(Reference.HintPath.IndexOf(...)) # Note: not supported by MSBuild
However, property functions in MSBuild are not allowed on item metadata, so that is not going to help you either.
What you could work around this, by invoking a separate target which is basically called for every item.
<Target Name="CheckProjectReferences">
<MSBuild
Projects="$(MSBuildProjectFullPath)"
Properties="Identity=%(Reference.Identity);HintPath=%(Reference.HintPath)"
Targets="_Warn"/>
</Target>
<Target Name="_Warn">
<Warning Text="Reference $(Identity) should be a project reference. HintPath: $(HintPath)"
Condition="$(HintPath.IndexOf('bin\debug')) != -1"/>
</Target>
Frankly, I'm not sure if that is enough to catch all "violations". For example, the above will only work for bin\debug, but not for bin\Debug or other mixed-cased variations, which are functionally equivalent. To look for them as well, you'd need to call the IndexOf(string, StringComparison) overload, however just doing:
$(HintPath.IndexOf('bin\debug', System.StringComparison.OrdinalIgnoreCase))
Will not work, because the MSBuild overload resolution will pick IndexOf(char, Int32) and give you this error:
MSB4184: The expression ""bin\debug".IndexOf(bin\debug, System.StringComparison.OrdinalIgnoreCase)" cannot be evaluated. String must be exactly one character long.
So, you'll need to convince it by using the IndexOf(String, Int32, Int32, StringComparison) overload directly:
$(HintPath.IndexOf('bin\debug', 0, 9, System.StringComparison.OrdinalIgnoreCase))
You may need to also check for bin\Release or other variations. I'm not sure if that is the best way to figure out a reference should be a project reference, but if you know (and to a certain extend control) your environment it might be feasible.
#Christian.K is right in his analysis. Another solution would be to force the overload of type string using " for the quotes:
<Warning
Text="..."
Condition="$(HintPath.IndexOf("bin\debug", System.StringComparison.OrdinalIgnoreCase)) != -1" />

MSBuild tasks can accept primitive arrays, but how do you write one to pass into the task?

I would guess it has to be an ITaskItem since it's a vector instead of scalar, I've got the only 2 MsBuild books here on my desk, and I can't find examples of how to pass in an array to a task. I want to do a string array, but I'd like to know the proper way that would work with any primitive type.
How do you pass in an array of string (or int) to a MsBuild task?
MSBuild tasks can accept ITaskItem, primitives, string or an array of any of those for parameters. You just declare the type in your task and then the values will be converted before passed to the task. If the value cannot convert to the type then an exception will be raised and the build will be stopped.
For example if you have a task which accepts an int[] named Values then you could do.
<Target Name="MyTarget">
<MyTask Values="1;45;657" />
<!-- or you can do -->
<ItemGroup>
<SomeValues Include="7;54;568;432;79" />
</ItemGroup>
<MyTask Values="#(SomeValues) />
</Target>
Both approaches are essentially the same. The other answers stating that all parameters are strings or that you have to use ITaskItem are incorrect.
You said you have two books on MSBuild, then I presume one is my Inside the Microsoft Build Engine book, you should read the chapter on Custom Tasks so that you get a full grasp on these topics. There is a section explaining parameter types specifically.
IIRC, msbuild items are always string arrays - that is the only option. So an array of integers would be stored as an array numeric strings.

MSBuild: how to control the parsing of a semicolon delimited property

When a single property contains semicolons, MSBuild automatically parse the property into a list of properties when used within an itemgroup. Here's a snippet from my project:
<PropertyGroup>
<ConnectionString>workstation id=.;packet size=4096;Integrated Security=SSPI;data source=.;initial catalog=$(SqlDbName)</ConnectionString>
</PropertyGroup>
<ItemGroup>
<InstallShieldProperties Include="
CONNECTIONSTRING=$(ConnectionString);
Another=$(value)"/>
</ItemGroup>
When a task consumes the #(InstallShieldProperties) itemgroup, MSBuild will parse the ConnectionString property into a list of subset properties since it contains semicolons.
foreach (string property in Properties)
{
// Properties array parsed to pieces
}
I know I can change the delimiter of the itemgroup, but that won't make any difference.
I'm trying to avoid manipulating the string[] array within the custom task.
In MSBuild 4.0, you can use $([MSBuild]::Escape($(ConnectionString))).
AFAICS, you can either escape the semicolon in the $(ConnectionString) property like:
<ConnectionString>workstation id=.%3Bpacket size=4096%3B.."</ConnectionString>
Or use some task to replace the ';' in the ConnectionString property to '%3B' and then use that property in InstallShieldProperties item.
The other way could be to change the property type in the custom task from string[] to string, and then split it yourself, the way you want it. You could use enclosing quotes to separate Connection string part from other key/value pairs.
Or if it makes sense for your custom task, then maybe connection string is a special enough property to have as a separate task property.
In MSBuild 4.0, there are now Property Functions. One thing these allow you to do is call .NET String instance methods directly on your properties as if they are strings (which they are).
In your example, instead of using:
$(ConnectionString)
You could use:
$(ConnectionString.Replace(';', '%3B'))
Which will call the String method Replace() to replace semicolons with %3B