I have attempted the following:
<!-- Specify the inputs by type and file name -->
<ItemGroup>
<CSFile Include = "$(MSBuildProjectDirectory)\..\Mine.cs"/>
</ItemGroup>
<Target Name = "Compile">
<!-- Run the Visual C# compilation using input files of type CSFile -->
<Csc Sources="#(CSFile)" />
<!-- Log the file name of the output file -->
<Message Text="The output file is done"/>
</Target>
This does not work as all the namespaces used in the project throw errors. Does anyone know how I can explicitly get the assemblies to pick up from the solution file, as the paths are ok and if loaded in Visual Studio all is fine. I need to script this and the above is not working. Is there an obvious mishap?
Appreciate the inpuT :-)
I have realised that this is not going to work as the file I have has several external dependancies. Hence I would need to use the devenv.exe. Problem is that I get the follwing:
What I get is that the command exits with Code 1? I want to get the project to build all the dependant dlls that it requires without having to open visual studio.
Any ideas?
Thnxes :-)
try this (add you own dlls references)
<ItemGroup>
<CSFile Include = "$(MSBuildProjectDirectory)\..\Mine.cs"/>
<Reference Include="System.dll"/>
<Reference Include="System.Data.dll"/>
<Reference Include="System.Drawing.dll"/>
<Reference Include="System.Windows.Forms.dll"/>
<Reference Include="System.XML.dll"/>
</ItemGroup>
<Target Name = "Compile">
<!-- Run the Visual C# compilation using input files of type CSFile -->
<Csc Sources="#(CSFile)"
References="#(Reference)"
OutputAssembly="$(builtdir)\$(MSBuildProjectName).exe"
TargetType="exe" />
/>
<!-- Log the file name of the output file -->
<Message Text="The output file is done"/>
</Target>
Related
I'm deploying a MsBuild task using nuget, that generates a .dll before each build.
I fail to get the generated dll referenced in the References node in the Visual Studio project of consumers.
I'm also using MSBuild to build the .nupkg file. Generating and compiling works just fine, I deploy the following target in the build/directory
<Project>
<!-- this will automatically run before the 'Build' target -->
<Target Name="GenerateAndBuild" BeforeTargets="Build">
<!--the deployed task that generates the code-->
<Utils.CreateUtilResourceTask/>
<ItemGroup>
<CompileGeneratedSources Include="GeneratedClass.cs" />
</ItemGroup>
<MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
<Csc Sources="#(CompileGeneratedSources )" OutputAssembly="$(OutputPath)Util.dll" TargetType="library" EmitDebugInformation="true" />
<Delete Files="#(CompileGeneratedSources )" />
</Target>
</Project>
That generates the util.dll in the project output folder but I fail to get it referenced in the consuming project.
I thought that this would work in the .csproj file:
<PropertyGroup>
<TargetFrameworks>netstandard1.6;net46</TargetFrameworks>
<BuildOutputTargetFolder>tasks</BuildOutputTargetFolder>
<VersionPrefix>0.1.1</VersionPrefix>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageOutputPath>$(MSBuildThisFileDirectory)</PackageOutputPath>
<PackageId>BuildUtil</PackageId>
<!-- that does not add a refenrece in consuming projects -->
<references>
<reference file="$(OutputPath)Util.dll"/>
</references>
<files>
<file src="$(OutputPath)Util.dll"/>
</files>
</PropertyGroup>
Perhaps someone has a hint on that?
You need to declare the assembly reference in an ItemGroup, not a PropertyGroup. For example:
<ItemGroup>
<Reference Include="$(OutputPath)Util.dll" />
</ItemGroup>
I have a Silverlight project with multiple configuration files, and am using the transformation approach shown here:
App.Config Transformation for projects which are not Web Projects in Visual Studio 2010?
This approach doesn't work as-is for Silverlight projects though. I've re-written the MSBuild project to look like this:
<ItemGroup>
<None Include="App.config" />
<None Include="App.QABuild.config">
<DependentUpon>App.config</DependentUpon>
</None>
</ItemGroup>
....
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="BeforeCompile" Condition="Exists('App.$(Configuration).config')">
<!-- Generate transformed app config in the output directory -->
<Message Importance="high" Text="Transforming 'App.$(Configuration).config' to output config file..." />
<TransformXml Source="App.config" Destination="$(OutputPath)App.config" Transform="App.$(Configuration).config" />
<ItemGroup>
<Content Include="$(OutputPath)App.config" />
</ItemGroup>
</Target>
<Target Name="BeforeCompile" Condition="!Exists('App.$(Configuration).config')">
<Message Importance="high" Text="Using default 'App.config' as output config file..." />
<Copy SourceFiles="App.config" DestinationFiles="$(OutputPath)App.config" />
<ItemGroup>
<Content Include="$(OutputPath)App.config" />
</ItemGroup>
</Target>
This code generates the correct output file for the correct configuration, however it is never included in the XAP file, even though I am putting the output config into the Content item group. All I need to happen is for the output config to get included in the output XAP but I can't get this to happen.
Can anyone tell me what I'm doing wrong? I'm not an MSBuild expert by any means!
Found the solution by digging into the Silverlight 4 targets. Turns out the XAP packager target actually takes an item called XapFilesInputCollection, which is where the input files come from. The Content item looks likes it is copied to this item before my target runs, so modifying the Content item afterwards is the wrong approach.
All I did was add the transformed files directly to the XapFilesInputCollection item and it worked as I expected.
In the process of cleaning up the folder/file structure on a project I inherited, I'm running into a problem with organizing the required external libraries. I want to keep them in their own .\dll\ folder, but they're not being copied to the build directory properly. They should be in the root build directory, but they're being moved to a subfolder instead.
My .csproj file contains the following xml:
<Project>
<ItemGroup>
<None Include="dlls\libraryA.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Then, on build, the libraryA.dll file is copied to the bin\Debug\dll\ folder, but I want it in the bin\Debug\ folder.
I tried this and msbuild always wants to copy the files using their directory path, but there is a workaround...
Edit the csproj file and after this line:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Add these lines:
<PropertyGroup>
<PrepareForRunDependsOn>$(PrepareForRunDependsOn);MyCopyFilesToOutputDirectory</PrepareForRunDependsOn>
</PropertyGroup>
<Target Name="MyCopyFilesToOutputDirectory">
<Copy SourceFiles="#(None)" DestinationFolder="$(OutDir)" />
</Target>
The copy of the output files happens in the PrepareForRun target. This adds your own target to the list of targets that are executed as part of PrepareForRun.
This example copies all items in the None item group. You could create your own item group (e.g. MyFiles) and do the copy on that item group if you have other "None" files you don't want copied. When I tried this I had to change the item group name by editing the csproj file directly. Visual Studio did not allow me to set the item group of a file from the UI, but after I edited the csproj and changed it, Visual Studio displayed my custom item group name correctly.
If you only want to change it for one file, it may be easier to use the property:
<None Include="dlls\libraryA.dll">
<Link>%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Including content files in .csproj that are outside the project cone
This approach works
If you need to force copy of a specific file/nuget package into an asp.net core project (2.2), add at the end of your csproj :
<!-- Force copy MathNet because we need it in compilation -->
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="Build">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\MathNet.Numerics.4.8.1\lib\netstandard2.0\MathNet.Numerics.dll')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MathNet.Numerics.4.8.1\lib\netstandard2.0\MathNet.Numerics.dll'))" />
</Target>
<ItemGroup>
<ContentWithTargetPath Include="..\packages\MathNet.Numerics.4.8.1\lib\netstandard2.0\MathNet.Numerics.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>MathNet.Numerics.dll</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
In SDK-style csproj you can write something like:
<Target Name="CopyFilesTargetName" AfterTargets="Build">
<Copy SourceFiles="$(OutDir)\dlls\Some.dll;$(OutDir)\dlls\SomeOther.dll" DestinationFolder="$(OutDir)" />
</Target>
You can also use <Move instead of <Copy to move files
I created a custom common target "RealClean" which remove every files in the output and "intermediate output" directory. I put it in the Microsoft.Common.targets file.
When I run MsBuild on my csproj everything is fine.
But when I run MsBuild on my sln (which just references a list of csproj) I have the following error
error MSB4057: The target "RealClean" does not exist in the project.
Here is the command line I enter to run MsBuild
C:\Windows\Microsoft .NET\Framework\v3.5\MsBuild.exe /p:Configuration="Release";OutputPath="..\..\MSBuild.Referentiel.net35";nowarn="1591,1573" /t:RealClean mySolution.sln
Any hint?
I had the same issue but didn't want to modify things outside of the source tree in order to get this to work. Adding files to C:\Program Files... means that you have to do this manually on every dev machine to get the same behavior.
I did three things:
1) Created a Custom targets file which I import into every C# and/or VB/F# project in my solution by adding the following to each proj file:
<!-- Rest of project file -->
<PropertyGroup Condition="'$(SolutionDir)' == '' or '$(SolutionDir)' == '*undefined*'">
<!-- Relative path to containing solution folder -->
<SolutionDir>..\</SolutionDir>
</PropertyGroup>
<Import Project="$(SolutionDir)CommonSettings.targets" />
2) Added a clean target which gets called after the real Clean (using the AfterTargets attribute from MSBuild 4.0):
<Target Name="CleanCs" AfterTargets="Clean">
<Message Text="Deep cleaning C# project..." />
<CreateItem Include="$(OutDir)**\*.*; $(ProjectDir)\obj\**\*.*; $(IntermediateOutputPath)**\*.*"
Exclude="**\bin\**\*.vshost.exe; $(IntermediateOutputPath)**\*.log">
<Output TaskParameter="Include" ItemName="AfterClean_FilesToDelete"/>
</CreateItem>
<Delete Files="#(AfterClean_FilesToDelete)" />
<CreateItem Include="$(ProjectDir)\obj\" >
<Output TaskParameter="Include" ItemName="AfterClean_DirectoriesToDelete" />
</CreateItem>
<CreateItem Include ="$(ProjectDir)\bin\" Condition="'$(TargetExt)' != '.exe'" >
<Output TaskParameter="Include" ItemName="AfterClean_DirectoriesToDelete"/>
</CreateItem>
<RemoveDir ContinueOnError="true" Directories="#(AfterClean_DirectoriesToDelete)" />
</Target>
3) In my continuous integration MSBuild project I check and make sure that all proj files have #1:
<ItemGroup>
<!-- Exclude viewer acceptance tests as they must compile as x86 -->
<CheckProjects_CsProjects Include="**\*.csproj" />
</ItemGroup>
<Target Name="CheckProjects">
<!--
Look for C# projects that don't import CommonSettingsCs.targets
-->
<XmlRead XPath="//n:Project[count(n:Import[#Project[contains(string(), 'CommonSettingsCs.targets')]]) = 0]/n:PropertyGroup/n:AssemblyName/text() "
XmlFileName="%(CheckProjects_CsProjects.Identity)"
Namespace="http://schemas.microsoft.com/developer/msbuild/2003"
Prefix="n" >
<Output TaskParameter="Value" ItemName="CheckProjects_CsMissingImports"/>
</XmlRead>
<Error Text="Project missing CommonSettingsCs.targets: %(CheckProjects_CsMissingImports.Identity)"
Condition="'%(CheckProjects_CsMissingImports.Identity)' != ''" />
</Target>
This prevents developers from forgetting to add #1. You could create your own project template to ensure that al new projects have this by default.
The advantage to this approach is setting up a new source tree enlistment doesn't involve anything more than getting the current source tree. The downside is that you have to edit the project files once when you create them.
To work on solution file, MSBuild creates a temporary MSBuild project file containing only some targets like Build and Clean. So you can't call your custom target on a solution file.
Madgnome is probably right. But I wanted to add that you should not be editing the Microsoft.common.targets files. If you do so you risk having a different build process on that machine versus what everybody else has. In your case you could have created a new MSBuild file with just the RealClean target and placed it at
C:\Program Files (x86)\MSBuild\v4.0\Custom.After.Microsoft.Common.targets
or for 32 bit
C:\Program Files\MSBuild\v4.0\Custom.After.Microsoft.Common.targets
and essentially that would be the same as putting that file inside of Microsoft.Common.targets, except you don't have to modify that file.
I have an MSBuild file and I am building C# projects like this:
<ItemGroup>
<ProjectsToBuild Include="./source/ProjectA/ProjectA.csproj"/>
<ProjectsToBuild Include="./source/ProjectB/ProjectB.csproj"/>
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="#(ProjectsToBuild)" Targets="Build">
<Output ItemName="ProjectOutputs" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="#ProjectOutputs"/>
</Target>
I successfully get an Item containing all of the .dll files that were built:
Build:
c:\code\bin\ProjectA.dll;c:\code\bin\ProjectB.dll
I would also like to get the Content item from each project without modifying the .csproj files. After digging around in the Microsoft .targets files, I was almost able to get it working with this:
<MSBuild Projects="#(ProjectsToBuild)" Targets="ContentFilesProjectOutputGroup">
<Output ItemName="ContentFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="#(ContentFiles->'%(RelativeDir)')"/>
The problem with this approach is the RelativeDir is not being set correctly. I am getting the full path instead of relative:
Build:
c:\ProjectA\MyFolder\MyControl.ascx;c:\ProjectB\MyOtherFolder\MyCSS.css;
instead of:
Build:
MyFolder\MyControl.ascx;MyOtherFolder\MyCSS.css;
Is there a property I can pass to the MSBuild task that will make RelativeDir behave correctly?
Or, even better, is there an easier way to get the Content item?
You can do this but it is not very intutive. I've discussed this type of technique a few times on my blog ( which is currently down :( ).
So create a new file, I named it GetContentFiles.proj which is shown here.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Projects Include="WindowsFormsApplication1\WindowsFormsApplication1.csproj"/>
</ItemGroup>
<!-- This target will be executed once for each file declared in the Project target -->
<Target Name="PrintFiles" Outputs="%(Projects.Identity)">
<Message Text="PrintFiles" Importance="high"/>
<MSBuild Projects="$(MSBuildProjectFile)"
Targets="GetContentFiles"
Properties="ProjectToGetFiles=%(Projects.Identity)">
<Output ItemName="projContent" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="ProjContent: #(projContent)" Importance="high"/>
<!-- Transform the projContent to have correct path -->
<!--
Get the relative path to the project itself, this serves as the base for
the Content files path
-->
<PropertyGroup>
<_ProjRelativeDir>%(Projects.RelativeDir)</_ProjRelativeDir>
</PropertyGroup>
<!-- This item will contain the item with the corrected path values -->
<ItemGroup>
<ProjContentFixed Include="#(projContent->'$(_ProjRelativeDir)%(RelativeDir)%(Filename)%(Extension)')"/>
</ItemGroup>
<!-- Create a new item with the correct relative dirs-->
<Error Condition="!Exists('%(ProjContentFixed.FullPath)')"
Text="File not found at [%(ProjContentFixed.FullPath)]"/>
</Target>
<Import Project="$(ProjectToGetFiles)" Condition="'$(ProjectToGetFiles)'!=''"/>
<Target Name="GetContentFiles" Condition="'$(ProjectToGetFiles)'!=''" Outputs="#(Content)">
<Message Text="Content : #(Content)" Importance="high"/>
<Message Text="Inside GetContentFiles" Importance="high"/>
</Target>
</Project>
I will try and explain this, but it may be tough to follow. Let me know if you need me to expand on it. This file has two targets PrintFiles and GetContentFiles. The entry point into this file is the PrintFiles target, in the sense that this is the target that you are going to call. So you call the PrintFiles target which it then uses the MSBuild task to call the GetContentFiles target on itself, also it passes a value for the ProjectToGetFiles property. Because of that the Import elemnent will be executed. So what you are really doing is taking the project defined in the ProjectToGetFiles property and extending it to include the target GetContentFiles (and whatever other content is inside the GetContentFiles.proj file). So we are effectively extending that file. I'm calling this technique "MSBuild Inheritance" because. So inside the GetContentFiles target we can access all properties and items that are declared inthe ProjectToGetFiles property. So I take advantage of that by simply putting the content of the Content item into the outputs for the target, which can be accessed by the original file using the TargetOutputs from the MSBuild task.
You mentioned in your post that you wanted to correct the path values to be the right ones. The problem here is that in the .csproj file all items are declared relative to the original project file. So if you "extend" the project file in this way from a file in a different directory you must correct the file path values manually. I've done this inside the PrintFiles target, check it out.
If you execute the command msbuild GetContentFile.proj /fl /t:PrintFiles the result would be:
Build started 7/3/2009 12:56:35 AM.
Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" on node 0 (PrintFiles target(s)).
PrintFiles
Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (1) is building "C:\Data\Development\My Co
de\Community\MSBuild\FileWrites\GetContentFile.proj" (1:2) on node 0 (GetContentFiles target(s)).
Content : Configs\Config1.xml;Configs\Config2.xml
Inside GetContentFiles
Done Building Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (GetContentFiles target(s)).
PrintFiles:
ProjContent: Configs\Config1.xml;Configs\Config2.xml
Done Building Project "C:\Data\Development\My Code\Community\MSBuild\FileWrites\GetContentFile.proj" (PrintFiles target(s)).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.03
Sayed Ibrahim Hashimi
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build
In case this helps someone else - use TargetPath instead of RelativeDir:
<MSBuild Projects="#(ProjectsToBuild)" Targets="ContentFilesProjectOutputGroup">
<Output ItemName="ContentFiles" TaskParameter="TargetOutputs"/>
</MSBuild>
<Message Text="#(ContentFiles->'%(TargetPath)')"/>
This will give you the relative path for each item.