Being in the same boat as you, as in not finding a proper solution, I decided to create my own. Maybe better late than never! :)
In my case I used the very useful Automatic Versions Settings tool (available on NuGet) to automatically update my assembly info, but wanted that to also update the Info.plist information as that's what HockeyApp uses to track and notify of new releases.
In the end, I kludged together a minimal C# program, which reads AssemblyInfo.cs, grabs the version info from there and edits the Info.plist XML file and writes it back.
It'd be a 20 line program if I hadn't put in a lot of paranoid checks, so as not to risk mangling Info.plist irretrievably (and even then it creates a backup of that file).
The "magic" comes down to two methods, the first one which I found here on SO:
Read AssemblyInfo.cs:
private static string GetVersionFromAssemblyInfo(string infile)
{
var version = String.Empty;
var readText = File.ReadAllLines(infile);
var versionInfoLines = readText.Where(t => t.Contains("[assembly: AssemblyVersion"));
foreach (var item in versionInfoLines)
{
version = item.Substring(item.IndexOf('(') + 2, item.LastIndexOf(')') - item.IndexOf('(') - 3);
}
return version;
}
Edit Info.plist, where the first 3 elements of the assembly info tuple becomes the CFBundleShortVersionString
and the last element becomes CFBundleVersion
which HockeyApp uses for build number.
The wonkiness in the LINQ is due to the slight weirdness of Apple's way of presenting the key/value pairs in that file:
private static bool SetVersionInInfoPlist(string infoplistFile, string version, string number)
{
var xelements = XDocument.Load(infoplistFile);
var dict = from el in xelements.Root?.Elements() select el;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (dict == null) return false;
var cfshortversion =
from el in dict.Descendants("key")
where el.Value == "CFBundleShortVersionString"
select el.ElementsAfterSelf().FirstOrDefault();
;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (cfshortversion == null) return false;
cfshortversion.FirstOrDefault()?.SetValue(version);
var cfversion =
from el in dict.Descendants("key")
where el.Value == "CFBundleVersion"
select el.ElementsAfterSelf().FirstOrDefault();
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (cfversion == null) return false;
cfversion.FirstOrDefault()?.SetValue(number);
// Make backup
try
{
File.Copy(infoplistFile, $"{infoplistFile}-backup", true);
}
catch (Exception)
{
Console.WriteLine($"Failed to create backup of {infoplistFile}. Will not edit.");
return false;
}
try
{
using (StringWriter sw = new StringWriter())
{
using (XmlWriter xWrite = XmlWriter.Create(sw))
{
xelements.Save(xWrite);
}
}
xelements.Save(infoplistFile);
}
catch (Exception)
{
Console.WriteLine($"Failed to save the edited {infoplistFile}.");
return false;
}
Console.WriteLine($"Successfully edited and saved new {infoplistFile}.");
return true;
}
EDIT: I should have also added that I use Bamboo for CI and build automation. This program therefore becomes a capability for the remote build agent and then I can add it as a Task in the Bamboo build Plan.