How to handle versioning with fastlane for iOS/android - android

digging into fastlane for the first time on a react native project, and in the process of getting a demo version up for internal testing on both the google play store, and testflight.
After following some tutorials for getting the android bundle up successfully, I ended up with some lanes that do increment both the versionCode and versionName, then bundle and push to the google play store via the supply command.
Now I'm moving onto some iOS lanes, and I'm wondering the best way to sync these up. Ideally, I'd like to use the package.json version as the one source of truth for this, but I'm lost a little on the best way to tie it all in, so I'm asking for some advice/workflows that others have found successful with fastlane and building/versioning their ios/android apps. Thanks!
Here are my current android lanes.
desc "Build a version of the app, that allows params for task and type"
lane :build do |options|
build_task = options.fetch(:build_task, "bundle")
build_type = options.fetch(:build_type, "Release")
gradle(task: "clean")
gradle_params = {
task: build_task,
build_type: build_type,
}
gradle(gradle_params)
end
##### ---------------------------------------------
desc "Build and push a new internal build to the Play Store"
lane :internal do
build()
supply_params = {
track: "internal",
release_status: "draft",
}
supply(supply_params)
end
##### ---------------------------------------------
desc "Increment build number and push to repository"
lane :inc_build_number do |options|
params = {
:app_project_dir => 'app'
}
# Specify a custom build number to be passed in
if options[:build_number]
params[:version_code] = options[:build_number].to_i
end
android_increment_version_code(params)
new_version_code = Actions.lane_context[Actions::SharedValues::ANDROID_VERSION_CODE]
UI.important("Incremented android version code to #{new_version_code}")
end
##### ---------------------------------------------
desc "Increment version number and push to repository"
lane :inc_version_number do |options|
should_commit = options.fetch(:should_commit, true)
commit_message = options.fetch(:commit_message, "android: bump version code & number[skip ci]")
should_push = options.fetch(:should_push, true)
ensure_git_status_clean if should_commit
# increment build number first
inc_build_number
increment_type = options.fetch(:increment_type, "patch")
new_version_params = options[:version]
params = {
app_project_dir: 'app',
increment_type: increment_type,
}
unless new_version_params.nil?()
params[:version_name] = new_version_params
end
android_increment_version_name(params)
new_version_name = Actions.lane_context[Actions::SharedValues::ANDROID_VERSION_NAME]
UI.important("Incremented android version name to #{new_version_name}")
if should_commit
path = "android/app/build.gradle"
git_add(path: path)
git_commit(path: path, message: commit_message)
push_to_git_remote if should_push
end
end

