I have a situation where i want the versioning to be dynamic at build time.
Version Pattern: <year>.<month>.<day>.<hhmm>
But i have read where the String value used in the Attribute is reparsed at compile time.
Any advise on how to get this dynamic versioning completed?
Ideal situation:
<Assembly: AssemblyVersion("4.0.0.0")>
<Assembly: AssemblyFileVersion(Year(Now) & "." & Month(Now()) & "." & Day(Now()) & "." & String.format("hhmm", now()))>
I know it wont work but should get the point acrossed.
You can use the MsbuildCommunityTasks to generate the build number and to customize the assembly file version on pre-build time.
Download the zip at MsbuildCommunityTasks
Unzip to the folder [SolutionFolder]\MsBuildExtensions\MSBuildCommunityTasks
Add the sample below on your project (csproj), just after the Microsoft.CSharp.Targets import.
<PropertyGroup>
<MSBuildCommunityTasksPath>$(MSBuildThisFileDirectory)..\MsBuildExtensions\MSBuildCommunityTasks</MSBuildCommunityTasksPath>
<My-PropertiesDir>Properties</My-PropertiesDir>
</PropertyGroup>
<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets"/>
<Target Name="BeforeBuild">
<Time Format="yyyy.MM.dd.HHmm">
<Output TaskParameter="FormattedTime" PropertyName="My-VersionNumber" />
</Time>
<Message Text="Building $(My-VersionNumber) ...">
</Message>
<ItemGroup>
<My-AssemblyInfo Include="$(My-PropertiesDir)\AssemblyVersionInfo.cs" />
<Compile Include="#(My-AssemblyInfo)" />
</ItemGroup>
<MakeDir Directories="$(My-PropertiesDir)" />
<AssemblyInfo OutputFile="#(My-AssemblyInfo)"
CodeLanguage="CS"
AssemblyFileVersion="$(My-VersionNumber)"
AssemblyInformationalVersion="$(My-VersionNumber)"
Condition="$(My-VersionNumber) != '' "
/>
</Target>
<Target Name="AfterBuild">
<Delete Files="#(My-AssemblyInfo)" />
</Target>
Wipe the AssemblyFileVersion attribute from your AssemblyInfo.cs. It will be generated at build time.
You'll see the version number being printed on the console when you build. The generated file is deleted on the AfterBuild target, to keep your source control clean.
BeforeBuild:
Building 2013.01.14.1016 ...
Created AssemblyInfo file "Properties\AssemblyVersionInfo.cs".
(...)
AfterBuild:
Deleting file "Properties\AssemblyVersionInfo.cs".
If you want do this to many projects with less msbuild code, it will be necessary to create a customized build script to wrap up your solution.
Related
I have the below MSBuild to generate .cs files from my proto files. The build works fine until I do a rebuild where it complains of Source file 'generated-proto-output/Trade.cs# specified multiple times.
How do I delete my .cs files before building/rebuilding everytime?
Error
Severity Code Description Project File Line Suppression State
Warning CS2002 Source file 'generated-proto-output\ErrorTrade.cs' specified multiple times MyComp.Trade.Model C:\dev\workspaces\trade-model-workspace\model\csharp\MyComp.Trade.Model
build snippet in csproj file
<ItemGroup>
<Protobuf Remove="%(RelativePath)generated-proto-output/**/*.cs" />
<Protobuf Include="../../proto/**/*.proto" ProtoRoot="../../proto/" OutputDir="%(RelativePath)generated-proto-output/" GrpcServices="None" />
<Protobuf Update="../../proto/**/*Service.proto" GrpcServices="Both" />
</ItemGroup>
UPDATE - Complete CSProj file (as requested by Lance)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>TradeStore.Model</PackageId>
<ProtoIncludes>.;../../proto</ProtoIncludes>
<OutputType>Library</OutputType>
<TargetFramework>netstandard2.0</TargetFramework>
<Protobuf_NoWarnMissingExpected>true</Protobuf_NoWarnMissingExpected>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.6.1" />
<PackageReference Include="Grpc" Version="1.19.0" />
<PackageReference Include="Grpc.Tools" Version="1.19.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<FilesToDelete Include="%(RelativePath)generated-proto-output/*.cs" />
</ItemGroup>
<Target Name="DeleteSpecificFiles" BeforeTargets="Build">
<Message Text="Specific Files: #(FilesToDelete)"/>
<Message Text ="Beginning to delete specific files before build or rebuild..."/>
<Delete Files="#(FilesToDelete)"/>
</Target>
<ItemGroup>
<Protobuf Include="../../proto/**/*.proto" ProtoRoot="../../proto/" OutputDir="%(RelativePath)generated-proto-output/" GrpcServices="None" />
<Protobuf Update="../../proto/**/*Service.proto" GrpcServices="Both" />
</ItemGroup>
</Project>
Try adding CompileOutputs="false" to the directive. This will suppress the warning and won't require you to delete files before building csharp protobuf build integration
How do I delete my .cs files before building/rebuilding everytime?
Try the following script with BeforeTargets below:
<Project...>
...
<ItemGroup>
<FilesToDelete Include="MyPath/*.cs" />
</ItemGroup>
<Target Name="DeleteSpecificFiles" BeforeTargets="build">
<Message Text="Specific Files: #(FilesToDelete)"/>
<Message Text ="Beginning to delete specific files before build or rebuild..."/>
<Delete Files="#(FilesToDelete)"/>
</Target>
</Project>
In addition:
Not seeing the whole content of your .csproj, so I can't figure out why the build snippet you use can't work. But a message task may help output some message whether the engine finds the files by your given path.
In visual studio, if you go Tools=>Options=>Project and Solutions=>Build and Run to change the build out verbosity to Detailed, you will see detailed output message after every build and rebuild.Ctrl+Fand type the Target name you will find the details about delete process:
Hope it makes some help for your trouble-shooting.
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'm working on an ASP.NET 4 WebAPI project and am including a wpp.targets file. I need to use MSBuild.ExtensionPack.Xml.XmlFile to replace a value in one of my configuration XML files.
The problem is that I don't want to install MSBuild.ExtensionPack on all the machines so I packaged it up with the project. On my local build, the path to the MSBuild.ExtensionPack.dll resolves correctly. On my build machine though, I keep getting this error: The "MSBuild.ExtensionPack.Xml.XmlFile" task could not be loaded from the assembly C:\Program Files (x86)\MSBuild\ExtensionPack\4.0\MSBuild.ExtensionPack.dll.
It seems to be resolving to the default install location of the package.
Here's what's in my wpp.targets file:
<?xml version="1.0" encoding="utf-8"?>
<!-- Sets the assembly which will run the transformation on Web.config (Should be installed on Dev machines) -->
<UsingTask TaskName="TransformXml"
AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v11.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
<!-- Get the path to the MSBuild.Extension.Pack -->
<PropertyGroup>
<TPath>$(MSBuildProjectDirectory)\..\packages\MSBuild.Extension.Pack.1.3.0\tools\net40\MSBuild.ExtensionPack.tasks</TPath>
<TPath Condition="Exists('$(MSBuildProjectDirectory)\..\packages\MSBuild.Extension.Pack.1.3.0\tools\net40\MSBuild.ExtensionPack.tasks')">$(MSBuildProjectDirectory)\..\packages\MSBuild.Extension.Pack.1.3.0\tools\net40\MSBuild.ExtensionPack.tasks</TPath>
</PropertyGroup>
<!--Import the MSBuild.Extension.Pack package -->
<Import Project="$(TPath)"/>
<!-- Make sure web.config and transformation files exist -->
<Target Name="ConfigurationTransform" BeforeTargets="BeforeBuild" Condition="Exists('Web.config')" />
<Target Name="ConfigurationTransform" BeforeTargets="BeforeBuild" Condition="Exists('Web.$(Configuration).config')" />
<!-- Make sure web.config will be there even for package/publish -->
<Target Name="CopyWebConfig" BeforeTargets="Build;Rebuild">
<Copy SourceFiles="Web.Base.config"
DestinationFiles="Web.config"
OverwriteReadOnlyFiles="true"
SkipUnchangedFiles="false" />
</Target>
<!-- Run Web.Config transformation on a build as well (not just a publish) -->
<Target Name="CustomTransformWebConfigOnBuild" AfterTargets="CopyWebConfig" >
<Message Text="Transforming: Web.$(Configuration).config" Importance="high" />
<TransformXml Source="Web.Base.config"
Transform="Web.$(Configuration).config"
Destination="Web.config" />
</Target>
<!-- Update Web.Config's config attribute -->
<Target Name="UpdateConfigAttribute" AfterTargets="CustomTransformWebConfigOnBuild" Condition="$(Configuration) != 'Release'">
<Message Text="Transforming: Web.config" Importance="high" />
<MSBuild.ExtensionPack.Xml.XmlFile TaskAction="UpdateAttribute"
File="Web.config"
XPath="/configuration/appSettings/add[#key='config_url']"
Key="value"
Value="www.randomurl.com"/>
</Target>
When you have more than 1 option for the source location for a helper-dll...I like to do it like with the style below.
Something like this (Obviously, you'll have to put real locations in for "PossibleLocationOne" and "PossibleLocationTwo").
<PropertyGroup>
<MyFoundMSBuildExtensionPackLocation Condition="Exists('..\PossibleLocationOne\MSBuild.ExtensionPack.dll')">..\PossibleLocationOne\MSBuild.ExtensionPack.dll</MyFoundMSBuildExtensionPackLocation>
<MyFoundMSBuildExtensionPackLocation Condition="Exists('..\PossibleLocationTwo\MSBuild.ExtensionPack.dll')">..\PossibleLocationTwo\MSBuild.ExtensionPack.dll</MyFoundMSBuildExtensionPackLocation>
<!--Now check to see if either of the two above resolved , if not , add something to the path so you can at least -->
<MyFoundMSBuildExtensionPackLocation Condition="'$(MyFoundMSBuildExtensionPackLocation)'==''">DID_NOT_FIND_A_PATH_FOR_MSBUILDEXENSIONPACK\MSBuild.ExtensionPack.dll</MyFoundMSBuildExtensionPackLocation>
</PropertyGroup>
<UsingTask AssemblyFile="$(MyFoundMSBuildExtensionPackLocation)" TaskName="TransformXml"/>
Add all options for possible locations..and one extra for "I didn't find a match"....
Then use the "UsingTask".
"UsingTask" is ~~after~~ the MyFoundMSBuildExtensionPackLocation(PropertyGroup).......so the $(MyFoundMSBuildExtensionPackLocation) resolves before the UsingTask is called.
I would like to read an option out of my appSettings.config file to create a conditional section in my CSPROJ. I know how to do the conditional references with help from visual studio 2010 conditional references but I am not sure how to access the appSettings file from within.
Is this possible and if so, could someone provide some guidance please.
EDIT Following #palo's answer I now have:
<Target Name="BeforeBuild">
<XmlPeek XmlInputPath="SiteSettings.config" Query="appSettings/add[#key='cProjectNumber']/#value">
<Output TaskParameter="Result" ItemName="value" />
</XmlPeek>
<Message Text="TESTING: #(value)" Importance="high" />
</Target>
This works well and prints out the project number i.e Testing: 012. Now, how do I go about using it in some compile includes? I have tried:
<ItemGroup>
<Compile Include="Accounts\#(value)\Controls\MyControl.ascx.cs">
<SubType>ASPXCodeBehind</SubType>
</Compile>
</ItemGroup>
But I get an error saying:
The expression "Accounts\#(value)\Controls\MyControl.ascx.cs" cannot
be used in this context. Item lists cannot be concatenated with other
strings where an item list is expected. Use a semicolon to separate
multiple item lists.
If I understand correctly you need to read elements’ value for appconfig (xml file) and then use its value in your csproj file.
Try to use XmlPeek - http://msdn.microsoft.com/en-us/library/ff598684.aspx ; How to use XmlPeek task?
Keep in mind you the order of evaluation.
You cannot override global properties/items. But with msbuild 4.0 you can override items within BeforeTargets or by AfterTargets
Following #palo's answer I came up with the following (I will mark this as the answer as it details information on how to achieve what I wanted):
<Target Name="BeforeBuild">
<XmlPeek XmlInputPath="SiteSettings.config" Query="appSettings/add[#key='cProjectNumber']/#value">
<Output TaskParameter="Result" ItemName="value" />
</XmlPeek>
<Message Text="TESTING: #(value)" Importance="high" />
<PropertyGroup>
<ProjectNumber>#(value)</ProjectNumber>
</PropertyGroup>
<ItemGroup>
<Compile Include="Accounts\$(ProjectNumber)\Controls\MyControl.ascx.cs">
<SubType>ASPXCodeBehind</SubType>
</Compile>
</ItemGroup>
</Target>
With an XML structure like:
<appSettings>
<add key="cProjectNumber" value="123" />
</appSettings>
In my C# ClickOnce application, there is an auto-incremented publish version in the Project -> Properties -> Publish tab. I'd like to display that version in my menu Help -> About box, but the code I'm using apparently accesses the assembly Version, which is different.
The Assembly Version can be changed manually in the Project -> Properties -> Application -> Assembly Information dialog. So for now, every time before I publish I've been copying the publish version to the assembly version, so my dialog shows the current version of
the application. There must be a better way to do this.
All I really want to do is have an accurate, auto-updated, code-accessible version number.
Here's the code I'm using to access the assembly version number:
public string AssemblyVersion
{
get
{
return Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
}
An alternative would be to find code that accesses the publish version.
sylvanaar's last line looks like the way to go, in my experience; but with the caveat that it is only available to deployed versions of the application. For debugging purposes, you might want something like:
static internal string GetVersion()
{
if (ApplicationDeployment.IsNetworkDeployed)
{
return ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString();
}
return "Debug";
}
I modified my .csproj file to update the assembly version. I created a configuration called "Public Release" for this, but it's not required to do that.
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<PropertyGroup Condition="'$(BuildingInsideVisualStudio)' == 'true'">
<MSBuildCommunityTasksPath>$(SolutionDir)Tools\MSBuildCommunityTasks</MSBuildCommunityTasksPath>
</PropertyGroup>
<!-- Required Import to use MSBuild Community Tasks -->
<Import Project="$(SolutionDir)Tools\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" Condition="'$(BuildingInsideVisualStudio)' == 'true'" />
<Target Name="BeforeCompile" Condition="'$(BuildingInsideVisualStudio)|$(Configuration)' == 'true|PublicRelease'">
<FormatVersion Version="$(ApplicationVersion)" Revision="$(ApplicationRevision)">
<Output TaskParameter="OutputVersion" PropertyName="AssemblyVersionToUse" />
</FormatVersion>
<AssemblyInfo CodeLanguage="CS" OutputFile="$(ProjectDir)Properties\VersionInfo.cs" AssemblyVersion="$(AssemblyVersionToUse)" AssemblyFileVersion="$(AssemblyVersionToUse)" />
</Target>
The published version may be:
ApplicationDeployment.CurrentDeployment.CurrentVersion
I would like to expand on Sylvanaar's answer, as some of implementation details weren't obvious to me. So:
Manually install community build tasks found at: https://github.com/loresoft/msbuildtasks/releases Note: Don't install by nuget if you clean your packages, as the build will fail before getting a chance to restore the packages, since msbuildtasks are referenced as a task in the build file. I put these in folder next to solution file called .build
Add a completely empty file to your projects properties folder called VersionInfo.cs
3 Remove these lines if they exist in AssemblyInfo.cs
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.*")]
4 Modify your csproj file
<!-- Include the build rules for a C# project. -->
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!--INSERT STARTS HERE-->
<!--note the use of .build directory-->
<PropertyGroup Condition="'$(BuildingInsideVisualStudio)' == 'true'">
<MSBuildCommunityTasksPath>$(SolutionDir)\.build\MSBuildCommunityTasks</MSBuildCommunityTasksPath>
</PropertyGroup>
<!-- Required Import to use MSBuild Community Tasks -->
<Import Project="$(SolutionDir)\.build\MSBuild.Community.Tasks.targets" Condition="'$(BuildingInsideVisualStudio)' == 'true'" />
<Target Name="BeforeCompile" Condition="'$(BuildingInsideVisualStudio)|$(Configuration)' == 'true|Release'">
<FormatVersion Version="$(ApplicationVersion)" Revision="$(ApplicationRevision)">
<Output TaskParameter="OutputVersion" PropertyName="AssemblyVersionToUse" />
</FormatVersion>
<AssemblyInfo CodeLanguage="CS" OutputFile="$(ProjectDir)Properties\VersionInfo.cs" AssemblyVersion="$(AssemblyVersionToUse)" AssemblyFileVersion="$(AssemblyVersionToUse)" />
</Target>
5 Use a method like the following to access the version text:
public string Version()
{
Version version = null;
if (ApplicationDeployment.IsNetworkDeployed)
{
version = ApplicationDeployment.CurrentDeployment.CurrentVersion;
}
else
{
version = typeof(ThisAddIn).Assembly.GetName().Version;
}
return version.ToString();
}
I modified sylvanaar's solution for use with VB:
- Microsoft.VisualBasic.targets instead of Microsoft.CSharp.targets
- CodeLanguage="VB" instead of CodeLanguage="CS"
- AssemblyInfo.vb instead of VersionInfo.cs
, differences in paths:
- $(SolutionDir).build instead of $(SolutionDir)Tools\MSBuildCommunityTasks
- $(ProjectDir)AssemblyInfo.vb instead of $(ProjectDir)Properties\VersionInfo.cs
, and to remove conditions:
- Condition="'$(BuildingInsideVisualStudio)' == 'true'"
- Condition="'$(BuildingInsideVisualStudio)|$(Configuration)' == 'true|PublicRelease'"
I also synchronized Company and Product with ClickOnce PublisherName and ProductName respectively and generated a Copyright based on the current date:
- AssemblyCompany="$(PublisherName)"
- AssemblyProduct="$(ProductName)"
- AssemblyCopyright="© $([System.DateTime]::Now.ToString(`yyyy`)) $(PublisherName)"
I ended up adding this to my vbproj file. It requires the MSBuildTasks NuGet package to be installed first:
<Import Project="$(MSBuildBinPath)\Microsoft.VisualBasic.targets" />
<PropertyGroup Condition="'$(BuildingInsideVisualStudio)' == 'true'">
<MSBuildCommunityTasksPath>$(SolutionDir).build</MSBuildCommunityTasksPath>
</PropertyGroup>
<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets" Condition="'$(BuildingInsideVisualStudio)' == 'true'" />
<Target Name="BeforeCompile">
<FormatVersion Version="$(ApplicationVersion)" Revision="$(ApplicationRevision)">
<Output TaskParameter="OutputVersion" PropertyName="AssemblyVersionToUse" />
</FormatVersion>
<AssemblyInfo CodeLanguage="VB" OutputFile="$(ProjectDir)AssemblyInfo.vb" AssemblyVersion="$(AssemblyVersionToUse)" AssemblyFileVersion="$(AssemblyVersionToUse)" AssemblyCompany="$(PublisherName)" AssemblyProduct="$(ProductName)" AssemblyCopyright="© $([System.DateTime]::Now.ToString(`yyyy`)) $(PublisherName)"/>
</Target>
I'm not sure how much the location within the project file matters, but I added this to the end of my project file, just before:
</Project>
I did it the other way around, used a wildcard for my assembly version - 1.0.* - so Visual Studio/MSBuild generated a version number automatically:
// AssemblyInfo.cs
[assembly: AssemblyVersion("1.0.*")]
And then I added the following AfterCompile target to the ClickOnce project to assign synchronize PublishVersion with the assembly version:
<Target Name="AfterCompile">
<GetAssemblyIdentity AssemblyFiles="$(IntermediateOutputPath)$(TargetFileName)">
<Output TaskParameter="Assemblies" ItemName="TargetAssemblyIdentity" />
</GetAssemblyIdentity>
<PropertyGroup>
<PublishVersion>%(TargetAssemblyIdentity.Version)</PublishVersion>
</PropertyGroup>
</Target>