Why is MsBuild's ItemGroup->EndWith not working for '.exe'? - c#

I'm trying to make an item group of dll's and exe's but filtering out any msi's and test.dll's.
The following snippet doesn't include the .exe in the UniqueAssemblies itemgroup. It does contain all the dll's and removes the msi as expected though. UniqueCompiledFiles does contain all the expected output files (.test.dll, .dll, .msi, .exe)
<Target Name="CustomCompile">
<MSBuild
BuildInParallel="true"
Projects="#(ProjectFiles)"
Properties="$(ProjectProperties)"
>
<Output TaskParameter="TargetOutputs" ItemName="CompiledFiles" />
</MSBuild>
<RemoveDuplicates Inputs="#(CompiledFiles)">
<Output TaskParameter="Filtered" ItemName="UniqueCompiledFiles" />
</RemoveDuplicates>
<ItemGroup>
<UniqueAssemblies
Include="%(UniqueCompiledFiles.Identity)"
Condition=" '#(UniqueCompiledFiles->EndsWith('.dll'))' == 'true' " />
<UniqueAssemblies
Include="%(UniqueCompiledFiles.Identity)"
Condition=" '#(UniqueCompiledFiles->EndsWith('.exe'))' == 'true' " />
</ItemGroup>
I also figured out this workaround that does properly filter the .exe.
<ItemGroup>
<UniqueAssemblies2
Include="%(UniqueCompiledFiles.Identity)"
Condition=" $([System.String]::new('%(UniqueCompiledFiles.Identity)').EndsWith('.exe')) " />
</ItemGroup>
Found out the culprit lines that could be removed to fix the problem but it doesn't actually answer the question.
<ItemGroup>
<!-- Workaround for MSBuild defect: https://github.com/Microsoft/msbuild/issues/69 -->
<UniqueCompiledFiles Include="Project\bin\release\Project.exe">
<MSBuildSourceProjectFile>Project\Project.csproj</MSBuildSourceProjectFile>
<Platform>x86</Platform>
</UniqueCompiledFiles>
</ItemGroup>
Why is the #(UniqueCompiledFiles->EndsWith('.exe')) syntax not working as expected?

You're trying to use a Property Function on an item (instead of a property).
You should be able to get the desired result by using something like this:
<ItemGroup>
<UniqueAssemblies2
Include="%(UniqueCompiledFiles.Identity)"
Condition=" '%(Extension)' == '.exe' " />
</ItemGroup>

Related

Python as a post-build step to C#

