How to tell when gradle is being run from AndroidStudio? - android

I need to build some hacks into my gradle build file so that Android Studio understands some things. I don't need these hacks when I run the build from the command line directory. Is there a way to detect when the build is being run from within Android Studio? Maybe through environment variables, etc?

Use gradle -P blah=val from command line and in your build.gradle use project.hasProperty("blah") or project.getProperty("test") or if (blah ... ) to decide whether run your hack or not.
Updated:
OK I found the direct way :)
def env = System.getProperties()
if (env['com.android.studio.gradle.project.path'] != null) {
// build from Android Studio, do magic here
}

With AndroidStudio 2.1.1, you can use the idea.platform.prefix property:
def sysprops = System.getProperties()
if (sysprops['idea.platform.prefix'] != null) {
// Built from AndroidStudio
} else {
// Built from command line
}

Jake Wharton suggests android.injected.invoked.from.ide to speed butterknife at development time by using reflection:
dependencies {
if (properties.containsKey('android.injected.invoked.from.ide')) {
implementation 'com.jakewharton:butterknife-reflect:<version>'
} else {
implementation 'com.jakewharton:butterknife:<version>'
kapt 'com.jakewharton:butterknife-compiler:<version>'
}
}
From Twitter:
Hey ButterKnife users: I'm working on a reflection-based implementation for use during development so the annotation processor is not needed.
A follow-up:
What is this? A property from (link: http://gradle.properties) gradle.properties?
The answer we want:
No it's added by the IDE

Related

How to add two or more kotlin native modules on an iOS project

TL;DR;
How to add two or more kotlin native modules on an iOS project without getting duplicate symbols error?
The detailed question
Let's assume a multi-module KMP project as a follow where there exists a native app for Android and a native app for iOS and two common modules to hold shared kotlin code.
.
├── android
│ └── app
├── common
│ ├── moduleA
│ └── moduleB
├── ios
│ └── app
The module A contains a data class HelloWorld and has no module dependencies:
package hello.world.modulea
data class HelloWorld(
val message: String
)
Module B contains an extension function for HelloWorld class so it depends on module A:
package hello.world.moduleb
import hello.world.modulea.HelloWorld
fun HelloWorld.egassem() = message.reversed()
The build.gradle configuration of the modules are:
Module A
apply plugin: "org.jetbrains.kotlin.multiplatform"
apply plugin: "org.jetbrains.kotlin.native.cocoapods"
…
kotlin {
targets {
jvm("android")
def iosClosure = {
binaries {
framework("moduleA")
}
}
if (System.getenv("SDK_NAME")?.startsWith("iphoneos")) {…}
}
cocoapods {…}
sourceSets {
commonMain.dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72"
}
androidMain.dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.72"
}
iosMain.dependencies {
}
}
}
Module B
apply plugin: "org.jetbrains.kotlin.multiplatform"
apply plugin: "org.jetbrains.kotlin.native.cocoapods"
…
kotlin {
targets {
jvm("android")
def iosClosure = {
binaries {
framework("moduleB")
}
}
if (System.getenv("SDK_NAME")?.startsWith("iphoneos")) {…}
}
cocoapods {…}
sourceSets {
commonMain.dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72"
implementation project(":common:moduleA")
}
androidMain.dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.72"
}
iosMain.dependencies {
}
}
}
It looks pretty straightforward and it even works on android if I configure the android build gradle dependencies as a following:
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72"
implementation project(":common:moduleA")
implementation project(":common:moduleB")
}
However, this does not seem to be the correct way to organize multi modules on iOS, because running the ./gradlew podspec I get a BUILD SUCCESSFUL as expected with the following pods:
pod 'moduleA', :path => '…/HelloWorld/common/moduleA'
pod 'moduleB', :path => '…/HelloWorld/common/moduleB'
Even running a pod install I get a success output Pod installation complete! There are 2 dependencies from the Podfile and 2 total pods installed. whats looks correctly once the Xcode shows the module A and module B on the Pods section.
However, if I try to build the iOS project I get the following error:
Ld …/Hello_World-…/Build/Products/Debug-iphonesimulator/Hello\ World.app/Hello\ World normal x86_64 (in target 'Hello World' from project 'Hello World')
cd …/HelloWorld/ios/app
…
duplicate symbol '_ktypew:kotlin.Any' in:
…/HelloWorld/common/moduleA/build/cocoapods/framework/moduleA.framework/moduleA(result.o)
…/HelloWorld/common/moduleB/build/cocoapods/framework/moduleB.framework/moduleB(result.o)
… a lot of duplicate symbol more …
duplicate symbol '_kfun:kotlin.throwOnFailure$stdlib#kotlin.Result<#STAR>.()' in:
…/HelloWorld/common/moduleA/build/cocoapods/framework/moduleA.framework/moduleA(result.o)
…/HelloWorld/common/moduleB/build/cocoapods/framework/moduleB.framework/moduleB(result.o)
ld: 9928 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
My knowledge in iOS is not that much, so to my untrained eyes, it looks like each module is adding its own version of the things instead of using some resolutions strategy to share it.
If I use only the module A the code works and run as expected, so I know the code itself is correct, the problem is how to manage more than 1 module, so that the question, how to add both (module A and module B) on iOS and make things works?
P.S
I did reduce the code as much as I could, trying to keep only the parts that I guess is the source of the problem, however, the complete code is available here if you want to check anything missing in the snippets, or if you want to run and try to solve the problem…
Multiple Kotlin frameworks can be tricky, but should be working as of 1.3.70 which I see you have.
The issue seems to be that both frameworks are static, which is currently an issue in 1.3.70 so it isn't working. (This should be updated by 1.40). It looks like by default the cocoapods plugin sets the frameworks to be static which won't work. I'm unaware of a way to change cocoapods to set it as dynamic but I've tested building without cocoapods and using the isStatic variable in a gradle task, and have gotten an iOS project to compile. Something like:
binaries {
framework("moduleA"){
isStatic = false
}
}
For now you can work around the issue using this method by using the code above and creating a task to build the frameworks(here's an example)
Another thing worth noting is that on the iOS side, the HelloWorld classes will appear as two separate classes despite both coming from moduleA. It's another strange situation with multiple Kotlin frameworks, but I think the extension will still work in this case since you're returning a string.
I actually just wrote up a blog post about multiple Kotlin frameworks that may help with some other questions if you'd like to take a look. https://touchlab.co/multiple-kotlin-frameworks-in-application/
EDIT: Looks like cocoapodsext also has an isStatic variable, so set it to isStatic = false
tl:dr You currently can't have more than one static Kotlin frameworks in the same iOS project. Set them to not be static using isStatic = false.
However, if I try to build the iOS project I get the following error:
This particular error is a known issue. Multiple debug static frameworks are incompatible with compiler caches.
So to workaround the issue you can either disable compiler caches by putting the following line into your gradle.properties:
kotlin.native.cacheKind=none
or make the frameworks dynamic by adding the following snippet to your Gradle build script:
kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
binaries.withType<org.jetbrains.kotlin.gradle.plugin.mpp.Framework> {
isStatic = false
}
}
}
See https://youtrack.jetbrains.com/issue/KT-42254 for more details.
I guess current behaviour for multiple frameworks doesn't make much sense for the original topic starter, I'm just putting my answer here for anyone who might encounter the same issue.
My knowledge in iOS is not that much, so to my untrained eyes, it looks like each module is adding its own version of the things instead of using some resolutions strategy to share it.
This is exactly how it is supposed to work at this moment. But "versions of the things" in each of the frameworks are put into the separate independent namespaces, so there should be no linkage errors, and the one you've encountered is a bug.

