Setting CultureInfo before compilation and always resetting after - visual-studio

I am trying to execute a task which changes the locale/culture used at compile time for a XNA content pipeline project, and restores the original after the compilation ended. The intention is to allow proper parsing of floats in non-English machines.
So far I am using BeforeBuild and AfterBuild like this:
<UsingTask TaskName="PressPlay.FFWD.BuildTasks.SetLocale" AssemblyFile="PressPlay.FFWD.BuildTasks.dll" />
<Target Name="BeforeBuild">
<SetLocale> <!-- By default, set to 'en-US' -->
<Output TaskParameter="PrevLocale" ItemName="OrigLocale" />
</SetLocale>
</Target>
<Target Name="AfterBuild">
<SetLocale Locale="#(OrigLocale)" />
</Target>
It works properly, except when an error occurs during compilation (an invalid XML or ContentSerializer error), after which the locale will not be reset.
Answers in SO are contradictory, since some say AfterBuild always executes (not in my case) and others say there's no way to ensure a target is always ran after build. I haven't found precise info regarding this around google.
I know there is the option of using PostBuildEvent and setting it to always run, but it'd use Exec to run the command and I suspect it would run in a separate thread, defeating its purpose (I set CurrentThread.CultureInfo to change the locale).
So, is there a way to ensure the target is always ran? Alternatively, is there any other way to tell VS2010 to run a compilation with a specific culture?
Links to documentation explicitly clarifying the issue would be very appreciated.
-- Final solution, following Seva's answer --
XNA's content pipeline does not declare PreBuildEvent nor PostBuildEvent. Other required properties (RunPostBuildEvent, PreBuildEventDependsOn and PostBuildEventDependsOn) aren't defined, either. However, if you define them, the content pipeline will make good use of them as in any other project.
So, the changes I had to make to the contentcsproj file were:
<!-- Added to ensure the locale is always restored -->
<PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<!-- Reference includes, project references and other stuff -->
<!-- ... -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\$(XnaFrameworkVersion)\Microsoft.Xna.GameStudio.ContentPipeline.targets" />
<!-- Customizations to change locale before compilation and restore it after -->
<!-- Needed to properly treat dots in the XMLs as decimal separators -->
<UsingTask TaskName="PressPlay.FFWD.BuildTasks.SetLocale" AssemblyFile="PressPlay.FFWD.BuildTasks.dll" />
<!-- Apparently ContentPipeline.targets does not define PreBuildEvent and PostBuildEvent -->
<!-- However, they are still used if defined -->
<Target Name="PreBuildEvent" DependsOnTargets="$(PreBuildEventDependsOn)"/>
<Target Name="PostBuildEvent" DependsOnTargets="$(PostBuildEventDependsOn)"/>
<PropertyGroup>
<PreBuildEventDependsOn>
$(PreBuildEventDependsOn);
EstablishUSLocale
</PreBuildEventDependsOn>
</PropertyGroup>
<PropertyGroup>
<PostBuildEventDependsOn>
$(PostBuildEventDependsOn);
RestoreOriginalLocale
</PostBuildEventDependsOn>
</PropertyGroup>
<Target Name="EstablishUSLocale">
<SetLocale Locale="en-US">
<Output TaskParameter="PrevLocale" ItemName="OrigLocale" />
</SetLocale>
</Target>
<Target Name="RestoreOriginalLocale">
<SetLocale Locale="#(OrigLocale)" />
</Target>
With this solution another problem is indirectly taken care of, which is the potential issues that could arise if another project redefined BeforeBuild or AfterBuild, resulting in one of the definitions overriding the other.

