How do I package extension functions in a library jar? - gradle

I have a set of extension functions that I would like to package into a jar and include in a project, but I can't seem to assess the extension functions from the jar. I can access non-extension functions from the same jar.
For example if I create a project in Idea including the following file, and build the jar.
package mypackagename.swtbuilder
import org.eclipse.swt.SWT
import org.eclipse.swt.widgets.*
fun myFun() {
println("here")
}
fun Composite.tree(treeStyle: Int = SWT.NONE, init: Tree.() -> Unit): Tree {
val treeWidget = Tree(this, treeStyle)
treeWidget.init()
return treeWidget
}
Then include the jar as a dependency in another project using something like this in the build.gradle file.
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
compile "org.eclipse.swt:org.eclipse.swt.gtk.linux.x86_64:4.5.2"
compile files('/home/john/development/swt-builder/build/libs/swt-builder-1.0-SNAPSHOT.jar')
}
I can use myFun in the new project, but all the extension functions are unresolved.
import mypackagename.swtbuilder.myFun // this import works
import mypackagename.swtbuilder.tree // this import is "grayed out"
fun main(args: Array<String>) {
val display = Display()
myFun() // this function call works
val myTree = tree { // tree is un-resolved
}
}
What do I need to do to make the extension functions visible?

Silly mistake, the extension functions are visible, they just need to be called in the context of object they are extensions of. In this case a Shell which is a subclass of Composite. I had all this working fine when the extensions and the main were in the same project. I was trying to make the extensions a separate library and got ahead of myself trying to make sure they were imported properly.
fun main(args: Array<String>) {
val display = Display()
val myShell = shell {
tree { }
}
myShell.open()
while (!myShell.isDisposed) {
if (!display.readAndDispatch()) display.sleep()
}
display.dispose()
}

Related

Configuring a custom Gradle sourceSet using a closure

