In an attempt to consolidate project settings into property sheets for both C++ and C# projects, the following property sheet was constructed:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
Trying to support both C++ and C# projects by introducing derived
properties and setting the appropriate output properties.
-->
<PropertyGroup Label="UserMacros">
<ProjectOrAssemblyName Condition="'$(AssemblyName)'==''">$(ProjectName)</ProjectOrAssemblyName>
<ProjectOrAssemblyName Condition="'$(ProjectName)'==''">$(AssemblyName)</ProjectOrAssemblyName>
<ShortPlatform Condition="'$(Platform)'=='Win32'">x86</ShortPlatform>
<ShortPlatform Condition="'$(Platform)'=='x86'">x86</ShortPlatform>
<ShortPlatform Condition="'$(Platform)'=='x64'">x64</ShortPlatform>
<ShortPlatform Condition="'$(Platform)'=='AnyCPU'">AnyCPU</ShortPlatform>
</PropertyGroup>
<PropertyGroup>
<OutputPath>$(OutputRelativePath)/$(ProjectOrAssemblyName)_$(ShortPlatform)_$(Configuration)/</OutputPath>
<BaseIntermediateOutputPath>$(OutputRelativePath)/Obj_Exe/$(ProjectOrAssemblyName)_$(ShortPlatform)</BaseIntermediateOutputPath>
<IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)/</IntermediateOutputPath>
<IntDir>$(IntermediateOutputPath)</IntDir>
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>
</Project>
This property sheet will move all build output to a separate location OutputRelativePath (defined in separate property sheet or directly in project file) outside directories that contain source code for easier cleanup etc. However, after setting this up and build works fine and all unit tests work fine, it was clear that a WPF executable project was not fine, since running the application with above property sheet results in the infamous:
IOException was unhandled "Cannot locate resource 'app.xaml'."
Why does changing the output paths result in this error? And how can it be determined that the cause is project build output paths? Can this be seen in generated code? I could not find it? And isn't this a bug?
NOTE: Using the following property sheet works, but only if IntermediateOutputPath contains BaseIntermediateOutputPath.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputPath>$(OutputRelativePath)/$(AssemblyName)_$(Platform)_$(Configuration)</OutputPath>
<BaseIntermediateOutputPath>$(OutputRelativePath)/Obj_Exe/$(AssemblyName)_$(Platform)</BaseIntermediateOutputPath>
<IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)</IntermediateOutputPath>
</PropertyGroup>
</Project>
So it appears, that somehow it is expected that output paths contain the AssemblyName properties or similar.
UPDATE FOR XAML STYLES IN ANOTHER ASSEMBLY: The same applies to xaml ResourceDictionary if these - e.g. Brushes.xaml - are located in another assembly and this assembly has changed the OutputPath also, this also throws an exception:
XamlParseException was unhandled for set property Source
with InnerException "Cannot locate resource 'Brushes.xaml'"
So all in all it appears output location changes the xaml resource names so these cannot be discovered at runtime, somehow. The odd thing is it is not a problem at design time...
UPDATE: Minimal steps to reproduce the exception:
Open Visual Studio 2013
Create new C# project WPF Application e.g. XamlIntermediateOutputPathBug
Unload Project
Edit Project File
After first PropertyGroup insert new PropertyGroup as:
<PropertyGroup>
<OutputRelativePath>$(ProjectDir)..Build</OutputRelativePath>
<OutputPath>$(OutputRelativePath)/$(AssemblyName)_$(Platform)_$(Configuration)/</OutputPath>
<BaseIntermediateOutputPath>$(OutputRelativePath)/Obj_Exe/$(AssemblyName)_$(Platform)</BaseIntermediateOutputPath>
<IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)/</IntermediateOutputPath>
<IntDir>$(IntermediateOutputPath)</IntDir>
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>
Delete OutputPath
properties in remaining PropertyGroups e.g.
<OutputPath>binDebug</OutputPath>
and:
<OutputPath>binRelease</OutputPath>
This should then throw an IOException
on start for mainwindow.xaml
. This is due to the $(AssemblyName).g.resources
embedded resource is given the following name:
.mresource public 'Build/Obj_Exe/XamlIntermediateOutputPathBug_AnyCPU_Debug/XamlIntermediateOutputPathBug.g.resources' as Build_Obj_Exe_XamlIntermediateOutputPathBug_AnyCPU_Debug_XamlIntermediateOutputPathBug.g.resources
{
// Offset: 0x00000000 Length: 0x000003BC
}
.mresource public 'Build/Obj_Exe/XamlIntermediateOutputPathBug_AnyCPU_Debug/XamlIntermediateOutputPathBug.Properties.Resources.resources' as Build_Obj_Exe_XamlIntermediateOutputPathBug_AnyCPU_Debug_XamlIntermediateOutputPathBug.Properties.Resources.resources
{
// Offset: 0x000003C0 Length: 0x000000B4
}
as can be seen with ildasm.exe
and opening the MANIFEST
for the assembly. As can also be seen the normal resources also gets a wrong name with the output path prefixed. This can, however, be fixed by setting the LogicalName
in the project file for this resource (see MissingManifestResourceException when running tests after building with MSBuild (.mresource has path in manifest)). This does not appear to be possible for xaml resources...
Having looked at the configuration I noticed I use /
at the end of the OutputPath
and IntermediateOutputPath
, removing these it appears to work, see below:
<PropertyGroup>
<OutputRelativePath>$(ProjectDir)..Build</OutputRelativePath>
<OutputPath>$(OutputRelativePath)/$(AssemblyName)_$(Platform)_$(Configuration)</OutputPath>
<BaseIntermediateOutputPath>$(OutputRelativePath)/Obj_Exe/$(AssemblyName)_$(Platform)</BaseIntermediateOutputPath>
<IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)</IntermediateOutputPath>
<IntDir>$(IntermediateOutputPath)/</IntDir>
<OutDir>$(OutputPath)/</OutDir>
</PropertyGroup>
I find this rather curious... any insight into why this would be the case or if this is actually true is appreciated. Note that the C++ IntDir
and OutDir
instead must have a trailing backslash, otherwise you will get warnings about this.
See Question&Answers more detail:
os