I'm fairly new to MSBuild, and I've done some customization on a WPF project file that I'm building both in Visual Studio 2010 and TFS 2010. I've customized the output path as follows:
<OutputPath Condition=" '$(TeamBuildOutDir)' == '' ">$(SolutionDir)build\binaries\$(ProjectName)\$(Configuration)\$(Platform)</OutputPath>
<OutputPath Condition=" '$(TeamBuildOutDir)' != '' ">$(TeamBuildOutDir)binaries\$(ProjectName)\$(Configuration)\$(Platform)</OutputPath>
This allows me to build to a centralized binaries directory when building on the desktop, and allows TFS to find the binaries when CI builds are running.
However, it seems that in both cases, the $(ProjectDir) property is evaluating to '' at build time, which creates strange results. Doing some debugging, it appears as if $(ProjectName) is set by the time BeforeBuild executes, but that my OutputPath property is evaluating it prior to that point.
<ProjectNameUsedTooEarly Condition=" '$(ProjectName)' == '' ">true</ProjectNameUsedTooEarly>
The preceding property is in the same property group as my OutputPath property. In the BeforeBuild target, $(ProjectNameUsedTooEarly) evaluates to true, but $(ProjectName) evaluates to the project name as normal by that point.
What can I do to ensure that $(ProjectName) has got a value when I use it?
I just used Attrice's MSBuild Sidekick to debug through my build file, and in the very first target available for breakpoint (_CheckForInvalidConfigurationAndPlatform) all the properties seem to be set already. ProjectName is already set correctly, but my OutputPath property has already been set using the blank value of ProjectName.
Hmm - bit of confusion going on there which I'll try to sort out
Don't use $(ProjectDir) - use $(MSBuildProjectDir) - that's the location of your csproj in the source tree and is set by MSBuild.exe as a reserved property. I don't think $(ProjectDir) is available until after Microsoft.Common.Targets has been imported (which is done by Microsoft.Csharp.targets). Property evaluation is always carried out "in-place" within the file, and not when all the Imports have completed. This may explain why you are seeing the property as valid in the SideKick tool
Likewise use $(MSBuildProjectName) (which I think will address your problem)
I'm unsure about VS2010 and TFS2010 (as that uses MSBuild 4.0 and no doubt a new TeamBuild), but in 2008, it's pretty hard within a .csproj to figure out if your build was called from a command line/IDE build or from within TeamBuild. What I'm trying to say is that I don't think $(TeamBuildOutDir) is available within your csproj. I normally test $(TeamBuildConstants) property, as that property is passed down when teambuild calls your proj file. YMMV as I haven't played with 2010 yet..
Related
I am using Directory.Build.props in my .NET 6 solution, to set common properties for multiple projects. I use MSBuild macros for example: $(MsBuildProjectName), $(Configuration), $(TargetFramework), however the $(TargetFramework) is completely disregarded.
Diagnostics: The property setting and the other parts of the path is in effect, so the issue if not about the Directory.Build .props is not in effect.
If I use exactly the very same line in the .csproj files, the $(TargetFramework) part of the path is correctly honored.
Any thoughts?
"TargetFramework" is (typically) defined in the actual .csproj file - since you indicate nothing to the contrary in your question, I assume in your case that happens as well.
Directory.Build.props is imported very early in the build process, before the content of your .csproj files is read. Thus, if you reference any property in there that is (only) defined by your .csproj file it will be empty. MSBuildProjectName and others are directly defined by MSBuild itself and thus are also available in Directory.Build.props.
You could simulate this effect by simply defining a property FooBar in your .csproj file and then try to "use" it in Directory.Build.props. You will observe it is also not set there.
(Also see this for more information.)
(The observed effects could be different if you build for multiple TargetFrameworks. Because then, the outer MSBuild invocation for multiple TFMs will invoke MSBuild for each specified TFM specified in TargetFrameworks and passes that as property TargetFramework from "outside". Thus, if Directory.Build.props is imported then, TargetFramework will be defined. But your question doesn't suggest this could be the problem, so I won't go any deeper here.)
I am in the process of upgrading my VS2008 project to VS2010. Currently the problem is with the custom build rule file.
I used the wizard to port it to the new version of visual studio and managed to resolve most of the problems.
My custom build rule is defined for the .xxx extension. There is a bunch of options you can configure for this file. I have specified the default values in the .props values and overriden some of them for a specific file.
However, looking at the command line that is executed for the file it appears that the default values are being passed instead of the custom values specified for that file.
This is the snipplet of my .props file:
<ItemDefinitionGroup>
<MyTool>
<MTPath>C:\DefaultPath</MTPath>
...
<CommandLineTemplate>
set MT_Path=%(MTPath)
call MyTool.exe
</CommandLineTemplate>
I have been working closely with the masm sample, but I can't figure out what I am doing wrong.
Thanks.
I have found the solution, variables should be enclosed with [] braces so they are evaluated correctly.
<ItemDefinitionGroup>
<MyTool>
<MTPath>C:\DefaultPath</MTPath>
...
<CommandLineTemplate>
set MT_Path=[MTPath]
call MyTool.exe
</CommandLineTemplate>
According to this it should be possible to reference projects outside solution and have it working in VS and command line but not TFS.
Unfortunately, when I've tried to partition my solution this way, it didn't work neither in VS2010/devenv nor in msbuild.
In both cases the error was:
The OutputPath property is not set for project 'Common.csproj'.
Please check to make sure that you have specified a valid combination
of Configuration and Platform for this project. Configuration='Debug'
Platform='AnyCPU'. This error may also appear if some other project
is trying to follow a project-to-project reference to this project,
this project has been unloaded or is not included in the solution, and
the referencing project does not build using the same or an equivalent
Configuration or Platform.
However, current Platform is "x86" and no matter which platform and configuration I set in VS or msbuild it's always trying Debug|AnyCPU. In case of msbuild if I set /p:OutputPath=bin\x86\Debug it propagates to child projects correctly.
Is this a bug, can I work-around it ?
UPDATE
Found the bug in MS Connect. Unfortunately closed as Won't Fix :(
UPDATE 2
Found workaround: set ShouldUnsetParentConfigurationAndPlatform=false. Both on command line for msbuild and in project file (before any imports) to fix Visual Studio.
If I understand the problem correctly, it's actually because the AssignProjectConfiguration target is not correctly setting the configuration / platform properties for those projects.
If you know what their configurations and platforms should be, you could always just inject a target to run right after the AssignProjectConfiguration target, and override the SetConfiguration and SetPlatform properties on each item representing an unresolved (meaning not part of the solution configuration) inter-project reference.
For some stupid reason, the Microsoft-provided target stores the list of unresolved project references in the same collection as the resolved ones (but nowhere else), which leaves you with 2 options:
Just set each project's properties manually (ie. hard-coding via a dynamic ItemGroup element inside your injected target).
Call the AssignProjectConfiguration task yourself from your injected target, collect the unassigned outputs to assign them all a default configuration / platform of your choosing.
Either way, once you have your list of correctly configured project references, you can simply replace the unresolved items in the ProjectReferenceWithConfiguration item group with their manually modified counterparts (using another dynamic ItemGroup element with a Remove and then an Include).
Mind you, I wouldn't do things the way you are doing them. If you want to split your product into several solutions, then I would just have each solution publish shared outputs into a common staging area and have .proj scripts to link them together. I've learnt the hard way that command-line-style MSBuild and inside-VS-style MSBuild do not mix (they have made some odd compromises to ensure interoperability with non-MSBuild project systems, of which the whole AssignProjectConfiguration-with-VS-provided-solution-config-XML process is one).
I encountered something similar in VS2012. For me, it was related to the ShouldUnsetParentConfigurationAndPlatform property of the AssignProjectConfiguration target being set to true during the build. This resulted in "GlobalPropertiesToRemove = Configuration;Platform" which caused the Configuration and Platform properties to be cleared for the project reference.
I was able to see this occurring by looking in the Build output window after setting Tools -> Options -> Projects and Solutions -> Build and Run -> MSBuild project build output verbosity -> Diagnostic.
With a blank Configuration/Platform, this caused the following line in some of my projects to match, resulting in Debug assemblies in the project output even in a Release build:
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
The solution was to modify those csproj files to specify Release as the configuration to use when the Configuration variable was empty/blank:
<Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
Still not knowing too much about .NET, I am tasked with converting a rather big .NET solution from VS2008 to VS2010. Part of it are a set of C++ projects (/clr), which I migrated to VS2010. I set their target framework to 2.0, because they are used in projects that are not to be converted right now.
After a lot of hassle I am at the point where the whole solution builds in VS2010, but for automated builds and tests I need to have the thing built using MSBuild, too, and that fails. The problem is that somewhere the /d1clr:nostdlib switch gets appended to the compiler command line, leading to a nasty error message:
error MSB6001: Invalid command line switch for "CL.exe". Illegal characters in path. [C:\blah\foo.vcxproj]
When I look at the command line emitted by MSBuild the only odd thing I see is that it ends with said switch: ...foo.cpp bar.cpp baz.cpp /d1clr:nostdlib
I suppose this switch fails because the older compiler invoked for .NET 2.0 stuff doesn't know how to deal with it?
Where would I start to look for where this switch gets appended? I don't see it on the project's property page's C/C++/Command Line option.
Are you sure you need to set the target framework to 2.0?
Can't a 2.0 project reference a 4.0 project (or whatever the converted project is)?
From poking around the MSBuild files, the command seems to be added because of these lines in Microsoft.CppBuild.targets (on my machine, located under C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0:
<ClCompile Condition="'#(ClCompile)' != '' and '$(CLRSupport)' != 'false' and '$(CLRSupport)' != ''">
<AdditionalUsingDirectories>$(TargetFrameworkDirectory);%(ClCompile.AdditionalUsingDirectories)</AdditionalUsingDirectories>
<AdditionalOptions Condition="('$(TargetFrameworkVersion)' == 'v3.5' or '$(TargetFrameworkVersion)' == 'v3.0' or '$(TargetFrameworkVersion)' == 'v2.0')">/d1clr:nostdlib %(ClCompile.AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="'$(TargetFrameworkVersion)' == 'v4.0'">/clr:nostdlib %(ClCompile.AdditionalOptions)</AdditionalOptions>
</ClCompile>
In other words, it is added simply because you set the target framework version to something other than 4.0. (If it had been set to 4.0, it would instead add the /clr:nostdlib flag)
I have no idea why it wouldn't work when invoked directly via MSBuild, though. Perhaps it uses a different compiler version (PATH or some other environment variable not set up correctly, perhaps?)
When building via Visual Studio, it also invokes MSBuild, so really, it shouldn't make any difference that you call MSBuild "directly", unless some part of the environment is set up differently. (or you're calling MSBuild with the wrong flags)
Of course there's nothing "magical" about these MSBuild files, so you can modify them, or edit your project to reference separate versions of them instead (which you've modified to not insert the flag). It is, if you ignore the XML clutter, just a general-purpose build system. It doesn't have any "built-in" understanding of VC++ projects, beyond what is specified in these XML files.
I have a csproj file, being part of two different Visual Studio solutions. The project file should be able to behave slightly different, depending on the solution it will be used from. What I would need, is something usable as a 'Condition' - a property named for example $(SolutionName) - filled in automagically.
At least, this is my idea. I didn't found anything like that.
I also considered to have two small project files importing the common parts. This would prevent editing all these properties from inside Visual Studio, I guess. It would write changes only in the active 'master file', correct?
So, is there any other way to discriminate at project level using solution information?
Turns out there is a property named exactly $(SolutionName). Try this; first set an environment variable as:
> set MSBuildEmitSolution=1
Then build your solution file using MSBuild from the same command line
> MSBuild My.sln
You will find the MSBuild project transformation of your solution file, it will be named My.sln.metaproj.
Just open that in a text editor and you can see the other properties. Examine the "Build" target in this projectd file, you can see that all these properties are passed in to the MSBuild task when it builds your projects, so you should be able to discriminate conditions based on any of them.