How to configure Firebase Performance Monitoring plugin extension in Gradle Kotlin DSL

I have an Android app using Gradle with Kotlin DSL. I'm adding Firebase Performance Monitoring, but I would like for it to be enabled only for a specific build type.
I've been following the instructions provided at Firebase - Disable Firebase Performance Monitoring. Unfortunately the provided snippets are in Groovy.
I've tried to get a reference to the Firebase Performance Monitoring extension in my app level Gradle script by doing the following:
plugins {
...
id("com.google.firebase.firebase-perf")
kotlin("android")
kotlin("android.extensions")
kotlin("kapt")
}
buildTypes {
getByName(BuildTypes.DEBUG) {
configure<com.google.firebase.perf.plugin.FirebasePerfExtension> {
setInstrumentationEnabled(false)
}
}
...
}
...
dependencies {
val firebaseVersion = "17.2.1"
implementation("com.google.firebase:firebase-core:$firebaseVersion")
implementation("com.google.firebase:firebase-analytics:$firebaseVersion")
implementation("com.google.firebase:firebase-perf:19.0.5")
}
Android Studio doesn't see any problem in this and auto-completes FirebasePerfExtension.
Unfortunately upon running a Gradle sync I get the following:
Extension of type 'FirebasePerfExtension' does not exist.
Currently registered extension types: [ExtraPropertiesExtension, DefaultArtifactPublicationSet, ReportingExtension, SourceSetContainer, JavaPluginExtension, NamedDomainObjectContainer<BaseVariantOutput>, BaseAppModuleExtension, CrashlyticsExtension, KotlinAndroidProjectExtension, KotlinTestsRegistry, AndroidExtensionsExtension, KaptExtension]
There's no plugin extension related to Firebase Performance Monitoring.
This is in my project level build.gradle file dependencies block:
classpath("com.google.firebase:perf-plugin:1.3.1")
Any help is appreciated!
Update 1
As recommended on the Gradle - Migrating build logic from Groovy to Kotlin guide at "Knowing what plugin-provided extensions are available" I've ran the kotlinDslAccessorsReport task. None of the resulting extensions seems to be related to Firebase.
Had the same issue and was going to apply from groovy file, but seems i found the solution in here: https://docs.gradle.org/5.0/userguide/kotlin_dsl.html#sec:interoperability
withGroovyBuilder {
"FirebasePerformance" {
invokeMethod("setInstrumentationEnabled", false)
}
}
We used this answer, util we discovered a better working way in the team
check(this is ExtensionAware)
configure<com.google.firebase.perf.plugin.FirebasePerfExtension> { setInstrumentationEnabled(false) }

