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

name.remal.gradle_plugins.toolkit.build_logic.cross-compile.gradle Maven / Gradle / Ivy

There is a newer version: 0.64.11
Show newest version
import groovy.transform.MapConstructor
import java.util.regex.Matcher
import java.util.regex.Pattern
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.7")
        classpath 'org.ow2.asm:asm-tree'
    }
    repositories {
        mavenCentral()
    }
}

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

NamedDomainObjectContainer allCrossCompileDependencies = project.objects.domainObjectContainer(CrossCompileDependencyInfo, { String name -> new CrossCompileDependencyInfo(name) })

project.ext.registerCrossCompileDependencyInfo = { Map params ->
    CrossCompileDependencyInfo crossCompileDependencyInfo = new CrossCompileDependencyInfo(params)
    allCrossCompileDependencies.add(crossCompileDependencyInfo)
}

allCrossCompileDependencies.create('java') {
    maxVersionNumbers = 1
    configureCrossCompileProject = { Project crossCompileProject, CrossCompileProjectInfo crossCompileProjectInfo ->
        int majorVersion = Integer.parseInt(crossCompileProjectInfo.version)
        JavaVersion javaVersion = JavaVersion.toVersion(majorVersion)
        crossCompileProject.java.sourceCompatibility = crossCompileProject.java.targetCompatibility = javaVersion.toString()
    }
}

allCrossCompileDependencies.create('gradle') {
    maxVersionNumbers = 3
    transitiveDependencyNotations.add('name.remal.gradle-api:local-groovy')
    dependencyNotations.add('name.remal.gradle-api:gradle-api')
    dependencyNotations.add('name.remal.gradle-api:gradle-test-kit')
}

@MapConstructor
class CrossCompileDependencyInfo implements Named {

    int maxVersionNumbers = Integer.MAX_VALUE

    Collection transitiveDependencyNotations = [].toSet()
    Collection dependencyNotations = [].toSet()

    Closure configureCrossCompileProject = { Project crossCompileProject, CrossCompileProjectInfo crossCompileProjectInfo ->
        // do nothing by default
    }

    final String name

    CrossCompileDependencyInfo(String name) {
        this.name = name
    }

    void setMaxVersionNumbers(int maxVersionNumbers) {
        if (maxVersionNumbers <= 0) throw new IllegalArgumentException("maxVersionNumbers <= 0: $maxVersionNumbers")
        this.maxVersionNumbers = maxVersionNumbers
    }

    @Override
    String getName() {
        return this.name
    }

    @Override
    String toString() {
        return this.name
    }

}

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

NamedDomainObjectContainer allCrossCompileProjects = project.objects.domainObjectContainer(CrossCompileProjectInfo, { throw new UnsupportedOperationException() })
project.ext.allCrossCompileProjects = allCrossCompileProjects

allprojects {
    if (CrossCompileProjectInfo.matches(project)) {
        allCrossCompileProjects.add(new CrossCompileProjectInfo(project))
    }
}

