Related to this ticket Parameters for annotation processors are disabled and undocumented
How do we use annotation_processors and annotation_processor_deps ?
Im using realm in a sample Android app and without the annotations (for #RealmClass and #RealmMoudule) the app crashes when built via buck (works normally if built via gradle).
In case anyone stumbles on this, the way to use annotation processors with buckbuild is:
The annotation_processors is a immutable list of processor class. You can identify this by the package name used in META-INF/services/javax.annotation.processing.Processor file, example: Realm Processor
The annotation_processor_deps is a immutable list of Rules (generally prebuilt_jar or android_prebuilt_aar) holding the annotation processor
A sample buck build file of a project that uses Realm Java
prebuilt_jar(
name = 'realm',
binary_jar = 'libs/realm-android-0.82.2.jar'
)
android_library(
name = 'main-lib',
srcs = glob(['app/src/main/java/com/yourcompany/project/**/*.java']),
deps = [
':supportv4',
':all-jars',
':build-config',
':res',
],
annotation_processors = ['io.realm.processor.RealmProcessor'],
annotation_processor_deps = [':realm']
)
Related
Background
Suppose I make an Android library called "MySdk", and I publish it on Jitpack/Maven.
The user of the SDK would use it by adding just the dependency of :
implementation 'com.github.my-sdk:MySdk:1.0.1'
What I'd like to get is the "1.0.1" part from it, whether I do it from within the Android library itself (can be useful to send to the SDK-server which version is used), or from the app that uses it (can be useful to report about specific issues, including via Crashlytics).
The problem
I can't find any reflection or gradle task to reach it.
What I've tried
Searching about it, if I indeed work on the Android library (that is used as a dependency), all I've found is that I can manage the version myself, via code.
Some said I could use BuildConfig of the package name of the library, but then it means that if I forget to update the code a moment before I publish the dependency, it will use the wrong value. Example of using this method:
plugins {
...
}
final def sdkVersion = "1.0.22"
android {
...
buildTypes {
release {
...
buildConfigField "String", "SDK_VERSION", "\"" + sdkVersion + "\""
}
debug {
buildConfigField "String", "SDK_VERSION", "\"" + sdkVersion + "-unreleased\""
}
}
Usage is just checking the value of BuildConfig.SDK_VERSION (after building).
Another possible solution is perhaps from gradle task inside the Android-library, that would be forced to be launched whenever you build the app that uses this library. However, I've failed to find how do it (found something here)
The question
Is it possible to query the dependency version from within the Android library of the dependency (and from the app that uses it, of course), so that I could use it during runtime?
Something automatic, that won't require me to update it before publishing ?
Maybe using Gradle task that is defined in the library, and forced to be used when building the app that uses the library?
You can use a Gradle task to capture the version of the library as presented in the build.gradle dependencies and store the version information in BuildConfig.java for each build type.
The task below captures the version of the "appcompat" dependency as an example.
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.0'
}
task CaptureLibraryVersion {
def libDef = project.configurations.getByName('implementation').allDependencies.matching {
it.group.equals("androidx.appcompat") && it.name.equals("appcompat")
}
if (libDef.size() > 0) {
android.buildTypes.each {
it.buildConfigField 'String', 'LIB_VERSION', "\"${libDef[0].version}\""
}
}
}
For my example, the "appcompat" version was 1.4.0. After the task is run, BuildConfig.java contains
// Field from build type: debug
public static final String LIB_VERSION = "1.4.0";
You can reference this field in code with BuildConfig.LIB_VERSION. The task can be automatically run during each build cycle.
The simple answer to your question is 'yes' - you can do it. But if you want a simple solution to do it so the answer transforms to 'no' - there is no simple solution.
The libraries are in the classpath of your package, thus the only way to access their info at the runtime would be to record needed information during the compilation time and expose it to your application at the runtime.
There are two major 'correct' ways and you kinda have described them in your question but I will elaborate a bit.
The most correct way and relatively easy way is to expose all those variables as BuildConfig or String res values via gradle pretty much as described here. You can try to generify the approach for this using local-prefs(or helper gradle file) to store versions and use them everywhere it is needed. More info here, here, and here
The second correct, but much more complicated way is to write a gradle plugin or at least some set of tasks for collecting needed values during compile-time and providing an interface(usually via your app assets or res) for your app to access them during runtime. A pretty similar thing is already implemented for google libraries in Google Play services Plugins so it would be a good place to start.
All the other possible implementations are variations of the described two or their combination.
You can create buildSrc folder and manage dependencies in there.
after that, you can import & use Versions class in anywhere of your app.
I have an .aar third party library that I want to use in Xamarin Android. So I created a new Android Bindings Library, added the aar-library and changed the Build action of the aar file to LibraryProjectZip like described here: https://learn.microsoft.com/en-us/xamarin/android/platform/binding-java-library/binding-an-aar
Nothing else was changed and I would expect the project to compile and generate a dll file.
Instead I get a lot of errors saying Error CS0542 'xy': member names cannot be the same as their enclosing type.
When I jump to the origin of the error, I find the errors in generated code by Visual Studio with the classes looking something like:
public abstract class Albumin : Java.Lang.Object {
internal Albumin ()
{
}
// (removed for readability)
[Register ("ALBUMIN")]
public const string Albumin = (string) "albumin";
I cannot modify the source code of the library.
What can I do in order to build the Binding Library successfully?
Thank you very much #Leo Zhu for the answer in the comments:
The solution is Renaming Members.
So in my case the Metadata.xml in die Binings Library would look like the following:
<attr path="/api/package[#name='com.company.android.sdk.dataclass']/interface[#name='DataClass.Albumin']/field[#name='ALBUMIN']" name="name">ALBUMIN_Binding</attr>
I wanted to create sbt cross platforms (Hello world app ) including JVM , Scala Native , Android and scalajs , I have succeed in compiling all of them except the android and this is my build.sbt:
name := "Cross-Platforms-ScalaNative-JVM"
import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType}
val sharedSettings = Seq(scalaVersion := "2.11.12")
lazy val bar =
// select supported platforms
crossProject(JSPlatform, JVMPlatform, NativePlatform)
.crossType(CrossType.Full)
.settings(sharedSettings)
.jsSettings(
libraryDependencies += "org.querki" %%% "jquery-facade" % "1.2")
.jvmSettings(/* ... */)
.nativeSettings(/* ... */)
lazy val barJS = bar.js
lazy val barJVM = bar.jvm
lazy val barNative = bar.native
My question how I can create an android platform in this project; what changes should be to my SBT?
Android build is basically an JVM build with an extra steps - taking JVM bytecode, optionally running proguard to minimize code and compiling it into Android format.
Here you have a lot of information about how to setup normal android build in sbt: https://scala-android.org/quickstart/.
The difference would be by applying androidBuild (and all Android-related settings) only to JVM projects:
val myProjectJVM = myProject.jvm
.settings(androidBuild: _*)
However, considering that Android build and normal Scala build will look differently (as normal JVM application has architecturally little to do with Android apps - e.g. Main.main(Array[String]) vs Android fragments and services) it would be a better idea to have a module for some shared JVM code and a dedicated projects for the platform-specific parts:
val commonJVM = myProject.jvm
val myProjectJVM = project.in(file("java-specific"))
.dependsOn(commonJVM)
val myProjectAndroid = project.in(file("android-specific"))
.settings(androidBuild)
.dependsOn(commonJVM)
That architectural difference is most likely a reason why nobody even considers making Android a fourth option in a cross project - you have a different runtime dependencies, a different way to start the application, so you would have more parts of the code that differ than in common. Submodules with code dedicated to platform depending on common dependency (even cross-compiled one) is a better idea.
I’m working on a multiplaform project, iOS and JVM (I’m not targeting Android directly). Depending on the build type (debug or release) I want to configure the logging level (i.e. to print only errors in release). Since there is no a BuildConfig class available, how can I know from commonMain the build type?
Not a direct answer to the question, but for android/ios one can define a property like this:
in commonMain:
expect val isDebug: Boolean
in androidMain:
actual val isDebug = BuildConfig.DEBUG
in iosMain:
actual val isDebug = Platform.isDebugBinary
It is not possible right now for all possible targets. Build type is resolved only for android.
But you can create two versions of the module e.g. module and module-debug.
Then declare in module:
const val IS_DEBUG = false
in module-debug:
const val IS_DEBUG = true
Finally in the resulting application or module gradle configuration you can declare dependency on what you need. E.g.:
if (DEBUG_ENV) // you need to set DEBUG_ENV from property or environment variable
implementation(project(":module-debug"))
else
implementation(project(":module"))
or for android:
debugImplementation(project(":module-debug"))
releaseImplementation(project(":module"))
This way you can change logic using IS_DEBUG constant for every target or can create even completely different implementations of something in debug and release modules.
I'm working on a project where we have several modules, each one of these with a number of dependencies to other modules, and so on and so forth. We migrated our project to Gradle (and also changed the structure to accommodate it to Gradle's defaults) some time ago because we needed versatility when building different versions of the app: free vs paid, debug (no proguard) vs release (proguard), etc. We were really thrilled when we completed the migration, but this happiness quickly became diluted in a puddle of mud when the build time started to become a pain. Making a rather simple change in the code and deploy the app on the phone takes anywhere 90-120 seconds, and that is simply unacceptable.
So we decided to give Buck a try, as we have heard nothing but good words from other developers. After very little time (way less than with Gradle) we managed to build our app successfully (which, unfortunately, doesn't mean "generating an APK correctly", but at least it built). The thing is that, as a general rule, we have one manifest file per flavor, and unless I have overlooked something fundamental, Buck only lets you specify manifest files when using the rule android_binary. The result of this is that the generated APK's manifest file contains only the boilerplate code of the first level module from which Gradle's build chain starts:
main -> debug -> free/paid -> common
That is, the manifest file only contains main's manifest. This rule is located in the top level BUCK file, which is the file that contains the alias specified in .buckconfig.
I'm pretty sure I'm doing something wrong. It wouldn't make sense Buck doesn't allow you to not have multiple manifests.
Any ideas?
Francis Toth (https://stackoverflow.com/users/1873643/francis-toth) came up with a very comprehensive explanation in the Buck discussion group:
Just to give you the context, I have an application composed of different library modules, each having an Android-Manifest, some resources, assets etc... and a BUCK file which pretty much look like this :
android_library(
name = 'src',
srcs = glob(['src/main/java/**/*.java']),
deps = DEPENDENCIES + [':res'],
visibility = [ 'PUBLIC' ],
exported_deps = DEPENDENCIES
)
android_resource(
name = 'manifest',
manifest = 'src/main/AndroidManifest.xml',
deps = ['//othermodule:manifest'],
visibility = [ 'PUBLIC' ],
)
android_resource(
name = 'assets',
assets = 'src/main/assets',
visibility = [ 'PUBLIC' ],
)
android_resource(
name = 'res',
res = 'src/main/res',
package = 'com.sherpa.android',
visibility = [ 'PUBLIC' ],
)
project_config(
src_target = ':src',
src_roots = ['src/main/java']
)
My app BUCK file looks like this :
android_binary (
name = 'bin',
manifest = ':manifest', # Here I make a reference on the android_manifest build rule described below
target = 'Google Inc.:Google APIs:19',
keystore = ':debug_keystore',
deps = BINARY_DEPENDENCIES
)
MANIFEST_DEPENDENCIES = ['//module1:manifest', '//module2:manifest']
android_manifest (
name = 'manifest',
skeleton = 'AndroidManifest.xml', # The app's base manifest
deps = MANIFEST_DEPENDENCIES # App's manifest will be merged with all its dependencies (module1 and module2's manifest)
)
Your problem, as far as I understand seems to be pretty similar. In order to merge the manifest, you have to create a android_resource build rule which tells where is your module library along with the manifests it has to be merged with.
And that solved my problem :-)
In buck the manifest merger will never override anything in the manifest tag. That includes the package attribute, the debuggable attribute, etc.
There is a feature of manifest_entries in android_binary that isn't documented but it's how OKBuck does build flavors. Before packaging the manifest, any placeholders in manifest_entries get substituted for any expressions in AndroidManifest.xml
So if your manifest tag looks something like this:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package = "com.whatever${flavor}"
android:debuggable = "${debuggable}"
>
And your manifest_entries in your android_binary looks like this:
manifest_entries = {
'placeholders': {
'flavor': '.test',
'debuggable': 'true',
},
},
The resulting manifest will look like this:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package = "com.whatever.test"
android:debuggable = "true"
>
Then you can build up your manifest_entries placeholders with dictionary manipulation and flatten_dicts()