I think you should not use a single source for Android and iOS while versioning. Because versioning presents how your app changes in time.
Anyway, creating a good Fastlane configuration not an easy job, so I decide to share what I have got.
For Android, I created a file called version.properties, I increment version number in this file before submitting a new version to Google Play Store and a Gradle script automatically generates build number in build time. I'm just increment version number (following Semver) and the script handles the rest. When I use Fastlane I'm doing same thing, Fastlane ask me new version, I give a version number to it then it changes the version.properties file all after then it compiles the app.
Follow Below Steps:
Create a file named version.properties under android/version folder
Write your app current version in it like: VERSION=7.0.5
Create a file named versioning.gradle under android folder and write below code in this file:
ext {
buildVersionCode = {
def versionName = buildVersionName()
def (major, minor, patch) = versionName.toLowerCase().tokenize('.')
(major, minor, patch) = [major, minor, patch].collect { it.toInteger() }
(major * 10000) + (minor * 100) + patch
}
buildVersionName = {
def props = new Properties()
file("../version/version.properties").withInputStream { props.load(it) }
return props.getProperty("VERSION")
}
}
Add below marked lines in your android/app/build.gradle file
// ...
apply from: "../../node_modules/react-native/react.gradle"
apply from: '../versioning.gradle' // <- add this line
// ...
// ...
// ...
android {
// ...
defaultConfig {
// ...
versionCode buildVersionCode() // <- add this line
versionName buildVersionName() // <- add this line
// ...
}
// ...
}
// ...
You can change the version from android/version/version.properties file now. Version code will automatically create while building. For an example, if you typed version like 7.3.5 your build number will be 70305.
Now let's make Fastlane integration,
Install property_file_read plugin to ability read property files: fastlane add_plugin property_file_read
You can use below Fastlane configuration for Android:
# CONSTANTS
NOTIFICATION_TITLE = "FOO APP FINISHED!"
platform :android do
desc "Choose release name"
private_lane :determine_release_name do |options|
versions = google_play_track_release_names(track: options[:track])
if versions.empty?
UI.user_error!("Whoops, current version not found!")
else
current_version = versions[0]
end
parts = current_version.split(".")
major = parts[0]
minor = parts[1]
patch = parts[2]
target_major = (major.to_i + 1).to_s + ".0.0"
target_minor = major + "." + (minor.to_i + 1).to_s + ".0"
target_patch = major + "." + minor + "." + (patch.to_i + 1).to_s
properties = property_file_read(file: "android/version/version.properties")
file_version = properties["VERSION"]
target_version_label = UI.select("What version do you want to use?", [
"Bump patch (#{target_patch})",
"Bump minor (#{target_minor})",
"Bump major (#{target_major})",
"KEEP EXISTING (#{file_version})",
"CUSTOM",
])
next target_major if target_version_label.match(/major/)
next target_minor if target_version_label.match(/minor/)
next target_patch if target_version_label.match(/patch/)
next file_version if target_version_label.match(/FILE/)
custom_version = prompt(text: "\nEnter New Version Number:")
custom_version
end
desc "Build and Deploy to Google Play Internal App Sharing"
lane :beta do
newVersion = determine_release_name(track: "internal")
# update version
File.open("../android/version/version.properties", "w") do |file|
file.write("VERSION=#{newVersion}")
end
# gradle(task: "clean", project_dir: "./android/")
gradle(task: "bundle", build_type: "Release", project_dir: "android")
upload_to_play_store(track: "internal", aab: "android/app/build/outputs/bundle/release/app-release.aab")
notification(title: NOTIFICATION_TITLE, subtitle: "Google Play - Internal App Sharing", message: "Finished!!")
end
end
I think it is the best system you can find for Fastlane integration on Android, I use these settings myself.
In iOS, I'm asking new version number then I compile and send app to the TestFlight, there is nothing special. You can check my iOS configuration below:
# CONSTANTS
NOTIFICATION_TITLE = "FOO APP FINISHED!"
IOS_XCWORKSPACE = "ios/Foo.xcworkspace"
IOS_XCODEPROJ = "ios/Foo.xcodeproj"
IOS_SCHEME = "Foo"
IOS_TARGET = "Foo"
APP_STORE_KEYFILE = "fastlane/app-store-auth-key.p8"
APP_STORE_KEY_ID = "xxxxxxxxxx"
APP_STORE_ISSUER_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
platform :ios do
desc "Choose release name"
private_lane :determine_release_name do
current_build_number = latest_testflight_build_number(api_key: lane_context[SharedValues::APP_STORE_CONNECT_API_KEY])
current_version = lane_context[SharedValues::LATEST_TESTFLIGHT_VERSION]
parts = current_version.split(".")
major = parts[0]
minor = parts[1]
patch = parts[2]
target_major = (major.to_i + 1).to_s + ".0.0"
target_minor = major + "." + (minor.to_i + 1).to_s + ".0"
target_patch = major + "." + minor + "." + (patch.to_i + 1).to_s
selected_version_ok = false
while selected_version_ok === false
target_version_label = UI.select("What version do you want to use?", [
"KEEP EXISTING (#{current_version})",
"Bump patch (#{target_patch})",
"Bump minor (#{target_minor})",
"Bump major (#{target_major})",
"CUSTOM",
])
if target_version_label.match(/CUSTOM/)
custom_version = prompt(text: "\nEnter New Version Number:")
if custom_version < current_version
UI.important "Wahaha, version (#{custom_version}) can't lower than the current version (#{current_version})"
else
selected_version_ok = true
end
else
selected_version_ok = true
end
end
next { version: target_major, type: "major" } if target_version_label.match(/major/)
next { version: target_minor, type: "minor" } if target_version_label.match(/minor/)
next { version: target_patch, type: "patch" } if target_version_label.match(/patch/)
next { version: current_version, type: "current" } if target_version_label.match(/KEEP/)
{ version: custom_version, type: "custom" }
end
desc "Push a new beta build to TestFlight"
lane :beta do
api_key = app_store_connect_api_key(
key_id: APP_STORE_KEY_ID,
issuer_id: APP_STORE_ISSUER_ID,
key_filepath: APP_STORE_KEYFILE,
duration: 1200, # optional (maximum 1200)
in_house: false, # optional but may be required if using match/sigh
)
new_version = determine_release_name
if new_version[:type] === "current"
# get latest build number from App Store
build_num = app_store_build_number(
initial_build_number: 1,
live: false,
version: get_version_number(xcodeproj: IOS_XCODEPROJ, target: IOS_TARGET),
api_key: api_key,
)
increment_build_number(build_number: build_num + 1, xcodeproj: IOS_XCODEPROJ)
else
increment_version_number(version_number: new_version[:version], xcodeproj: IOS_XCODEPROJ)
end
# 🏗️ Build app, this method has other options few we will explore in next section
build_app(
silent: true,
workspace: IOS_XCWORKSPACE,
scheme: IOS_SCHEME,
)
# ⏫ Its time to upload
upload_to_testflight(api_key: api_key, skip_waiting_for_build_processing: true)
# 🧽 Clear artifacts
clean_build_artifacts
sh "rm -rf \"#{lane_context[SharedValues::XCODEBUILD_ARCHIVE]}\""
notification(title: NOTIFICATION_TITLE, subtitle: "Testflight", message: "Finished!!")
end
end
Let me what you think in comments! (Btw sorry for my poor England :p)