I'm looking into ways to execute python as part of a C# build.
Specifically, I want to create a Python package based on a C# project through python.net. My general idea was to build the C# project first. And then, as some sort of post-build step, invoke python to build a package based on the newly generated NET assemblies.
I can't presume python will installed on the build host, so ideally I want to include a "portable" - even more ideally, nuget-based - python distribution.
I have found a promising nuget package, but am not entirely sure of its usage. It incldues no C# code, but has all python binaries included, and has build props as copy/pasted below for reference.
Given on that package's props - can I somehow reference its binaries from my own project as a post-build step?
Say, for example, I want to add a post-build step to my own project, that simply just invokes "python.exe" after the build. How could I do that?
My own project:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="python" Version="3.10.0-a6" />
</ItemGroup>
<Target Name="MyCustomStep" AfterTargets="Build">
<!-- .. now what? I can't seem to access. e.g. #(PythonHome) or $(PythonHome) from here --/>
<Target>
</Project>
Props of the python package from nuget:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="$(Platform) == 'X64'">
<PythonHome Condition="$(PythonHome) == ''">$([System.IO.Path]::GetFullPath("$(MSBuildThisFileDirectory)\..\..\tools"))</PythonHome>
<PythonInclude>$(PythonHome)\include</PythonInclude>
<PythonLibs>$(PythonHome)\libs</PythonLibs>
<PythonTag>3.10</PythonTag>
<PythonVersion>3.10.0-a6</PythonVersion>
<IncludePythonExe Condition="$(IncludePythonExe) == ''">true</IncludePythonExe>
<IncludeDistutils Condition="$(IncludeDistutils) == ''">false</IncludeDistutils>
<IncludeLib2To3 Condition="$(IncludeLib2To3) == ''">false</IncludeLib2To3>
<IncludeVEnv Condition="$(IncludeVEnv) == ''">false</IncludeVEnv>
<GetPythonRuntimeFilesDependsOn>_GetPythonRuntimeFilesDependsOn310_None;$(GetPythonRuntimeFilesDependsOn)</GetPythonRuntimeFilesDependsOn>
</PropertyGroup>
<ItemDefinitionGroup Condition="$(Platform) == 'X64'">
<ClCompile>
<AdditionalIncludeDirectories>$(PythonInclude);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(PythonLibs);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<Target Name="GetPythonRuntimeFiles" Returns="#(PythonRuntime)" DependsOnTargets="$(GetPythonRuntimeFilesDependsOn)" />
<Target Name="_GetPythonRuntimeFilesDependsOn310_None" Returns="#(PythonRuntime)">
<ItemGroup>
<_PythonRuntimeExe Include="$(PythonHome)\python*.dll" />
<_PythonRuntimeExe Include="$(PythonHome)\python*.exe" Condition="$(IncludePythonExe) == 'true'" />
<_PythonRuntimeExe>
<Link>%(Filename)%(Extension)</Link>
</_PythonRuntimeExe>
<_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.pyd" />
<_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.dll" />
<_PythonRuntimeDlls>
<Link>DLLs\%(Filename)%(Extension)</Link>
</_PythonRuntimeDlls>
<_PythonRuntimeLib Include="$(PythonHome)\Lib\**\*" Exclude="$(PythonHome)\Lib\**\*.pyc;$(PythonHome)\Lib\site-packages\**\*" />
<_PythonRuntimeLib Remove="$(PythonHome)\Lib\distutils\**\*" Condition="$(IncludeDistutils) != 'true'" />
<_PythonRuntimeLib Remove="$(PythonHome)\Lib\lib2to3\**\*" Condition="$(IncludeLib2To3) != 'true'" />
<_PythonRuntimeLib Remove="$(PythonHome)\Lib\ensurepip\**\*" Condition="$(IncludeVEnv) != 'true'" />
<_PythonRuntimeLib Remove="$(PythonHome)\Lib\venv\**\*" Condition="$(IncludeVEnv) != 'true'" />
<_PythonRuntimeLib>
<Link>Lib\%(RecursiveDir)%(Filename)%(Extension)</Link>
</_PythonRuntimeLib>
<PythonRuntime Include="#(_PythonRuntimeExe);#(_PythonRuntimeDlls);#(_PythonRuntimeLib)" />
</ItemGroup>
<Message Importance="low" Text="Collected Python runtime from $(PythonHome):%0D%0A#(PythonRuntime->' %(Link)','%0D%0A')" />
</Target>
</Project>
That is for the use of internal nuget rather than your main project. You cannot get that property under main project.
You have to use my function:
1) edit csproj file and set this for your PackageReference python
<GeneratePathProperty>true</GeneratePathProperty>
Like this:
<ItemGroup>
<PackageReference Include="python" Version="3.10.0-a6">
<GeneratePathProperty>true</GeneratePathProperty>
</PackageReference>
</ItemGroup>
2) Then, you can use $(Pkgpython) to get that path.
<Target Name="MyCustomStep" AfterTargets="Build">
<Exec Command="$(Pkgpython)\tools\python.exe" />
</Target>

Disabling a specific C# 9 source generator

