Customizing Visual Studio Library Build

Markbnj

Elite Member <br>Moderator Emeritus
Moderator
Sep 16, 2005
15,682
14
81
www.markbetz.net
Ok, so I have a library I've written that I think will be generally useful to people writing .NET apps and Silverlight apps. It was created as a .NET 3.5 C# class library, with the idea that after I finished it I would create a version for Silverlight (it only needs a little tweaking to go from one to the other). This question is about how to create that version.

The brute force approach would be to just copy the code over and create a separate project, but I don't want two code bases. What I want is to have SILVERLIGHT defined and have a configuration that builds against the right version of the framework.

The default project file pulls in the default targets in an <import> element, like this:

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

To make it a Silverlight build most of what is needed is to do this:

<Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight\v2.0\Microsoft.Silverlight.CSharp.targets" />

How to do that? After some reading I realized that Conditions work on the import element. Aha! So I can do this:

<Import Condition="'$(ConfigurationName)'=='Debug'" Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

And so on for the other configuration names. Perfect! Except it didn't work. After some more reading I discovered that when MSBuild is hosted in Visual Studio it doesn't evaluate conditions on the import element. It does for Item and PropertyGroups... but not Import. The docs state that it evaluates import using the defaults at load time and doesn't change it thereafter. Two workarounds are suggested, first to place the conditional imports in the target files; and second to "write code in a Choose element".

Well, I don't want to edit the standard target files for this library, and while the Choose element is neat and all it doesn't accept an Import element as a child (not sure what they meant by suggesting that).

So I thought I would try this: I created two files dn35.targets and sl20.targets, each containing the correct Import element. I put an <import Project="current.targets" /> in the project file. I then created a batch file as follows:

echo off
IF /i %1=="Debug" (copy /y %2\dn35.targets %2\current.targets)
IF /i %1=="Release" (copy /y %2\dn35.targets %2\current.targets)
IF /i %1=="Silverlight Debug" (copy /y %2\sl20.targets %2\current.targets)
IF /i %1=="Silverlight Release" (copy /y %2\sl20.targets %2\current.targets)
echo on

I created a prebuild step that called the batch file with the configuration name and project directory, and tested it in and out of the VS ide. It worked great...

...except the changes were ignored. I could see the step fire off and the contents of current.targets change, but apparently it was already loaded and evaluated and the IDE doesn't load it again.

Curiously, I can create two entirely separate project files under, say, MyLib.sl20 and MyLib.dn35 and copy the correct one to MyLib.csproj and it works exactly as I want it to... but the IDE then pops a message saying that the project file has been changed outside the environment. I have eight assemblies in this library and I don't find this to be an elegant solution either, especially since I might change other things in the project file and then have to remember to migrate them to both versions.

I know I can make this all work if I ditch the hosted MSBuild and have the IDE execute the command line build, since it will evaluate conditional imports. I may have to do that, but it really seems like this should be easier to do.

Anyone have any ideas?
 

imported_Dhaval00

Senior member
Jul 23, 2004
573
0
0
Usually, isn't this something that your source code repository will handle? We run into this from time to time (we use Source Safe and Synergy), and the way we solve it is by checking in the root solution and then adding a link to the referenced class files, etc in the projects that need components from the root solution. I mean I see what you're trying to do, but this is going to create issues later - what is stop M$ from changing the way MSBuild works in a future service pack!

And even the first "solution" I mentioned is finicky - really, the best way (IMO) would be to actually create a third solution (a library) with all the common components and reference this in the two projects you have right now?
 

Markbnj

Elite Member <br>Moderator Emeritus
Moderator
Sep 16, 2005
15,682
14
81
www.markbetz.net
Originally posted by: Dhaval00
Usually, isn't this something that your source code repository will handle? We run into this from time to time (we use Source Safe and Synergy), and the way we solve it is by checking in the root solution and then adding a link to the referenced class files, etc in the projects that need components from the root solution.

Yeah that's what I should do. This is just a hobby project, and I don't use SCC here. I just use a fairly flat solution-oriented directory structure for all my projects and back it up every night. But that will send me off down another path and I really just wanted to get this done and move on.

I mean I see what you're trying to do, but this is going to create issues later - what is stop M$ from changing the way MSBuild works in a future service pack!

Well, they always can do that, but there are already many custom build scripts out there that are dependent on the core schema and interfaces. I don't think they will be quick to change that in a breaking way. The problem isn't MSBuild, it's Visual Studio not evaluating the conditional attributes for imports at build time. That's what those attributes were designed for, and that's how they work with the cli build tool. I guess the VS designers wanted to keep the build within a certain defined project sandbox, which completely defines the targets to build against. If they allowed the imports to be evaluated then you could easily break the whole thing. If you're using cli build you're assumed to be able to deal with it :).

