MSBuild task does not cause generated c# files to compile - c#

I've been trying to write an MSBuild task to "compile" CoCo/R .ATG files into C# files which will then get compiled into the executable, this is to replace the pre-build event.
I've managed to get the .ATG -> .cs process working, however something is not right since the generates .cs files do not get compiled.
If I then modify the .ATG file again, the "old" .cs files seems to get compiled then new ones generated.
I'm quite sure I'm missing something that will inform the rest of the build process that these files have changed.
Here is the target definition that I have included in my Visual Studio 2010 project.
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AvailableItemName Include="BuildATG" />
</ItemGroup>
<PropertyGroup>
<CoreBuildDependsOn>
BuildATGTarget;
$(CoreBuildDependsOn)
</CoreBuildDependsOn>
</PropertyGroup>
<Target Name="BuildATGTarget" Inputs="#(BuildATG)" Outputs="#(BuildATG -> '%(RelativeDir)Parser.cs')">
<Exec Command="Coco.exe %(BuildATG.Identity)" Outputs="%(BuildATG.RelativeDir)Parser.cs" />
</Target>
</Project>
I am completly new to MSBuild, so any advice / pointers would be appreciated.

One possible solution I have found is to do the following changes and add an ItemGroup inside the target.
<Target Name="BuildATGTarget" Inputs="#(BuildATG)" Outputs="#(BuildATG -> '%(RelativeDir)Parser.cs')">
<Exec Command="Coco.exe %(BuildATG.Identity)" Outputs="%(BuildATG.RelativeDir)Parser.cs" />
<ItemGroup>
<Compile Include="%(BuildATG.RelativeDir)Parser.cs" />
<Compile Include="%(BuildATG.RelativeDir)Scanner.cs" />
</ItemGroup>
</Target>
To avoid duplicate files in your build, you also need to mark the files (Parser.cs and Scanner.cs) in the Visual Studio 2010 project with Build Action: None

Related

Distributing .editorConfig custom settings through nuget package

I am having an issue trying to implement compiler-based code analysis in my C# .NetFramework solution. I decided to use Microsoft.CodeAnalysis.NetAnalyzers nuget package with some custom .editorconfig ruleset that I do not want to keep directly in the consuming project. I created another nuget with the .editorconfig and the idea is to copy the file to each consuming project before it is built in order to trigger the analysis (during build).
I tried an approach (described here) where the .editorconfig is copied to csproj location in a beforeBuild task defined in .props file
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="('$(Configuration)' == 'Debug')">
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
</PropertyGroup>
<ItemGroup>
<EditorConfigFilesToCopy Include="$(MSBuildThisFileDirectory)..\content\.editorconfig" />
</ItemGroup>
<Target Name="CopyEditorConfig" BeforeTargets="BeforeBuild">
<Message Text="Copying the .editorconfig file from '#(EditorConfigFilesToCopy)' to '$(MSBuildProjectDirectory)'"></Message>
<Copy
SourceFiles="#(EditorConfigFilesToCopy)"
DestinationFolder="$(MSBuildProjectDirectory)"
SkipUnchangedFiles="true"
UseHardlinksIfPossible="false" />
</Target>
</Project>
Unfortunately, the file seems to be copied too late as the msbuild ignores it during the build itself and does not fail due to CA violations as I would expect. If the file had been copied manually before running msbuild, it works.
Do You have any idea why it is like that and how to handle this issue properly?

