After coming to the new company, PHS began to take over the core technology – fast plug-in, see the legend of the core technology, PHS was stunned, ah this, groovy write plug-in, groovy seriously, 2202 years ago, plug-in zha also use Groovy to write, MY new handwriting plug-in also changed Kotlin, Open your mouth and you’ll never write Groovy. Forget it. But, uh, it’s not a bad job, so learn.

At first, I talked to some of the big boys in the group, and I was ready to do something to the historical code, and all of them moved to Kotlin, but it turned out… I don’t know how to write kt. The article ended and PHS was dismissed.

I’m just kidding. I’m still on duty. Work has to go on. Since I can’t move them all, I can write the new requirements in Kotlin. Hey, that’s interesting.

How does Groovy mix with Java and Kotlin

How to implement mixed editing

I don’t know. Let’s see what the official says. Gradle source code has this code to illustrate how groovy compilation is prioritized over Java compilation.

// tag::compile-task-classpath[]
tasks.named('compileGroovy') {
    // Groovy only needs the declared dependencies
    // (and not longer the output of compileJava)
    classpath = sourceSets.main.compileClasspath
}
tasks.named('compileJava') {
    // Java also depends on the result of Groovy compilation
    // (which automatically makes it depend of compileGroovy)
    classpath += files(sourceSets.main.groovy.classesDirectory)
}
// end::compile-task-classpath[]
Copy the code

Oh, you can write it like that. Should I just copy it and change the name? I can write Kotlin. Oh yeah!

compileKotlin {
    classpath = sourceSets.main.compileClasspath
}
compileGroovy {
    classpath += files(sourceSets.main.kotlin.classesDirectory)
}
Copy the code

Run one run, and if there are no accidents, you will see this error.

Hey, why can’t I just copy it and run? I suspect the kotlin classesDiretory has a problem, the breakpoint to see a wave of compileGroovy sourceSets in this task. The main. Kotlin. ClassesDirectory is what. It looks something like this. It’s DefaultDirectoryVar.

If the value of a classesDirectory is undefined, the value of a classesDirectory should be undefined

Kotlin’s classDirectory is not available at this point. Try adding a catch breakpoint

Specific why is not available at this time, I do not have a more detailed in-depth, big guy know, can be generous to give advice.

SO searched through a bunch of solutions and saw a solid response from compile-groovy-and-kotlin.

compileGroovy.dependsOn compileKotlin
compileGroovy.classpath += files(compileKotlin.destinationDir)
Copy the code

It works if you try, but why does it work? And what does the top official code mean? What are some weird nouns? Let’s blow them

About souceset

We’ve all seen/written code like this when we started writing Android

sourceSets {
    main.java.srcDirs = ['src/java']}Copy the code

My understanding is to specify the source directory of Java in main Sourceset. SourceSets is a container for creating a set of SourceSets, such as main, test. The Java, Groovy, and Kotlin directories in main are source directorysets. And turn them into the class files in the build/classes/sourceDirectorySet below, namely destinationDirectory.

For example, main corresponds to the SourceSet interface, whose implementation is DefaultSourceSet. And the main following groovy, Java, is kotlin SourceDirectorySet interface, its implementation is DefaultSourceDirectorySet.

Gradle defines sourceset as:

  • The source files and where they’re located locate the source code
  • The compilation classpath, including any required Dependencies (via Gradle Configurations) Compile-time classpath
  • Where the compiled Class files are placed

Input file + compile time classpath goes through AbstractCompile Task to get the output class directory

The second compile-time classpath, also seen in the project, the sourceSetImplementation declares the sourceSet dependency. SourceDirectorySet#destinationDirectory is used to specify the output directory of compile task. The SourceDirectorySet#classesDirectory is consistent with this value. Once again, think of the SourceDirectorySet as Java, Groovy, kt written in DSL.

The official documentation describes classesDirectory as

The directory property that is bound to the task that produces the output via SourceDirectorySet.compiledBy(org.gradle.api.tasks.TaskProvider, java.util.function.Function). Use this as part of a classpath or input to another task to ensure that the output is created before it is used. Note: To define the path of the output folder use SourceDirectorySet.getDestinationDirectory()

Carelessness is classesDirectory compile with this task output is associated, concrete is through SourceDirectorySet.com piledBy () method, this field is decided by destinationDirectory field. Check the DefaultSourceDirectorySet# compiledBy method

    public <T extends Task> void compiledBy(TaskProvider<T> taskProvider, Function<T, DirectoryProperty> mapping) {
        this.compileTaskProvider = taskProvider;
        taskProvider.configure(task -> {
            if (taskProvider == this.compileTaskProvider) { mapping.apply(task).set(destinationDirectory); }}); classesDirectory.set(taskProvider.flatMap(mapping::apply)); }Copy the code