I'm trying to develop a Gradle plugin for a language I use (SystemVerilog). I'm still experimenting and figuring things out. Before I write the entire thing as a plugin, I thought it would be best to try out the different parts I need inside a build script, to get a feel of how things should work.
I'm trying to define a container of source sets, similar to how the Java plugin does it. I'd like to be able to use a closure when configuring a source set. Concretely, I'd like to be able to do the following:
sourceSets {
main {
sv {
include '*.sv'
}
}
}
I defined my own sourceSet class:
class SourceSet implements Named {
final String name
final ObjectFactory objectFactory
#Inject
SourceSet(String name, ObjectFactory objectFactory) {
this.name = name
this.objectFactory = objectFactory
}
SourceDirectorySet getSv() {
SourceDirectorySet sv = objectFactory.sourceDirectorySet('sv',
'SystemVerilog source')
sv.srcDir("src/${name}/sv")
return sv
}
SourceDirectorySet sv(#Nullable Closure configureClosure) {
configure(configureClosure, getSv());
return this;
}
}
I'm using org.gradle.api.file.SourceDirectorySet because that already implements PatternFilterable, so it should give me access to include, exclude, etc.
If I understand the concept correctly, the sv(#Nullable Closure configureClosure) method is the one that gives me the ability to write sv { ... } to configure via a closure.
To add the sourceSets property to the project, I did the following:
project.extensions.add("sourceSets",
project.objects.domainObjectContainer(SourceSet.class))
As per the Gradle docs, this should give me the possibility to configure sourceSets using a closure. This site, which details using custom types, states that by using NamedDomainObjectContainer, Gradle will provide a DSL that build scripts can use to define and configure elements. This would be the sourceSets { ... } part. This should also be the sourceSets { main { ... } } part.
If I create a sourceSet for main and use it in a task, then everything works fine:
project.sourceSets.create('main')
task compile(type: Task) {
println 'Compiling source files'
println project.sourceSets.main.sv.files
}
If I try to configure the main source set to only include files with the .sv extension, then I get an error:
sourceSets {
main {
sv {
include '*.sv'
}
}
}
I get the following error:
No signature of method: build_47mnuak4y5k86udjcp7v5dkwm.sourceSets() is applicable for argument types: (build_47mnuak4y5k86udjcp7v5dkwm$_run_closure1) values: [build_47mnuak4y5k86udjcp7v5dkwm$_run_closure1#effb286]
I don't know what I'm doing wrong. I'm sure it's just a simple thing that I'm forgetting. Does anyone have an idea of what that might be?
I figured out what was going wrong. It was a combination of poor copy/paste skills and the fact that Groovy is a dynamic language.
First, let's look at the definition of the sv(Closure) function again:
SourceDirectorySet sv(#Nullable Closure configureClosure) {
configure(configureClosure, getSv());
return this;
}
Once I moved this code to an own Groovy file and used the IDE to show me what is getting called, I noticed that it wasn't calling the function I expected. I was expecting a call to org.gradle.util.ConfigureUtil.configure. Since this is part of the public API, I expected it to be imported by default in the build script. As this page states, this is not the case.
To solve the issue, it's enough to add the following import:
import static org.gradle.util.ConfigureUtil.configure
This will get rid of the cryptic closure related error. It is replaced by the following error, though:
Cannot cast object 'SourceSet_Decorated#a6abab9' with class 'SourceSet_Decorated' to class 'org.gradle.api.file.SourceDirectorySet'
This is caused by the copy/paste error I mentioned. When I wrote the SourceSet class, I drew heavily from org.gradle.api.tasks.SourceSet (and org.gradle.api.internal.tasks.DefaultSourceSet). If we look at the java(Closure) method there, we'll see it has the following signature:
SourceSet java(#Nullable Closure configureClosure);
Notice that it returns SourceSet and not SourceDirectorySet like in my code. Using the proper return type fixes the issue:
SourceSet sv(#Nullable Closure configureClosure)
With this new return type, let's look again at the configuration code for the source set:
sourceSets {
main {
sv {
include '*.sv'
}
}
}
Initially, I thought it was supposed to work as follows: pass main { ... } as a Closure to sourceSets, pass sv { ... } as a Closure to main, and handle the include ... part inside sourceDirectorySet. I banged my head against the wall for a while, because I couldn't find any code in that class hierarchy that takes closures like this.
Now, I think the flow is slightly different: pass main { ... } as a Closure to sourceSets (as initially thought), but call the sv(Closure) function on main (of type sourceSet), passing it { include ... } as the argument.
Bonus: There was one more issue that wasn't related to the "compile" errors I was having.
Even after getting the code to run without errors, it still wasn't behaving as expected. I had some files with the *.svh extension that were still getting picked up. This is because, when calling getSv(), it was creating a new SourceDirectorySet each time. Any configuration that was done previously was getting thrown away each time that this function was called.
Making the sourceDirectorySet a class member and moving its creation to the constructor fixed the issue:
private SourceDirectorySet sv
SourceSet(String name, ObjectFactory objectFactory) {
// ...
sv = objectFactory.sourceDirectorySet('sv',
'SystemVerilog source')
sv.srcDir("src/${name}/sv")
}
SourceDirectorySet getSv() {
return sv
}

Groovy #CompileStatic and #TypeChecked order, bug or misunderstanding

I started getting a strange failure when compiling a gradle task class. This is the task I created:
package sample
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked
import org.gradle.api.artifacts.Dependency
import org.gradle.api.provider.Property
import org.gradle.api.tasks.AbstractCopyTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.bundling.Zip
import sample.internal.DataSourceXmlConfig
#TypeChecked
#CompileStatic
class DataSource extends Zip {
#Internal
final Property<File> warFile = project.objects.property(File.class)
DataSource() {
warFile.convention(project.provider {
def files = project.configurations.getByName('warApp').fileCollection { Dependency d ->
d.name == (archiveFileName.getOrElse("") - (~/\.[^.]+$/))
}
files.empty ? null : files.first()
})
}
/**
* This function is used to specify the location of data-sources.xml
* and injects it into the archive
* #param dsConf The configuration object used to specify the location of the
* file as well as any extra variables which should be injected into the file
*/
#Input
void dataSourceXml(#DelegatesTo(DataSourceXmlConfig) Closure dsConf) {
filesToUpdate {
DataSourceXmlConfig ds = new DataSourceXmlConfig()
dsConf.delegate = ds
dsConf.resolveStrategy = Closure.DELEGATE_FIRST
dsConf.call()
exclude('**/WEB-INF/classes/data-sources.xml')
from(ds.source) {
if (ds.expansions) {
expand(ds.expansions)
}
into('WEB-INF/classes/')
rename { 'data-sources.xml' }
}
}
}
private def filesToUpdate(#DelegatesTo(AbstractCopyTask) Closure action) {
action.delegate = this
action.resolveStrategy = Closure.DELEGATE_FIRST
if (warFile.isPresent()) {
from(project.zipTree(warFile)) {
action.call()
}
}
}
}
When groovy compiles this class, I get the following error:
Execution failed for task ':buildSrc:compileGroovy'.
BUG! exception in phase 'class generation' in source unit '/tmp/bus-server/buildSrc/src/main/groovy/sample/DataSource.groovy'
At line 28 column 28 On receiver: archiveFileName.getOrElse() with
message: minus and arguments: .[^.]+$ This method should not have
been called. Please try to create a simple example reproducing this
error and file a bug report at
https://issues.apache.org/jira/browse/GROOVY
Gradle version: 5.6
Groovy version: localGroovy() = 2.5.4
tl;dr, is this a bug or am I missing something about how these annotations work?
The first thing I tried to do was to remove either one of #TypeChecked and #CompileStatic annotations to see if the error goes away.
This actually fixed the problem right away. Compiling the source with either annotations added was successful, but fails when both are present.
I read some questions and answers regarding the use of both annotations, but none of them seemed to suggest that one cannot use both at the same time.
Finally, I tried switching the order of the annotations to see if that helps and to my surprise, it worked! No compilation errors!
This works:
#CompileStatic
#TypeChecked
class DataSource extends Zip { ... }
At this point, I guess my question would be, is this a bug or is there something I am not understanding about the use of both of these annotations? I'm leaning more towards it being a bug just because of the fact that the order made the error message go away.

How to write extension method for Gradle dependencies {} block

I'm trying to write extension methods for DependencyHandler.
One of main goals to have autocompletion of these methods.
So I wrote extension function in buildSrc project like this (Shortcuts.kt):
fun DependencyHandler.autoValue() {
add("compileOnly", Libs.Auto.autoValueAnnotations)
add("annotationProcessor", Libs.Auto.autoValueCompiler)
}
And registered it as extension module as described here:
# File: src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = buildSrc
moduleVersion = 1.0
extensionClasses = com.example.test.ShortcutsKt
I want to use these methods in build.gradle files like:
dependencies {
...
autoValue()
}
It appears in autocompletion list inside dependencies{} block, but at configuration time I got error:
org.gradle.internal.metaobject.AbstractDynamicObject$CustomMessageMissingMethodException: Could not find method autoValue() for arguments [] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
at org.gradle.internal.metaobject.AbstractDynamicObject.methodMissingException(AbstractDynamicObject.java:179)
at org.gradle.internal.metaobject.ConfigureDelegate.invokeMethod(ConfigureDelegate.java:87)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeOnDelegationObjects(ClosureMetaClass.java:430)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:369)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:69)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:158)
at build_dh4v5lw1dkt4b2nii5ope5rmy$_run_closure1.doCall(/.../app/build.gradle:29)
Gradle DefaultDependencyHandler implements custom method resolution strategy (to handle scopes notation like testCompile(smth)), so additional methods can be added via dependencies.ext property:
dependencies.ext.autoValue = {
dependencies.add("compileOnly", Libs.Auto.autoValueAnnotations)
dependencies.add("annotationProcessor", Libs.Auto.autoValueCompiler)
}
But in this case you don't get autocompletion.
To enable autocompletion you can mix this two approaches and proxy extension methods from buildSrc via dependencies.ext:
import com.example.test.ShortcutsKt
import java.lang.reflect.Method
import java.lang.reflect.Modifier
// DependencyHandler implementation resolves all undeclared methods by self,
// so we need to add these extension methods to dependencies.ext
def methodNames = ShortcutsKt.declaredMethods
.findAll { isDependencyHandlerExtension(it) }
.collect { it.name }
.unique()
methodNames.each { String methodName ->
dependencies.ext[methodName] = { Object... args ->
ShortcutsKt."$methodName"(dependencies, *args)
}
}
private static boolean isDependencyHandlerExtension(Method method) {
return Modifier.isPublic(method.getModifiers()) &&
Modifier.isStatic(method.getModifiers()) &&
method.parameterCount > 0 &&
method.parameterTypes[0] == DependencyHandler.class
}

How to access variant.outputFileName in Kotlin

We've been using a snippet like this one to rename the APK file generated by our Gradle build:
android.applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "${variant.name}-${variant.versionName}.apk"
}
}
Source: https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration#variant_output
I am now in the process of converting my build.gradle to build.gradle.kts, i. e. to the Gradle Kotlin DSL. This is one of the last missing pieces: I can't figure out how to access outputFileName.
According to the API docs it does not even seem to exist:
BaseVariant.getOutputs() returns a DomainObjectCollection<BaseVariantOutput> which provides the all method used in the snippet.
BaseVariantOutput extends OutputFile which extends VariantOutput but none of these has an outputFileName or any getters or setters of a matching name.
So, I suspect there is some advanced Groovy magic at work to make this work - but how do I get there in Kotlin?
A little simplified version of #david.mihola answer:
android {
/**
* Notes Impl: Use DomainObjectCollection#all
*/
applicationVariants.all {
val variant = this
variant.outputs
.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
.forEach { output ->
val outputFileName = "YourAppName - ${variant.baseName} - ${variant.versionName} ${variant.versionCode}.apk"
println("OutputFileName: $outputFileName")
output.outputFileName = outputFileName
}
}
}
Browsing through the source code of the Android Gradle plugin, I think I found the answer - here we go:
We are actually dealing with objects of type BaseVariantOutputImpl and this class does have both these methods:
public String getOutputFileName() {
return apkData.getOutputFileName();
}
public void setOutputFileName(String outputFileName) {
if (new File(outputFileName).isAbsolute()) {
throw new GradleException("Absolute path are not supported when setting " +
"an output file name");
}
apkData.setOutputFileName(outputFileName);
}
Using this knowledge we can now:
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
and then cast our target objects like so:
applicationVariants.all(object : Action<ApplicationVariant> {
override fun execute(variant: ApplicationVariant) {
println("variant: ${variant}")
variant.outputs.all(object : Action<BaseVariantOutput> {
override fun execute(output: BaseVariantOutput) {
val outputImpl = output as BaseVariantOutputImpl
val fileName = output.outputFileName
.replace("-release", "-release-v${defaultConfig.versionName}-vc${defaultConfig.versionCode}-$gitHash")
.replace("-debug", "-debug-v${defaultConfig.versionName}-vc${defaultConfig.versionCode}-$gitHash")
println("output file name: ${fileName}")
outputImpl.outputFileName = fileName
}
})
}
})
So, I guess: Yes, there is some Groovy magic at work, namely that Groovy's dynamic type system allows you to just access getOutputFileName and setOutputFileName (by way of the abbreviated outputImpl.outputFileName syntax, as in Kotlin) from your code, hoping they will be there at runtime, even if the compile time interfaces that you know about don't have them.
Shorter version using lambdas:
applicationVariants.all{
outputs.all {
if(name.contains("release"))
(this as BaseVariantOutputImpl).outputFileName = "../../apk/$name-$versionName.apk"
}
}
This will place APK into app/apk folder with name made of variant name and version code.
You can change the format of filename as you wish.
Important: it must be done only on release builds, because ".." in path corrupts debug build process with strange errors.
For libraryVariants it is possible to change output file name without accessing internal api:
libraryVariants.all {
outputs.all {
packageLibraryProvider {
archiveFileName.set("yourlibrary-${buildType.name}.aar")
}
}
}
For Kotlin KTS.
NOTE: This is considered a temporal soluciĆ³n, until a proper way to do it in KTS is released by Android team.
Working in AGP v7.1.2 it might work also in lower versions of AGP.
:app build.gradle
android {
// ...
this.buildOutputs.all {
val variantOutputImpl = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
val variantName: String = variantOutputImpl.name
val outputFileName = "custom-name-${variantName}.apk"
variantOutputImpl.outputFileName = outputFileName
}
}