Related

API 'variantOutput.getProcessManifest()' is obsolete

I am trying to change version code, version name and app icon. Below is my code
variant.outputs.all { output ->
def newApkName
if (output.zipAlign) {
newApkName = "Application.apk"
}
output.processManifest.doLast{
// Stores the path to the maifest.
String manifestPath = "$manifestOutputDirectory/AndroidManifest.xml"
// Stores the contents of the manifest.
def manifestContent = file(manifestPath).getText()
// Changes the version code in the stored text.
manifestContent = manifestContent.replace('android:versionName="dev_build"',
String.format('android:versionName="%s"', variant.versionName))
// Overwrites the manifest with the new text.
file(manifestPath).write(manifestContent)
}
output.processManifest.doLast{
// Stores the path to the maifest.
String manifestPath = "$manifestOutputDirectory/AndroidManifest.xml"
// Stores the contents of the manifest.
def manifestContent = file(manifestPath).getText()
// Changes the version code in the stored text.
manifestContent = manifestContent.replace('android:icon="#drawable/app_icon_main"',
String.format('android:icon="#drawable/%s"', getVersionIconName()))
// Overwrites the manifest with the new text.
file(manifestPath).write(manifestContent)
}
outputFileName = "Application.apk"
}
Getting bellow warning:
WARNING: API 'variantOutput.getProcessManifest()' is obsolete and has been replaced with 'variantOutput.getProcessManifestProvider()'.
It will be removed at the end of 2019.
For more information, see https://d.android.com/r/tools/task-configuration-avoidance.
To determine what is calling variantOutput.getProcessManifest(), use -Pandroid.debug.obsoleteApi=true on the command line to display a stack trace.
Affected Modules: Application
Environment Details:
Android Studio : 3.3.1
Gradle Version: 4.10.0
Build Tool Version 28.0.2
Change the following
output.processManifest.doLast {
//your code
}
with
output.getProcessManifestProvider().get().doLast {
//your code
}