allCrossCompileProjects.all { CrossCompileProjectInfo crossCompileProjectInfo ->
    Project crossCompileProject = crossCompileProjectInfo.project
    if (!crossCompileProject.subprojects.isEmpty()) {
        throw new GradleException("Cross-compile project ${crossCompileProject} can't have subprojects")
    }

    Project targetProject = crossCompileProject.parent
    if (targetProject == null) {
        throw new GradleException("Cross-compile project ${crossCompileProject} can't be root project")
    }

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

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

    targetProject.fatJarWith(crossCompileProject)

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

    if (crossCompileProjectInfo.dependency == null) {
        targetProject.configurations.matching { it.name == 'compileOnlyAll' }.configureEach { Configuration conf ->
            Dependency crossCompileDep = targetProject.dependencies.create(crossCompileProject)
            conf.dependencies.add(crossCompileDep)
        }

    } else {
        targetProject.findProject('cross-compile--common')?.with { Project commonProject ->
            Dependency dep = crossCompileProject.dependencies.create(commonProject)
            crossCompileProject.configurations.optional.dependencies.add(dep)
        }

        Configuration depsConf = crossCompileProject.configurations.create("crossCompile${crossCompileProjectInfo.dependency.capitalize()}") { Configuration conf ->
            conf.canBeResolved = false
            conf.canBeConsumed = false
            crossCompileProject.configurations.optionalHidden.extendsFrom(conf)
        }

        Configuration transitiveDepsConf = crossCompileProject.configurations.create("crossCompile${crossCompileProjectInfo.dependency.capitalize()}Transitive") { Configuration conf ->
            conf.canBeResolved = false
            conf.canBeConsumed = false
            conf.defaultDependencies { DependencySet deps ->
                deps.addAll(
                    depsConf.dependencies.toList().collect { Dependency dep ->
                        dep = dep.copy()
                        if (dep instanceof ModuleDependency) {
                            dep.transitive = true
                        }
                        return dep
                    }
                )
            }
        }

        allCrossCompileDependencies.matching { it.name == crossCompileProjectInfo.dependency }.all { CrossCompileDependencyInfo crossCompileDependencyInfo ->
            int versionNumbers = crossCompileProjectInfo.version.split(/\./).length
            if (versionNumbers > crossCompileDependencyInfo.maxVersionNumbers) {
                throw new GradleException("Cross-compile project $crossCompileProject can have version of not more than ${crossCompileDependencyInfo.maxVersionNumbers} numbers only")
            }


            crossCompileDependencyInfo.configureCrossCompileProject(crossCompileProject, crossCompileProjectInfo)


            Set allDependencyNotations = (crossCompileDependencyInfo.transitiveDependencyNotations + crossCompileDependencyInfo.dependencyNotations).toSet()

            List> allDependencyExclusions = allDependencyNotations.collect { String notation ->
                Dependency dep = crossCompileProject.dependencies.create(notation)
                return [
                    group: dep.group,
                    module: dep.name,
                ]
            }

            Closure removeCrossCompileDependenciesFrom = { Configuration conf ->
                if (conf.state != Configuration.State.UNRESOLVED) return

                conf.dependencies.all {
                    conf.dependencies.removeIf { Dependency dep ->
                        allDependencyNotations.any { it == "${dep.group}:${dep.name}" }
                    }
                }
                conf.dependencyConstraints.all {
                    conf.dependencyConstraints.removeIf { DependencyConstraint constraint ->
                        allDependencyNotations.any { it == "${constraint.group}:${constraint.name}" }
                    }
                }

                conf.dependencies.withType(ModuleDependency).all { ModuleDependency dep ->
                    allDependencyExclusions.forEach { exclusion ->
                        dep.exclude(exclusion)
                    }
                }
            }

            crossCompileProject.allSourceSetsConfigurations.all(removeCrossCompileDependenciesFrom)
            [
                crossCompileProject.configurations.optional,
                crossCompileProject.configurations.projectDependencyConstraints,
            ].forEach(removeCrossCompileDependenciesFrom)


            String dependencyVersion
            if (crossCompileProjectInfo.versionOperator == 'lt') {
                dependencyVersion = "(,${crossCompileProjectInfo.version})"
            } else if (crossCompileProjectInfo.versionOperator == 'lte') {
                dependencyVersion = "(,${crossCompileProjectInfo.version}.9999)"
            } else if (crossCompileProjectInfo.versionOperator == 'gt') {
                dependencyVersion = "[${crossCompileProjectInfo.nextVersion},${crossCompileProjectInfo.nextVersion}.9999)"
            } else {
                dependencyVersion = "[${crossCompileProjectInfo.version},${crossCompileProjectInfo.version}.9999)"
            }
            allDependencyNotations.forEach { dependencyNotation ->
                Dependency dep = crossCompileProject.dependencies.create(dependencyNotation) {
                    version { strictly(dependencyVersion) }
                    transitive = crossCompileDependencyInfo.transitiveDependencyNotations.contains(dependencyNotation)
                }
                depsConf.dependencies.add(dep)

                DependencyConstraint depConstraint = crossCompileProject.dependencies.constraints.create(dependencyNotation) {
                    version { strictly(dependencyVersion) }
                }
                crossCompileProject.configurations.projectDependencyConstraints.dependencyConstraints.add(depConstraint)
            }
        }
    }

    if (crossCompileProjectInfo.dependency != null) {
        crossCompileProject.tasks.withType(AbstractCompile).configureEach { AbstractCompile task ->
            doLast {
                task.destinationDirectory.asFileTree
                    .matching { it.include('**/*.class') }
                    .matching { it.exclude('**/package-info.class') }
                    .matching { it.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/gradle_plugins/toolkit/internal/RemalGradlePluginsCrossCompilation;')
                        annotation.values = [
                            'dependency', crossCompileProjectInfo.dependency,
                            'version', crossCompileProjectInfo.version,
                            'versionOperator', crossCompileProjectInfo.versionOperator,
                        ]
                        annotations.add(annotation)
                        classNode.invisibleAnnotations = annotations

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


class CrossCompileProjectInfo implements Named {

    private static final Pattern PATTERN = Pattern.compile(/^cross-compile--(common|(.+)-(\d+(?:\.\d+){0,2})-(lt|lte|eq|gte|gt))$/)

    static boolean matches(Project project) {
        return PATTERN.matcher(project.name).matches()
    }


    final Project project
    final String dependency
    final String version
    final String nextVersion
    final String versionOperator

    CrossCompileProjectInfo(Project project) {
        this.project = project

        Matcher matcher = PATTERN.matcher(project.name)
        if (!matcher.matches()) {
            throw new GradleException("Project name of cross-compile project ${project} doesn't match to /${PATTERN}/")
        }

        if (matcher.group(1) == 'common') {
            this.dependency = null
            this.version = null
            this.nextVersion = null
            this.versionOperator = null

        } else {
            this.dependency = matcher.group(2)
            this.version = matcher.group(3)
            this.versionOperator = matcher.group(4)

            String[] versionTokens = this.version.split(/\./)
            versionTokens[versionTokens.length - 1] = versionTokens[versionTokens.length - 1].toInteger() + 1
            this.nextVersion = versionTokens.join('.')
        }
    }

    @Override
    String getName() {
        return project.path
    }

    @Override
    String toString() {
        return name
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy