I have a C# solution that I would like to get the path of the solution be set to the app.config during build time. for instance. Lets say I have the solutions c:\temp\visual studio\super fun project\super_fun_project.sln open. I build and in one of the test projects a app setting is changed to be the full path of the solution. ie
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="fullSolutionPath" value="{setAtBuild}"/>
</appSettings>
</configuration>
that if I were to go to c:\temp\visual studio\super fun project\Foobar.Tests\bin\Debug\Foobar.Tests.dll.config it would look be
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="fullSolutionPath" value="c:\temp\visual studio\super fun project\super_fun_project.sln"/>
</appSettings>
</configuration>
or however it needs to get formated so that when at runtime I ask for the value I indeed get the correct path. I've looked down Transformation, but I can't figure out how I would get the solution path set. Are there any other tricks to get this?
What you can do is modify the project file and add an MsBuild Target.
The target can use a Custom Inline Task, a task with its source code integrated into the project file.
So to add this task:
1) unload the project (right click on project node, select "Unload Project")
2) edit the project file (right click on project node, select "Edit ")
3) add the following to the project file (for example to the end) and reload it, now when you build, the config file will be modified accordingly.
<Project ...>
...
<Target Name="AfterBuild">
<RegexReplace FilePath="$(TargetDir)$(TargetFileName).config" Input="setAtBuild" Output="$(SolutionPath)" />
</Target>
<UsingTask TaskName="RegexReplace" TaskFactory="CodeTaskFactory" AssemblyName="Microsoft.Build.Tasks.Core" >
<ParameterGroup>
<FilePath Required="true" />
<Input Required="true" />
<Output Required="true" />
</ParameterGroup>
<Task>
<Using Namespace="System.Text.RegularExpressions"/>
<Code Type="Fragment" Language="cs"><![CDATA[
File.WriteAllText(FilePath, Regex.Replace(File.ReadAllText(FilePath), Input, Output));
]]></Code>
</Task>
</UsingTask>
</Project>
Here, I've defined Output to use a Visual Studio's MSBuild Property named SolutionPath, but you can reuse this RegexReplace task and update Input and Output parameters to various needs.
I dont know what your use case is but you can call a homegrown batch file to do this from the post-build events of your project.
example: create a batch script in your project entitled 'updateconf.bat', ensure that it is ANSII encoded (maybe use notepad++ to write the script and confirm ansii) or you'll get an exception indicating that the file is prefixed with an illegal character when you compile your VS project and check the output.
Contents of batch script:
#echo off > newfile & setLocal ENABLEDELAYEDEXPANSION
set old="{setAtBuild}"
set new=%2
set targetBinary=%3
cd %1
for /f "tokens=* delims= " %%a in (%targetBinary%.config) do (
set str=%%a
set str=!str:%old%=%new%!
>> newfile echo !str!
)
del /F /Q %targetBinary%.config
rename "newfile" "%targetBinary%.config"
Then add a post-build event in your project properties that calls the batch script:
call $(ProjectDir)\updateconf.bat "$(TargetDir)" "$(SolutionPath)" $(TargetFileName)
Related
I currently have a solution with a web.api project that I want to deploy to different virtual directories in my local IIS. Currently I am doing the following in the .csproj of the api:
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)' == 'CustomerOne.Debug'">
<CustomerName>CustomerOne</CustomerName>
....
</PropertyGroup>
...
These variables are used extenisvely further on for web.config transforms, copying to different locations, etc., by referencing them like $(CustomerName).
The only place where it does not work is in the definition of the virtual directory, i.e., I'd like to connect the build configuration to the IISUrl below, which you can hardcode:
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
...
<IISUrl>http://localhost/api/something</IISUrl>
...
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
Replacing this by <IISUrl>http://localhost/api/$(CustomerName)</IISUrl> does not work. Ideas?
Replacing this by http://localhost/api/$(CustomerName) does not work. Ideas?
That because Anything inside of a ProjectExtensions element will be ignored by MSBuild.
You can get the detailed info from this document ProjectExtensions Element (MSBuild):
Allows MSBuild project files to contain non-MSBuild information.
Anything inside of a ProjectExtensions element will be ignored by
MSBuild.
That is the reason why the Msbuild variables not work in Project Extensions.
Hope this helps.
You could update the underlying project file. A Target like this in your project file would do it.
<Target Name="AfterBuild">
<PropertyGroup>
<NewUrl>http://localhost/api/$(CustomerName)</NewUrl>
</PropertyGroup>
<Message Text="Updating IISUrl: $(NewUrl) in $(MSBuildProjectFile)" />
<XmlPeek Namespaces="<Namespace Prefix='msb' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>" XmlInputPath="$(MSBuildProjectFile)" Query="/msb:Project/msb:ProjectExtensions/msb:VisualStudio/msb:FlavorProperties/msb:WebProjectProperties/msb:IISUrl/text()">
<Output TaskParameter="Result" ItemName="Peeked" />
</XmlPeek>
<Message Text="Current Url: #(Peeked)" />
<!-- Only update the IISUrl if its changed -->
<XmlPoke Condition=" '#(Peeked)'!='$(NewUrl)' " XmlInputPath="$(MSBuildProjectFile)" Namespaces="<Namespace Prefix='msb' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>" Query="/msb:Project/msb:ProjectExtensions/msb:VisualStudio/msb:FlavorProperties/msb:WebProjectProperties/msb:IISUrl" Value="$(NewUrl)" />
</Target>
However it does have side affects. Changing the underlying project file means Visual Studio decides it should reload the project.
To use it you cannot go directly into Debug. Instead build, reload the project and then go into debug. If you go directly into Debug (with a compile) it will use the old url.
I created a C# file. Normally we build it using the Visual Studio IDE. But I want to build it using MSBuild.
I have one .bat and .xml file. What exactly I have to do with .xml file to build that .cs file successfully?
My .xml file:
<property environment="env" />
<property name="tools.dir" location="${env.TOOLS_DIR}"/>
<target name="Buildproject" >
<echo message="I am building project"/>
</target>
<target name="runtests" depends="Buildproject" description="This is my first ant target">
<echo message="Running tests"/>
</target>
<target name="publish" depends="runtests">
<echo message="Publish artifact"/>
</target>
My .bat file
SET WORKSPACE=%~dp0 SET TOOLS_DIR=buildtools SET
BUILD_FILE=sample_build.xml #echo off echo WORKSPACE: %WORKSPACE% echo
TOOLS_DIR: %TOOLS_DIR% echo BUILD_FILE: %BUILD_FILE%
%TOOLS_DIR%/apache-ant/bin/ant -Divy.cache.ttl.default=eternal
-buildfile %BUILD_FILE%
Your xml file is not right. It looks like maybe a nant file or some other specification.
Here is your most basic .proj (msbuild definition) file.
Save this (in the same directory as your .sln file) as "MyMsbuildDef.proj"
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="AllTargetsWrapped">
<PropertyGroup>
<!-- Always declare some kind of "base directory" and then work off of that in the majority of cases -->
<WorkingCheckout>.</WorkingCheckout>
</PropertyGroup>
<Target Name="AllTargetsWrapped">
<CallTarget Targets="BuildItUp" />
</Target>
<Target Name="BuildItUp" >
<MSBuild Projects="$(WorkingCheckout)\MySolution_Or_MyProject_File.sln" Targets="Build" Properties="Configuration=$(Configuration)">
<Output TaskParameter="TargetOutputs" ItemName="TargetOutputsItemName"/>
</MSBuild>
<Message Text="BuildItUp completed" />
</Target>
</Project>
Obviously, change "MySolution_Or_MyProject_File.sln" to your .sln name.
Now create a .bat file called "MyMsbuildCallIt.bat" and put this in:
REM set __msBuildDir=%WINDIR%\Microsoft.NET\Framework\v2.0.50727
REM set __msBuildDir=%WINDIR%\Microsoft.NET\Framework\v3.5
set __msBuildDir=%WINDIR%\Microsoft.NET\Framework\v4.0.30319
call "%__msBuildDir%\msbuild.exe" /target:AllTargetsWrapped "MyMsbuildDef.proj" /p:Configuration=Debug;FavoriteFood=Popeyes /l:FileLogger,Microsoft.Build.Engine;logfile=MyMsbuildDef_Debug.log
call "%__msBuildDir%\msbuild.exe" /target:AllTargetsWrapped "MyMsbuildDef.proj" /p:Configuration=Release;FavoriteFood=Popeyes /l:FileLogger,Microsoft.Build.Engine;logfile=MyMsbuildDef_Release.log
set __msBuildDir=
And run the .bat file.
That is your most basic msbuild scenario.
I'm using MSBuild to manipulate my Project (.csproj) file to update a reference to a static file. The static file will be built by my CI Server (TeamCity) and then the reference the Project uses will need to be updated before the Project itself is built.
Here is an example of the Xml from my csproj file (full version):
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="12.0">
<ItemGroup>
<Content Include="packages\pMixins.0.1.7.nupkg">
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
I have written an MSBuild Task:
<Target Name="ReplaceNugetPackageDependency" BeforeTargets="PrepareForBuild" >
<XmlPoke
XmlInputPath="$(MSBuildProjectFile)"
Query="//n:Project/n:ItemGroup/
n:Content[starts-with(#Include, 'packages')]/#Include"
Value="TEST-TEST"
Namespaces="<Namespace Prefix='n'
Uri='http://schemas.microsoft.com/developer/msbuild/2003'
Name='DoNotKnowWhatThisIsFor-ButItIsRequired' />" >
</XmlPoke>
</Target>
But when I run it I get the message 0 replacements.
So I added an XmlPeek task to test the query:
<XmlPeek
XmlInputPath="$(MSBuildProjectFile)"
Query="/n:Project/n:ItemGroup/
n:Content[starts-with(#Include, 'packages')]/#Include"
Namespaces="<Namespace Prefix='n'
Uri='http://schemas.microsoft.com/developer/msbuild/2003'
Name='DoNotKnowWhatThisIsFor-ButItIsRequired' />">
<Output TaskParameter="Result" ItemName="Peeked" />
</XmlPeek>
<Message Text="Text: #(Peeked)"/>
When I run MSBuild XmlPeek is able to read the Xml:
Text: packages\pMixins.0.1.7.nupkg
The queries are exactly the same! Why can't XmlPoke manipulate the Xml if XmlPeek can read it?
UPDATE
After hours of playing with this, I finally found an XPath query that will get XmlPoke to do what I want:
Query="//n:Project/n:ItemGroup/
n:Content[starts-with(#Include, 'packages')]/n:IncludeInVSIX/../#Include"
Why is it necessary to add /n:IncludeInVSIX/..? Is this a bug??
Just wanted to confirm for anyone else who encounters this, that this is how, in fact, you get around the issue of not being able to use the same exact XPath query in XmlPeek task and XmlPoke task.
Original query to replace "file" attribute value of AppSettings element in regular web.config:
<appSettings file="devsettings.config">
<add key="BuildVersion" value="" />
</appSettings>
To get at the "file" attribute in XmlPeek task I used following XPath query:
//appSettings[#file='devsettings.config']/#file
However this same query wouldn't work in XmlPoke task. Instead the following worked like what #philip-pittle discovered in his update to the question
//appSettings[#file='devsettings.config']/add/../#file
<XmlPeek XmlInputPath="$(_BuildPath)web.config"
Query="//appSettings[#file='devsettings.config']/#file">
<Output TaskParameter="Result" ItemName="ExistingPeeked" />
</XmlPeek>
<XmlPoke XmlInputPath="$(_BuildPath)web.config"
Query="//appSettings[#file='devsettings.config']/add/../#file"
Value="$(_EnvironmentConfig)" />
This was using following versions of MSBuild.
Microsoft (R) Build Engine version 12.0.31101.0
[Microsoft .NET Framework, version 4.0.30319.18444]
May be it's a bug? But definitely odd behavior.
I am trying to have the AssemblyInfo.cs file updated to reflect the next Publish version of the project BEFORE any other build steps occur.
in my project file i added before the end:
<Import Project="$(ProjectDir)Properties\PublishVersion.proj" />
PublishVersion.proj looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<Target Name="BeforeBuild">
<FormatVersion Version="$(ApplicationVersion)" Revision="$(ApplicationRevision)">
<Output TaskParameter="OutputVersion" PropertyName="SetVersion" />
</FormatVersion>
<FileUpdate Files="$(ProjectDir)Properties\AssemblyInfo.cs"
Regex="\d+\.\d+\.\d+\.\d+"
ReplacementText="$(SetVersion)" />
</Target>
</Project>
Now it does execute somewhere before building completes but definitely not before it starts, because when i look at the generated exe it has a file version that was in AssemblyInfo.cs BEFORE build started but the .application file and manifest file have various references to the new version.
resulting manifest file(1.0.0.0 before build start, 1.0.0.4 after build):
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly ...>
<asmv1:assemblyIdentity name="BHTechSupport.exe" version="1.0.0.4" ... />
...
<entryPoint>
<assemblyIdentity name="BHTechSupport" version="1.0.0.0" ... />
...
</entryPoint>
...
<dependency>
<dependentAssembly ... codebase="BHTechSupport.exe" ...>
<assemblyIdentity name="BHTechSupport" version="1.0.0.0" ... />
...
</dependentAssembly>
</dependency>
...
</asmv1:assembly>
so how do i ensure that my target gets executed before EVERYTHING else?
making changes to PublishVersion.proj sometimes seem to not take effect and i need to clean the solution and restart visual studio before effecting.
Maybe you have to use BeforeBuild target.
so how do i ensure that my target gets executed before EVERYTHING
else?
To check your target execution, you can change the MSBuild project build output verbosity to Diagnostic. Find it under the Tools > Options > Projects and Solutions > Build and Run. Then build the project and find your target in the output window.
making changes to PublishVersion.proj sometimes seem to not take
effect and i need to clean the solution and restart visual studio
before effecting.
AFAIK, the Visual Studio loads target files once, so you have to reload your project to see the result.
Update I don't know your versioning system, whereas it's not important. Anyway, I tried to provide a simple example for you.
using System;
using System.IO;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
namespace Foo.Build.Tasks {
public sealed class GenerateVersion : Task {
[Output]
public ITaskItem[] GeneratedFiles { get; private set; }
public override bool Execute() {
string objPath = Path.Combine(Path.GetDirectoryName(this.BuildEngine3.ProjectFileOfTaskNode), "obj");
string path = Path.Combine(objPath, "VersionInfo.cs");
File.WriteAllText(path, #"using System.Reflection;
[assembly: AssemblyVersion(""2.0.0"")]");
GeneratedFiles = new[] { new TaskItem(path) };
return true;
}
}
}
The preceding task generates a file that contains AssemblyVersion metadata or everything you want.
At last, you have to change your project file as follows:
<UsingTask TaskName="Foo.Build.Tasks.GenerateVersion" AssemblyFile="Foo.Build.Tasks.dll" />
<Target Name="BeforeBuild">
<GenerateVersion>
<Output TaskParameter="GeneratedFiles" ItemName="Compile" />
</GenerateVersion>
</Target>
If you want a target to run at the beginning of every build regardless of what end-goal target is called (Clean, Restore, Build, Publish) you can use the InitialTargets attribute of the Project element.
I'm trying to build a project(A) using NANT. The project(A) relies upon another project(B) which is also built with NANT. I want to be able to invoke the build of the dependent project(B) from within the build of project(A). I've tried including the build file of project B in the build file of project A. This creates an error because the two build files contain targets that share the same name.
Is there a way to alias the included build file?
You can do it like this by creating a "parent" buildfile, that uses the "nant" action to call other buildfiles.
<target name="rebuild" depends="" >
<nant target="${target::get-current-target()}">
<buildfiles>
<include name="projectB.build" />
<include name="projectC.build" />
</buildfiles>
</nant>
</target>
I was trying to do this using an include task but have found that the nant task is actually what I required.
You can have several of such targets in your 'master' file. I often use the following construction to share a set of build files between targets to make script maintenance easier.
<fileset id="buildfiles.all">
<include name="projectB.build"/>
<include name="projectB.build"/>
</fileset>
<target name="build">
<nant target="${target::get-current-target()}">
<buildfiles refid="buildfiles.all" />
</nant>
</target>
<target name="clean">
<nant target="${target::get-current-target()}">
<buildfiles refid="buildfiles.all" />
</nant>
</target>
<target name="publish">
<nant target="${target::get-current-target()}">
<buildfiles refid="buildfiles.all" />
</nant>
</target>