How do I access my app code from my tests in a Swift project?

I have an Swift Xcode project with code such as:
class Utils: NSObject {
class func cleanString (input: String, trim: Bool) -> String {
// ...
}
}
and then I try to test it:
import XCTest
class AppTests: XCTestCase {
func testConfiguratio() {
Utils.cleanString("foo", trim: true)
}
}
but I get this error:
/Users/pupeno/Projects/macninja/AppTests/AppTests.swift:35:9: Use of unresolved identifier 'Utils'
I have Host Application APIs enabled:
What am I missing?
As it has been said already, the library code and the test code are 2 different modules. So you have to import the library into the test code and also make the functions that you want to test public, e.g:
public class Utils: NSObject {
public class func cleanString (input: String, trim: Bool) -> String {
// ...
}
}
and
import XCTest
import Utils
class AppTests: XCTestCase {
func testConfiguratio() {
Utils.cleanString("foo", trim: true)
}
}
If you want to see working code look at my IBANtools library project which implements exactly this scenario (class functions, swift framework, lots of testing).
If this is an OSX project - make sure you included
#Testable import YOURPROJECTNAME
Above the 'class AppTests: XCTestCase'
and clean your project files.
My Previous question where I had a similar issue is here
Hope this helps ( even a year later...)
The module that contains your tests is distinct from the module that contains your app code. When you want to access classes that are contained within a separate module you need to ensure that the to-be-imported classes are marked as public:
public class Utils: NSObject {
class func cleanString (input: String, trim: Bool) -> String {
// ...
}
}

Resources