All Downloads are FREE. Search and download functionalities are using the official Maven repository.

name.remal.gradleplugins.toolkit.buildlogic.cross-compile.gradle Maven / Gradle / Ivy

import static org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE
import static org.gradle.api.attributes.Category.DOCUMENTATION
import static org.gradle.api.attributes.Category.ENFORCED_PLATFORM
import static org.gradle.api.attributes.Category.REGULAR_PLATFORM
import static org.gradle.api.attributes.Category.VERIFICATION

import java.util.regex.Matcher
import java.util.regex.Pattern
import org.gradle.util.GradleVersion
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

buildscript {
    dependencies {
        classpath platform("org.ow2.asm:asm-bom:9.4")
        classpath 'org.ow2.asm:asm-tree'
    }
    repositories {
        mavenCentral()
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

allprojects {
    Collection crossCompileProjects = project.subprojects.findAll { it.name.startsWith('cross-compile--') }

    crossCompileProjects.forEach { crossCompileProject ->
        if (!crossCompileProject.subprojects.isEmpty()) {
            throw new GradleException("Cross-compile project ${crossCompileProject} can't have subprojects")
        }
    }

    crossCompileProjects.forEach { crossCompileProject ->
        ; [
            'maven-publish',
        ].forEach { forbiddenPluginId ->
            crossCompileProject.pluginManager.withPlugin(forbiddenPluginId) { appliedPlugin ->
                throw new GradleException("${appliedPlugin.id} plugin can't applied for cross-compile project ${crossCompileProject}")
            }
        }
    }

    pluginManager.withPlugin('java') {
        crossCompileProjects.forEach { crossCompileProject ->
            Pattern pattern = Pattern.compile(/^cross-compile--(.+)-(common|(\d(?:\.\d)*)-(lt|lte|eq|ne|gte|gt))$/)
            Matcher matcher = pattern.matcher(crossCompileProject.name)
            if (!matcher.matches()) {
                throw new GradleException("Project name of cross-compile project ${crossCompileProject} doesn't match to /${pattern}/")
            }

            crossCompileProject.apply plugin: 'java-library'
            crossCompileProject.apply plugin: 'name.remal.test-source-sets'

            tasks.named('classes') { dependsOn(project.provider { crossCompileProject.tasks.classes }) }
            tasks.named('jar') { filesMatching('**/package-info.class') { duplicatesStrategy(DuplicatesStrategy.EXCLUDE) } }
            tasks.matching { it.name == 'sourcesJar' }.configureEach { filesMatching('**/package-info.java') { duplicatesStrategy(DuplicatesStrategy.EXCLUDE) } }
            afterEvaluate {
                sourceSets.main.allSource.srcDirs(project.provider { crossCompileProject.sourceSets.main.allSource.srcDirs })
                sourceSets.main.output.dir(project.provider { crossCompileProject.sourceSets.main.output })
            }

            ; [
                'classes',
                'javadocJar',
                'sourcesJar',
            ].forEach { String taskName ->
                tasks.matching { it.name == taskName }.all {
                    dependsOn(project.provider { crossCompileProject.tasks.matching { it.name == taskName } })
                }
            }

            crossCompileProject.tasks.withType(Javadoc).configureEach { enabled = false }
            tasks.withType(Javadoc).configureEach { Javadoc task ->
                task.source(
                    project.provider {
                        crossCompileProject.tasks.withType(Javadoc)
                            .collect { it.source }
                    }
                )
            }

            ; [
                'api',
                'implementation',
            ].forEach { confName ->
                crossCompileProject.configurations.getByName(confName).allDependencies.all { Dependency dependency ->
                    String category = dependency.attributes.getAttribute(CATEGORY_ATTRIBUTE)?.name
                    if (![REGULAR_PLATFORM, ENFORCED_PLATFORM, DOCUMENTATION, VERIFICATION].contains(category)) {
                        throw new GradleException("Can't add ${dependency} dependcency to cross-compile project $crossCompileProject '$confName' configuration, as this configuration can't have dependencies")
                    }
                }
            }

            String dependency = matcher.group(1)
            String versionSpec = matcher.group(2)
            if (versionSpec == 'common') {
                dependencies {
                    compileOnly project(crossCompileProject.path)
                }

            } else {
                crossCompileProjects.find { it.name == "cross-compile--$dependency-common" }?.with { commonProject ->
                    crossCompileProject.dependencies {
                        compileOnly project(commonProject.path)
                    }
                }

                String version = matcher.group(3)
                String versionExtender = matcher.group(4)

                if (dependency == 'java') {
                    if (!version.matches(/^\d+$/)) throw new GradleException("Invalid Java version for cross-compile project $crossCompileProject (only major versions are supported): $version")
                    int majorVersion = Integer.parseInt(version)
                    JavaVersion javaVersion = JavaVersion.toVersion(majorVersion)
                    crossCompileProject.sourceCompatibility = crossCompileProject.targetCompatibility = javaVersion.toString()

                } else if (dependency == 'gradle') {
                    GradleVersion gradleVersion = GradleVersion.version(version)
                    for (GradleVersion currentGradleVersion : project.getAllGradleVersions()) {
                        if (currentGradleVersion.version == version
                            || currentGradleVersion.version.startsWith("${version}.")
                            || currentGradleVersion.version.startsWith("${version}-")
                        ) {
                            gradleVersion = currentGradleVersion
                            break
                        }
                    }

                    crossCompileProject.configurations.matching { it.name != 'optionalHidden' }.all { Configuration conf ->
                        conf.dependencies.all { Dependency dep ->
                            if (dep.group == 'name.remal.gradle-api') {
                                conf.dependencies.remove(dep)
                            }
                        }
                    }

                    crossCompileProject.dependencies {
                        optionalHidden 'name.remal.gradle-api:gradle-api'
                        optionalHidden 'name.remal.gradle-api:gradle-test-kit'

                        constraints {
                            projectDependencyConstraints "name.remal.gradle-api:local-groovy:${gradleVersion.version}"
                            projectDependencyConstraints "name.remal.gradle-api:gradle-api:${gradleVersion.version}"
                            projectDependencyConstraints "name.remal.gradle-api:gradle-test-kit:${gradleVersion.version}"
                        }
                    }
                }

                crossCompileProject.tasks.withType(AbstractCompile).configureEach { AbstractCompile task ->
                    doLast {
                        task.destinationDirectory.asFileTree
                            .matching { include('**/*.class') }
                            .matching { exclude('module-info.class') }
                            .visit { FileVisitDetails details ->
                                if (details.directory) return

                                File file = details.file
                                ClassReader classReader = new ClassReader(file.bytes)
                                ClassNode classNode = new ClassNode()
                                classReader.accept(classNode, 0)

                                List annotations = classNode.invisibleAnnotations ?: []
                                if (annotations.any { it.desc.endsWith('/RemalGradlePluginsCrossCompilation;') }) return
                                AnnotationNode annotation = new AnnotationNode('Lname/remal/gradleplugins/toolkit/internal/RemalGradlePluginsCrossCompilation;')
                                annotation.values = [
                                    'dependency', dependency,
                                    'version', version,
                                    'versionExtender', versionExtender ?: '',
                                ]
                                annotations.add(annotation)
                                classNode.invisibleAnnotations = annotations

                                ClassWriter classWriter = new ClassWriter(classReader, 0)
                                classNode.accept(classWriter)
                                byte[] bytecode = classWriter.toByteArray()
                                file.bytes = bytecode
                            }
                    }
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy