I'm trying to setup a csproj with assembly info automatically generated from compilation datetime and mercurial revision.
<!-- assembly info -->
<PropertyGroup>
<!-- versioning number: Maj.Min.TimeStamp.Rev -->
<Major>2</Major>
<Minor>90</Minor>
<!-- define Build based on the compilation date: yyddd that sould be integer in [0..65535] -->
<Build>$([System.DateTime]::Now.ToString(`yy`))$([System.DateTime]::Now.DayOfYear.ToString(`000`))</Build>
<Revision>0</Revision>
<Version>$(Major).$(Minor).$(Build)</Version>
<AssemblyVersion>$(Major).$(Minor).$(Build).$(Revision)</AssemblyVersion>
<FileVersion>$(Major).$(Minor).$(Build).$(Revision)</FileVersion>
</PropertyGroup>
<Import Project="..\packages\MSBuild.Mercurial.1.2.1\build\MSBuild.Mercurial.targets" Condition="Exists('..\packages\MSBuild.Mercurial.1.2.1\build\MSBuild.Mercurial.targets')" />
<Target Name="GetHgRev" BeforeTargets="GenerateAssemblyInfo" DependsOnTargets="PrepareForBuild">
<HgVersion LocalPath="$(SolutionDir)" Timeout="5000">
<Output TaskParameter="Revision" PropertyName="Revision" />
</HgVersion>
<Message Text="Last revision from HG: $(Revision)" Importance="High" />
<Message Text="$(Major).$(Minor).$(Build).$(Revision)" Importance="High" />
</Target>
On console output, I get something consistent: 2.90.20160.239
But only $(Build) is properly used in tmp AssemblyInfo.cs, $(Revision) is basically 0.
In previous csproj format (2015), I got the job done with explicit cmd in BeforeBuild target:
<PropertyGroup>
<AssemblyCompany>AAA</AssemblyCompany>
<MyAppName>BBB</MyAppName>
<Major>2</Major>
<Minor>90</Minor>
<Build>0</Build>
<Revision>0</Revision>
</PropertyGroup>
<Target Name="BeforeBuild">
<!-- get the Mercurial revision no -->
<HgVersion LocalPath="$(SolutionDir)" Timeout="5000">
<Output TaskParameter="Revision" PropertyName="Revision" />
</HgVersion>
<Message Text="Last revision from HG: $(Revision), today date $([System.DateTime]::Now.ToString(`yyyy.MM.dd`)) -> $([System.DateTime]::Now.ToString(`yy`))$([System.DateTime]::Now.DayOfYear.ToString(`000`))" Importance="High" />
<!-- define Build based on the compilation date: yyddd that sould be integer in [0..65535] -->
<CreateProperty Value="$([System.DateTime]::Now.ToString(`yy`))$([System.DateTime]::Now.DayOfYear.ToString(`000`))">
<Output PropertyName="Build" TaskParameter="Value" />
</CreateProperty>
<!-- generate version file, i.e. AssemblyInfo.cs used later for compilation -->
<Message Text="Creating Version File: $(Major).$(Minor).$(Build).$(Revision)" Importance="High" />
<AssemblyInfo CodeLanguage="CS" OutputFile=".\Properties\AssemblyInfo.cs" AssemblyTitle="AAA" AssemblyDescription="BBB" AssemblyCompany="$(AssemblyCompany)" AssemblyProduct="CCC" AssemblyCopyright="Copyright © DDD $([System.DateTime]::Now.ToString(`yyyy`))" AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)" AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" />
</Target>
Would you see any fix to this issue?
New test:
<Import Project="..\packages\MSBuild.Mercurial.1.2.1\build\MSBuild.Mercurial.targets" Condition="Exists('..\packages\MSBuild.Mercurial.1.2.1\build\MSBuild.Mercurial.targets')" />
<Target Name="GetHgRev" BeforeTargets="GenerateAssemblyInfo" DependsOnTargets="PrepareForBuild">
<HgVersion LocalPath="$(SolutionDir)" Timeout="5000">
<Output TaskParameter="Revision" PropertyName="Revision" />
</HgVersion>
<Message Text="Proposed number: $(Major).$(Minor).$(Build).$(Revision)" Importance="High" />
<Message Text="AssemblyVersion before: $(AssemblyVersion)" Importance="High" />
<PropertyGroup>
<AssemblyVersion>$(Major).$(Minor).$(Build).$(Revision)</AssemblyVersion>
</PropertyGroup>
<Message Text="AssemblyVersion after: $(AssemblyVersion)" Importance="High" />
</Target>
Kind regards,
Charles
The static portion of a project file (everything outside a <Target>) is evaluated completely before beginning to run the actual build logic (which is made up by targets).
Properties are like a string-to-string dictionary, so Version and others will be set to the string values, with $() replacements being evaluated to in the string but not logically preserved.
This means that once the target runs, you will also need to update all the properties you now need to change again (Version, AssemblyVersion, FileVersion etc.). You can do so by adding a <PropertyGroup> inside the target after the HgVersion task invocation.
Related
I'm trying to include a Project Reference based on a value in the ItemGroup Condition
This is what I have tried. With BeforeTargets Compile it does recognize the project but it won't let me build the project.
<Target Name="ErpSystemToUse" BeforeTargets="Compile">
<ReadLinesFromFile File="$(MSBuildProjectDirectory)\..\..\Presentation\Nop.Web\App_Data\erpSystemToUse.txt">
<Output TaskParameter="Lines" ItemName="ValueTextFile" />
</ReadLinesFromFile>
<Message Text="#(ValueTextFile)" Importance="high" />
<ItemGroup Condition="'#(ValueTextFile)' == 'Twinfield'">
<ProjectReference Include="..\..\Dimerce.Twinfield\Dimerce.Plugin.Misc.Twinfield\Dimerce.Plugin.Misc.Twinfield.csproj" />
</ItemGroup>
</Target>
Is what I am trying to achieve possible?
I have a project where I had to inspect output of System.Reflection.Emit. Decided to include package reference only when appropriate constant is defined
Main difference is that target executes before CollectPackageReferences. Try to change BeforeTargets.
Also use Inputs and Outputs to reduce build time
<Target Name="IncludeIlPackReference"
Inputs="$(DefineConstants)" Outputs="#(PackageReference)"
BeforeTargets="CollectPackageReferences"
Condition="$(DefineConstants.Contains('TRACE_GENERATED_IL'))">
<ItemGroup>
<PackageReference Include="Lokad.ILPack" Version="0.1.6" />
</ItemGroup>
</Target>
During a building of my binaries I am able to set lots of VERSIONINFO properties like FILEVERSION, PRODUCTVERSION and StringFileInfo, for example:
[assembly: AssemblyCompany("XXX")]
[assembly: AssemblyProduct("My Product")]
[assembly: AssemblyCopyright("© XXX, 2017")]
[assembly: AssemblyVersion("9.5")]
[assembly: AssemblyFileVersion("9.5.1001")]
My question is how to set FILEFLAGS field?
In vcxproj I am including a rc-file with correct VERSIONINFO structure. Is similar possible for the csproj projects?
Note, I have a lots of csproj-projects and I don't want to add custom build step to every project file (as I can forget to do it in new projects).
You can't set the FILEFLAGS with CSC (it's implicitly equal to 0). You can set the following though:
AssemblyInformationalVersion /productversion
AssemblyVersion /version
AssemblyFileVersion /version
AssemblyCopyright /copyright
AssemblyCompany /company
AssemblyProduct /product
you can add a custom build action to your project file
in your csproj file add this line
<Import Project="Properties\build.targets" />
build.targets file
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(ProjectDir)..\packages\MSBuildTasks.1.5.0.235\tools\MSBuild.Community.Tasks.Targets" />
<Import Project="$(ProjectDir)..\packages\MSBuild.Extension.Pack.1.9.1\build\net40\MSBuild.Extension.Pack.targets"/>
<!-- Overriding the Microsoft.CSharp.targets target dependency chain -->
<!-- Call our custom AssemblyVersion target before build, even from VS -->
<PropertyGroup>
<BuildDependsOn>
AssemblyVersion;
$(BuildDependsOn)
</BuildDependsOn>
<CompanyName>My Company</CompanyName>
<Copyright>Copyright © My Company 2017</Copyright>
<Title>My Project </Title>
</PropertyGroup>
<ItemGroup>
<AssemblyVersionFiles Include="$(MSBuildProjectDirectory)\Properties\AssemblyInfo.cs"/>
</ItemGroup>
<Target Name="AssemblyVersion"
Inputs="#(AssemblyVersionFiles)"
Outputs="UpdatedAssemblyVersionFiles">
<Attrib Files="%(AssemblyVersionFiles.FullPath)"
Normal="true"/>
<Message Importance="High" Text="-------------------------------------------------------------------------"/>
<Message Importance="High" Text="[#(AssemblyVersionFiles)]"/>
<Message Importance="High" Text="-------------------------------------------------------------------------"/>
<MSBuild.ExtensionPack.Xml.XmlFile TaskAction="ReadAttribute" File="$(SolutionDir)AnyFolder\data.xml"
XPath="//Config/VersionNum/#value">
<Output PropertyName="ExecutableVersion" TaskParameter="Value"/>
</MSBuild.ExtensionPack.Xml.XmlFile>
<AssemblyInfo
CodeLanguage="CS"
OutputFile="%(AssemblyVersionFiles.FullPath)"
AssemblyCompany="$(CompanyName)"
AssemblyCopyright="$(Copyright)"
AssemblyVersion="$(ExecutableVersion)"
AssemblyFileVersion="$(ExecutableVersion)"
AssemblyTitle="$(Title)"
AssemblyProduct="$(Title)"
ComVisible="false"
AssemblyDescription=""
AssemblyConfiguration=""
AssemblyTrademark=""
AssemblyCulture="">
<Output TaskParameter="OutputFile"
ItemName="UpdatedAssemblyVersionFiles"/>
</AssemblyInfo>
</Target>
</Project>
it also includes how to log some info to the output window
and even read an xml file to get the version of your application
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>
I have a msbuild script used for automating ClickOnce installation. While trying to add version info to the publish directory I get an error when trying to build a path string using that version.
<Target Name="GetVersion">
<Message Text="Getting version info..."/>
<GetAssemblyIdentity AssemblyFiles="#(GetVersionAssembly)">
<Output TaskParameter="Assemblies"
ItemName="GetVersionAssemblyInfo"/>
</GetAssemblyIdentity>
<Message Text="%(GetVersionAssemblyInfo.Version)..."/>
</Target>
<Target Name="CopyFilesToVirtualRoot" DependsOnTargets="PrepareClickOnceDeployment;DeleteVirtualRootFiles;GetVersion">
<Message Text="Copying files to $(ClickOnceVirtualRootDir)..."/>
<Copy SourceFiles="#(ClickOnceInstallationFiles)"
DestinationFiles="#(ClickOnceInstallationFiles->'$(ClickOnceVirtualRootDir)\%(Filename)%(Extension)')"/>
<Copy SourceFiles="#(AppManifestContents)"
DestinationFiles="#(AppManifestContents->'$(ClickOnceVirtualRootDir)\Application Files\Version_%(GetVersionAssemblyInfo.Version)\%(RecursiveDir)%(Filename)%(Extension).deploy')"/>
</Target>
When running I get the following error
error MSB4043: The item metadata reference "%(GetVersionAssemblyInfo.Version)" is invalid
because it is qualified with an item name. Item metadata referenced in transforms do not
need to be qualified, because the item name is automatically deduced from the items being
transformed. Change "%(GetVersionAssemblyInfo.Version)" to "%(Version)".
If I change it to %(Version) all I get is a blank.
Try using target batching so MSBuild runs the CopyFilesToVirtualRoot target once per assembly version. Then you can stuff the version into a property and reference that in your item transform.
<Target Name="CopyFilesToVirtualRoot"
DependsOnTargets="PrepareClickOnceDeployment;DeleteVirtualRootFiles;GetVersion"
Outputs="%(GetVersionAssemblyInfo.Version)">
<PropertyGroup>
<AppVersion>%(GetVersionAssemblyInfo.Version)</AppVersion>
</PropertyGroup>
<Message Text="Copying files to $(ClickOnceVirtualRootDir)..."/>
<Copy
SourceFiles="#(ClickOnceInstallationFiles)"
DestinationFiles="#(ClickOnceInstallationFiles->'$(ClickOnceVirtualRootDir)\%(Filename)%(Extension)')"/>
<Copy
SourceFiles="#(AppManifestContents)"
DestinationFiles="#(AppManifestContents->'$(ClickOnceVirtualRootDir)\Application Files\Version_$(AppVersion)\%(RecursiveDir)%(Filename)%(Extension).deploy')"/>
</Target>
I guess this is weird because if any of your assemblies have different versions, you will have multiple copies of the manifest contents.
That said, you could just create a property from with the item meta data.
<Target Name="CopyFilesToVirtualRoot"
DependsOnTargets="PrepareClickOnceDeployment;DeleteVirtualRootFiles;GetVersion"
Outputs="%(GetVersionAssemblyInfo.Version)">
<CreateProperty
Value="%(GetVersionAssemblyInfo.Version)">
<Output PropertyName="AppVersion" TaskParameter="Value" />
</CreateProperty>
<Message Text="Copying files to $(ClickOnceVirtualRootDir)..."/>
<Copy
SourceFiles="#(ClickOnceInstallationFiles)"
DestinationFiles="#(ClickOnceInstallationFiles->'$(ClickOnceVirtualRootDir)\%(Filename)%(Extension)')"/>
<Copy
SourceFiles="#(AppManifestContents)"
DestinationFiles="#(AppManifestContents->'$(ClickOnceVirtualRootDir)\Application Files\Version_$(AppVersion)\%(RecursiveDir)%(Filename)%(Extension).deploy')"/>
</Target>
I think either will get you what you need.