[MonoTouch] Please advise wrt two apps with same source files
Guido Van Hoecke
guivho at gmail.com
Tue Oct 25 12:32:07 EDT 2011
Hi,
Thanks to suggestions and remarks from you and this list I came up with a
working setup. Just for the record, I want to describe how one can easily setup
a solution with two apps with distinct names but sharing all source and
resource files. These apps produce different code using
[Conditional("OPTIONXYZ")] compilation.
Create a 'MyApp' solution and app.
That gives you a directory /Users/you/.../MyApp/MyApp or /Users/you/.../MyApp if
you do not create a solution directory, which is fine if you like it that way.
You now want a lite version of your app, called MyApp_Lite. All sources are the
same as for MyApp, only certain parts will compile differently depending upon
the presence or absence of e.g. the LITE symbol at compile time.
Rather than creating a new app, just copy your MyApp.csproj file to
MyApp_Lite.csproj in the same directory and add it as existing project to your
solution. You now have two projects which are identical apart from their name.
You are free to change the compiler symbols for the lite app. These symbols are
stored in the csproj files, so each app can have its own values.
You must edit the output path for both projects (again the output path is
stored in the csproj file, so they may and should be different for each of the
apps). The normal output path, as you all know, is 'bin/Platform/Configuration'
(e.g. bin/iPhone/Release)
Obviously this has to be changed if both apps reside in the same directory.
Just change 'bin/Platform/Configuration' into
'bin/AppName/Platform/Configuration' (e.g. 'bin/MyApp_Lite/iPhone/Release').
Don't forget to set this for each combination of both apps.
You now can build all platform and configuration combinations for both your
apps.
There is one more file that is different for each project, namely the
Info.plist file. Unfortunately, its name is not AppName dependent. It contains
the Bundle Identifier com.yourcompany.MyApp or com.yourcompany.MyApp_Lite and
the bundle display name MyApp or MyApp_Lite. You don't want to change these
each time you build the other project.
So when it's setup correctly for MyApp you should copy Info.plist as
Info.plist.MyApp and then you set it up for MyApp_Lite and copy the Info.plist
as Info.plist.MyApp_Lite. So now you have Info.plist, Info.plist.MyApp and
Info.plist.MyApp_Lite and all you need is some automated system to copy
Info.plist.MyApp or Info.plist.MyApp_Lite before building MyApp or MyAPP_Lite.
Custom commands to the rescue: further down, you'll find a MtPlistCopier.pl
perl script which you should enter in your project options (again, that's the
csproj file) as a 'Before Build' and an 'After Build' command, with the Working
Directory set to the target directory $(TargetDir). This little script will
rename your plist out of the way and copy your project plist as Info.plist when
called before the build; when called after the build, it will restore the saved
plist. This prevents version control from detecting irrelevant changes to
Info.plist each time you build the other app.
You now have a fully automated setup where you can build either app to whatever
platform and configuration combination.
As a freebie, I also want to show you how you can register the version control
revision used to build your app within your app. I use svn here, but this can
easily be adapted to other version control systems. The MtSvnRevision.pl perl
script further down is to be setup as an 'After Build' custom command, again to
be ran from the target dir. I just creates inside your MyApp.app or
MyApp_Lite.app directory a little file 'svn.info' with the output of the 'svn
info' command run in your project directory. This info is then freely available
from within your app, e.g. with the VersionAndRevision method which you can
also find here after. Don't forget to set MtSvnRevision.pl as After Build
custom command for all platform / configuration combinations of both your apps.
This setup is probably far from perfect and I'm well interested in your
suggestions and improvements. At the very least, I hope that it will help some
of you in setting up this or some similar system to build apps with shared
sources and resources.
(One last note: it is less work if you first define the output paths, Before
and After Build commands for your MyApp. If you then copy the MyApp.csproj as
MyApp_Lite.csproj, it will already have all these things setup)
Guido.
------ MtPlistCopier.pl ------
#!/usr/bin/perl
#{= MtPlistCopier.pl 2011.10.25 14:54 guivho at gmail.org =}
# This script copies either renames Info.plist to Info.plist._Saved
# and copies ProjectName.Info.plist to Info.plist
# or it renames Info.plist._saved back to Info.plist
#
# This is only needed if your project directory houses more than one
# project, i.e. more than one .csproj file.
#
# The script is to be called with $(TargetDir) as current directory.
# Currently there is no way in MonoDevelop to pass a parameter to an
# external command, but the 'active' project name can be retrieved
# from the target directory path, which is to be the current directory
# when calling this script.
#
# This script assumes that the current (target) directory is as follows:
# /ProjectPath/bin/AppName/Platform/Configuration
# where
# - /ProjectPath/ is the fully qualified path with your .csproj
# and Info.plist files (e.g. /users/me/solution/project/)
# - bin is the default bin directory as is the case for all apps
# - AppName is the non-standard additional path component required to
# ensure that each of the .csproj files in the project directory
# generates the app in a dedicated directory without interfering
# with the output of the other csproj files in the project directory
# - Platform is iPhone or iPhoneSimulator
# - Configuration is Debug, Release or Distribution
# This script needs the ProjectPath and the AppName
#
# The project directory is expected to contain a default Info.plist
# belonging to whatever project and an Info.plist.AppName file
# It will rename the existing Info.plist to Info.plist._saved
# and copy Info.plist.AppName to Info.plist
# However, if it detects an existing Info.plist._saved, it will
# simply rename it as Info.plist
#
# The idea is to call this script as pre-build as well as
# as after-build externbal command. The saving and restoring part
# avoids version control changes that are really irrelevant as far
# as version control is concerned.
#
use Cwd;
use File::Copy;
my $dir = cwd();
# extract the project name
my ($ProjectPath, $AppName) = $dir =~ m{(.*)/bin/([^/]+)};
warn "Couldn't define the ProjectPath directory from $dir" unless $ProjectPath;
warn "Couldn't define the AppName name from $dir" unless $AppName;
# define the Info.plist names
my $dest = "$ProjectPath/Info.plist";
my $from = "$dest.$AppName";
my $save = "$dest._saved";
warn "There's no $from to copy as $dest" unless -e "$from";
warn "There's no $dest to be copied to" unless -e "$dest";
if (-e $save) {
# we're called as after-build command, just rename saved plist
rename ($save, $dest) or warn "Failed to rename $save yo $dest : $!";
} elsif (-e $dest && -e $from) {
# called as a pre-build command: save the Info.Plist
rename ($dest, $save) or warn "Failed to rename $desy to $save : $!";
copy ($from, $dest) or warn "Failed to copy $from to $dest : $!"
}
#{= MtPlistCopier.pl 2011.10.25 14:54 guivho at gmail.org =}
------ end of MtPlistCopier.pl ------
------ MtSvnRevision.pl ------
#!/usr/bin/perl
#{= MtSvnRevision.pl 2011.10.25 15:09 guivho at gmail.org =}
#
# This script writes an svn.info file in the target monotouch app directory
# Get the current working directory, supposed to be the target directory
# for a directory with one or several projects
# e.g. either /Users/guivho/Mono/Solution/AppName/bin/iPhone/Release
# or /Users/guivho/Mono/Solution/Project/bin/AppName/iPhone/Release
# The second case is where there are several project files in the same directory
use Cwd;
my $dir = cwd();
# extract the project name
my ($root, $p1, $p2) = $dir =~ m{(.*/([^/]+))/bin/([^/]+)};
my $proj = $p2 =~ /iPhone|iPhoneSimulator/ ? $p1 : $p2;
print "===> Project name: $proj\n";
# The app should have that part followed by .app
$app .= "$proj.app";
print "=======> App name: $app\n";
# only proceed if the app does exist
die "There's no '$app" unless -e "$app";
# Get the svn status info for the project directory
my $svn = `svn info $root`;
print "=======> SVN info:\n$svn\n";
# Write this info to svn.info in the app directory, so it could be
# /Users/guivho/Mono/bin/iPhone/Release/AppName.app/svn.info or
# /Users/guivho/Mono/bin/AppName/iPhone/Release/AppName.app/svn.info
my $fout = "$app/svn.info";
open (FH, ">$fout") or die "Failed to create $fout ($!)";
print FH $svn;
close FH;
#{= MtSvnRevision.pl 2011.10.25 15:09 guivho at gmail.org =}
------ end of MtSvnRevision.pl ------
------ VersionAndRevision ------
/// <summary>
/// Get the version and revision of the current app.
/// <para>This is composed of the value of the "CFBundleVersion" entry
/// in the app's Info.plist, followed by the current svn
revision.</para>
/// <para>The svn revision is extracted from the svn.info file
that is expected
/// in the root of the .app directory.</para>
/// <para>This file is to be created by an external command
executed after the
/// build. </para>
/// </summary>
/// <returns>The current version and revision</returns>
public static string VersionAndRevision()
{
var version =
NSBundle.MainBundle.ObjectForInfoDictionary("CFBundleVersion").ToString();
if (!File.Exists("svn.info"))
return version;
var sr = new StreamReader("svn.info");
var s = sr.ReadToEnd();
sr.Close();
var svn = Regex.Replace(s, @".*Revision: (\d+).*", "$1",
RegexOptions.Singleline);
return string.Format("v{0} r{1}", version, svn);
}
------ end of VersionAndRevision ------
More information about the MonoTouch
mailing list