Is there any way to disable a specific C# 9 source generator? Or alternatively disable them all?
the package in question is https://github.com/Husqvik/GraphQlClientGenerator#c-9-source-generator which is mean to be able to be used as both a lib and a source generator. but those are mutually exclusive, ie the majority of use cases it make no sense to gen code both by executing code and by code gen
seems this will disable all
<Target Name="DisableAnalyzers"
BeforeTargets="CoreCompile">
<ItemGroup>
<Analyzer Remove="#(Analyzer)" />
</ItemGroup>
</Target>
removing a named one uses the file path
<Target Name="DisableAnalyzers"
BeforeTargets="CoreCompile">
<ItemGroup>
<Analyzer Remove="D:\nugets\nugetx\0.9.2\analyzers\dotnet\cs\NugetXAnalizer.dll" />
</ItemGroup>
</Target>
ok and finally u can remove based on filename
<Target Name="DisableAnalyzers"
BeforeTargets="CoreCompile">
<ItemGroup>
<Analyzer Remove="#(Analyzer)"
Condition="'%(Filename)' == 'NugetXAnalizer'"/>
</ItemGroup>
</Target>

MSBuild. Create EmbeddedResource before build

I want to embed local references in the assembly before compiling the main unit. But the written target does not work.
<Target Name="EmbedLocal" BeforeTargets="CoreCompile">
<Message Text="Run EmbedLocal for $(MSBuildProjectFullPath)..." Importance="high"/>
<ItemGroup>
<EmbeddedResource Include="#( ReferencePath->WithMetadataValue( 'CopyLocal', 'true' )->Metadata( 'FullPath' ) )"/>
</ItemGroup>
<Message Text="Embed local references complete for $(OutputPath)$(TargetFileName)." Importance="high" />
</Target>
#(EmbeddedResource) at this moment contains valid list of paths.
Update:
Now my import file contains:
<Project ToolsVersion="$(MSBuildToolsVersion)" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<EmbedLocalReferences Condition=" '$(EmbedLocalReferences)' == '' ">True</EmbedLocalReferences>
</PropertyGroup>
<Target Name="EmbedLocal" BeforeTargets="ResolveReferences" Condition=" '$(EmbedLocalReferences)' == 'True' ">
<Message Text="Run EmbedLocal for $(MSBuildProjectFullPath)..." Importance="high"/>
<ItemGroup>
<EmbeddedResource Include="#(ReferenceCopyLocalPaths->WithMetadataValue( 'Extension', '.dll' )->Metadata( 'FullPath' ))">
<LogicalName>%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Message Text="Embed local references complete for $(OutputPath)$(TargetFileName)." Importance="high" />
</Target>
</Project>
It works fine. Output assembly contains all .dll references as EmbeddedResource.
MSBuild. Create EmbeddedResource before build
You can try to use BeforeBuild action to the csproj file to include the embedded resources:
<Target Name="BeforeBuild">
...
<ItemGroup>
<EmbeddedResource Include="..."/>
</ItemGroup>
...
</Target>
Now MSBuild will add this file as embedded resource into your assembly.
Update:
Thanks #Martin Ullrich. He pointed out the correct direction, we could use <Target Name="EmbedLocal" BeforeTargets="PrepareForBuild"> in the Directory.Build.props to resolve this issue. You can check if it works for you.
<Target Name="EmbedLocal" BeforeTargets="PrepareForBuild">
...
<ItemGroup>
<EmbeddedResource Include="..."/>
</ItemGroup>
...
</Target>

Change assembly name for build settings in .csproj