You can use PostBuildEvent, because you can configure to execute it always after the build. However as you correctly noticed, using Exec task will not work here. However PostBuildEvent is actually extendable through a property called $(PostBuildEventDependsOn). You will need to define this property:
<PropertyGroup>
<PostBuildEventDependsOn>RestoreOriginalLocale</PostBuildEventDependsOn>
</PropertyGroup>
The target RestoreOriginalLocale is what you had in your AfterBuild target:
<Target Name="RestoreOriginalLocale">
<SetLocale Locale="#(OrigLocale)" />
</Target>
Your BeforeBuild target is still needed, it remains as what you wrote in your question.
To ensure PostBuildEvent is executed on failure (and thus require RestoreOriginalLocale to be executed), you need to set property RunPostBuildEvent to Always. You can do it through IDE, or by manually editing your .csproj file.

Related

Emulate Devenv/Runexit under MSBuild

I am new to MSBuild and busy automating tests of Visual Studio solutions.
I previously worked with the command line of Devenv, which provides a convenient /Runexit mode of operation. From the manual:
/Runexit (devenv.exe)
Compiles and runs the specified solution, minimizes the IDE when the solution is run,
and closes the IDE after the solution has finished running.
This is exactly the functionality that I need. I am now migrating to MSBuild. I have discovered that the project files in the Solution can be directly used for building, as the default target is precisely Build.
What can I do to handle a different target, that will have the same effect as /Runexit ? Can you help me through the maze ?
This is the most basic Target which runs a projects' output file:
<Target Name="RunTarget">
<Exec Command="$(TargetPath)" />
</Target>
For c++ unittests I use something like this; it's a property sheet so it's easy to add to any project without needing to manually modify it. It automatcially runs the output after the build so there is no need to specify an extra target and it works the same for VS and from the command line. Moreover in VS you'll get unittest errors from frameworks like Unittest++ or Catch displayed right away in the error list, so you can doubleclick them. Also the UnitTestExtraPath property can be set elsewhere just in case (e.g. on a buildserver we always want to keep the PATH clean but sometimes we do need to modify it to run built exes).
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup />
<ItemGroup />
<!--Used to be AfterTargets="AfterBuild", but that is unusable since a failing test marks the build as unsuccessful,
but in a way that VS will always try to build again. As a consequence debugging in VS is impossible since
VS will build the project before starting the debugger but building fails time and time again.-->
<Target Name="RunUnitTests" AfterTargets="FinalizeBuildStatus">
<Exec Condition="$(UnitTestExtraPath)!=''" Command="(set PATH="%PATH%";$(UnitTestExtraPath)) & $(TargetPath)" />
<Exec Condition="$(UnitTestExtraPath)==''" Command="$(TargetPath)" />
</Target>
</Project>

Can I build multiple configurations of a project within one solution configuration?

I would like to build the same project twice in the same solution configuration, varying some #define flags to toggle features. Both binaries will be deployed with different names.
The solutions that I know could work:
Add a solution configuration - But I will then need to build the solution twice, which I would prefer to avoid. Both project configurations will always be built.
Copy the project - But then I have the overhead of maintaining a new project when I only want to maintain a different configuration.
Batch build - I avoid using batch build as I use both devenv for local development and msbuild for continuous integration.
Any other ideas or suggestions?
Just figured out a way to do what you asked for. Create one msbuild file (I named mine multiple.proj) and add the script below.
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Choose>
<When Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<ItemGroup>
<ProjectToBuild Include="$(MSBuildProjectName).csproj">
<Properties>Configuration=Release</Properties>
</ProjectToBuild>
</ItemGroup>
</When>
</Choose>
<Target Name="BeforeBuild">
<Message Text="Building configuration $(Configuration)..." />
</Target>
<Target Name="AfterBuild">
<MSBuild Projects="#(ProjectToBuild)"/>
</Target>
</Project>
</type>
</this>
Import the script on your projects (csproj or vbproj):
<Import Project="..\multiple.proj" />
This script tells msbuild to build again your project with another configuration as an AfterBuild event. I used Debug/Release to make the example, but you can easily change the script to support other configurations, or make the decision to build again based on other variables.
Be careful because you're running two builds at once, so build errors can be harder to understand.
Hope this helps.

Using SlowCheetah's app.config transformations with Setup projects