Use Gradle Play Publisher's version name override in Java

Gradle Play publisher lets you override the version name before publishing to the play store.
play {
// ...
resolutionStrategy = "auto"
outputProcessor { // this: ApkVariantOutput
versionNameOverride = "$versionNameOverride.$versionCode"
}
}
Is it possible to use the value of versionNameOverride in Java Code? We display the version name in the about page of the app using the versionName attribute. GPP updates the versionNameOverride value so the play store listing shows the correct version number but the app's about page keeps showing a different version number that's based on versionName.
Using the versionNameOverride could be done like this:
outputProcessor { output ->
output.versionNameOverride = output.versionNameOverride + "." + output.versionCode
def versionPropertyFile = file "version.properties"
def versionProperties = new Properties()
versionProperties.setProperty('versionCode', "$output.versionCode")
versionProperties.setProperty('versionName', output.versionNameOverride)
versionPropertyFile.withWriter { versionProperties.store(it, "Generated by the outputProcessor for the play plugin during publishing.") }
}
But if you want to show the versionName in your app it would be easier to use
try {
PackageInfo pInfo =
context.getPackageManager().getPackageInfo(getPackageName(), 0);
String version = pInfo.versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
The reason why your app did show the wrong values might be that your app was using BuildConfig. But the versionCode and versionName there do not reflect what has been set through versionNameOverride, but those values from your build gradle.

How to generate java files from swig?Is this done by gradle or do it with command line?

This is my Build.gradle file:-
project.ext {
// * SWIG options *
// This and the SWIG "task" at the bottom are loosely based on:
//
// http://androiddeveloper.co.il/using-swig/
swigModuleFiles = ['yourfy.i']
swigIncludeDirs = ['src/main/cpp/yourfy/src', 'src/main/cpp/yourfy/src/nxcommon/src/libnxcommon']
swigJavaOutputDir = file("src/main/java/com/yourfy/yourfy/swig").absolutePath
swigCxxOutputDir = file("src/main/cpp/swig").absolutePath
swigModuleFilesAbs = []
swigIncludeDirOpts = []
swigCxxModuleFilesAbs = []
swigModuleFiles.each { moduleFile ->
swigModuleFilesAbs.add(file("src/main/cpp/yourfy/src/yourfy/" + moduleFile).absolutePath)
swigCxxModuleFilesAbs.add(swigCxxOutputDir + "/" + moduleFile + ".cxx")
}
swigIncludeDirs.each { incDir ->
swigIncludeDirOpts.add("-I" + file(incDir).absolutePath)
}
}
// * Generate SWIG wrappers *
// Generate .java and .cxx files for the SWIG bindings.
//
// It has to be done in Gradle (as opposed to the native library's CMakeLists.txt), because
// Gradle calls the Java implementationr BEFORE running the native build, so the .java SWIG files will
// not have been generated yet when the implementationr runs. We have to ensure that they are generated
// before the Java implementationr runs.
//
// Thanks, Gradle!
//
// I would love to do this as a task, but unfortunately, it does not work. For some reason,
// when building from Android Studio (NOT when running Gradle from command line), CMake is
// actually invoked first, before any other tasks, which means that the Gradle-generated SWIG
// CXX source files might still be missing, which CMake then complains about and aborts. For
// now, we will have to run SWIG at the top level all the time to get it working. Right now,
// it's reasonably fast to do so, let's see how long this holds.
// More info:
def swigExec = '/usr/local/bin/swig'
// TODO: Add some auto-detection
if (project.hasProperty('swig.executable')) {
swigExec = project.property('swig.executable')
}
if (swigExec != null && file(swigExec).isFile()) {
file(project.swigJavaOutputDir).mkdirs()
file(project.swigCxxOutputDir).mkdirs()
// Delete previous output files (.cxx, .h and *.java in respective directories)
(file(project.swigJavaOutputDir).listFiles() + file(project.swigCxxOutputDir).listFiles()).each { file ->
if (file.name.toLowerCase().endsWith(".cxx")
|| file.name.toLowerCase().endsWith(".h")
|| file.name.toLowerCase().endsWith(".java")
) {
file.delete()
}
}
[project.swigModuleFilesAbs, project.swigCxxModuleFilesAbs].transpose().each { moduleFile, cxxFile ->
exec {
commandLine(
swigExec,
'-java',
'-c++',
'-package', 'com.yourfy.yourfy.swig',
*project.swigIncludeDirOpts,
'-outdir', project.swigJavaOutputDir,
'-o', cxxFile,
moduleFile
)
}
}
} else {
logger.error('Property swig.executable not set or invalid! You should set it in ' +
'the gradle.properties file of your gradle user home directory, pointing to ' +
'a SWIG > 3.0 executable')
}
How to generate swig wrapper files in android?There are some links added to here but still its not figure out.
Also tried following links also mentioned in the comment:-
Stackoverflow question link
Github link
I was trying both the links but didn't understand but still seems it difficult.

New Relic in Android Studio - newrelic.properties - variants

I'm integrating New Relic in my project (with Android Studio & Gradle) which has 2 variants. Each variant has its own generated token, which I store in each variant's string.xml file.
In the New Relic documentation, it states the following:
In your project’s root directory (projectname/app), add a newrelic.properties file with the following line:
com.newrelic.application_token=generated_token
The problem is, if I do this, how can make the correct token appear for the correct variant? If this file must appear in the project root, I can't create one per variant, and so I'm forced to use the same token for both variants, which doesn't work for my requirements.
Any insight would be appreciated.
Okay, so after contacting the support team at New Relic, there is apparently no direct solution for this as of today, although they said they've opened a feature request, and so this problem might be solved soon.
From what I managed to understand, the reason this file is needed is so that the New Relic system can display an un-obfuscated error log when an exception occurs on a production version which has been obfuscated with ProGuard.
The New Relic system, with the help of this file, will upload the ProGuard mapping.txt file to the New Relic servers and associate it with your app according to the specified token. With this, New Relic can un-obfuscate stack traces and display a descriptive stack trace with actual class & method names, rather a, b, c, etc.
As a workaround, I was told that I can forego this file all together, if I upload the mapping file manually.
The mapping file can be found at:
build/outputs/proguard/release/mapping.txt
In order to manually upload the file, perform the following via command line:
curl -v -F proguard=#"<path_to_mapping.txt>" -H "X-APP-LICENSE-KEY:<APPLICATION_TOKEN>" https://mobile-symbol-upload.newrelic.com/symbol
This must be done for each variant which is being obfuscated with ProGuard (classically, release builds).
Source
Hope this helps someone else.
I solved creating some Gradle tasks. Please, take a look at https://discuss.newrelic.com/t/how-to-set-up-newrelic-properties-file-for-project-with-multiple-build-variants/46176/5
I following a code that worked pretty well for me.
Add the New Relic application token on a string resource file. i.e.: api.xml.
Create a new Gradle file. i.e: newrelic-util.gradle.
Add the following content on the newly created Gradle file:
apply plugin: 'com.android.application'
android.applicationVariants.all { variant ->
//<editor-fold desc="Setup New Relic property file">
def variantName = variant.name.capitalize()
def newRelicTasksGroup = "newrelic"
def projectDirPath = project.getProjectDir().absolutePath
def newRelicPropertyFileName = "newrelic.properties"
def newRelicPropertyFilePath = "${projectDirPath}/${newRelicPropertyFileName}"
// Cleanup task for New Relic property file creation process.
def deleteNewRelicPropertyFile = "deleteNewRelicPropertyFile"
def taskDeleteNewRelicPropertyFile = project.tasks.findByName(deleteNewRelicPropertyFile)
if (!taskDeleteNewRelicPropertyFile) {
taskDeleteNewRelicPropertyFile = tasks.create(name: deleteNewRelicPropertyFile) {
group = newRelicTasksGroup
description = "Delete the newrelic.properties file on project dir."
doLast {
new File("${newRelicPropertyFilePath}").with {
if (exists()) {
logger.lifecycle("Deleting file ${absolutePath}.")
delete()
} else {
logger.lifecycle("Nothing to do. File ${absolutePath} not found.")
}
}
}
}
}
/*
* Fix for warning message reported by task "newRelicMapUploadVariantName"
* Message:
* [newrelic] newrelic.properties was not found! Mapping file for variant [variantName] not uploaded.
* New Relic discussion:
* https://discuss.newrelic.com/t/how-to-set-up-newrelic-properties-file-for-project-with-multiple-build-variants/46176
*/
def requiredTaskName = "assemble${variantName}"
def taskAssembleByVariant = project.tasks.findByName(requiredTaskName)
def createNewRelicPropertyFileVariantName = "createNewRelicPropertyFile${variantName}"
// 0. Searching task candidate to be dependent.
if (taskAssembleByVariant) {
logger.debug("Candidate task to be dependent found: ${taskAssembleByVariant.name}")
// 1. Task creation
def taskCreateNewRelicPropertyFile = tasks.create(name: createNewRelicPropertyFileVariantName) {
group = newRelicTasksGroup
description = "Generate the newrelic.properties file on project dir.\nA key/value propety " +
"will be written in file to be consumed by newRelicMapUploadVariantName task."
logger.debug("Creating task: ${name}")
doLast {
def newRelicPropertyKey = "com.newrelic.application_token"
def newRelicStringResourceKey = "new_relic_key"
def targetResourceFileName = "api.xml"
def variantXmlResourceFilePath = "${projectDirPath}/src/${variant.name}/res/values/${targetResourceFileName}"
def mainXmlResourceFilePath = "${projectDirPath}/src/main/res/values/${targetResourceFileName}"
def xmlResourceFilesPaths = [variantXmlResourceFilePath, mainXmlResourceFilePath]
xmlResourceFilesPaths.any { xmlResourceFilePath ->
// 1.1. Searching xml resource file.
def xmlResourceFile = new File(xmlResourceFilePath)
if (xmlResourceFile.exists()) {
logger.lifecycle("Reading property from xml resource file: ${xmlResourceFilePath}.")
// 1.2. Searching for string name new_relic_key api.xml resource file.
def nodeResources = new XmlParser().parse(xmlResourceFile)
def nodeString = nodeResources.find {
Node nodeString -> nodeString.'#name'.toString() == newRelicStringResourceKey
}
// 1.3. Checking if string name new_relic_key was found.
if (nodeString != null) {
def newRelicApplicationToken = "${nodeString.value()[0]}"
logger.lifecycle("name:${nodeString.'#name'.toString()};" +
"value:${newRelicApplicationToken}")
// 1.4 Checking the content of newRelicApplicationToken
if (newRelicApplicationToken == 'null' || newRelicApplicationToken.allWhitespace) {
logger.warn("Invalid value for key ${newRelicStringResourceKey}. " +
"Please, consider configuring a value for key ${newRelicStringResourceKey}" +
" on ${xmlResourceFile.name} resource file for buildVariant: ${variantName}. " +
"The ${newRelicPropertyFileName} will be not created.")
return true // break the loop
}
// 1.5. File creation.
File fileProperties = new File(newRelicPropertyFilePath)
fileProperties.createNewFile()
logger.lifecycle("File ${fileProperties.absolutePath} created.")
// 1.6. Writing content on properties file.
def fileComments = "File generated dynamically by gradle task ${createNewRelicPropertyFileVariantName}.\n" +
"Don't change it manually.\n" +
"Don't track it on VCS."
new Properties().with {
load(fileProperties.newDataInputStream())
setProperty(newRelicPropertyKey, newRelicApplicationToken.toString())
store(fileProperties.newWriter(), fileComments)
}
logger.lifecycle("Properties saved on file ${fileProperties.absolutePath}.")
return true // break the loop
} else {
logger.warn("The key ${newRelicStringResourceKey} was not found on ${xmlResourceFile.absolutePath}.\n" +
"Please, consider configuring a key/value on ${xmlResourceFile.name} resource file for buildVariant: ${variantName}.")
return // continue to next xmlResourceFilePath
}
} else {
logger.error("Resource file not found: ${xmlResourceFile.absolutePath}")
return // continue next xmlResourceFilePath
}
}
}
}
// 2. Task dependency setup
// To check the task dependencies, use:
// logger.lifecycle("Task ${name} now depends on tasks:")
// dependsOn.forEach { dep -> logger.lifecycle("\tTask: ${dep}") }
tasks['clean'].dependsOn taskDeleteNewRelicPropertyFile
taskCreateNewRelicPropertyFile.dependsOn taskDeleteNewRelicPropertyFile
taskAssembleByVariant.dependsOn taskCreateNewRelicPropertyFile
} else {
logger.error("Required task ${requiredTaskName} was not found. " +
"The task ${createNewRelicPropertyFileVariantName} will be not created.")
}
//</editor-fold>
}
On app/build.gradle file, apply the Gradle file.
apply from: './newrelic-util.gradle'
That’s it. I created a file named newrelic-util.gradle on project app dir. If you execute the task assembleAnyVariantName, the task createNewRelicPropertyFileAnyVarianteName will be performed first. Tip: don’t track the generated file newrelic.properties file. Ignore it on your VCS.
Additionally, the task deleteNewRelicPropertyFile will be performed right before the tasks ‘clean’ and ‘createNewRelicPropertyFileAnyVarianteName’ in order to avoid a file with a wrong New Relic application token.

How can I add 'Options' to the gradlew help --task documentation

I have the following setup in my build.gradle file:
// Task designed to bump version numbers. This should be the first task run
// after a new release branch is created.
task bumpVersion(description: 'Bumps the version number of the current Android release. Should be used as a standalone task, and should only be the first task called after creating a release branch.', group: 'Management') << {
Properties props = new Properties();
File propsFile = new File('gradle.properties');
props.load(propsFile.newDataInputStream());
def currentVersionCode = props.getProperty("CORE_VERSION_CODE") as int;
def currentVersionName = props.getProperty("CORE_VERSION_NAME") as String;
def intPortionsOfVersionName = currentVersionName.tokenize('.').toArray();
def leastSignificantPortion = intPortionsOfVersionName[intPortionsOfVersionName.length - 1] as int;
def newVersionCode = currentVersionCode + 1;
def newVersionName = "";
if (!project.hasProperty('newVersion')) {
leastSignificantPortion = leastSignificantPortion + 1;
intPortionsOfVersionName[intPortionsOfVersionName.length - 1] = leastSignificantPortion;
newVersionName = intPortionsOfVersionName.collect{ it }.join(".");
} else {
newVersionName = project.getProperty('newVersion');
}
props.setProperty("CORE_VERSION_NAME", newVersionName as String);
props.setProperty("CORE_VERSION_CODE", newVersionCode as String);
props.store(propsFile.newWriter(), null);
}
Under the line newVersionName = project.getProperty('newVersion') I try to acquire the property called "newVersion", if it exists, and bump the least significant digit if it's not available.
This works fine, but what I want to do is add a way to specify this option in the documentation (i.e. gradle help --task bumpVersion). For instance, if I run gradle help --task help, it gives me:
:help
Detailed task information for help
Path
:help
Type
Help (org.gradle.configuration.Help)
Options
--task The task, detailed help is requested for.
Description
Displays a help message
Notice how '--task' is under the Options section. I'm wondering how to do this with my own code.
This can be done using the #Option annotation.
#Option(option = "version", description = "Version number to use")
public void setVersion(String version) { ... }
Note: This is an internal API so it may change.
Edit: May have forgotten to mention you will have to implement your task as a custom task class to leverage this capability.

Categories

Resources