Generate C# using MSBuild - c#

I have a batch file which generates a cs file. The new cs file is stored inside one of my projects solution.
Here is a snippet of my msbuild task:
<Target Name="MyDummyGenerator" BeforeTargets="Build">
<Message Text="Generating file" />
<Exec Command="./path_to_batch_file/dummy.bat" ContinueOnError="false" />
</Target>
The problem is that my generated file is not listed in the files to compile and is not a part of the assembly. After trying to add this file implicitly to the csproj file using <Compile Include="MyGeneratedFile.cs"> I got this error (MyGeneratedFile.cs is the file written from the batch file:
"Duplicate 'Content' items were included. The .NET SDK includes 'Content' items from your project directory by default".
BTW - When compiling it again the generate file is a part of the assembly but it is because the file is in the project source before start the entire build step...
I even tried to run it in the PreBuild event with no luck.
What is the proper way of generating c# code using msbuild?
P.S I'm using .net Framework 4.7.1

I guess this post answer the question:
https://mhut.ch/journal/2015/06/30/build-time-code-generation-in-msbuild
In a nutshell, you need to hook the CoreCompile target to generate your file before compilation (or intellisense) starts and generate a Compile item inside a target.
See this doc for details on how to create items during build:
https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-items?view=vs-2019#create-items-during-execution
Also you should generate the file to the intermediate output (obj folder) rather than in your source to not mess up.
In the example below, the inputs and outputs are mainly here for MSBUILD to know what to refresh when files are changing.
<Target Name="UpdateGeneratedFiles"
DependsOnTargets="_UpdateGeneratedFiles"
Condition=="'#(ResourceFile)' != ''"
>
<ItemGroup>
<Compile Include="$(IntermediateOutputDir)GeneratedFile.g.cs" />
<!-- see https://mhut.ch/journal/2016/04/19/msbuild_code_generation_vs2015
<FileWrites Include="$(IntermediateOutputDir)GeneratedFile.g.cs" />
-->
</ItemGroup>
</Target>
<Target Name="_UpdateGeneratedFiles"
Inputs="$(MSBuildProjectFile);#(ResourceFile)"
Outputs="$(IntermediateOutputDir)GeneratedFile.g.cs"
>
<FileGenerationTask
Inputs="#(ResourceFile)"
Output="$(IntermediateOutputDir)GeneratedFile.g.cs"
>
</Target>

Related

When using MSBuild to build a WPF project which files should I specify for compilation? does order matter?

I'm attempting to automate the build process of my WPF project and set up a CI system. I'm following details found in Continuous Integration in .NET by Marcin Kawalerowicz and Craig Bernston. Specifically, details in chapter 3 pertaining to build automation in MSBuild. The authors provide a sample build file for a project consisting of a single C# file, which looks similar to
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build;Deploy;Execute"
xmlns="http://schemas.microsoft.com/developer/msuild/2003">
<PropertyGroup>
<Debug Condition="'$(Delete)'==''">false</Debug>
<OutputFile>SampleMSBuild.exe</OutputFile>
<OutputDirectory>Output</OutputDirectory>
</PropertyGroup>
<ItemGroup>
**<CompileFile Include="SampleMSBuild.cs" />**
<DeleteFiles Include="SampleMSBuild.exe;SampleMSBuild.pdb" />
</ItemGroup>
<Target Name="Clean">
...
</Target>
<Target Name="Build" DependsOnTarget="Clean">
**<Csc Sources="#(CompileFiles)"**
OutputAssembly="$(OutputFile)"
EmitDebugInformation="$(Debug)" />
<Target Name="Deploy">
...
</Target>
<Target Name="Execute">
...
</Target>
</Project>
My confusion arises from the two lines of code I've surrounded in asterisks. Although I now know how to build a project with a single source file, I am unsure how to scale that up to a WPF project where there is a predefined template with many source files that are placed in different directories. Is it simply a matter of including every source file in the project like so
...
<CompileFile Include="source1.cs;source2.cs;source3.cs;...;sourceN.cs"
...
What about files in a project that are NOT source code? For example, files found in the References folder of a WPF project. I can't imagine all these are to be compiled as well. Does the complied source code communicate with the rest of the uncompiled files in the project? What exactly is the relationship between source files and other non-source files when it comes to build automation in MSBuild?

Reference AssemblyFileVersion in csproj file

Once my nant build has completed I'd like to rename the generated .exe file using a post-build command which is appended to the end of the projects .csproj file (within the Project element):
<Target Name="AfterBuild">
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(TargetDir)\MyApplication-$(AssemblyFileVersion).exe" SkipUnchangedFiles="true" />
</Target>
As you can see above, I am trying to rename the executable to: MyApplication-$(AssemblyFileVersion).exe however, this is obviously wrong, as the resulting executable is simply named: MyApplication-.exe (so the version I am trying to add at the end of the file name is missing).
I have defined the AssemblyFileInfoVersion in the AssemblyInfo.cs file as follows:
[assembly: AssemblyFileVersion("1.5.1")]
So the question is: How can I access the AssemblyFileVersion in the csproj file of that same project?
GetAssemblyIdentity can get information about complied assemblies. The task output contain metadata entries about Version, PublicKeyToken, and Culture.
I used $(TargetDir)\$(TargetName).exe as the assembly file.
<ItemGroup>
<AssembliesPath Include="$(TargetDir)\$(TargetName).exe" />
</ItemGroup>
<Target Name="GetAssemblyInfo">
<GetAssemblyIdentity AssemblyFiles="#(AssembliesPath)">
<Output TaskParameter="Assemblies" ItemName="AssemblyInfo"/>
</GetAssemblyIdentity>
</Target>
And then:
<Target Name="AfterBuild">
<GetAssemblyInfo />
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(TargetDir)\MyApplication-%(AssemblyInfo.Version).exe" SkipUnchangedFiles="true" />
</Target>
The following code is from ISun's original answer and this is actually how I ended up doing it in the end, as I had problems defining a custom task (ms build references were constantly auto-kicked and the build kept failing over and again).
As you can see from the comments under ISun's answer I always got the version 0.0.0.0 - despite having changed the version for AssemblyFileVersion and AssemblyVersion to 1.0.0.0 by manually opening the AssemblyInfo.cs in a texteditor. I later read how to edit the AssemblyInfo.cs from my Visual Studio, here is how:
Right-click on your project (that generates the exe file)
Click on Properties
Open the Application tab (first tab on the left) on the window that'll open
You'll see fields for setting the Assembly name, Default namespace etc however to edit the
AssemblyInfo.cs, simply click on the Button called Assembly Information to the right
And for some reason - I have no clue why it suddenly worked, after I had put in all the information via the above method (using Visual Studio) ! When I first opened the AssemblyInfo.cs using the above way, all my fields were actually empty, despite them being filled in the actual file.
And now that I got the AssemblyInfo.cs to finally function correctly, I used ISun's original code to achieve my goal. By adding the following snippet just before the closing tag in my project's .csproj file:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="AfterBuild">
<GetAssemblyIdentity AssemblyFiles="$(TargetDir)\$(TargetName).exe">
<Output TaskParameter="Assemblies" ItemName="AssemblyInfo"/>
</GetAssemblyIdentity>
<Copy SourceFiles="$(TargetDir)\$(TargetName).exe" DestinationFiles="$(TargetDir)\MyApplication-%(AssemblyInfo.Version).exe" SkipUnchangedFiles="true" />
</Target>

MSBuild task does not cause generated c# files to compile

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

Get the assembly project file name from Output TaskParameter

I am using msbuild.exe to automate my build from the command line.
I do the following steps.
Compile projects into a folder each outside the project directory
zip every compiled project
here are my targets
first one to compile
<Target Name="BuildProjects" DependsOnTargets="BeforeBuild">
<ItemGroup>
<BuildProjectFiles Include="**\MyCompany.Project1\MyCompany.Project1.csproj" />
<BuildProjectFiles Include="**\MyCompany.Project2\MyCompany.Project2.csproj" />
<BuildProjectFiles Include="**\MyCompany.Project2\MyCompany.Project2-CE.csproj" />
... and some more
</ItemGroup>
<MSBuild Projects="#(BuildProjectFiles)" Properties="AllowUnsafeBlocks=true;Configuration=$(Configuration);OutputPath=$(MSBuildProjectDirectory)\Deploy\bin\%(BuildProjectFiles.FileName)">
<Output TaskParameter="TargetOutputs"
ItemName="BuildProjectsOutputFiles" />
</MSBuild>
</Target>
and now the target to zip every compiled project to its one file.
<Target Name="ZipProjects" DependsOnTargets="BuildProjects">
<CreateItem
Include="#(BuildProjectOutputFiles)"
AdditionalMetadata="AssemblyName=%(Filename);ProjectName=%(RelativeDir)">
<Output TaskParameter="Include" ItemName="BuildProjectsOutputFiles123"/>
</CreateItem>
<CreateItem Include="%(BuildProjectsOutputFiles123.RelativeDir)*" AdditionalMetadata="OutputFileName=$(MSBuildProjectDirectory)\Deploy\dist\$(Configuration)\%(BuildProjectsOutputFiles123.AssemblyName)-$(MajorNumber).$(MinorNumber).$(ReleaseNumber).zip;WorkingDirectory=%(BuildProjectsOutputFiles123.RelativeDir)">
<Output TaskParameter="Include" ItemName="BuildProjectsOutputFiles456"/>
</CreateItem>
<Zip Files="#(BuildProjectsOutputFiles456)"
WorkingDirectory="%(BuildProjectsOutputFiles456.WorkingDirectory)"
ZipFileName="%(BuildProjectOutputFiles456.OutputFileName)" />
<!-- ZipLevel="9"/> -->
</Target>
So what happens is that every project I specified in BuildProjectFiles gets compiled into the folder <rootdir>\deploy\bin\<name of the csproj file without extension\
In the second step I use the MSBuild.Community.Tasks Zip-Task to zip every project and copy it to <rootdir>\deploy\dist\release\<assemblyname>-<version>.zip
So basically the assembly project1.exe and its dependencies are in the file project1-2.4.7.zip after executing msbuild.
That works very well. But now I have a change that I can't figure out how solve. I have two assemblys with the same assembly name (one for Windows and the other for Windows CE) so the first project compiles and creates a folder project2-2.4.7.zip and then the next project compiles and overwrites the zip file.
Now I want the Zip-File to be named after the .csproj file (like the bin folder).
Since my one project file is called mycompany.project2.csproj and the other one mycompany.project2-ce.csproj I should be fine.
In short: How can I pass the project name to the zip target?
Is this suitable?
$(MSBuildProjectName)
MSBuild Reserved Properties

external file for build tasks

I am creating a .net web application and have some build tasks eg msbuild copy task. I add this by editing the project file for the application and adding the task.
While this works ok, is there any way I can use an external xml file to the project file and have my build tasks in this with the main project file referencing it? I would much prefer this as I wouldn't then have to edit the main project file and there is separation between the project file and the build tasks.
You can simply reference any external project or target file by adding an import to your main project file:
...
<!-- this is the default import for (c#) web project files -->
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" />
<!-- import your custom project/target file here -->
<Import Project="MyCustom.targets" Condition="Exists('MyCustom.targets')" />
...
Adding the Condition will allow to build your main project even if your custom project/target file is missing because the build is run in a different environment.
I'm using this approach to run FxCop and StyleCop targets on my local machine but the same main project file can be built without any changes in my staging environment.
Update
Your comment suggests that you actually are looking rather for a solution that should work the other way round: You want to execute some steps before and after building your project without modifying the project configuration itself.
In that case the best way is to create your custom project to call the build of your web project. This could look something like this:
<Project DefaultTargets="MyTargetAfterBuild">
<!-- some Project attributes omitted for readability -->
<Target Name="MyTargetBeforeBuild" ContinueOnError="false">
<Exec Command="svn export" />
</Target>
<Target Name="Build" DependsOnTargets="MyTargetBeforeBuild">
<MSBuild Projects="MyWebProject.csproj" Targets="Build" Properties="Configuration=Release" >
</MSBuild>
</Target>
<Target Name="MyTargetAfterBuild" DependsOnTargets="Build">
<Exec Command="powershell.exe .\MyCustomScript.ps1" />
</Target>
</Project>
You might be interested in this answer on a similar scenario with a more detailed example.
You can call msbuild.exe, targetting your separate build file, in a pre-build or post-build event.

Categories

Resources