I'm using the SlowCheetah XML Transforms extension to handle web.config-like transformations with app.config. That part works great.
I added a setup project and configured it to include the project output of the first project. I noticed that when I ran the installer, it installed the non-transformed app.config. Looking at the Primary output Outputs (say that 10 times fast), I noticed that its finding the binary in Project\bin\Debug\Project.exe, but Project.exe.config comes from Project\app.config instead of Project\bin\Debug\Project.exe.config.
I could exclude app.config from the Primary output, and hard-code the path to a specific configuration's app.config (Project\bin\Debug\Project.exe.config), but then I'd get the same app.config regardless of which configuration I used to build it.
Is there a workaround for getting the appropriate transformed app.config in a Setup project?
Hi we are planning on releasing a new version which has ClickOnce support in the next few days. If you need a build of the add in before than which has the fix please contact me and I can get that out to you.
This may not be exactly the answer you're looking for but I have previously wrestled with how to get the correct app.config file into a setup project. I have a TFSBuild.proj msbuild file that uses transforms. The SlowCheetah transforms I think use the same msbuild task but I may be incorrect. SlowCheetah certainly provides a more useful user experience when working with transform files. My build file takes a slightly different approach. At the end of the automated build I wanted to generate installers for each of the target deployment environments. I use a number of msbuild extensions, including the TransformXml build task - not all required for the following but FWIW these are the imports:
<!-- import extensions -->
<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks"/>
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<UsingTask TaskName="TransformXml"
AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
I have the following environments defined:
<ItemGroup>
<!-- target deployment environments -->
<Configs Include="Prod" />
<Configs Include="Staging" />
<Configs Include="Test" />
</ItemGroup>
Then the standard AfterCompileSolution target contains a call to the target that generates the installer for each environment:
<Target Name="AfterCompileSolution">
<!-- Create installers for target deployment environments -->
<CallTarget Targets="MyProject" />
</Target>
<Target Name="MyProject" Outputs="%(Configs.Identity)">
<ItemGroup>
<MyProjectTempConfig Include="$(SolutionRoot)\MyProjectService\Temp.config" />
<MyProjectConfigFrom Include="$(SolutionRoot)\MyProjectService\App.%(Configs.Identity).config" />
<MyProjectConfigTo Include="$(SolutionRoot)\MyProjectService\App.config">
<Attributes>ReadOnly</Attributes>
</MyProjectConfigTo>
</ItemGroup>
<Message Text="MyProject - Target environment: %(Configs.Identity)" />
<!-- transform app.config using appropriate -->
<Copy SourceFiles="#(MyProjectConfigTo)"
DestinationFiles="#(MyProjectTempConfig)"
OverwriteReadOnlyFiles="true"
ContinueOnError="true"
Condition="!Exists(#(MyProjectTempConfig))"/>
<File TaskAction="RemoveAttributes" Files="#(MyProjectConfigTo)"/>
<TransformXml Source="#(MyProjectTempConfig)"
Transform="#(MyProjectConfigFrom)"
Destination="#(MyProjectConfigTo)" />
<!-- run setup -->
<Exec Command=""$(ProgramFiles)\Microsoft Visual Studio 10.0\Common7\IDE\devenv" "$(SolutionRoot)\MyProject.sln" /build Release /project MyProjectService.Setup"/>
<!-- rename output for target deployment environment -->
<Copy SourceFiles="$(SolutionRoot)\MyProjectService.Setup\Release\MyProjectService.msi"
DestinationFiles="$(OutDir)\%(Configs.Identity)_MyProjectService.msi"
OverwriteReadOnlyFiles="true"
ContinueOnError="true"/>
</Target>

How to turn off caching of build definitions in Visual studio

