I have a C# project where I need to include a file as a resource. I don't know the full name of the file in advance, as it will include a version number that I can't easily access at build time. So I'm using a wildcard like this:
<ItemGroup>
<EmbeddedResource Include="..\..\setup\bin\MyApp-setup-*.exe">
<Link>Setup\%(filename)%(extension)</Link>
<LogicalName>Setup\%(filename)%(extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
It works, but the problem is that it can include any number of files, and I want it to include exactly one. There should be only one matching file in the folder. If there are zero or several files that match the wildcard, I want it to be a build error. Is there a way to assert that only one file is included?
EDIT: Thanks to #stijn's answer, I was able to do it like this:
<Target Name="BeforeBuild">
<ItemGroup>
<EmbeddedResource Include="..\..\setup\bin\MyApp-setup-*.exe">
<Link>AppFiles\%(filename)%(extension)</Link>
<LogicalName>AppFiles\%(filename)%(extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
<PropertyGroup>
<SetupFileCount>#(EmbeddedResource->Count())</SetupFileCount>
</PropertyGroup>
<Error Text="Expected exactly one file matching 'MyApp-setup-*'; found $(SetupFileCount)." Condition="'$(SetupFileCount)' != '1'"/>
</Target>
Use the Item Function 'Count' to get the number of items and raise an error if it's not 1:
<Target Name="ErrorIfNotOneEmbeddedResourceFound" BeforeTargets="Build">
<ItemGroup>
<EmbeddedResource Include="..\..\setup\bin\MyApp-setup-*.exe">
<Link>Setup\%(filename)%(extension)</Link>
<LogicalName>Setup\%(filename)%(extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Error Text="Didn't find one match" Condition="'#(EmbeddedResource->Count())' != '1'"/>
</Target>
Related
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>
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>
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>
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>
I have a resource file strings.resx, and the generated resource class is in strings1.designer.cs. Why is this the case? The problem specifically is the "1". The class name inside that file is "strings", as it should be.
Note that I did try deleting the designer.cs and regenerating it by saving the resx file, but that didn't change anything.
I just ran into this problem and found the issue inside the .csproj file. It appears that Visual Studio saves the last filename it used and attempts to generate using that filename again. So if for some reason the Strings.designer.cs is ever generated as Strings1.designer.cs it looks like VS will continue to use Strings1.
Below you can see the portions of the .csproj file that was giving me an issue. Most importantly the <LastGenOutput> at the end where Strings1.designer.cs is saved.
<Compile Include="App_GlobalResources\Strings.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Strings.resx</DependentUpon>
</Compile>
<Compile Include="App_GlobalResources\Strings.es.designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Strings.es.resx</DependentUpon>
</Compile>
...
<ItemGroup>
<EmbeddedResource Include="App_GlobalResources\Strings.es.resx">
<Generator>GlobalResourceProxyGenerator</Generator>
<LastGenOutput>Strings.es.designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="App_GlobalResources\Strings.resx">
<Generator>GlobalResourceProxyGenerator</Generator>
<LastGenOutput>Strings1.designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
Changing the <LastGenOutput> to the following solved my problem:
<LastGenOutput>Strings.designer.cs</LastGenOutput>