We have a big C# program that is split into 2 major groups: Kernel libraries and (Customer) specific applications & services.
Our TFS structure (simplified) is like this:
Kernel
DLVP
Release 1
Release 2
Release 3
...
CustomerA
DLVP
Release
CustomerB
DLVP
Release
We used nugets to compile and distribute our kernel code and include it into the Customer applications. So we can easily move to a new version/release. However, we are not content with only dll's. We want to have the full debugging and editing experience everywhere.
You can include kernel projects in the customer solutions, but they will have a reference to Release X, so if we want to move to a new release, we have to change every solution and project file for every costumer, which is a lot (N customers x M services/programs = a lot)
I read that you can use Environment variabels to change some values in the .sln & .csproj files, but we would like to have something that is more controllable across all developers. I prefer referencing a shared variable that can also be stored in TFS.
I've made a small .sln file to clarify:
Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp", "ConsoleApp\ConsoleApp.csproj", "{B6B9AE41-99ED-47CE-B35C-F693C5F5F736}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibKernel", "..\..\Release 1\LibKernel\LibKernel\LibKernel.csproj", "{439BD82B-340D-4D69-B367-E52E0DF27983}"
EndProject
I want to change this part:
"LibKernel", "....\ Release 1 \LibKernel\LibKernel\LibKernel.csproj", "{439B....}"
into
"LibKernel", "....\ {CustomerA.CurrentRelease} \LibKernel\LibKernel\LibKernel.csproj", "{439B....}"
Something like that (and also for .csproj files), but if they are better approaches, I'm glad to hear them.
Thx a lot
We use this kind of idea for referencing the assemblies located in particular location per as user needs.
First you need to save an external file with content as shown below..
Suppose filename is buildpath.xml
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
<PropertyGroup>
<AssemblyDirLocation>C:\inetpub\wwwroot\SiteInstance\Website</AssemblyDirLocation>
</PropertyGroup>
</Project>
Next we import this config in .csproj. I believe it shall work in .sln files also
For proper understanding I have included some surrounding text here from .csproj file
<?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')" />
<Import Project="..\build\buildpath.xml" Condition="Exists('..\build\buildpath.xml')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<!-- more text -->
Notice following line from above text
<Import Project="..\build\buildpath.xml" Condition="Exists('..\build\buildpath.xml')" />
Usage:
In .csproj or .sln file where you would have imported above file, you can write below macro to replace the value. Notice the tag name matches the macro written i.e. $(AssemblyDirLocation).
<ItemGroup>
<Reference Include="Site.Kernel">
<HintPath>$(AssemblyDirLocation)\bin\Site.Kernel.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
Whenever you make changes in buildpath.xml external file you need to reload the referencing project.
I hope that was clear and will be helpful.
Related
I have a bunch of project that requires tweaks to be build in a continuous environement.
I put every tweaks in a separate .target file to reuse this file across all projects.
At the very end of my csproj files, I put (before the closing) Project element:
This is working quite well unless I try to include additional reference path.
If I specify using command line the path (msbuild myproject.csproj /p:ReferencePath="C:\path\to\dlls"). The project compile.
My target file is :
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- some tweaks here -->
<PropertyGroup Condition="'$(CompileFor)' == 'SP2013'">
<SomeProperty>some value</SomeProperty>
<AdditionalReferencePaths>C:\path\to\dlls</AdditionalReferencePaths>
</PropertyGroup>
</Project>
But this does not works (dll cannot be resolved).
I also tried :
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AdditionalReferencePaths Include="C:\path\to\dlls"/>
</ItemGroup>
</Project>
This is not working, because the ItemGroup element can't be out of a Target element
Lastly, I tried:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="SomeTarget" BeforeTargets="BeforeBuild">
<ItemGroup>
<AdditionalReferencePaths Include="C:\path\to\dlls"/>
</ItemGroup>
</Target>
</Project>
This still isn't working. No error, I can see the target is called in the build log, but the DLLs are still not resolved.
How to fix it?
To give a bit of context, tweaks I include in the target file allows me to compile the project against different version of DLLs. The code is a plugin of a 3rd party application (SharePoint to name it), and I want to compile for several different versions of the product. Using some conditional, I can target either a folder with one version of the product or another folder for other version of the product.
I get rid of this issue after two fixes.
The correct property wasn't AdditionalReferencePath but ReferencePath
I also have to move the Import before the first ItemGroup of my csproj. I guess this was required to have to properties set before the Reference element
I'm using the Nuget Package Explorer to create some nuget packages. I've managed to do so just building a project in Release mode in VS and adding both the dll and pdb files to the package.
So far so good, but when I add the package to another project and try to step into the code while debugging, it will step over it instead.
I understand that I need to build and add the Debug dll and pdb to my package if I want to step into the code while debugging. I'm not sure though how to add these to the package I've already create, which already contains the Release dll and pdb file, which are named the same.
Any thoughts?
My thoughts are, NuGet packaging is a lot about conventions.
There is no problem in packaging same namespaces and same names for different platforms (as in lib/net40/mydll.dll, lib/net35/mydll.dll etc in the same package), as NuGet will filter registered dependencies by platform.
Building several versions for the same platform seems unconventional, this discussion is biased towards making a package per build. That doesn't mean you can't do it, but you should first ask yourself if you should.
That said, if your debug and release builds are very different (conditional compiling etc) this might useful though. But how will end-users choose Release or Debug when installing your package?
An idea could be, one version per build configuration. Both can be installed into the project. To do that, either add a targets file to your package or build a powershell install script (unsupported since Nuget v3) that adds conditional references directly in the target project file, if you want something less basic than whatever MsBuild can do for you.
Example of the first tactic: Create a .target file (in your package, create a build folder and then create build\YourLib.targets with the following contents):
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<Reference Include="YourLib">
<HintPath>..\packages\YourLib.1.0.0\lib\Debug\YourLib.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<Reference Include="YourLib">
<HintPath>..\packages\YourLib.1.0.0\lib\Release\YourLib.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
Providing you created debug and release folders (platform folder is optional), the build output will effectively change depending on configuration - provided packet consumers have conventional configuration names, but you could always extend the condition logic a bit with $(Configuration).Contains etc or just put that in the package readme
Inspired by #Tewr I've found a cumbersome but a working solution.
Create a nuget with the following file structure:
lib\net\$(Configuration)\YourLib.1.0.0.dll <---- put here some dummy file named YourLib.1.0.0.dll
tools\release\YourLib.1.0.0.dll <--- put here the release version
tools\debug\YourLib.1.0.0.dll <--- put here the debug version
build\YourLib.targets
The targets file content:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CopyReferences" BeforeTargets="Build" Condition="Exists('..\packages\YourLib.1.0.0\lib\net\%24(Configuration)')">
<Exec Command="mkdir ..\packages\YourLib.1.0.0\lib\net\Release" />
<Exec Command="mkdir ..\packages\YourLib.1.0.0\lib\net\Debug" />
<Exec Command='copy "..\packages\YourLib.1.0.0\tools\Release\YourLib.1.0.0.dll" "..\packages\YourLib.1.0.0\lib\net\Release"' />
<Exec Command='copy "..\packages\YourLib.1.0.0\tools\Debug\YourLib.1.0.0.dll" "..\packages\YourLib.1.0.0\lib\net\Debug"' />
<Exec Command='rmdir /S /Q "..\packages\YourLib.1.0.0\lib\net\%24(Configuration)"' />
</Target>
The dlls in lib folder will be automatically added as references creating the following in the project file:
<Reference Include="YourLib>
<HintPath>..\packages\YourLib.1.0.0\lib\net\$(Configuration)\YourLib.1.0.0.dll</HintPath>
<Private>True</Private>
</Reference>
Once you build the project for the first time, the target will copy the release and debug version from tools\release and tools\debug folders to lib\net\release and lib\net\debug folders. In the end, it will delete the lib\net\$(Configuration) folder
Enjoy (or not - I personally don't like the solution).
Thanks #Tewr In new nuget format and sdk style csproj format, we can use some constant as $(MSBuildThisFileDirectory) to get the current file path.
The code that uses the version will increase maintenance difficulty. The sdk style csproj format use the new package format that will do not output the package file to package folder.
We can add the targets file to build folder and use $(MSBuildThisFileDirectory) to get the file path.
<ItemGroup Condition="'$(Configuration)' == 'DEBUG'">
<Reference Include="YourLib">
<HintPath>$(MSBuildThisFileDirectory)..\lib\debug\YourLib.dll</HintPath>
</Reference>
</ItemGroup>
See the file
I have a Jenkins server that executes MSBuild for me.
There's a whole bunch of builds that need to be done, they all use the same solution file but for every build some custom settings need to change.
The app needs to be able to read these settings.
For example if I'd build like this:
msbuild MyProject.sln /p:MyVar=SomeValue
Then when the app launches, it should be able to retrieve what the value is of MyVar.
For example, the app should be able to show a messagebox when it starts with the title 'SomeValue'.
I know I can define properties as described here by Microsoft, but I can't figure out how to read those properties when the app starts.
Or maybe I'm just looking at a completely wrong way of doing it.
Any ideas?
You would need msbuild to create a text file with the value inside of it. For that you can use the TemplateFile task of the MSBuild.Community.Tasks.Target in your msbuild file.
CreateAppVarFile.targets file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildCommunityTasksPath>$(SolutionDir)\.build</MSBuildCommunityTasksPath>
</PropertyGroup>
<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets" />
<Target Name="CreateAppVarFile">
<ItemGroup>
<TemplateContext Include="MyVar">
<ReplacementValue>$(MyVar)</ReplacementValue>
</TemplateContext>
</ItemGroup>
<TemplateFile Template="$(MSBuildThisFileDirectory)appvar.txt" OutputFilename="$(ProjectDir)appvar.txt" Tokens="#(TemplateContext)"/>
</Target>
</Project>
This target would then be included in your project file
<Import Project=".\build\CreateAppVarFile.targets" />
<Target Name="BeforeBuild" DependsOnTargets="CreateAppVarFile">
and your .\build\appvar.txt template file in the folder where your CreateAppVarFile.targets file is located can look just like this:
$(MyVar)
and be read from your app during runtime. Don't forget to include the appvar.txt in your project. It will contain:
SomeValue
I have a solution with many projects that would like to share source files and properties. If I put the sources files in, for instance, a common .props file, the source files affect the build but don't show up in Visual Studio. A short example is:
Scratch.csproj:
<?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')" />
<ItemGroup>
<Compile Include="Program.cs"/>
</ItemGroup>
<Import Project="a.props" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
A.props:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Include="a.cs"/>
</ItemGroup>
</Project>
a.cs, Program.cs:
namespace Scratch
{
public class A {}
}
This code will not compile because class A has been declared twice. However, the file a.cs appears nowhere in the visual studio solution. Is there a good fix for this so that we can have shared properties and source files that also appear in the solution? I saw one possibility being instead of a.props, using a.csproj and adding it as well to the solution. However, that too has problems. If you only instead on using some of the properties from that project (depending on conditions set), it won't be clear which source file or property goes to which project.
Visual Studio doesn't deal with the full flexibility of MSBuild files. Builds work because Visual Studio invokes MSBuild libraries but the project designers, Solution Explorer, etc can't represent things they haven't been designed for.
The common way to include shared source (that is, if you don't make a separate project of it), is to add files to the project as links: Add Exiting Item..., select the files, and instead of clicking the button, click the arrow on the right side of the button and adds links. Solution Explorer is designed to recognize linked files.
Properties should be fine in a shared Import file but note that Visual Studio doesn't recognize changes to Imports; you have to reload the project manually.
Quick question about MSBuild. I have the following MSBuild file in directory d:\MyDirectory
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
<Import Project="d:\MyDirectory\BuildTest\BuildTest\BuildTest.csproj"></Import>
</Project>
When I run this the build fails because BuildTest.csproj can't find the .cs source files and it seems to be looking in d:\MyDirectory. I was expecting the working directory to be set to that of the BuildTest.csproj and thus be able to resolve the references. What am I doing wrong?
I think ,the import element is used to import other MSBuild projects into this one (see here).
If you want to specify C# projects to build, you should do it like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
<ItemGroup>
<ProjectReferences Include="d:\MyDirectory\BuildTest\BuildTest\BuildTest.csproj" />
<!--more-->
</ItemGroup>
Then if you want to build all referenced projects, you can for example do this:
<Target Name="BuildAllProjects">
<MSBuild ContinueOnError="False" Projects="#(ProjectReferences)"
Targets="Clean;ReBuild">
</MSBuild>
</Target>
Remember though, that this is just 1 of many ways of doing things with MSBuild. There are a lot of good articles on MSDN on this topic, should you require more customization.