Semantically classesDirectory == destinationDirectory.

Now we can understand the official demo, which simply says Compile Groovy task first, then Compile Java Task.

tasks.named('compileGroovy') {
    classpath = sourceSets.main.compileClasspath / / 1
}
tasks.named('compileJava') {
    classpath += files(sourceSets.main.groovy.classesDirectory) / / 2
}
Copy the code

What you might not understand is what’s going on in comment 1, 2, where I asked my group leader, reset the classpath of compileGroovy task so it doesn’t depend on compile Java classpath, There’s a line in the GroovyPlugin source code

        classpath.from((Callable<Object>) () -> sourceSet.getCompileClasspath().plus(target.files(sourceSet.getJava().getClassesDirectory())));
Copy the code

As you can see, GroovyPlugin is actually dependent on Java’s classpath. Here we need to change the compilation timing of Groovy and Java to disconnect this dependency.

CompileJava < compileGroovy output property > compileJava < compileGroovy output property > compileJava < compileGroovy dependson > compileGroovy

Specifically why Kotlin’s not good, I have not figured out, know the big guy can give advice.

The answer on SO is similar and more direct

compileGroovy.dependsOn compileKotlin
compileGroovy.classpath += files(compileKotlin.destinationDir)
Copy the code

Make compileGroovy dependent on the compileKotlin task and add the compileKotlin output to the compileGroovy classPath. Adding another task’s output to the task’s classPath automatically depends on another task’s output. So you could actually write it this way

compileGroovy.classpath += files(compileKotlin.destinationDir)
Copy the code

I’ve tested that sparrow food can run. Since Groovy and Java both have a classpath of main, compileGroovy will automatically dependency compileKotlin if your classpath is set to Main. Try bai

compileKotlin.classpath = sourceSets.main.compileClasspath
Copy the code

You can see that Kotlin’s order of execution goes to the front.

In the project, I noticed that Kotlin was running ahead of compile, and that Kotlin classes cannot rely on any Java or Groovy dependencies. Circular dependsOn Hierarchy found in the Kotlin source sets In my personal opinion, this is a compromise on the transformation of the historical code, using Kotlin to develop on the new requirements, some of the same function of the tool class can be translated into KT translation, not just rewrite a set.

summary

  • In this section, two schemes for implementing mixed programming are described. Written differently, they essentially make one task depend on the output of another task
/ / 1
compileGroovy.classpath += files(compileKotlin.destinationDir)
/ / 2
compileKotlin.classpath = sourceSets.main.compileClasspath
Copy the code
  • My understanding of SourceSet and SourceDirectorySet
  • The present situation of the practice mixed programming scheme in the project

Groovy’s interesting syntactic candy

In the process of writing Groovy, I encountered a major problem, the code is not understand, there are some strange and strange syntax candy, at first glance confused, you want to take a look.

includes*.tasks

The warehouse of our company is in the structure of large warehouse, and the warehouse and sub-warehouse are connected by Composite build. So how can the task in the host trigger the includeBuild repository to execute the corresponding repository? It’s done with this line of code

tasks.register('publishDeps') {
    dependsOn gradle.includedBuilds*.task(':publishIvyPublicationToIvyRepository')}Copy the code

What does *. Task come after includeBuilds*. Task? IncludeBuilds look at the source and find a List. I don’t understand Groovy, but AT least I can understand Kotlin. Let me see what kt is written on the right side of the official documentation.

tasks.register("publishDeps") {
    dependsOn(gradle.includedBuilds.map { it.task(":publishMavenPublicationToMavenRepository")})}Copy the code

This is a List map operation. Take a look at the groovy syntactic sugar and write code to see how it compiles to class

def list = ["1"."22"."333"]
def lengths = list*.size()
lengths.forEach{
    println it
}
Copy the code

Compiled into the class

        Object list = ScriptBytecodeAdapter.createList(new Object[]{"1"."22"."333"});
        Object lengths = ScriptBytecodeAdapter.invokeMethod0SpreadSafe(Groovy.class, list, (String)"size");
        var1[0].call(lengths, new Groovy._closure1(this.this));
Copy the code

In ScriptBytecodeAdapter. InvokeMethod0SpreadSafe implementation inside actually built a List one by one again for the map element in the List.