React Native - Automatic version name from package.json to android build manifest

Currently I have a react native app and the issue that I have is that is very time consuming to update the version on every build or commit.
Also, I have Sentry enabled so every time I build, some builds get the same version so some crashes are hard to determine where they came from.
Lastly, updating the version manually is error prone.
How can I setup my builds to generate an automatic version every time I build and forget about all of this manual task?
While the currently accepted answer will work, there is a much simpler, and therefore more reliable way to do it.
You can actually read the value set in package.json right from build.gradle.
Modify your android/app/build.gradle:
// On top of your file import a JSON parser
import groovy.json.JsonSlurper
// Create an easy to use function
def getVersionFromNpm() {
// Read and parse package.json file from project root
def inputFile = new File("$rootDir/../package.json")
def packageJson = new JsonSlurper().parseText(inputFile.text)
// Return the version, you can get any value this way
return packageJson["version"]
}
android {
defaultConfig {
applicationId "your.app.id"
versionName getVersionFromNpm()
}
}
This way you won't need a pre-build script or anything, it will just work.
Since I was working with this for several days, I decided to share with everyone how I did it, because it could help others.
Tools used:
GitVersion: We will use GitVersion to generate a semantic version automatically depending on many factors like current branch, tags, commits, etc. The toold does an excellent job and you can forget about naming your versions. Of course, if you set a tag to a commit, it will use that tag as name.
PowerShell: This command line OS built by Microsoft has the ability to be run from Mac, Linux or Windows, and I chose it because the builds can be agnostic of the OS version. For example I develop on Windows but the build machine has MacOS.
Edit App build.gradle
The app gradle only needs one line added at the end of it. In my case I have the Google Play Services gradle and I added it after that.
apply from: 'version.gradle'
version.gradle
This file should be in the same folder as your app gradle and this is the content:
task updatePackage(type: Exec, description: 'Updating package.json') {
commandLine 'powershell', ' -command ' , '$semver=(gitversion /showvariable Semver); Set-Content -path version.properties -value semver=$semver; npm version --no-git-tag-version --allow-same-version $semver'
}
preBuild.dependsOn updatePackage
task setVariantVersion {
doLast {
if (plugins.hasPlugin('android') || plugins.hasPlugin('android-library')) {
def autoIncrementVariant = { variant ->
variant.mergedFlavor.versionName = calculateVersionName()
}
if (plugins.hasPlugin('android')){
//Fails without putting android. first
android.applicationVariants.all { variant -> autoIncrementVariant(variant) }
}
if (plugins.hasPlugin('android-library')) {
//Probably needs android-library before libraryVariants. Needs testing
libraryVariants.all { variant -> autoIncrementVariant(variant) }
}
}
}
}
preBuild.dependsOn setVariantVersion
setVariantVersion.mustRunAfter updatePackage
ext {
versionFile = new File('version.properties')
calculateVersionName = {
def version = readVersion()
def semver = "Unknown"
if (version != null){
semver = version.getProperty('semver')
}
return semver
}
}
Properties readVersion() {
//It gets called once for every variant but all get the same version
def version = new Properties()
try {
file(versionFile).withInputStream { version.load(it) }
} catch (Exception error) {
version = null
}
return version
}
Now, let's review what the script is actually doing:
updatePackage: This task runs at the very beginning of your build (actually before preBuild) and it executes gitversion to get the current version and then creates a version.properties file which later be read by gradle to take the version.
setVariantVersion: This is called afterEvaluate on every variant. Meaning that if you have multiple builds like debug, release, qa, staging, etc, all will get the same version. For my use case this is fine, but you might want to tweak this.
Task Order: One thing that bothered me was that the version was being run before the file was generated. This is fixed by using the mustRunAfter tag.
PowerShell Script Explained
This is the script that gets run first. Let's review what is doing:
$semver=(gitversion /showvariable Semver);
Set-Content -path props.properties -value semver=$semver;
npm version --no-git-tag-version --allow-same-version $semver
Line 1: gitversion has multiple type of versions. If you run it without any parameter you will get a json file with many variants. Here we are saying that we only want the SemVer. (See also FullSemVer)
Line 2: PowerShell way to create a file and save the contents to it. This can be also made with > but I had encoding issues and the properties file was not being read.
Line 3: This line updates your package.json version. By default it saves a commit to git with the new version. --no-git-tag-version makes sure you don't override it.
And that is it. Now every time you make a build, the version should be generated automatically, your package.json updated and your build should have that specific version name.
App Center
Since I am using App Center to make the builds, I will tell you how you can use this in a Build machine. You only need to use a custom script.
app-center-pre-build.sh
#!/usr/bin/env sh
#Installing GitVersion
OS=$(uname -s)
if [[ $OS == *"W64"* ]]; then
echo "Installing GitVersion with Choco"
choco install GitVersion.Portable -y
else
echo "Installing GitVersion with Homebrew"
brew install --ignore-dependencies gitversion
fi
This is needed because GitVersion is not currently a part of the build machines. Also, you need to ignore the mono dependency when installing, otherwise you get an error when brew tries to link the files.
The #MacRusher version was fine for me. Just for further readers, I had to add .toInteger() to make it work. Since I'm using yarn version --patch to automatically upgrade the version in package.json I also had to take only the two first characters.
Here is the new version:
// On top of your file import a JSON parser
import groovy.json.JsonSlurper
def getVersionFromPackageJson() {
// Read and parse package.json file from project root
def inputFile = new File("$rootDir/../package.json")
def packageJson = new JsonSlurper().parseText(inputFile.text)
// Return the version, you can get any value this way
return packageJson["version"].substring(0,2).toInteger()
}
android {
defaultConfig {
applicationId "your.app.id"
versionName getVersionFromPackageJson()
}
}