I have an application which I want to publish with ClickOnce via command line. I have a test and a live version. It should be allowed to have both installed at the same time, which means that I need to change the assemble name (and preferably also the product name) for one of the builds. I would like to do this in the build settings.
I have managed to make some build settings, which works fine, but I cannot figure out how to change the assembly and product name, for just one of them.
I have added the following code to my .csproj file, which I call with the command msbuild /target:Test or msbuild /target:Live. But where do I implement the assembly and product name change?
<PropertyGroup>
<ProjLocation>$(ProjectDir)</ProjLocation>
<ProjLocationReleaseDir>$(ProjLocation)\bin\Debug</ProjLocationReleaseDir>
<ProjPublishLocation>$(ProjLocationReleaseDir)\app.publish</ProjPublishLocation>
<DeploymentFolder>C:\MyProjects\Software\Publish\</DeploymentFolder>
</PropertyGroup>
<!-- Build settings for live version -->
<Target Name="Live" DependsOnTargets="Clean">
<MSBuild Projects="$(ProjLocation)\$(ProjectName).csproj"
Properties="$(DefaultBuildProperties)"
Targets="Publish"/>
<ItemGroup>
<SetupFiles Include="$(ProjPublishLocation)\*.*"/>
<UpdateFiles Include="$(ProjPublishLocation)\Application Files\**\*.*"/>
</ItemGroup>
<Copy SourceFiles="#(SetupFiles)" DestinationFolder="$(DeploymentFolder)\Live\" />
<Copy SourceFiles="#(UpdateFiles)" DestinationFolder="$(DeploymentFolder)\Live\Application Files\%(RecursiveDir)"/>
</Target>
<!-- Build settings for test version -->
<Target Name="Test" DependsOnTargets="Clean">
<MSBuild Projects="$(ProjLocation)\$(ProjectName).csproj"
Properties="$(DefaultBuildProperties)"
Targets="Publish"/>
<ItemGroup>
<SetupFiles Include="$(ProjPublishLocation)\*.*"/>
<UpdateFiles Include="$(ProjPublishLocation)\Application Files\**\*.*"/>
</ItemGroup>
<Copy SourceFiles="#(SetupFiles)" DestinationFolder="$(DeploymentFolder)\Public Test\" />
<Copy SourceFiles="#(UpdateFiles)" DestinationFolder="$(DeploymentFolder)\Public Test\Application Files\%(RecursiveDir)"/>
</Target>
You can add "AssemblyName" property to the PropertyGroup. like this:
<PropertyGroup>
<AssemblyName>YourAppName</AssemblyName>
</PropertyGroup>
or you can use the MSBuild command line switch. like this :
msbuild /property:AssemblyName=YourAppName
I had almost the same task (need to distinguish between Staging and Production) and I solved it with the following MSBuild-Target:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- call "nuget restore Deployment\packages.config" before executing this script -->
<Import Project="..\packages\MSBuildTasks.1.5.0.235\build\MSBuildTasks.targets" />
<PropertyGroup>
<Configuration>Release</Configuration>
<ClientProject>..\MyProject\MyProject.vbproj</ClientProject>
<ClientPublishDir Condition="$(Environment) == 'Staging'">\\deployment-staging\MyProject\</ClientPublishDir>
<ClientPublishDir Condition="$(Environment) == 'Production'">\\deployment\MyProject\</ClientPublishDir>
</PropertyGroup>
<ItemGroup>
<ClientModifiedAppConfig Include="$(ClientProject)\..\App.$(Environment).config" />
</ItemGroup>
<Target Name="DeployClient">
<Error Condition="$(Environment) == ''" Text="The Property 'Environment' has not been set." />
<Error Condition="$(Environment) != 'Staging' AND $(Environment) != 'Production'" Text="The Property 'Environment' has not been set properly. Valid values are 'Staging' and 'Production'." />
<!-- Sets different assembly names for INT and PRD applications. Due to this, both INT and PRD applications with the
same version number can be installed on the same system. -->
<XmlUpdate Condition="$(Environment) == 'Staging'" Prefix="n" Namespace="http://schemas.microsoft.com/developer/msbuild/2003" XmlFileName="$(ClientProject)" Xpath="/n:Project/n:PropertyGroup/n:AssemblyName" Value="MyProjectStaging" />
<XmlUpdate Condition="$(Environment) == 'Staging'" Prefix="n" Namespace="http://schemas.microsoft.com/developer/msbuild/2003" XmlFileName="$(ClientProject)" Xpath="/n:Project/n:PropertyGroup/n:ProductName" Value="MyProject Staging" />
<XmlUpdate Condition="$(Environment) == 'Production'" Prefix="n" Namespace="http://schemas.microsoft.com/developer/msbuild/2003" XmlFileName="$(ClientProject)" Xpath="/n:Project/n:PropertyGroup/n:AssemblyName" Value="MyProject" />
<XmlUpdate Condition="$(Environment) == 'Production'" Prefix="n" Namespace="http://schemas.microsoft.com/developer/msbuild/2003" XmlFileName="$(ClientProject)" Xpath="/n:Project/n:PropertyGroup/n:ProductName" Value="MyProject" />
<!-- Overwrites the original App.config with the environment-dependent App.config.
Reason: ClickOnce only uses App.config and does not apply transformations, as it is done in Web Projects. -->
<Copy
SourceFiles="#(ClientModifiedAppConfig)"
DestinationFiles="$(ClientProject)\..\App.config"
OverwriteReadOnlyFiles="true"
/>
<!-- Publish -->
<MSBuild
Projects="$(ClientProject)"
Targets="Publish"
Properties="
PublishDir=$(ClientPublishDir);
Configuration=$(Configuration);
Platform=x86" />
</Target>
</Project>
To make this work, you have to restore the NuGet package MSBuildTasks.
Finally, all I have to call is msbuild Deployment.targets /t:DeployClient /p:Environment=Staging
Hope that helps!

