Apps have two different type of version numbers plus there's differences between versioning for different platforms. Titanium 4.1.0 comes with better support for versioning iOS apps. Let's explore how you would use this in an app for iOS and Android.
First of all there's the release version number. This is the version your users see. Each public release must have a unique version. Apple calls this the CFBundleShortVersionString and for Android this is android:versionName.
Both suggest and to different lengths require you to follow a subset of Semantic Versioning.
In semantic versioning you use a string comprised of three period-separated integers. Normally the first integer would be incremented for breaking changes, but assuming your app won't crash your users' phones we typically use it to indicate new features or major changes. The second integer would be changes or less prominent features and the third one is incremented for fixes and other maintenance.
- 1.0.0: Initial release
- 1.0.1: Fixed a few bugs
- 1.1.0: Changed something
- 2.0.0: Added a new feature
The other type of version number is the build version.
Before every public release, you probably go through several iterations of development and test builds that you distribute via alpha/beta testing on Google Play, TestFlight Beta Testing with Apple or another service like Installr.
This is where both Android and iOS (and others) do not follow semantic versioning anymore. If they did, the test builds for 1.1.0
would be versioned as 1.1.0-X
where X
can be many sorts of pre-release identifiers.
Instead, a separate build version is used. This version is normally not visible to the user and should be unique for each test build.
Apple says you should follow the same simplified semantic versioning format for this CFBundleVersion as well, but the default for a new XCode project is 1
. On Android android:versionCode must be an integer. This makes more sense, since if you would follow Apple's guidelines then how would you version test builds for an upcoming 1.1.0
release? If you would use 1.1.1
, 1.1.2
etc then what would the release after 1.1.0
be and the build versions for that? We'll come back to that.
The tiapp.xml
has a <version>
tag that can edit via Studio's TiApp Editor. But there's more.
Titanium will use <version>
as-is for CFBundleVersion
(build) and will try to format it as three period-separated semantic version for CFBundleShortVersionString
(release).
Until 4.1.0 you could not manually override this. The only way to set a different build version was to set version
to something like 1.1.0.123
since Titanium would then normalize it to 1.1.0
for the release and leave it as-is for the build version number. This of course is not a proper semantic version, but Apple did accept it - allowing you to distribute test multiple unique test builds via TestFlight without having to increase the release version number.
Now with 4.1.0 you can now manually override the values for both CFBundleVersion
and CFBundleShortVersionString
via the <ios>
section in tiapp.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<ti:app xmlns:ti="http://ti.appcelerator.org">
<!-- not used but still available via Ti.App.version -->
<version>1.1.0.123</version>
<ios>
<plist>
<dict>
<key>CFBundleShortVersionString</key>
<string>1.1</string>
<key>CFBundleVersion</key>
<string>123</string>
</dict>
</plist>
</ios>
</ti:app>
For Android Titanium will use <version>
as-is for android:versionName
(release) but unlike iOS the build version (android:versionCode
) is not based of <version>
. You will have to set it manually via the <android>
section in tiapp.xml
or it will always default to 1
, which is.. well, not unique.
<?xml version="1.0" encoding="UTF-8"?>
<ti:app xmlns:ti="http://ti.appcelerator.org">
<!-- not used but still available via Ti.App.version -->
<version>1.1.0.123</version>
<android xmlns:android="http://schemas.android.com/apk/res/android">
<manifest android:versionName="1.0" android:versionCode="123">
</manifest>
</android>
All good developers are lazy, so lets automate this using a Grunt task. If you are not familiar with Grunt, read their Getting Started. We will be discussing Grunt and other task runners and how to use them for Appcelerator development in a later post.
The Grunt you will find down here allows you to bump the major, minor, patch or just the build version - which will always be incremented. It asummes <version>
to be major.minor.patch
and to find only android:versionCode
and CFBundleVersion
in the <android>
and <ios>
sections so that Titanium will set release version numbers.
$ npm install grunt-cli
$ grunt version:minor
Running "version:minor" (version) task
Bumped version to: 1.2.0
Bumped android:versionCode to: 124
Bumped CFBundleVersion to: 130
Gruntfile.js
var fs = require('fs');
grunt.registerTask('version', function (what) {
// map name to index and default to patch index
var index = ['major', 'minor', 'patch'].indexOf(what);
var tiapp = fs.readFileSync('tiapp.xml', {
encoding: 'utf-8'
});
if (index !== -1) {
tiapp = tiapp.replace(/(<version>)([^<]+)(<\/version>)/, function (match, before, version, after) {
version = version.split('.');
// bump index and reset following
for (var i = index; i <= 2; i++) {
version[i] = (i === index) ? (parseInt(version[i], 10) + 1).toString() : '0';
}
version = version.join('.');
grunt.log.writeln('Bumped version to: ' + version);
return before + version + after;
});
}
tiapp = tiapp.replace(/(android:versionCode=")([^"]+)(")/, function (match, before, versionCode, after) {
versionCode = parseInt(versionCode, 10) + 1;
grunt.log.writeln('Bumped android:versionCode to: ' + versionCode);
return before + versionCode + after;
});
tiapp = tiapp.replace(/(<key>CFBundleVersion<\/key>\s*<string>)([^<]+)(<\/string>)/mg, function (match, before, CFBundleVersion, after) {
CFBundleVersion = parseInt(CFBundleVersion, 10) + 1;
grunt.log.writeln('Bumped CFBundleVersion to: ' + CFBundleVersion);
return before + CFBundleVersion + after;
});
fs.writeFileSync('tiapp.xml', tiapp);
});
Happy versioning!
Windows has a 4 part version string. The details I could find are here: https://msdn.microsoft.com/en-us/library/windows/apps/gg442301(v=vs.105).aspx#BKMK_Updatingversionnumbers
Essentially the 4 part version is the "full version" and is used internally, but once submitted to the store only the first two parts are tracked (and I think you set them manually and they must match the first two digits in the app's manifest file").
The first part is major version, then minor, then build #, then revision number. It looks like in practice, you can set up the MSBuild tooling to fill in the 3rd and 4th places for you (or auto-increment them over time). Not sure if that would still work if all it does it keep track of it in the appx.manifest, which we generate dynamically during our builds (and therefore means it would get wiped).
Of course, all of this doesn't matter right now as we've hard-coded the app version and need to fix that! (ugh): https://jira.appcelerator.org/browse/TIMOB-19175