Pass cppFlag values to the C++ code using Android Studio and Experimental Gradle Plugin

void DoSomething()
{ ...
m_a = new SomeContext<SOMETHING>(m_data);
...
}
Using Android Studio's Experimental Gradle Plugin, how do I pass the value of SOMETHING as "somevalue"? I think I should be using cppFlags.add('-DSOMETHING=somevalue') but this is not working.
Inside your model, android, ndk block, you can add
#set cppFlag
cppFlags.add("-DSOMETHING=awesome".toString())
And it will be used during compilation.

Getting the Android SDK directory within a gradle task

Recently the gradle plugin for android got updated (with android studio), after which the previous way of getting to the SDK directory ceased to work. The expression
${android.plugin.sdkDirectory}
which worked in an older version now returns the error
Error:(42, 0) No such property: sdkDirectory for class: com.android.build.gradle.LibraryPlugin
What would be the proper way of getting the android SDK directory being used, preferably independent of the user's configuration such as plugin and gradle version? The script needs to be shareable with several users.
Since all the previous answers depend on the environment or specific user intervention on top of normal configuration, I'll just post my technically messy fix.
if (android.hasProperty('plugin')) {
if (android.plugin.hasProperty('sdkHandler')) {
androidPath = android.plugin.sdkHandler.sdkFolder
} else {
androidPath = android.plugin.sdkDirectory
}
} else {
androidPath = android.sdkDirectory
}
Unlike all previous methods, this actually works, but it still looks hacky.
In gradle.properties set location sdkdir=/home/user/android-sdk and then in gradle you can use $sdkdir
I'm using Android gradle plugin v1.2.3 and this works fine:
${android.sdkDirectory}
You can use
$System.env.ANDROID_HOME
export ANDROID_HOME=/xxx/xxx/ in shell, then use it by System.env.ANDROID_HOME in gradle file.

Categories

Resources