MSBuild in Visual Studio - Moving files before including them as content (C#)

I have a C# project which has two C++ .dll versions with the same name but differ based on their architecture (32-bit or 64-bit), they are stored in separate folders and must have the same name, I want to include the right file with that name.
So the plan is to first check the current platform when building, copy the right file from the right folder (based on the platform) into the project directory and then include that file as content to the project so it can be used.
<ItemGroup>
<Source32Bit Include="File_32bit\File.dll" />
<Source64Bit Include="File_64bit\File.dll" />
</ItemGroup>
<Target Name="CopyFiles" BeforeTargets="Build" >
<Copy SourceFiles="#(Source32Bit)" DestinationFolder="$(ProjectDir)" Condition=" '$(Platform)' == 'x86' " />
<Copy SourceFiles="#(Source64Bit)" DestinationFolder="$(ProjectDir)" Condition=" '$(Platform)' == 'x64'" />
</Target>
<ItemGroup>
<Content Include="File.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
But if I run this then it tries to perform Content Include before the Target "CopyFiles" has run and so it cannot find the File.dll in that directory. If I make a target for this content include and try to do AfterTarget="CopyFiles" it complains about the CopyToOutputDirectory.
How should I handle this? Any ideas? Thanks!
But if I run this then it tries to perform Content Include before the
Target "CopyFiles" has run and so it cannot find the File.dll in that
directory. If I make a target for this content include and try to do
AfterTarget="CopyFiles" it complains about the CopyToOutputDirectory.
The main reason is that Build target executes later than MSBuild to read the Item elements, so when you perform the copy operation, it is already later than reading the Item elements, so the project cannot find the copied file. Therefore, you only need to execute the CopyFiles target before reading the Item elements.
So I found a system target called FindInvalidProjectReferences which executes earlier than reading Item elements.
Solution
Try these:
<ItemGroup>
<Source32Bit Include="File_32bit\File.dll" />
<Source64Bit Include="File_64bit\File.dll" />
</ItemGroup>
<Target Name="CopyFiles" BeforeTargets="FindInvalidProjectReferences">
<Copy SourceFiles="#(Source32Bit)" DestinationFolder="$(ProjectDir)" Condition=" '$(Platform)' == 'x86' " />
<Copy SourceFiles="#(Source64Bit)" DestinationFolder="$(ProjectDir)" Condition=" '$(Platform)' == 'x64'" />
</Target>
<ItemGroup>
<Content Include="File.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
Note: when you first load the project, the test.dll has a yellow triangle which is a normal behavior because you did not build your project. And it needs the build process first.
You should build your project first and it will not turn out any errors and when the test.dll in Solution Explorer will not have the warning symbol.
This answer works with Visual Studio 2022.
If you want to use globs (**/*) to pick up generated files as <Content>, then define the fileset and <Content> twice - once at the project level, and once again inside the target that generates the files.
The first time the project builds, the project-level <Content> picks up zero files, but the target-level <Content> picks up produced files.
For later builds, both <Content> elements pick up the files, and that's harmless.
<ItemGroup>
<MyOutputFiles Include="where\output\was\generated\**"/>
<Content Include="#(MyOutputFiles)" CopyToOutputDirectory="PreserveNewest" Link="specific\out\dir\%(RecursiveDir)%(Filename)%(Extension)"/>
</ItemGroup>
<Target Name="GenerateMyFilesPlz" BeforeTargets="BeforeBuild">
<Exec Command=""$(ProjectDir)..\TheTool\TheTool.exe" some args" />
<ItemGroup>
<MyOutputFiles Include="where\output\was\generated\**"/>
<Content Include="#(MyOutputFiles)" CopyToOutputDirectory="PreserveNewest" Link="specific\out\dir\%(RecursiveDir)%(Filename)%(Extension)"/>
</ItemGroup>
</Target>
This is particularly more desirable than FindInvalidProjectReferences because
it runs only when you build. (FindInvalidProjectReferences tends to be invoked by Visual Studio whenever it feels like it)
it makes reasonable error messages when it fails. (when BeforeTargets="FindInvalidProjectReferences" fails, you get a thousand errors like "I can't find Newtonsoft.Json!" because no project references were resolved.)
it works the same whether building via Visual Studio or via dotnet build on the command line. (I've had mixed results with FindInvalidProjectReferences)
it allows the use of globbing (**/*) to define the fileset
Can you rename the path in which the DLL files reside?
If instead of naming those folders File_32bit and File_64bit, you can name them File_x86 and File_x64.
Then you could have ItemGroup:
<ItemGroup>
<Source32Bit Include="File_$(Platform)\File.dll" />
<Source64Bit Include="File_$(Platform)\File.dll" />
</ItemGroup>
Note: I don't really know if this works.

Build configuration for dynamically referenced projects

I have an ASP.NET project that is included in multiple solutions. In each solution I'd like a different unreferenced project to be included in the ASP.NET project's build output. The solutions look like this:
Foo.sln
WebApp.csproj
Foo.csproj
Bar.sln
WebApp.csproj
Bar.csproj
Ideally, this would work even when debugging with F5. I tried doing this with build configurations, but deviating from the typical 'Debug' and 'Release' seems brittle when working within Visual Studio. Is there a typical way of doing this?
Disclaimer: I don't think this is a very good idea to do but it seems like it can be done.
To test this solution I created two projects. ConsoleApplication1 and ClassLibrary1. ConsoleApplication1 does not have a reference (that is visible in Visual Studio) to ClassLibary1 but when building ConsoleApplication1 from Visual Studio it will build then copy the ClassLibary1.dll to the bin folder of ConsoleApplication1.
To import the target file you will go ahead and add this line to the project that you want to build the unreferenced project. This path will be relative to the current project so in my case the target file was at the root of my solution. Make sure you add this after the line <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> because unreferenced.target relies on targets that are setup in Microsoft.CSharp.targets.
<Import Project="..\unreferenced.target" />
Then you will go ahead and create a file name unreferenced.target and add the contents below to the file.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Start another msbuild task to build your unreferenced project -->
<Target Name="BuildBeforeResolveReferences" BeforeTargets="BeforeResolveReferences">
<MSBuild
BuildInParallel="False"
Projects="$(SolutionDir)ClassLibrary1\ClassLibrary1.csproj"
RunEachTargetSeparately="True"
StopOnFirstFailure="False"
UnloadProjectsOnCompletion="False">
</MSBuild>
</Target>
<Target Name="CopyUnreferencedProjectOutput" AfterTargets="Build">
<!-- This item group is here because we do not want it evaluated by msbuild until the ClassLibrary1.csproj has been compiled and its output is in its output directory -->
<ItemGroup>
<!-- Gets a list of all files at the OutputPath that end in .dll if you need the pdbs remove the .dll -->
<!-- To maintain folder structure in the bin folder use <SourceFiles Include="..\ClassLibary1\#(OutputPath)**\*.dll" /> the double ** is a recursive wild card and will look through all directorys -->
<SourceFiles Include="$(MSBuildProjectDirectory)\..\ClassLibrary1\$(OutputPath)*.dll" />
</ItemGroup>
<!-- To make sure the copy maintains folder structure switch it to this copy -->
<!-- <Copy SourceFiles="#(SourceFiles)" DestinationFiles="#(SourceFiles -> '$(MSBuildProjectDirectory)$(OutputPath)%(RecursiveDir)%(Filename)%(Extension)')" /> -->
<Copy SourceFiles="#(SourceFiles)" DestinationFolder="$(MSBuildProjectDirectory)\$(OutputPath)" />
</Target>
<!-- Cleans up all the files when clean is called -->
<Target Name="CleanUnreferenceProjectOutput" BeforeTargets="Clean">
<ItemGroup>
<!-- Removed the .dll from the end of this to clean up the pdbs as well -->
<SourceFiles Include="$(SolutionDir)\ClassLibrary1\$(OutputPath)*" />
<SourceFiles Include="$(SolutionDir)\ConsoleApplication1\$(OutputPath)*.dll" />
</ItemGroup>
<Delete Files="#(SourceFiles)" />
</Target>
</Project>
I think this is the best that can be done. You could extend this to have a list of projects that are not referenced but you want to build but for this example I just left it at one.
EDIT 2: Before getting to the current solution I did extensive research into injecting the reference into the ProjectReference itemgroup before assemblies were resolved. It can be done but you have to set the property BuildInVisualStudio to false because otherwise when the msbuild conditions are evaluted in the ResolveProjectReferences target in Microsoft.Common.Current.targets you will select a MSBuild task that only runs the GetManifest target. I was able to get the solution to build but given my lack of knowledge on what setting BuildInVisualStudio to false entails I opted for the solution above. Also I added a task for cleaning up the files that were moved to the bin folders because clean will only cleanup what {ProjectName}{ProjectExtension}FileListAbsoluteText.txt in the obj folder of your project.
EDIT: After doing some more research into the solution below it will only work from the command line. I am currently looking into why this is occuring.
I don't know if there is a typical way of doing what you are asking for (from IDE), but you have an options to accomplish this manually by editing the *.*proj files.
Each project will emit output (*.dll, *.exe, app.config, etc), and it will be copied to the folder specified in the $(OutputPath) property (internally it will use OutDir property). If you will build a solution, you will have the $(SolutionDir) property, as well as $(SolutionName). So, you can define new msbuild project, which will be referenced by the other ones, and you can set the property $(OutputPath) so that every output will go into one folder (let call it Common.props):
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SolutionDir Condition=" '$(SolutionDir)' == '' ">$(MSBuildThisFileDirectory)<SolutionDir>
<SolutionName Condition=" '$(SolutionName)' == '' " >DefaultSlnName</SolutionName>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<OutputPath>$(SolutionDir)$(SolutionName)\bin\$(Configuration)</OutputPath>
</PropertyGroup>
</Project>
After that, you should import that project by your other projects - *.*proj (you should specify correct path to the project):
<Import Project="..\Common.props" Condition="Exists('..\Common.props')" />
Using common $(OutputPath) property will place all of your binaries to the one folder - this should help to resolve your task.

mspublish outputs pdbs through Visual Studio, but won't do it through msbuild on the same publish profile

I have the following configuration in DevLocalPublish.pubxml:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>FileSystem</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<ExcludeApp_Data>False</ExcludeApp_Data>
<publishUrl>..\DevLocalPublish\</publishUrl>
<DeleteExistingFiles>True</DeleteExistingFiles>
<ExcludeGeneratedDebugSymbol>false</ExcludeGeneratedDebugSymbol>
</PropertyGroup>
It works perfectly when I export through Visual Studio. The pdb files get exported to ..\DevLocalPublish\bin as expected. However, when I run this from msbuild, it doesn't work correctly. Here's the target I created:
<Target Name="PublishDevLocally">
<MSBuild Projects="KepsPortalMvc\KepsPortalMvc.csproj" Targets="Clean;Rebuild" Properties="_DebugSymbolsProduced=true;BuildProjectReferences=true;Configuration=Release;Platform=Any CPU;DeployOnBuild=true;PublishProfile=DevLocalPublish;ExcludeGeneratedDebugSymbol=false;OutputPath=..\DevLocalPublish"/>
</Target>
I'm testing it with the following command:
msbuild direct.proj /t:PublishDevLocally
Everything else exports correctly. It's just the pdbs that aren't being published correctly. I've checked the bin folder in the project, and the pdbs are there, so the compiler is generated.
What am I doing wrong?
Edit: I've checked the obj\Release\Package\PackageTmp directory. I was able to get the pdb for the web project by adding the _DebugSymbolsProduced=true flag. However, it's not copying the pdbs of any of the referenced projects. I've uploaded PublishDevLocally to reflect my changes.
Turns out I needed to add the property DebugSymbols=true; The final target is
<Target Name="PublishDevLocally">
<MSBuild Projects="KepsPortalMvc\KepsPortalMvc.csproj" Targets="Clean;Rebuild" Properties="DebugSymbols=true;BuildProjectReferences=true;Configuration=Release;Platform=Any CPU;DeployOnBuild=true;PublishProfile=DevLocalPublish;OutputPath=..\DevLocalPublish"/>
</Target>

MSBuild Task from .csproj File

I Am trying to add a Post Build MSBuild event to my csproj to do this i am trying to call an MSBuild task from inside the Afterbuild target in the.csproj file
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.-->
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
<Message Text="Copying Files" />
<MSBuild Projects="post.build"
Targets="Copy"
ContinueOnError="false" />
</Target>
This is then the post.build file.
<Project DefaultTargets="Copy"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Copy">
<Message Text="Copying Files inside COPY" />
<CallTarget Targets="CopyToProfile"/> </Target>
</project>
It seems that the csproj cannot call the MSbuild task, can anyone suggest what might be going wrong here. I get the error
error MSB4057: The target "Copy" does
not exist in the project.
So What I eventually got working was.
I did as Martin Suggested and
<Import Project="post.build"/>
However the MSBuild Task still did not function as planned. So I ended up using the
<CallTarget Targets="copy"/>
To Call across the files. This sounds like it is a limitation in VS2008 and is fixed in VS2010.
Are you sure you haven't made the typo in your actual post.build file as well? That is, it should be not . XML is case-sensitive.
Also, I would double-check that the post.build file is placed in the same folder as the .csproj file.

Categories

Resources