In project file I import my own target file
<Import Project="Build\CopyDependencies.target" />
and later I call target from that target file
<CallTarget Targets="CopyDependencies" UseResultsCache="false" />
If I edit CopyDependencies.target file I have to reload whole solution and only then changes to CopyDependencies.target take effect. I believe it is some sort of build definitions caching in Visual Studio? If it is, maybe it can be turned off?
Thanks #KazR
Here is a smaller Solution that you can insert into your .csproj file
<Target Name="AfterBuild">
<PropertyGroup>
<TempProjectFile>Build.$([System.Guid]::NewGuid()).proj</TempProjectFile>
</PropertyGroup>
<Copy SourceFiles="Build.proj" DestinationFiles="$(TempProjectFile)" />
<MSBuild Projects="$(TempProjectFile)" />
<ItemGroup>
<TempProjectFiles Include="Build.????????-????-????-????-????????????.proj"/>
</ItemGroup>
<Delete Files="#(TempProjectFiles)" />
</Target>
Problem solved
I don't know how you would disable the VS cache, however I may have a workaround that would allow you to edit the build target without having to reload the solution.
You could use the MSBuild task in your proj file to call a wrapper target that copies your CopyDependencies.target file to CopyDependencies.[RandomNumber].target, then invokes your CopyDependencies target in the newly created file, and finally deletes it.
This would force VS to reload the target on each invocation as the filename is different.
Here's an example:
myProject.proj
Add this to the AfterBuild target:
<MSBuild Projects="Wrapper.target" Targets="MyWrappedTarget" UnloadProjectsOnCompletion="true"/>
Wrapper.target
Here we have the target that will - at build time - copy the real target file and invoke the desired build target within it (I've used an inline c# task which is only available in MSBuild 4.0):
<UsingTask TaskName="RandomNumber" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<Number ParameterType="System.Int32" Output="true"/>
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<!-- CDATA -->
Random rndGenerator = new Random();
Number = rndGenerator.Next(Int32.MaxValue);
<!-- CDATA -->
</Code>
</Task>
</UsingTask>
<Target Name="MyWrappedTarget">
<Message Text="MyWrappedTarget target called"/>
<RandomNumber>
<Output TaskParameter="Number" PropertyName="FileNumber"/>
</RandomNumber>
<PropertyGroup>
<CopiedTarget>inner.test.$(FileNumber).target</CopiedTarget>
</PropertyGroup>
<Copy SourceFiles="inner.test.target" DestinationFiles="$(CopiedTarget)"/>
<MSBuild Projects="$(CopiedTarget)" Targets="_innerTestTarget"/>
<Delete Files="$(CopiedTarget)"/>
</Target>
inner.test.target
This contains the real build target you want to execute, in this example it's a simple file copy.
<Target Name="_innerTestTarget">
<Message Text="This is a inner test text message"/>
<Copy SourceFiles="x.txt" DestinationFiles="x1.txt"/>
</Target>
This isn't production ready, but hopefully illustrates my point.
With this (slightly convoluted) process in place, you can change the inner.test.target file without having to reload the solution in VS.
Here's a solution that doesn't require any MSBuild scripting at all.
I noticed that unloading and reloading a project doesn't get around the cache, but closing and reopening the solution does. In addition, Visual Studio will prompt you to reload the solution if it notices the .sln file has changed. And finally, this superuser question explains how to touch a file in Windows.
Putting these together, I added a Visual Studio external tool to touch the current solution file. Here's how:
Select TOOLS > External Tools ...
Click the Add button to add a new tool.
Set properties as follows:
Title: Reload Solution
Command: cmd.exe
Arguments: /c copy "$(SolutionFileName)"+>nul
Initial directory: $(SolutionDir)
and turn on Use Output window
Click OK to close the External Tools window
Now if you have made changes to your MSBuild files, just select TOOLS > Reload Solution and all your build files will be reloaded.
I'm using Windows 7 64-bit and Visual Studio 2012 Express for Windows Desktop.
I have a different solution, not involving temporary files:
Include.targets file:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Foobar">
<Copy SourceFiles="test.source" DestinationFiles="testFoobar.dest" />
</Target>
</Project>
Project file:
....
<Target Name="BeforeBuild">
<Exec Command="$(MSBuildToolsPath)\MSBuild.exe Include.targets /t:Foobar" ContinueOnError="false" />
</Target>
....
in this case VS does not recognize the MSBuild command, and does not cache the file.
happy coding!
Before running MSBuild I run this to clear the download cache:
call "%VS120COMNTOOLS%vsvars32.bat"
echo Clear download cache
gacutil -cdl

MSBuild "Debug" configuration not working in VS 2010 Beta 2

I'm trying to set up my environment for developing, debugging and deploying Windows Desktop Gadgets. I've hit a bit of a roadblock in my project, where I can't run a build on my gadget when the configuration is set to "Debug". If the configuration is set to "Release", the build goes through the following custom tasks:
Copy gadget contents to a seperate folder.
Minify/obfuscate the javascript files, removing comments and whitespace.
Package the files into a CAB file.
Sign the CAB file with a digital certificate.
This runs just fine, my "Debug" configuration has the following tasks defined
Copy gadget folder to AppData\Local\Microsoft\Windows Sidebar\Gadgets\.
Start the gadget using the IDesktopGadget interface.
If I copy those two tasks to the "Release" configuration, they run just fine - no problems whatsoever. I've tried creating a seperate configuration called "Test", copied from the "Release" configuration.
If I try to build any configuration other than "Release", I get an instant message saying "Build succeeded" but no tasks have run at all.
EDIT: I've started a bounty because I still have the same problem with VS 2010 RC and it's very frustrating.
FURTHER EDIT:
Thanks to John I was able to debug the build process. It led me to realize that the <Target> element with condition for debugging was being completely ignored (not even processed). When I swapped the position of my <Target> elements, it worked:
<Target Name="Build" Condition="'$(Configuration)' == 'Release'">
<!--
<Obfuscate PathToJasob="C:\Program Files (x86)\Jasob.com\Jasob 3.5" Path="$(GadgetFolder)" Output="$(GadgetName)_obf" log="jasob_log.txt" />
-->
<BuildGadget BuildFormat="CAB" Path="$(GadgetFolder)" Target="$(GadgetName).gadget" />
<SignGadget CertName="Cert1" TimestampURL="http://timestamp.comodoca.com/authenticode" Target="$(GadgetName).gadget" />
</Target>
<Target Name="Build" Condition="'$(Configuration)' == 'Debug'">
<CopyToGadgets GadgetFolder="$(GadgetFolder)" GadgetName="$(GadgetName)" />
<RunGadget GadgetName="$(GadgetName)" />
</Target>
So it looks like the second <Target Name="Build"> element overrides the first, despite the Condition attribute being present. What can I do?
As Joe suggests:
Change your output path like this, and see if that fixes the issue:
<OutputPath>bin\Debug\</OutputPath>
Update
Have you tried running msbuild /verbosity:diagnostic ?
Can you try that and show the output?
Second Update
Make one target 'build', and then make two tasks in that target:
<Target Name="Build">
<CallTarget Targets="BuildRelease" Condition="'$(Configuration)' == 'Release'" />
<CallTarget Targets="BuildDebug" Condition="'$(Configuration)' == 'Debug'" />
</Target>
<Target Name="BuildRelease">
<!--
<Obfuscate PathToJasob="C:\Program Files (x86)\Jasob.com\Jasob 3.5" Path="$(GadgetFolder)" Output="$(GadgetName)_obf" log="jasob_log.txt" />
-->
<BuildGadget BuildFormat="CAB" Path="$(GadgetFolder)" Target="$(GadgetName).gadget" />
<SignGadget CertName="Cert1" TimestampURL="http://timestamp.comodoca.com/authenticode" Target="$(GadgetName).gadget" />
</Target>
<Target Name="BuildDebug">
<CopyToGadgets GadgetFolder="$(GadgetFolder)" GadgetName="$(GadgetName)" />
<RunGadget GadgetName="$(GadgetName)" />
</Target>
Just a guess:
Your Debug build has its output path set to bin\Release\.
The timestamps of the files in bin\Release\ are probably causing MSBuild to conclude that the debug build is already up to date. Try changing the the output path to bin\Debug\ for debug builds.

Resources