In my code I need a Xamarin assembly build date. On Windows I can use linker time stamp. However on iOS this does not work. I guess it would not work on OS X too as Portable Executable header is specific to Windows.
There is also an option to embed a resource with a date, however I would like to avoid using resources in this particular project.
Are there any way to find a Xamarin assembly build date that works on iOS, Android and OS X?
One approach would be to use an MSBuild task to substitute the build time into a string that is returned by a property on the app. We are using this approach successfully in an app that has Xamarin.Forms, Xamarin.Android, and Xamarin.iOS projects.
If using msbuild, this can be an MSBuild inline task, while on Mac using xbuild, it will need to be an MSBuild custom task compiled for Mono.
EDIT:
Simplified by moving all of the logic into the build task, and using Regex
instead of simple string replace so that the file can be modified by each build without a "reset".
The MSBuild inline task definition (saved in a SetBuildDate.targets file local to the Xamarin.Forms project for this example):
<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="12.0">
<UsingTask TaskName="SetBuildDate" TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
<ParameterGroup>
<FilePath ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs"><![CDATA[
DateTime now = DateTime.UtcNow;
string buildDate = now.ToString("F");
string replacement = string.Format("BuildDate => \"{0}\"", buildDate);
string pattern = @"BuildDate => ""([^""]*)""";
string content = File.ReadAllText(FilePath);
System.Text.RegularExpressions.Regex rgx = new System.Text.RegularExpressions.Regex(pattern);
content = rgx.Replace(content, replacement);
File.WriteAllText(FilePath, content);
File.SetLastWriteTimeUtc(FilePath, now);
]]></Code>
</Task>
</UsingTask>
</Project>
EDIT:
Added an MSBuild Exec step to remove readonly attribute. Gotta love TFS.
Invoking the MSBuild task (inline or compiled, inline approach is commented for xbuild) in the Xamarin.Forms csproj file in target BeforeBuild:
<!-- 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. -->
<!--<Import Project="SetBuildDate.targets" />-->
<UsingTask AssemblyFile="$(MSBuildProjectDirectory)\BI.Framework.BuildExtensions.dll" TaskName="Some.Framework.BuildExtensions.BuildDateTask" />
<Target Name="BeforeBuild">
<Exec Command="attrib $(MSBuildProjectDirectory)\BuildMetadata.cs -r" />
<!--<SetBuildDate FilePath="$(MSBuildProjectDirectory)\BuildMetadata.cs" />-->
<BuildDateTask FilePath="$(MSBuildProjectDirectory)\BuildMetadata.cs" />
</Target>
The FilePath
property is set to a BuildMetadata.cs
file in the Xamarin.Forms project that contains a simple class with a string property BuildDate
, into which the build time will be substituted:
public class BuildMetadata
{
public static string BuildDate => "This can be any arbitrary string";
}
Add this file BuildMetadata.cs
to project. It will be modified by every build, but in a manner that allows repeated builds (repeated replacements), so you may include or omit it in source control as desired.
ADDED:
Here is a custom MSBuild task to replace the MSBuild inline task for when building with xbuild
on Mac:
using System;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Some.Framework.BuildExtensions
{
public class BuildDateTask : Task
{
#region Methods
/// <summary>
/// Called automatically when the task is run.
/// </summary>
/// <returns><c>true</c>for task success, <c>false</c> otherwise.</returns>
public override bool Execute()
{
const string pattern = @"BuildDate => ""([^""]*)""";
var now = DateTime.UtcNow;
var buildDate = now.ToString("F");
var replacement = $"BuildDate => \"{buildDate}\"";
var content = File.ReadAllText(FilePath);
var rgx = new Regex(pattern);
content = rgx.Replace(content, replacement);
File.WriteAllText(FilePath, content);
File.SetLastWriteTimeUtc(FilePath, now);
return true;
}
#endregion Methods
#region Properties
[Required]
public string FilePath { get; set; }
#endregion Properties
}
}
Build this custom task for Release via xbuild, then copy the output custom task dll to the project directory of the project for which you want to set the build date.