String.execute

This is to execute a shell instruction such as “ls-al “.execute(). When I first saw this, I thought it was similar to Kotlin’s extension function

public static Process execute(final String self) throws IOException {
        return Runtime.getRuntime().exec(self);
 }
Copy the code

As you can see, receiver is its first parameter

public static String deco(final String self) throws IOException {
        return self + "deco"
    }
// println "".deco()
Copy the code

Run under, oh roar, can not run, reported MissingMethodException. It doesn’t seem to be universal. I went through the Groovy documentation and found this one

Static methods are used with the first parameter being the destination class, i.e. public static String reverse(String self) provides a reverse() method for String.

I’m not sure if there’s a way to customize it. If you know, please leave a comment in the comments section.

How do you write the Range

Groovy also has a concept similar to Kotlin’s Range, which contains a Range of.. , which does not include the right boundary (until) is.. <

Try with resources

I had an OKHttp connection leak problem and the code prototype looked something like this

if (xxx) {
  response.close()
} else {
  // behavior
}
Copy the code

So, Response is not being closed on the else branch, so of course you can simply close on the else branch and fill in the try and catch pocket, But the book Effective Java mentions that closing a try-with-resource for a resource is better than closing a try cactch. Groovy try-with-resource is implemented with the withCloseable extension – Public static def withCloseable(Closeable self, Closure Action). The final code looks like this

Response.withCloseable { reponse ->
  if (xxx) {
    
  } else{}}Copy the code
<<

The left shift operator in Groovy is also overridden, and Kotlin doesn’t support it. He uses more scenarios. My first impression is that Task overwrites this operator as a doLast shorthand, but it is not available on gradle7.x. Other common ones are file writes and list additions.

def file = new File("xxx")
file << "text"
def list = []
list << "aaa"
Copy the code

Groovy’s opinion

If Kotlin were better Java, groovy would be more than Java, a little more scripted, a little more dynamic (as evidenced by its decomcompiled bytecodes), with a high upstart curve, but one person mastering the language and maintaining a project on his own, In fact, Groovy is no less efficient than Kotlin and Java. The most obvious example is maven publish, which shows the difference between Groovy and Kotlin.

// Groovy
def mavenSettings = {
            groupId 'org.gradle.sample'
            artifactId 'library'
            version '1.1'
        }
 def repSettings = {
            repositories {
                maven {
                    url = mavenUrl
                }
            }
        }
​
afterEvaluate {
  publishing {
      publications {
          maven(MavenPublication) {
              ConfigureUtil.configure(mavenSettings, it)
              from components.java
          }
      }
     ConfigureUtil.configure(repoSettings, it)
  }
  def publication = publishing.publications.'maven' as MavenPublication
  publication.pom.withXml { 
     // inject msg}}Copy the code
// Kotlin
// Codes are borrowed from (sonatype-publish-plugin)[https://github.com/johnsonlee/sonatype-publish-plugin/]
fun Project.publishing(
        config: PublishingExtension. () - >Unit
) = extensions.configure(PublishingExtension::class.java, config)
val Project.publishing: PublishingExtension
    get() = extensions.getByType(PublishingExtension::class.java)
​
val mavenClosure = closureOf<MavenPublication> { 
   groupId = "org.gradle.sample"
   artifactId = "library"
   version = "1.1"
}
val repClosure = closureOf<PublishingExtension> {
    repositories {
        maven {
            url = mavenUrl
        }
    }
}
afterEvaluate {
    publishing {
        publications {
            create<MavenPublication>("maven") {
               ConfigureUtil.configure(mavenClosure, this)
                from(components["java"])
            }
        }
       ConfigureUtil.configure(repoClosure, this)}val publication = publishing.publications["maven"] as MavenPublication
    publication.pom.withXml { 
             // inject msg}}Copy the code

I think, if it’s faster and cleaner to write groovy in a plugin, as we’re good at it, and it’s a one-man commercial project, why not? It’s a good thing for him. There’s no good or bad language. I don’t want to compare dynamic and static languages.I chose Kotlin because I am not good at writing Groovy. I wrote For months and every time I applied groovy after a plugin was released, I would get a syntax error the first time and debugging scalp would get tingled, so I finally came to a compromise. I used Kotlin for new code and groovy for old code. And reference toKOGE@2BABGradle responded positively to the Groovy vs Kotlin debate.”Prefer using a statically-typed language to implement a plugin”@Gradle. Well, I’ll stick with Kotlin.