external file for build tasks - c#

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.

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?

How to reference or copy output of other C# project with a newer framework?

Scenario:
Loader.csproj, which is .NET 4.8; and outputs a .exe with some .dll dependencies
LoaderWrapper.csproj, which is .NET 4.6 and needs to call the .exe of Loader with some arguments.
Main.csproj, which is .NET 4.6 and references LoaderWrapper normally.
I can't create a normal project reference to Loader.csproj from LoaderWrapper.csproj because Loader.csproj is a newer .NET framework.
I can't create an assembly reference as that only copies the .exe file, and not any .dll files the .exe of Loader.csproj depends on.
I can't change the framework version as my application gets loaded in 3d party application Revit, which is .NET 4.6
I can't hardcode the output of Loader.csproj because in my build pipeline I have several different output directories. (eg. for unit tests)
In Visual Studio I can change "Build Dependencies"->"Project Dependencies" on LoaderWrapper.csproj to ensure Loader.csproj gets built, but this doesn't seem to copy/reference the output of Loader.csproj.
So I'm looking for another method to ensure all output of Loader.csproj is included in LoaderWrapper.csproj, and also would end up in Main.csproj through the projectreferences.
Some time ago I solved the similar task - to collect the Content files from one project and include them into the output of another project.
It is not exactly the same task as yours, but you can try to adopt my approach.
So, I has project named MainApp and the project called Lib. The Lib project have the txt file ContentFile.txt with Build Action = Content. I need to include ContentFile.txt into the output of MainApp.
I created CustomBuildActions.targets file in the Lib folder with the following contents
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
Define project to get content files from.
Definition relies on the fact that this target file stored
in the same folder with Lib.csproj
-->
<ItemGroup>
<LibProject Include="$(MSBuildThisFileDirectory)/Lib.csproj"/>
</ItemGroup>
<!--
Run msbuild for Lib to collect the list of Content files and store it to the LibContentFiles list.
Then perform string repace to convert paths to Content files to paths inside app bundle. And store results in the LibContentFileTargetPath.
-->
<Target Name="GetBundleFiles" Outputs="#(LibContentFiles)">
<MSBuild Projects="#(LibProject)" Targets="ContentFilesProjectOutputGroup">
<Output ItemName="LibContentFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
<ItemGroup>
<LibContentFileTargetPath Include="#(LibContentFiles->Replace($(MSBuildThisFileDirectory), $(AppBundleDir)/Contents/Resources/))"/>
</ItemGroup>
</Target>
<!-- These targets will fire after mmp creates your bundle but before code signing -->
<PropertyGroup>
<CreateAppBundleDependsOn>$(CreateAppBundleDependsOn);GetBundleFiles;CopyOurFiles;</CreateAppBundleDependsOn>
</PropertyGroup>
<!-- Since this has inputs/outputs, it will fire only when the inputs are changed or the output does not exist -->
<Target Name="CopyOurFiles" Inputs="#(LibContentFiles)" Outputs="#(LibContentFileTargetPath)">
<Message Text="This is us copying a file into resources!" />
<!-- This could have easily been done w/ a built in build action, but you can extend it arbitrary. -->
<Copy SourceFiles="#(LibContentFiles)" DestinationFiles="#(LibContentFileTargetPath)" />
</Target>
</Project>
Then import this target in the MainApp project
<Import Project="../Lib/CustomBuildActions.targets" />
This custom target will collect the Content files from Lib project, transform the paths to preserve folder structure inside MainApp output (e.g. if we will have inside Lib project something like Lib/ContentFolder/Subfolder/contentFile.txt, then inside the bundle if will be placed in Resources/ContentFolder/Subfolder/contentFile.txt) and then copy files inside the bundle.
So, what you can need to adopt:
Use output items of your Loader project instead of content files. So, instead of ContentFilesProjectOutputGroup you will need other target. Please refer to msbuild documentation to find what is best for you.
Change the target dependencies - your custom target should became a dependency of the LoaderWrapper project build steps (in my sample I use CreateAppBundleDependsOn dependency, but it is Xamarin-only thing and you will need to finde the better step to add dependency to).
The full description of my case may be found here https://forums.xamarin.com/discussion/comment/418130

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.

Make VisualStudio run a command to generate C# code