And even the first "solution" I mentioned is finicky - really, the best way (IMO) would be to actually create a third solution (a library) with all the common components and reference this in the two projects you have right now?

This I don't quite get. The thing I am trying to build is the library in this case. What is shared between the .NET 3.5 and Silverlight versions is almost everything, since the core code for this library started as part of a Silverlight app. I need to conditionally support some methods here and there, but that's about it. So even if I were to extract out a common something, I would still need to define how that something is built, and so I would need to build two versions or run into the same problem, just pushed down one level. Maybe I'm missing something.

What I really need is to organize things so that I have two projects that reference the same set of source files. I'm going to think about how to do that. Ultimately I may just switch to the cli build tool.

Edit: I thought about your last suggestion and I see what you're getting at. Say there are one or two classes that contain calls that are different in the two environments: I could break those out into two versions in projects that reference all the other stuff that doesn't change. The problem is that Silverlight won't load any assembly not built against its runtime. So while the code is nearly the same, the whole shebang needs to be built against the Silverlight targets in order to be used in the Silverlight runtime.
 

Markbnj

Elite Member <br>Moderator Emeritus
Moderator
Sep 16, 2005
15,682
14
81
www.markbetz.net
Dude, excellent find! I Googled all over last night and never stumbled on this. Reading it now. Btw, David Betz and I share the same last name, and years ago we both wrote for Dr. Dobb's Journal, so it's kind of cool that he appears to have the solution to my current problem :).
 

imported_Dhaval00

Senior member
Jul 23, 2004
573
0
0
Time to upgrade those Googling skills ;).

Actually, the netfx team has some good bloggers, so I follow them from time-to-time. I found some solutions to my WPF problems last time... this time it worked for Silverlight, too (via their main Website, not Google).
 

Markbnj

Elite Member <br>Moderator Emeritus
Moderator
Sep 16, 2005
15,682
14
81
www.markbetz.net
Some really good info in that post. In my case I need to not just reference the assemblies, but build them differently based on conditions, but it looks like the answer might be the "add as link" feature in the VS08 add item dialog, which I had no idea existed. Using that I will try to make two versions of each project referencing the same set of source files by link.
 

Rangoric

Senior member
Apr 5, 2006
530
0
71
I just want to say, I love this post.

Add as Link is exactly what I've been looking for for a couple projects.
 

DaveSimmons

Elite Member
Aug 12, 2001
40,730
670
126
I'm not even playing with Sileverlight yet, but I did bookmark an artcile on sharing code with WPF apps in the October 2008 MSDN, page 33 (Dino Esposito)
Dino
 

Markbnj

Elite Member <br>Moderator Emeritus
Moderator
Sep 16, 2005
15,682
14
81
www.markbetz.net
Thanks to everyone for the links. After a few hours work tonight I got it all set up the way I wanted. In the end it was easiest just to close out the solution, rearrange the folder structure, and hand edit the project files. It was tedious, since I have nine projects in this library, but once I was sure of the project file changes it went pretty fast.

Basically I use source file links to keep a central source tree, and then have 3.5 and Silverlight project branches and lib (build branches). The solution seems a little bulky because each version has it's own solution file, project subfolders, and individual project files. I think I could consolidate it into a single solution but this seemed easiest for now.

Normally when files are added to a project the ItemGroup looks like this...

<ItemGroup>
<Compile Include="file.cs" />
&lt/ItemGroup>

When you use "Add as link" for an existing item you get this instead...

<ItemGroup>
<Compile Include="..\..\src\file.cs">
<Link>file.cs</Link>
</Compile>
&lt/ItemGroup>

That's it: some additional path information and the Link element. On the question of dividing up resources and properties (assemblyinfo.cs) I decided to keep the resources with the source, and the properties with the project, so the assembly info can be different for the two platforms.

For resources, assuming an existing project like mine you have the .resx and the .resx.designer.cs. The .resx is listed in the project file in an EmbeddedResource element, and you can use the Link element and relative paths for these as well. The designer code is just listed with the other .cs files, and as long as the .resx has a Generator element specifying ResXFileCodeGenerator (the "custom tool" property for resources in the ide) it will figure out that the .cs goes with the .resx and fix everything up.

So with a couple of conditional compilation blocks I am building the assemblies for both Silverlight and .NET 3.5. Pretty sweet :).