How does the MSBuild:Compile generator work

I'm trying to use the MSBuild:Compile generator to trigger a compilation of my custom file type when the file is saved in Visual Studio (should work like a custom tool but with msbuild). The build process itself is working but it doesn't seem to be triggered if the file is saved.
Can someone explain what exactly the MSBuild:Compile entry is doing? As far I have just seen this used in the antlr msbuild scripts and for XAML.
Below I have an extract of the msbuild setup I use to compile a *.myext file to a *.g.ts file.
My targets file:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="SampleNamespace.CustomCompilerTask" AssemblyFile="MyTask.dll" />
<PropertyGroup>
<PrepareResourcesDependsOn>
CustomLayoutCompile;
$(PrepareResourcesDependsOn)
</PrepareResourcesDependsOn>
</PropertyGroup>
<ItemDefinitionGroup>
<CustomTypeCompile>
<Generator>MSBuild:Compile</Generator>
</CustomTypeCompile>
</ItemDefinitionGroup>
<Target Name="CustomLayoutCompile" Inputs="#(TypeScriptCompile);#(CustomTypeCompile)" Outputs="#(CustomTypeCompile->'%(RootDir)%(Directory)%(Filename).g.ts')">
<CustomCompilerTask TypeScriptFiles="#(TypeScriptCompile)" LayoutFiles="#(CustomTypeCompile)" />
</Target>
</Project>
Entries in the project file:
....
<ItemGroup>
<TypeScriptCompile Include="MyControl.ts">
<DependentUpon>MyControl.myext</DependentUpon>
</TypeScriptCompile>
<TypeScriptCompile Include="MyControl.g.ts">
<DependentUpon>MyControl.myext</DependentUpon>
</TypeScriptCompile>
</ItemGroup>
<ItemGroup>
<CustomTypeCompile Include="MyControl.myext">
<Generator>MSBuild:Compile</Generator>
</CustomTypeCompile>
</ItemGroup>
....
<Import Project="path/to/my/target/file/mytargets.targets" />
....
I am using VS 2019 and was suffering from the same exact issue. I finally figured out a workaround, which make the issue looks more like a VisualStudio/MSBuild bug to me. My workaround is that when you define your ItemDefinitionGroup, define a custom/extended file property page like below. It worked for me. Hope it also works for everyone else too.
<ItemGroup>
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)CustomPerperties.CSharp.xml">
<Context>File;BrowseObject</Context>
</PropertyPageSchema>
<AvailableItemName Include="CustomTypeCompile" />
</ItemGroup>

Categories