I'm trying to do something extremely simple, yet despite hours of scouring the Internet and desperately typing in every tiny example fragment I can find, I literally cannot make VS do what I want. Seriously frustrated here!
This is what I want:
When I do a build, I want VS to find every *.fubar file in the project.
For each *.fubar file, I want VS to execute fubar.exe with the input file as argument.
I want VS to compile the resulting *.cs files as usual.
I am damn-well convinced it must be possible to achieve this trivially simple task. I just need to figure out how.
So that's the problem. Let me explain what I've researched so far.
It seems that you can write a VS extension by implementing some COM interface. But that requires you to edit the Registry to tell VS where the plugin is. Obviously that's completely unacceptable; I should not have to reconfigure the OS just to run a command-line tool!
It appears that MSBuild is supposed to be this ultra-configurable build management tool where you can define an arbitrary set of build steps and their interdependencies. It seems like it should be trivial to add an extra task before the build C# compilation step that runs fubar.exe to generate the source code. And yet, after hours of trying, I cannot get a single C# file to appear.
Some documents talk about special pre-build and post-build steps. But it seems silly to need a special hook when the number and order of build steps is supposed to be arbitrary to begin with. Regardless, I tried it, and it still didn't work.
To be clear: Playing around with the project file makes VS display the item properties slightly differently, and doesn't make the build fail. But it also doesn't ever make any C# files appear. (At least, I can't find them anywhere on the disk. I can't tell if my tool is actually being run or not — I suspect "not".)
Can anybody tell me how to make this trivial, trivial thing work? There must be a way!
I agree that T4 may be a prettier solution, but the below MsBuild code works.
You can create a custom targets file that does the calling, I created one that simply copies files, but the process should be similar for invoking your executable.
Make sure you set your FuBar files to BuildAction "Fubars". The Build action has been defined in the targets file using the "AvailableItemName" property.
You may need to extend the below script with a couple of Conditions and Exists checks to make sure it runs when needed. I called it convert.targets and places it in the project directory.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Ensure Visual Studio puts the FuBars item in the Build Action dropdown -->
<ItemGroup>
<AvailableItemName Include="FuBars" />
</ItemGroup>
<!-- Execute action on all items matching FuBar (only if output is missing or input has changed) -->
<Target Name="GenerateCodeFubar" Inputs="#(Fubars)" Outputs="$(MSBuildProjectDirectory)/obj/$(Configuration)/Fubars/%(Fubars.Identity).g.cs" >
<MakeDir Directories="$(MSBuildProjectDirectory)/obj/$(Configuration)/Fubars/%(Fubars.RelativeDir)" />
<Exec Command="cmd /c copy "%(FuBars.FullPath)" "$(MSBuildProjectDirectory)/obj/$(Configuration)/Fubars/%(FuBars.Identity).g.cs""/>
</Target>
<!-- Pick up generated code in the Compile group. Separated from the execute action so that the previous action only runs when files have changes -->
<Target Name="IncludeCodeFubarInCompile">
<ItemGroup>
<GeneratedFubars Include="$(MSBuildProjectDirectory)/obj/$(Configuration)/Fubars/%(FuBars.Identity).g.cs"/>
</ItemGroup>
<CreateItem Include="%(GeneratedFubars.FullPath)">
<Output TaskParameter="Include" ItemName="Compile" />
</CreateItem>
</Target>
<!-- Clean up for Rebuild or Clean -->
<Target Name="DeleteCodeFubar">
<RemoveDir Directories="$(MSBuildProjectDirectory)/obj/$(Configuration)/Fubars/" />
<Delete Files="$(MSBuildProjectDirectory)/obj/$(Configuration)/fubars/**/*.g.cs" />
</Target>
<!-- Instruct MsBuild when to call target on Build-->
<PropertyGroup>
<BuildDependsOn>
GenerateCodeFubar;
IncludeCodeFubarInCompile;
$(BuildDependsOn);
</BuildDependsOn>
</PropertyGroup>
<!-- Instruct MsBuild when to call target on Clean-->
<PropertyGroup>
<CleanDependsOn>
DeleteCodeFubar;
$(CleanDependsOn);
</CleanDependsOn>
</PropertyGroup>
</Project>
Include it in your .csproj after the import the last targets file:
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- IMPORT HERE -->
<Import Project="convert.targets" />
<!-- END: IMPORT HERE -->
<!-- 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">
</Target>
-->
</Project>
You may have to tell Visual Studio to turn off the Host Compiler, as Visual Studio caches the contents of the <Compile> group for performance. Though a quick test shows this may not be needed.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<!-- The first property group should not have any condition on it -->
<PropertyGroup>
<!-- Include below item in the first PropertyGroup item:
<UseHostCompilerIfAvailable>FALSE</UseHostCompilerIfAvailable>
</PropertyGroup>
This is extremely easy.
In your project properties select the Build Events tab and enter a correct Pre-build event command line.
Without knowing what footer.exe is nor its command line args, I can't give you any specific help for that app. But there is a "Macros" button on the pre-build event dialog that can help you with various substitution variables.
Additionally, VS comes with the T4 templating system which might be a better fit than whatever footer.exe is.

App.config replacements for unit tests

my continuous integration server (TeamCity) is configured to run all the unit tests in our app on build. Prior to running those tests, i need to change some of the appSettings to make them valid for our CI server. I'm achieving something similar for my web project by using the deployment project provided with Visual Studio. Can i do the same for a Test project?
Thanks, Gonzalo
It's possible to use Web.config Transformations for App.config files through a workaround.
You simply have to invoke the appropriate MSBuild tasks at the right stage in your build process.Add this code snippet to your project file:
<UsingTask
TaskName="TransformXml"
AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('App.$(Configuration).config')">
<!-- Generates the transformed App.config in the intermediate directory -->
<TransformXml
Source="App.config"
Destination="$(IntermediateOutputPath)$(TargetFileName).config"
Transform="App.$(Configuration).config" />
<!-- Forces the build process to use the transformed configuration file -->
<ItemGroup>
<AppConfigWithTargetPath Remove="App.config"/>
<AppConfigWithTargetPath
Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetFileName).config</TargetPath>
</AppConfigWithTargetPath>
</ItemGroup>
</Target>
Then add additional App.config files to your project for each build configuration where you wish to apply a transformation. For example:
<ItemGroup>
<None Include="App.config" />
<None Include="App.Release.config">
<DependentUpon>App.config</DependentUpon>
</None>
</ItemGroup>
Related resources:
Web.config Transformation Syntax for Web Application Project Deployment
.Config File Transformation
I have created a Visual Studio add in which can be used to transform app.config in the same way that web.config is transformed. You can find the add-in, SlowCheetah, at http://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5.
I have posted a blog about how to get this working on a build server as well.
I suggest that you wrap your config lookups, extract an interface and stub this when testing.

Categories

Resources