From bbf67c53863ce681497e874aee33b84eea6d70a9 Mon Sep 17 00:00:00 2001 From: Lorenz Wrobel <43410952+DasBabyPixel@users.noreply.github.com> Date: Sun, 15 Mar 2026 18:40:20 +0100 Subject: [PATCH 1/8] update gradle --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2a84e18..37f78a6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From c8158652046eb8a4f9aed43cfd09932b1e61076a Mon Sep 17 00:00:00 2001 From: Lorenz Wrobel <43410952+DasBabyPixel@users.noreply.github.com> Date: Sun, 15 Mar 2026 18:40:57 +0100 Subject: [PATCH 2/8] flavors --- .../gradle/juppiter/JuppiterPlugin.kt | 3 + .../gradle/juppiter/flavor/Flavor.kt | 29 +++++ .../gradle/juppiter/flavor/FlavorExtension.kt | 114 ++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 src/main/kotlin/eu/cloudnetservice/gradle/juppiter/flavor/Flavor.kt create mode 100644 src/main/kotlin/eu/cloudnetservice/gradle/juppiter/flavor/FlavorExtension.kt diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt index ae6bdf5..3579071 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt @@ -16,6 +16,7 @@ package eu.cloudnetservice.gradle.juppiter +import eu.cloudnetservice.gradle.juppiter.flavor.FlavorExtension import eu.cloudnetservice.gradle.juppiter.util.GradleUtil import org.gradle.api.Plugin import org.gradle.api.Project @@ -36,6 +37,8 @@ class JuppiterPlugin : Plugin { GradleUtil.findOrAddExtension(extensions, "moduleJson", ModuleConfiguration::class) { ModuleConfiguration(target.objects) } + val flavorExtension = FlavorExtension(this) + extensions.add("flavors", flavorExtension) val generateModuleTask = tasks.register("genModuleJson") { diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/flavor/Flavor.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/flavor/Flavor.kt new file mode 100644 index 0000000..95771bf --- /dev/null +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/flavor/Flavor.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.gradle.juppiter.flavor + +import org.gradle.api.provider.SetProperty + +class Flavor internal constructor(val name: String, val dependsOn: SetProperty) { + init { + dependsOn.finalizeValueOnRead() + } + + fun dependOn(other: Flavor) { + dependsOn.add(other) + } +} diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/flavor/FlavorExtension.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/flavor/FlavorExtension.kt new file mode 100644 index 0000000..ed46ef7 --- /dev/null +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/flavor/FlavorExtension.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.gradle.juppiter.flavor + +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.jvm.tasks.Jar +import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.register +import org.gradle.kotlin.dsl.setProperty + +class FlavorExtension(private val project: Project) { + private var javaRegistered = false + private val flavors = HashSet() + + val main: Flavor = Flavor("main", project.objects.setProperty().apply { finalizeValue() }) + + fun addFlavor(name: String): Flavor { + return Flavor(name, project.objects.setProperty()).apply { + flavors.add(this) + if (javaRegistered) { + registerJava(this) + } + } + } + + fun registerJavaSourceSets() { + if (javaRegistered) return + javaRegistered = true + flavors.forEach { + registerJava(it) + } + } + + private fun registerJava(flavor: Flavor) { + val java = project.extensions.getByType() + val sourceSets = java.sourceSets + val sourceSet = sourceSets.register(flavor.name) + val files = project.objects.fileCollection() + files.from(flavor.dependsOn.map { flavors -> +// val flavors = collectDependencies(flavor = flavor).filter { it !== flavor } + flavors.map { dependencyFlavor -> + sourceSets.named(dependencyFlavor.name).map { it.output } + } + }) + val configurations = project.configurations + val apiConfiguration = configurations.register(sourceSet.get().apiConfigurationName) + val compileOnlyApiConfiguration = configurations.register(sourceSet.get().compileOnlyApiConfigurationName) + configurations.named(sourceSet.get().implementationConfigurationName).configure { + extendsFrom(apiConfiguration.get()) + } + configurations.named(sourceSet.get().compileOnlyConfigurationName).configure { + extendsFrom(compileOnlyApiConfiguration.get()) + } + + // Get around IDE loading this too early. Ugly but works + // Loading early causes flavor.dependsOn to be read-only + project.afterEvaluate { + project.dependencies.add(sourceSet.get().apiConfigurationName, files) + + apiConfiguration.configure { + flavor.dependsOn.get().map { sourceSets.getByName(it.name) }.forEach { + extendsFrom(configurations.getByName(it.apiConfigurationName)) + } + } + compileOnlyApiConfiguration.configure { + flavor.dependsOn.get().map { sourceSets.getByName(it.name) }.forEach { + extendsFrom(configurations.getByName(it.compileOnlyApiConfigurationName)) + } + } + configurations.named(sourceSet.get().runtimeOnlyConfigurationName).configure { + flavor.dependsOn.get().map { sourceSets.getByName(it.name) }.forEach { + extendsFrom(configurations.getByName(it.runtimeClasspathConfigurationName)) + } + } + } + + val task = project.tasks.register(sourceSet.get().jarTaskName) { + val dependencies = flavor.dependsOn.get() + archiveClassifier.set(flavor.name) + from(sourceSet.map { it.output }) + dependencies.forEach { dependency -> + val taskName = sourceSets.named(dependency.name).get().jarTaskName + from(project.tasks.named(taskName)) + } + } + project.tasks.named("assemble").configure { + dependsOn(task.get()) + } + } + + private fun collectDependencies(set: MutableSet = HashSet(), flavor: Flavor): MutableSet { + if (set.add(flavor)) { + flavor.dependsOn.get().forEach { + collectDependencies(set, it) + } + } + return set + } +} From 06ee8b4c0c88927bb98cf688b13d0e6ef94d8cc4 Mon Sep 17 00:00:00 2001 From: Lorenz Wrobel <43410952+DasBabyPixel@users.noreply.github.com> Date: Sun, 15 Mar 2026 18:41:25 +0100 Subject: [PATCH 3/8] test project for manual testing --- test/build.gradle.kts | 56 ++++ test/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48966 bytes test/gradle/wrapper/gradle-wrapper.properties | 7 + test/gradlew | 248 ++++++++++++++++++ test/gradlew.bat | 93 +++++++ test/settings.gradle.kts | 27 ++ test/src/common/java/test/TestCommon.java | 21 ++ .../1_21_1/java/test/TestFabric1_21_1.java | 28 ++ .../src/fabric/base/java/test/TestFabric.java | 26 ++ test/src/forge/java/test/TestForge.java | 21 ++ test/src/main/java/test/TestApi.java | 25 ++ 11 files changed, 552 insertions(+) create mode 100644 test/build.gradle.kts create mode 100644 test/gradle/wrapper/gradle-wrapper.jar create mode 100644 test/gradle/wrapper/gradle-wrapper.properties create mode 100755 test/gradlew create mode 100644 test/gradlew.bat create mode 100644 test/settings.gradle.kts create mode 100644 test/src/common/java/test/TestCommon.java create mode 100644 test/src/fabric/1_21_1/java/test/TestFabric1_21_1.java create mode 100644 test/src/fabric/base/java/test/TestFabric.java create mode 100644 test/src/forge/java/test/TestForge.java create mode 100644 test/src/main/java/test/TestApi.java diff --git a/test/build.gradle.kts b/test/build.gradle.kts new file mode 100644 index 0000000..8f379f0 --- /dev/null +++ b/test/build.gradle.kts @@ -0,0 +1,56 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("eu.cloudnetservice.juppiter") + `java-library` + id("net.fabricmc.fabric-loom") version "1.15-SNAPSHOT" apply false +// id("net.fabricmc.fabric-loom-remap") apply false +} + +moduleJson { + main = "abc" +} + +flavors { + registerJavaSourceSets() + val common = addFlavor("common") + common.dependOn(main) + val fabric = addFlavor("fabric/base") + fabric.dependOn(common) + val forge = addFlavor("forge") + forge.dependOn(common) + val fabric1211 = addFlavor("fabric/1_21_1") + fabric1211.dependOn(fabric) + + val merged = addFlavor("merged") + merged.dependOn(forge) + merged.dependOn(fabric1211) +} + +tasks.assemble { + dependsOn(tasks.named("mergedJar")) +} + +repositories { + mavenCentral() +} + +dependencies { +// api("com.google.code.gson:gson:2.13.2") + "compileOnlyApi"("com.google.code.gson:gson:2.13.2") +// "commonApi"("com.google.code.gson:gson:2.13.2") +} diff --git a/test/gradle/wrapper/gradle-wrapper.jar b/test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d997cfc60f4cff0e7451d19d49a82fa986695d07 GIT binary patch literal 48966 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

`Lem|V$A>eR0uN8Y&sdjtu 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

1bvi{DS7E@q1{$_8->K{_OWv2 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 literal 0 HcmV?d00001 diff --git a/test/gradle/wrapper/gradle-wrapper.properties b/test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..dbc3ce4 --- /dev/null +++ b/test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/test/gradlew b/test/gradlew new file mode 100755 index 0000000..0262dcb --- /dev/null +++ b/test/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/test/gradlew.bat b/test/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/test/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/test/settings.gradle.kts b/test/settings.gradle.kts new file mode 100644 index 0000000..2031a59 --- /dev/null +++ b/test/settings.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +pluginManagement { + includeBuild("..") + + repositories { + maven("https://maven.fabricmc.net/") { + name = "Fabric" + } + mavenCentral() + gradlePluginPortal() + } +} diff --git a/test/src/common/java/test/TestCommon.java b/test/src/common/java/test/TestCommon.java new file mode 100644 index 0000000..2d2b8f7 --- /dev/null +++ b/test/src/common/java/test/TestCommon.java @@ -0,0 +1,21 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test; + +public class TestCommon { + +} diff --git a/test/src/fabric/1_21_1/java/test/TestFabric1_21_1.java b/test/src/fabric/1_21_1/java/test/TestFabric1_21_1.java new file mode 100644 index 0000000..587e547 --- /dev/null +++ b/test/src/fabric/1_21_1/java/test/TestFabric1_21_1.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test; + +import com.google.gson.Gson; + +public class TestFabric1_21_1 { + { + TestFabric.class.getName(); + Gson.class.getName(); + TestApi.class.getName(); + TestCommon.class.getName(); + } +} diff --git a/test/src/fabric/base/java/test/TestFabric.java b/test/src/fabric/base/java/test/TestFabric.java new file mode 100644 index 0000000..d2ce5d9 --- /dev/null +++ b/test/src/fabric/base/java/test/TestFabric.java @@ -0,0 +1,26 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test; + +public class TestFabric { + + { + System.out.println(TestApi.class.getName()); + com.google.gson.Gson g; + } + +} diff --git a/test/src/forge/java/test/TestForge.java b/test/src/forge/java/test/TestForge.java new file mode 100644 index 0000000..648962d --- /dev/null +++ b/test/src/forge/java/test/TestForge.java @@ -0,0 +1,21 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test; + +public class TestForge { + +} diff --git a/test/src/main/java/test/TestApi.java b/test/src/main/java/test/TestApi.java new file mode 100644 index 0000000..f6dc111 --- /dev/null +++ b/test/src/main/java/test/TestApi.java @@ -0,0 +1,25 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test; + +import com.google.gson.Gson; + +public class TestApi { + { + Gson g; + } +} From b39442a18a48c3a3935cef4ebaea120f27817748 Mon Sep 17 00:00:00 2001 From: Lorenz Wrobel <43410952+DasBabyPixel@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:37:06 +0100 Subject: [PATCH 4/8] begin rewrite for new module system, example cloudnet-module.json generates --- gradle/wrapper/gradle-wrapper.jar | Bin 45457 -> 46175 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 3 - gradlew.bat | 3 +- .../gradle/juppiter/GenerateModuleJson.kt | 31 ++- .../gradle/juppiter/JuppiterPlugin.kt | 29 +-- .../gradle/juppiter/ModuleConfiguration.kt | 211 ------------------ .../juppiter/UnknownDependencyException.kt | 8 +- .../gradle/juppiter/data/ModuleArtifact.kt | 42 ++++ .../juppiter/data/ModuleConfiguration.kt | 126 +++++++++++ .../gradle/juppiter/data/ModuleContributor.kt | 33 +++ .../ModuleDependency.kt} | 30 +-- .../juppiter/data/ModuleExternalDependency.kt | 43 ++++ .../gradle/juppiter/util/MavenUtility.kt | 104 ++++----- test/build.gradle.kts | 46 +++- 15 files changed, 395 insertions(+), 316 deletions(-) delete mode 100644 src/main/kotlin/eu/cloudnetservice/gradle/juppiter/ModuleConfiguration.kt create mode 100644 src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleArtifact.kt create mode 100644 src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleConfiguration.kt create mode 100644 src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleContributor.kt rename src/main/kotlin/eu/cloudnetservice/gradle/juppiter/{util/GradleUtil.kt => data/ModuleDependency.kt} (58%) create mode 100644 src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleExternalDependency.kt diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8bdaf60c75ab801e22807dde59e12a8735a34077..61285a659d17295f1de7c53e24fdf13ad755c379 100644 GIT binary patch delta 36855 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%0Aonjyf`q#Bg+;H(_UceX22V^ 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 { + override fun serialize( + value: Provider<*>, + gen: JsonGenerator, + serializers: SerializerProvider + ) { + if (value.isPresent) { + println(value.get()) + gen.writeObject(value.get()) + } + else gen.writeNull() + } + }) + } val mapper = ObjectMapper(factory) .registerKotlinModule() - .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) + .registerModule(module) + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY).writerWithDefaultPrettyPrinter() val moduleConfiguration = moduleConfiguration.get() mapper.writeValue(outputDirectory.file(fileName).get().asFile, moduleConfiguration) diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt index 3579071..3ec1c39 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt @@ -16,8 +16,8 @@ package eu.cloudnetservice.gradle.juppiter +import eu.cloudnetservice.gradle.juppiter.data.ModuleConfiguration import eu.cloudnetservice.gradle.juppiter.flavor.FlavorExtension -import eu.cloudnetservice.gradle.juppiter.util.GradleUtil import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPlugin @@ -30,33 +30,20 @@ import org.gradle.kotlin.dsl.withType class JuppiterPlugin : Plugin { override fun apply(target: Project) { target.run { - val libraries = configurations.maybeCreate("moduleLibrary") - val moduleDependencies = configurations.maybeCreate("moduleDependency") + val libraries = configurations.register("moduleLibrary") + val moduleDependencies = configurations.register("moduleDependency") + + val moduleExtension = ModuleConfiguration(target.objects) + extensions.add("moduleJson", moduleExtension) - val moduleExtension = - GradleUtil.findOrAddExtension(extensions, "moduleJson", ModuleConfiguration::class) { - ModuleConfiguration(target.objects) - } val flavorExtension = FlavorExtension(this) extensions.add("flavors", flavorExtension) val generateModuleTask = tasks.register("genModuleJson") { - fileName.convention("module.json") + fileName.convention("cloudnet-module.json") outputDirectory.convention(layout.buildDirectory.dir("generated/module-json")) - moduleConfiguration.convention( - provider { - moduleExtension.setDefaults(this@run, libraries, moduleDependencies) - - // This provider is lazy, so it should be applied after project configuration - moduleExtension.resolveRepositories(project.repositories) - moduleExtension - }, - ) - - doFirst { - moduleExtension.validate() - } + moduleConfiguration.convention(moduleExtension) } plugins.withType { diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/ModuleConfiguration.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/ModuleConfiguration.kt deleted file mode 100644 index 34c36fc..0000000 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/ModuleConfiguration.kt +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 2019-present CloudNetService team & contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.cloudnetservice.gradle.juppiter - -import com.fasterxml.jackson.annotation.JsonIgnore -import com.fasterxml.jackson.annotation.JsonTypeInfo -import com.fasterxml.jackson.databind.annotation.JsonSerialize -import eu.cloudnetservice.gradle.juppiter.jackson.JavaVersionSerializer -import eu.cloudnetservice.gradle.juppiter.util.ChecksumHelper -import eu.cloudnetservice.gradle.juppiter.util.MavenUtility -import groovy.lang.Closure -import org.gradle.api.JavaVersion -import org.gradle.api.NamedDomainObjectContainer -import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.dsl.RepositoryHandler -import org.gradle.api.artifacts.repositories.MavenArtifactRepository -import org.gradle.api.internal.artifacts.repositories.resolver.MavenUniqueSnapshotComponentIdentifier -import org.gradle.api.model.ObjectFactory -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.Nested -import org.gradle.api.tasks.Optional -import java.util.concurrent.atomic.AtomicBoolean - -@Suppress("unused") -open class ModuleConfiguration( - objectFactory: ObjectFactory, -) { - // internal marker to prevent duplicate resolving of dependencies - // this was introduced initially because of a gradle issue introduces - // in version 7.4 but might be useful in the future too - // https://github.com/gradle/gradle/issues/19848 - @JsonIgnore - private val resolved: AtomicBoolean = AtomicBoolean() - - @Input - var runtimeModule = false - - @Input - var storesSensitiveData = false - - @Input - var main: String? = null - - @Input - @Optional - var name: String? = null - - @Input - @Optional - var group: String? = null - - @Input - @Optional - var author: String? = null - - @Input - @Optional - var version: String? = null - - @Input - @Optional - var website: String? = null - - @Input - @Optional - var dataFolder: String? = null - - @Input - @Optional - var description: String? = null - - @Input - @Optional - @JsonSerialize(using = JavaVersionSerializer::class, nullsUsing = JavaVersionSerializer::class) - var minJavaVersionId: JavaVersion? = null - - @Nested - val properties: Map = HashMap() - - @Nested - @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION, include = JsonTypeInfo.As.WRAPPER_OBJECT) - val repositories: NamedDomainObjectContainer = objectFactory.domainObjectContainer(Repository::class.java) - - @Nested - @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION, include = JsonTypeInfo.As.WRAPPER_OBJECT) - val dependencies: NamedDomainObjectContainer = objectFactory.domainObjectContainer(Dependency::class.java) - - // for groovy - fun repositories(closure: Closure) = repositories.configure(closure) - - fun dependencies(closure: Closure) = dependencies.configure(closure) - - data class Repository( - @Input val name: String, - ) { - @Input - var url: String? = null - } - - data class Dependency( - @Input val name: String, - ) { - @Input - var group: String? = null - - @Input - var version: String? = null - - @Input - @Optional - var url: String? = null - - @Input - @Optional - var repo: String? = null - - @Input - @Optional - var checksum: String? = null - - @Internal - @JsonIgnore - var classifier: String? = null - - @Internal - @JsonIgnore - var timestampedVersion: String? = null - - @Input - @JsonIgnore - var needsRepoResolve: Boolean = true - } - - fun setDefaults( - project: Project, - libraries: Configuration, - moduleDependencies: Configuration, - ) { - if (!this.resolved.getAndSet(true)) { - name = name ?: project.name - group = group ?: project.group.toString() - version = version ?: project.version.toString() - - // other stuff - author = author ?: "Anonymous" - website = website ?: "https://cloudnetservice.eu" - description = description ?: project.description ?: "Just another CloudNet module" - - // dependencies of the module we need to resolve - libraries.resolvedConfiguration.resolvedArtifacts.forEach { - val versionId = it.moduleVersion.id - val dependency = Dependency(it.name) - dependency.group = versionId.group - dependency.version = versionId.version - dependency.classifier = it.classifier - dependency.checksum = ChecksumHelper.fileShaSum(it.file) - - val componentIdentifier = it.id.componentIdentifier - if (versionId.version.endsWith("-SNAPSHOT") && componentIdentifier is MavenUniqueSnapshotComponentIdentifier) { - dependency.timestampedVersion = componentIdentifier.timestampedVersion - } - - dependencies.add(dependency) - } - - // dependencies of the module that are other modules, so we only need: group, name, version - moduleDependencies.resolvedConfiguration.firstLevelModuleDependencies - .map { it.module.id } - .forEach { - val dependency = Dependency(it.name) - dependency.group = it.group - dependency.version = it.version - dependency.needsRepoResolve = false - - dependencies.add(dependency) - } - } - } - - fun resolveRepositories(repositoryHandler: RepositoryHandler) { - val repos = repositoryHandler.filterIsInstance() - dependencies - .filter { it.needsRepoResolve } - .forEach { - val repo = MavenUtility.findRepository(it, repos) ?: return@forEach - repositories.add(repo) - } - } - - fun validate() { - if (main.isNullOrEmpty()) { - throw InvalidModuleDescription("main class must be set") - } - } -} diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/UnknownDependencyException.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/UnknownDependencyException.kt index 4c9fde8..db5f1d5 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/UnknownDependencyException.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/UnknownDependencyException.kt @@ -16,6 +16,8 @@ package eu.cloudnetservice.gradle.juppiter -class UnknownDependencyException( - dep: ModuleConfiguration.Dependency, -) : Exception("Unable to resolve dependency ${dep.group}:${dep.name}:${dep.version}") +import eu.cloudnetservice.gradle.juppiter.data.ModuleConfiguration + +//class UnknownDependencyException( +// dep: ModuleConfiguration.Dependency, +//) : Exception("Unable to resolve dependency ${dep.group}:${dep.name}:${dep.version}") diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleArtifact.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleArtifact.kt new file mode 100644 index 0000000..c9b526b --- /dev/null +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleArtifact.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.gradle.juppiter.data + +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty +import org.gradle.api.tasks.Input +import org.gradle.kotlin.dsl.property +import org.gradle.kotlin.dsl.setProperty + +class ModuleArtifact(objectFactory: ObjectFactory) { + @Input + val source: Property = objectFactory.property() + + @Input + val sourcePath: Property = objectFactory.property() + + @Input + val targetPath: Property = objectFactory.property() + + @Input + val environments: SetProperty = objectFactory.setProperty() +} + +enum class ModuleArtifactSource { + CLASSPATH, FILESYSTEM +} diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleConfiguration.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleConfiguration.kt new file mode 100644 index 0000000..e7ff29f --- /dev/null +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleConfiguration.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.gradle.juppiter.data + +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.Optional +import org.gradle.kotlin.dsl.mapProperty +import org.gradle.kotlin.dsl.property +import org.gradle.kotlin.dsl.setProperty + +class ModuleConfiguration( + objectFactory: ObjectFactory, +) { + @Input + val id: Property = objectFactory.property() + + @Input + val name: Property = objectFactory.property() + + @Input + @Optional + val description: Property = objectFactory.property() + + @Input + val entrypoint: Property = objectFactory.property() + + @Input + @Optional + val version: Property = objectFactory.property() + + @Nested + val artifacts: SetProperty = objectFactory.setProperty() + + @Nested + val dependencies: SetProperty = objectFactory.setProperty() + + @Nested + val externalDependencies: SetProperty = objectFactory.setProperty() + + @Nested + val contributors: SetProperty = objectFactory.setProperty() + + @Input + val properties: MapProperty = objectFactory.mapProperty() + +// fun setDefaults( +// project: Project, +// libraries: Configuration, +// moduleDependencies: Configuration, +// ) { +// if (!this.resolved.getAndSet(true)) { +// name = name ?: project.name +// group = group ?: project.group.toString() +// version = version ?: project.version.toString() +// +// // other stuff +// author = author ?: "Anonymous" +// website = website ?: "https://cloudnetservice.eu" +// description = description ?: project.description ?: "Just another CloudNet module" +// +// // dependencies of the module we need to resolve +// libraries.resolvedConfiguration.resolvedArtifacts.forEach { +// val versionId = it.moduleVersion.id +// val dependency = Dependency(it.name) +// dependency.group = versionId.group +// dependency.version = versionId.version +// dependency.classifier = it.classifier +// dependency.checksum = ChecksumHelper.fileShaSum(it.file) +// +// val componentIdentifier = it.id.componentIdentifier +// if (versionId.version.endsWith("-SNAPSHOT") && componentIdentifier is MavenUniqueSnapshotComponentIdentifier) { +// dependency.timestampedVersion = componentIdentifier.timestampedVersion +// } +// +// dependencies.add(dependency) +// } +// +// // dependencies of the module that are other modules, so we only need: group, name, version +// moduleDependencies.resolvedConfiguration.firstLevelModuleDependencies +// .map { it.module.id } +// .forEach { +// val dependency = Dependency(it.name) +// dependency.group = it.group +// dependency.version = it.version +// dependency.needsRepoResolve = false +// +// dependencies.add(dependency) +// } +// } +// } +// +// fun resolveRepositories(repositoryHandler: RepositoryHandler) { +// val repos = repositoryHandler.filterIsInstance() +// dependencies +// .filter { it.needsRepoResolve } +// .forEach { +// val repo = MavenUtility.findRepository(it, repos) ?: return@forEach +// repositories.add(repo) +// } +// } +// +// fun validate() { +// if (main.isNullOrEmpty()) { +// throw InvalidModuleDescription("main class must be set") +// } +// } +} diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleContributor.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleContributor.kt new file mode 100644 index 0000000..f3af8ee --- /dev/null +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleContributor.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.gradle.juppiter.data + +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Nested +import org.gradle.kotlin.dsl.mapProperty +import org.gradle.kotlin.dsl.property + +class ModuleContributor(objectFactory: ObjectFactory) { + @Input + val name: Property = objectFactory.property() + + @Input + val properties: MapProperty = objectFactory.mapProperty() +} diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/GradleUtil.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleDependency.kt similarity index 58% rename from src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/GradleUtil.kt rename to src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleDependency.kt index a0652b7..be22a69 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/GradleUtil.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleDependency.kt @@ -14,21 +14,21 @@ * limitations under the License. */ -package eu.cloudnetservice.gradle.juppiter.util +package eu.cloudnetservice.gradle.juppiter.data -import org.gradle.api.plugins.ExtensionContainer -import kotlin.reflect.KClass +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.kotlin.dsl.property -object GradleUtil { - fun findOrAddExtension( - extensions: ExtensionContainer, - name: String, - type: KClass, - factory: () -> E, - ): E = - extensions.findByType(type.java) ?: run { - val extension = factory.invoke() - extensions.add(name, extension) - extension - } +class ModuleDependency(objectFactory: ObjectFactory) { + @Input + val id: Property = objectFactory.property() + @Input + val versionRange: Property = objectFactory.property() +} + +enum class ModuleDependencyType { + REQUIRED, + SUGGESTED } diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleExternalDependency.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleExternalDependency.kt new file mode 100644 index 0000000..b61f2ab --- /dev/null +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleExternalDependency.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.gradle.juppiter.data + +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.Optional +import org.gradle.kotlin.dsl.mapProperty +import org.gradle.kotlin.dsl.property +import org.gradle.kotlin.dsl.setProperty + +class ModuleExternalDependency(objectFactory: ObjectFactory) { + @Input + val loader: Property = objectFactory.property() + + @Input + @Optional + val optional: Property = objectFactory.property() + + @Input + val environments: SetProperty = objectFactory.setProperty() + + @Input + val properties: MapProperty = objectFactory.mapProperty() +} diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/MavenUtility.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/MavenUtility.kt index 579de78..3e01af1 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/MavenUtility.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/MavenUtility.kt @@ -16,59 +16,59 @@ package eu.cloudnetservice.gradle.juppiter.util -import eu.cloudnetservice.gradle.juppiter.ModuleConfiguration -import eu.cloudnetservice.gradle.juppiter.UnknownDependencyException +import eu.cloudnetservice.gradle.juppiter.data.ModuleConfiguration +//import eu.cloudnetservice.gradle.juppiter.UnknownDependencyException import org.gradle.api.artifacts.repositories.MavenArtifactRepository import java.net.HttpURLConnection import java.net.URL -object MavenUtility { - fun findRepository( - dependency: ModuleConfiguration.Dependency, - repositories: Iterable, - ): ModuleConfiguration.Repository? { - repositories.forEach { - val urlInRepository = resolveUrlInRepository(dependency, it) ?: return@forEach - if (dependency.timestampedVersion != null || dependency.classifier != null) { - // timestamped version and classifier are not supported by CloudNet module loading currently - // therefore, we need to hack around this limitation by providing the url directly - dependency.url = urlInRepository.toExternalForm() - return null - } else { - // CloudNet can download this dependency directly from the maven repository - val repository = ModuleConfiguration.Repository(it.name) - repository.url = it.url.toURL().toExternalForm() - dependency.repo = repository.name - return repository - } - } - - throw UnknownDependencyException(dependency) - } - - private fun resolveUrlInRepository( - dependency: ModuleConfiguration.Dependency, - repository: MavenArtifactRepository, - ): URL? { - val groupForUrl = dependency.group!!.replace(".", "/") - val componentVersion = dependency.timestampedVersion ?: dependency.version - val classifier = if (dependency.classifier != null) "-${dependency.classifier}" else "" - val componentName = "${dependency.name}-$componentVersion$classifier.jar" - val urlPath = "$groupForUrl/${dependency.name}/${dependency.version}/$componentName" - val fullUrl = URL(repository.url.toURL(), urlPath) - return if (resourceExists(fullUrl)) fullUrl else null - } - - private fun resourceExists(url: URL): Boolean = - with(url.openConnection() as HttpURLConnection) { - useCaches = false - connectTimeout = 5000 - requestMethod = "HEAD" - instanceFollowRedirects = true - - setRequestProperty("User-Agent", "CloudNetService/juppiter Repository Resolve") - connect() - - responseCode == 200 - } -} +//object MavenUtility { +// fun findRepository( +// dependency: ModuleConfiguration.Dependency, +// repositories: Iterable, +// ): ModuleConfiguration.Repository? { +// repositories.forEach { +// val urlInRepository = resolveUrlInRepository(dependency, it) ?: return@forEach +// if (dependency.timestampedVersion != null || dependency.classifier != null) { +// // timestamped version and classifier are not supported by CloudNet module loading currently +// // therefore, we need to hack around this limitation by providing the url directly +// dependency.url = urlInRepository.toExternalForm() +// return null +// } else { +// // CloudNet can download this dependency directly from the maven repository +// val repository = ModuleConfiguration.Repository(it.name) +// repository.url = it.url.toURL().toExternalForm() +// dependency.repo = repository.name +// return repository +// } +// } +// +// throw UnknownDependencyException(dependency) +// } +// +// private fun resolveUrlInRepository( +// dependency: ModuleConfiguration.Dependency, +// repository: MavenArtifactRepository, +// ): URL? { +// val groupForUrl = dependency.group!!.replace(".", "/") +// val componentVersion = dependency.timestampedVersion ?: dependency.version +// val classifier = if (dependency.classifier != null) "-${dependency.classifier}" else "" +// val componentName = "${dependency.name}-$componentVersion$classifier.jar" +// val urlPath = "$groupForUrl/${dependency.name}/${dependency.version}/$componentName" +// val fullUrl = URL(repository.url.toURL(), urlPath) +// return if (resourceExists(fullUrl)) fullUrl else null +// } +// +// private fun resourceExists(url: URL): Boolean = +// with(url.openConnection() as HttpURLConnection) { +// useCaches = false +// connectTimeout = 5000 +// requestMethod = "HEAD" +// instanceFollowRedirects = true +// +// setRequestProperty("User-Agent", "CloudNetService/juppiter Repository Resolve") +// connect() +// +// responseCode == 200 +// } +//} diff --git a/test/build.gradle.kts b/test/build.gradle.kts index 8f379f0..ed5b4ce 100644 --- a/test/build.gradle.kts +++ b/test/build.gradle.kts @@ -1,3 +1,5 @@ +import eu.cloudnetservice.gradle.juppiter.data.* + /* * Copyright 2019-present CloudNetService team & contributors * @@ -21,8 +23,42 @@ plugins { // id("net.fabricmc.fabric-loom-remap") apply false } +data class Cls(@Input val x: String) + moduleJson { - main = "abc" + entrypoint = "abc" + id = "test" + name = "Test Module" + artifacts.add(ModuleArtifact(objects).apply { + this.source = ModuleArtifactSource.CLASSPATH + this.sourcePath = "test/source" + this.targetPath = "test/target" + this.environments.add("wrapper env ??") + }) + artifacts.add(ModuleArtifact(objects).apply { + this.source = ModuleArtifactSource.FILESYSTEM + this.sourcePath = "test/source" + this.targetPath = "test/target" + this.environments.add("wrapper env ??") + }) + dependencies.add(ModuleDependency(objects).apply { + this.id = "bridge" + this.versionRange = "69+" + }) + externalDependencies.add(ModuleExternalDependency(objects).apply { + this.environments.add("* or sth") + this.loader = "was auch immer loader sein soll" + this.optional = true +// this.properties.put("test1", Cls("val1")) +// this.properties.put("test2", provider { Cls("val2") }) + }) + contributors.add(ModuleContributor(objects).apply { + this.name = "se big bad noob" + this.properties.put("test1", "val1") + this.properties.put("test2", provider { "val2" }) + }) + this.properties.put("test1", "val1") + this.properties.put("test2", provider { "val2" }) } flavors { @@ -51,6 +87,12 @@ repositories { dependencies { // api("com.google.code.gson:gson:2.13.2") - "compileOnlyApi"("com.google.code.gson:gson:2.13.2") + "compileOnlyApi"("com.google.code.gson:gson:[2.13.0, 2.13.5]") // "commonApi"("com.google.code.gson:gson:2.13.2") } + +configurations.compileOnlyApi.get().run { + this.dependencies.forEach { + println(it.version) + } +} From 44f35b8c7c4a90688fa7b9f8b38a5454f529579f Mon Sep 17 00:00:00 2001 From: Lorenz Wrobel <43410952+DasBabyPixel@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:12:09 +0100 Subject: [PATCH 5/8] PropertyValueHolder --- .../gradle/juppiter/GenerateModuleJson.kt | 8 ++- .../gradle/juppiter/PropertyValueHolder.kt | 60 +++++++++++++++++++ .../juppiter/data/ModuleConfiguration.kt | 7 ++- .../gradle/juppiter/data/ModuleContributor.kt | 5 +- .../juppiter/data/ModuleExternalDependency.kt | 5 +- test/build.gradle.kts | 14 +++-- 6 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/eu/cloudnetservice/gradle/juppiter/PropertyValueHolder.kt diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/GenerateModuleJson.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/GenerateModuleJson.kt index 9183862..27e614b 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/GenerateModuleJson.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/GenerateModuleJson.kt @@ -59,12 +59,18 @@ abstract class GenerateModuleJson : DefaultTask() { serializers: SerializerProvider ) { if (value.isPresent) { - println(value.get()) gen.writeObject(value.get()) } else gen.writeNull() } }) + addSerializer(PropertyValueHolder::class.java, object : JsonSerializer() { + override fun serialize( + value: PropertyValueHolder, gen: JsonGenerator, serializers: SerializerProvider + ) { + gen.writeObject(value.value) + } + }) } val mapper = ObjectMapper(factory) diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/PropertyValueHolder.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/PropertyValueHolder.kt new file mode 100644 index 0000000..751135f --- /dev/null +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/PropertyValueHolder.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.gradle.juppiter + +import org.gradle.api.provider.MapProperty +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.Optional + +/** + * Gradle does not allow mixing @Input and @Nested. + * + * This class is to support both primitive @Input types and complex @Nested types + */ +class PropertyValueHolder { + private constructor(simple: Any?, complex: Any) { + this.simple = simple + this.complex = complex + } + + @Input + @Optional + val simple: Any? + + @Nested + val complex: Any + + @get:Internal + val value: Any + get() = simple ?: complex + + companion object { + fun simple(value: Any) = PropertyValueHolder(value, Empty) + fun complex(value: Any) = PropertyValueHolder(null, value) + } + + private object Empty +} + +fun MapProperty.putSimple(key: String, value: Any) = + put(key, PropertyValueHolder.simple(value)) + +fun MapProperty.putComplex(key: String, value: Any) = + put(key, PropertyValueHolder.complex(value)) + diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleConfiguration.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleConfiguration.kt index e7ff29f..9f3de05 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleConfiguration.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleConfiguration.kt @@ -16,6 +16,7 @@ package eu.cloudnetservice.gradle.juppiter.data +import eu.cloudnetservice.gradle.juppiter.PropertyValueHolder import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property @@ -59,8 +60,10 @@ class ModuleConfiguration( @Nested val contributors: SetProperty = objectFactory.setProperty() - @Input - val properties: MapProperty = objectFactory.mapProperty() + @Nested + val properties: MapProperty = objectFactory.mapProperty() + + // fun setDefaults( // project: Project, diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleContributor.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleContributor.kt index f3af8ee..23715a7 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleContributor.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleContributor.kt @@ -16,6 +16,7 @@ package eu.cloudnetservice.gradle.juppiter.data +import eu.cloudnetservice.gradle.juppiter.PropertyValueHolder import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property @@ -28,6 +29,6 @@ class ModuleContributor(objectFactory: ObjectFactory) { @Input val name: Property = objectFactory.property() - @Input - val properties: MapProperty = objectFactory.mapProperty() + @Nested + val properties: MapProperty = objectFactory.mapProperty() } diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleExternalDependency.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleExternalDependency.kt index b61f2ab..ba42bb3 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleExternalDependency.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleExternalDependency.kt @@ -16,6 +16,7 @@ package eu.cloudnetservice.gradle.juppiter.data +import eu.cloudnetservice.gradle.juppiter.PropertyValueHolder import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property @@ -38,6 +39,6 @@ class ModuleExternalDependency(objectFactory: ObjectFactory) { @Input val environments: SetProperty = objectFactory.setProperty() - @Input - val properties: MapProperty = objectFactory.mapProperty() + @Nested + val properties: MapProperty = objectFactory.mapProperty() } diff --git a/test/build.gradle.kts b/test/build.gradle.kts index ed5b4ce..1313bbc 100644 --- a/test/build.gradle.kts +++ b/test/build.gradle.kts @@ -1,4 +1,6 @@ import eu.cloudnetservice.gradle.juppiter.data.* +import eu.cloudnetservice.gradle.juppiter.putComplex +import eu.cloudnetservice.gradle.juppiter.putSimple /* * Copyright 2019-present CloudNetService team & contributors @@ -49,16 +51,16 @@ moduleJson { this.environments.add("* or sth") this.loader = "was auch immer loader sein soll" this.optional = true -// this.properties.put("test1", Cls("val1")) -// this.properties.put("test2", provider { Cls("val2") }) + this.properties.putComplex("test1", Cls("val1")) + this.properties.putComplex("test2", provider { Cls("val2") }) }) contributors.add(ModuleContributor(objects).apply { this.name = "se big bad noob" - this.properties.put("test1", "val1") - this.properties.put("test2", provider { "val2" }) + this.properties.putSimple("test1", "val1") + this.properties.putSimple("test2", provider { "val2" }) }) - this.properties.put("test1", "val1") - this.properties.put("test2", provider { "val2" }) + this.properties.putSimple("test1", "val1") + this.properties.putSimple("test2", provider { "val2" }) } flavors { From 58f28b293cc3799252c2170a2e7a594f58f80049 Mon Sep 17 00:00:00 2001 From: Lorenz Wrobel <43410952+DasBabyPixel@users.noreply.github.com> Date: Sat, 28 Mar 2026 01:29:27 +0100 Subject: [PATCH 6/8] more work on dependency logic --- .../gradle/juppiter/JuppiterPlugin.kt | 184 +++++++++++++++++- .../gradle/juppiter/data/ModuleArtifact.kt | 7 + .../juppiter/data/ModuleConfiguration.kt | 13 +- .../gradle/juppiter/data/ModuleContributor.kt | 6 +- .../gradle/juppiter/data/ModuleDependency.kt | 8 + .../juppiter/data/ModuleExternalDependency.kt | 8 +- .../{ => data}/PropertyValueHolder.kt | 2 +- .../{ => tasks}/GenerateModuleJson.kt | 19 +- .../gradle/juppiter/util/ChecksumHelper.kt | 11 +- .../ExtractModuleDependencyInformation.kt | 54 +++++ .../gradle/juppiter/util/MavenUtility.kt | 132 +++++++++---- .../{ => util}/UnknownDependencyException.kt | 10 +- test/build.gradle.kts | 60 ++++-- test/gradle.properties | 28 +++ 14 files changed, 451 insertions(+), 91 deletions(-) rename src/main/kotlin/eu/cloudnetservice/gradle/juppiter/{ => data}/PropertyValueHolder.kt (97%) rename src/main/kotlin/eu/cloudnetservice/gradle/juppiter/{ => tasks}/GenerateModuleJson.kt (85%) create mode 100644 src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/ExtractModuleDependencyInformation.kt rename src/main/kotlin/eu/cloudnetservice/gradle/juppiter/{ => util}/UnknownDependencyException.kt (69%) create mode 100644 test/gradle.properties diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt index 3ec1c39..cbbcee7 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt @@ -16,10 +16,18 @@ package eu.cloudnetservice.gradle.juppiter -import eu.cloudnetservice.gradle.juppiter.data.ModuleConfiguration +import eu.cloudnetservice.gradle.juppiter.data.* import eu.cloudnetservice.gradle.juppiter.flavor.FlavorExtension +import eu.cloudnetservice.gradle.juppiter.tasks.GenerateModuleJson +import eu.cloudnetservice.gradle.juppiter.util.ChecksumHelper +import eu.cloudnetservice.gradle.juppiter.util.ExtractModuleDependencyInformation +import org.gradle.api.Action +import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ConfigurationContainer +import org.gradle.api.artifacts.ResolvedDependency import org.gradle.api.plugins.JavaPlugin import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.SourceSetContainer @@ -28,12 +36,74 @@ import org.gradle.kotlin.dsl.register import org.gradle.kotlin.dsl.withType class JuppiterPlugin : Plugin { + fun ConfigurationContainer.declarable(name: String) = declarable(name) {} + fun ConfigurationContainer.declarable( + name: String, action: Action + ): NamedDomainObjectProvider = register(name) { + isCanBeResolved = false + isCanBeConsumed = false + action.execute(this) + } + + private fun Project.collect( + dependency: ResolvedDependency, + target: MutableSet = HashSet() + ): Set { + // Empty artifacts can exist for BOMs. We ignore those for now + if (dependency.moduleArtifacts.isEmpty()) { + return target + } + // For now, we require a single artifact + dependency.moduleArtifacts.single().apply { + val loader = "maven" + val environments = setOf("*") + val optional = false + val group = moduleVersion.id.group + val name = name + val version = moduleVersion.id.version + val classifier = classifier + val snapshot = version.endsWith("-SNAPSHOT") + val checksum = if (snapshot) null else ChecksumHelper.sha3256(file) + target.add( + IntermediateExternalDependency( + loader, + environments, + optional, + group, + name, + version, + classifier, + checksum + ) + ) + if (!snapshot) { + dependency.children.forEach { + collect(it, target) + } + } + } + return target + } + override fun apply(target: Project) { target.run { - val libraries = configurations.register("moduleLibrary") - val moduleDependencies = configurations.register("moduleDependency") + val libraries = configurations.declarable("moduleLibrary") + val moduleDependencies = configurations.declarable("moduleDependency") + val librariesOnly = configurations.declarable("moduleLibraryOnly") { + extendsFrom(libraries) + } + val moduleDependenciesOnly = configurations.declarable("moduleDependencyOnly") { + extendsFrom(moduleDependencies) + } + val librariesClasspath = configurations.resolvable("moduleLibraryClasspath") { extendsFrom(libraries) } + val dependenciesClasspath = + configurations.resolvable("moduleDependencyClasspath") { extendsFrom(moduleDependencies) } + val librariesOnlyClasspath = + configurations.resolvable("moduleLibraryOnlyClasspath") { extendsFrom(librariesOnly) } + val dependenciesOnlyClasspath = + configurations.resolvable("moduleDependencyOnlyClasspath") { extendsFrom(moduleDependenciesOnly) } - val moduleExtension = ModuleConfiguration(target.objects) + val moduleExtension = ModuleConfiguration(objects) extensions.add("moduleJson", moduleExtension) val flavorExtension = FlavorExtension(this) @@ -41,11 +111,102 @@ class JuppiterPlugin : Plugin { val generateModuleTask = tasks.register("genModuleJson") { - fileName.convention("cloudnet-module.json") - outputDirectory.convention(layout.buildDirectory.dir("generated/module-json")) + outputFile.convention( + layout.buildDirectory.dir("generated/module-json").map { it.file("cloudnet-module.json") }) moduleConfiguration.convention(moduleExtension) } + moduleExtension.dependencies.addAll(dependenciesOnlyClasspath.map { configuration -> + configuration.allDependencies.map { dependency -> + ModuleDependency(objects).apply { + // We resolve a detached configuration with our single dependency. + val extracted = configurations.detachedConfiguration(dependency) + .also { it.isTransitive = false }.incoming.artifacts.resolvedArtifacts.map { set -> set.single() } + .map { result -> ExtractModuleDependencyInformation.extract(result.file) } + + id.convention(extracted.map { it.id }) + versionRange.convention(dependency.version) + dependencyType.convention(ModuleDependencyType.REQUIRED) + } + } + }) + + moduleExtension.externalDependencies.addAll(librariesOnlyClasspath.map { configuration -> + configuration.allDependencies.flatMap { dependency -> + + val configuration = configurations.detachedConfiguration(dependency) + + + val intermediates = + configuration.resolvedConfiguration.firstLevelModuleDependencies.single().let { collect(it) } + intermediates.map { e -> + ModuleExternalDependency(objects).apply { + this.loader.convention(e.loader) + this.optional.convention(e.optional) + this.environments.convention(e.environments) + this.properties.putSimple("group", e.group) + this.properties.putSimple("name", e.name) + this.properties.putSimple("version", e.version) + e.classifier?.also { this.properties.putSimple("classifier", it) } + e.checksum?.also { this.properties.putSimple("checksum", it) } + } + } +// if (isSnapshot(dependency)) { +// configuration.isTransitive = false +// configuration.resolvedConfiguration.firstLevelModuleDependencies.forEach { dep -> +// println(dep) +// } +// configuration.resolvedConfiguration.resolvedArtifacts.map { resolvedArtifact -> +// ModuleExternalDependency(objects).also { externalDependency -> +// externalDependency.loader.convention("maven") +// externalDependency.environments.convention(setOf("*")) +// externalDependency.optional.convention(false) +// externalDependency.properties.putSimple("group", resolvedArtifact.moduleVersion.id.group) +// externalDependency.properties.putSimple("name", resolvedArtifact.name) +// externalDependency.properties.putSimple("version", resolvedArtifact.moduleVersion.id.version) +// resolvedArtifact.classifier?.let { externalDependency.properties.putSimple("classifier", it) } +// } +// } +// } else { +// configuration.resolvedConfiguration.firstLevelModuleDependencies.forEach { dep -> +// println(dep) +// dep.moduleArtifacts.forEach { artifact -> +// println(artifact) +// } +// println(dep.children) +// } +// configuration.resolvedConfiguration.resolvedArtifacts.map { resolvedArtifact -> +// ModuleExternalDependency(objects).also { externalDependency -> +// externalDependency.loader.convention("maven") +// externalDependency.environments.convention(setOf("*")) +// externalDependency.optional.convention(false) +// externalDependency.properties.putSimple("group", resolvedArtifact.moduleVersion.id.group) +// externalDependency.properties.putSimple("name", resolvedArtifact.name) +// externalDependency.properties.putSimple("version", resolvedArtifact.moduleVersion.id.version) +// resolvedArtifact.classifier?.let { externalDependency.properties.putSimple("classifier", it) } +// externalDependency.properties.putSimple("checksum", ChecksumHelper.fileShaSum(resolvedArtifact.file)) +// } +// } +// } + } + }) + + +// afterEvaluate { +// libraries.map { it.allDependencies }.map { +// it.map { +// val c = configurations.detachedConfiguration(it) +// c.isCanBeResolved = true +// c.isCanBeConsumed = false +// c.isCanBeDeclared = false +// +// c.resolvedConfiguration.firstLevelModuleDependencies.forEach { +// println(it) +// } +// } +// }.get() +// } + plugins.withType { extensions.getByType().named(SourceSet.MAIN_SOURCE_SET_NAME) { resources.srcDir(generateModuleTask) @@ -55,3 +216,14 @@ class JuppiterPlugin : Plugin { } } } + +data class IntermediateExternalDependency( + val loader: String, + val environments: Set, + val optional: Boolean, + val group: String, + val name: String, + val version: String, + val classifier: String?, + val checksum: String? +) diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleArtifact.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleArtifact.kt index c9b526b..dc488d1 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleArtifact.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleArtifact.kt @@ -35,6 +35,13 @@ class ModuleArtifact(objectFactory: ObjectFactory) { @Input val environments: SetProperty = objectFactory.setProperty() + + init { + source.finalizeValueOnRead() + sourcePath.finalizeValueOnRead() + targetPath.finalizeValueOnRead() + environments.finalizeValueOnRead() + } } enum class ModuleArtifactSource { diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleConfiguration.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleConfiguration.kt index 9f3de05..2c5fa84 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleConfiguration.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleConfiguration.kt @@ -16,7 +16,6 @@ package eu.cloudnetservice.gradle.juppiter.data -import eu.cloudnetservice.gradle.juppiter.PropertyValueHolder import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property @@ -63,6 +62,18 @@ class ModuleConfiguration( @Nested val properties: MapProperty = objectFactory.mapProperty() + init { + id.finalizeValueOnRead() + name.finalizeValueOnRead() + description.finalizeValueOnRead() + entrypoint.finalizeValueOnRead() + version.finalizeValueOnRead() + artifacts.finalizeValueOnRead() + dependencies.finalizeValueOnRead() + externalDependencies.finalizeValueOnRead() + contributors.finalizeValueOnRead() + properties.finalizeValueOnRead() + } // fun setDefaults( diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleContributor.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleContributor.kt index 23715a7..a73ccfd 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleContributor.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleContributor.kt @@ -16,7 +16,6 @@ package eu.cloudnetservice.gradle.juppiter.data -import eu.cloudnetservice.gradle.juppiter.PropertyValueHolder import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property @@ -31,4 +30,9 @@ class ModuleContributor(objectFactory: ObjectFactory) { @Nested val properties: MapProperty = objectFactory.mapProperty() + + init { + name.finalizeValueOnRead() + properties.finalizeValueOnRead() + } } diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleDependency.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleDependency.kt index be22a69..282b5d0 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleDependency.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleDependency.kt @@ -26,6 +26,14 @@ class ModuleDependency(objectFactory: ObjectFactory) { val id: Property = objectFactory.property() @Input val versionRange: Property = objectFactory.property() + @Input + val dependencyType: Property = objectFactory.property() + + init { + id.finalizeValueOnRead() + versionRange.finalizeValueOnRead() + dependencyType.finalizeValueOnRead() + } } enum class ModuleDependencyType { diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleExternalDependency.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleExternalDependency.kt index ba42bb3..3fb29e1 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleExternalDependency.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleExternalDependency.kt @@ -16,7 +16,6 @@ package eu.cloudnetservice.gradle.juppiter.data -import eu.cloudnetservice.gradle.juppiter.PropertyValueHolder import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property @@ -41,4 +40,11 @@ class ModuleExternalDependency(objectFactory: ObjectFactory) { @Nested val properties: MapProperty = objectFactory.mapProperty() + + init { + loader.finalizeValueOnRead() + optional.finalizeValueOnRead() + environments.finalizeValueOnRead() + properties.finalizeValueOnRead() + } } diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/PropertyValueHolder.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/PropertyValueHolder.kt similarity index 97% rename from src/main/kotlin/eu/cloudnetservice/gradle/juppiter/PropertyValueHolder.kt rename to src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/PropertyValueHolder.kt index 751135f..c0fe082 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/PropertyValueHolder.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/PropertyValueHolder.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package eu.cloudnetservice.gradle.juppiter +package eu.cloudnetservice.gradle.juppiter.data import org.gradle.api.provider.MapProperty import org.gradle.api.tasks.Input diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/GenerateModuleJson.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/tasks/GenerateModuleJson.kt similarity index 85% rename from src/main/kotlin/eu/cloudnetservice/gradle/juppiter/GenerateModuleJson.kt rename to src/main/kotlin/eu/cloudnetservice/gradle/juppiter/tasks/GenerateModuleJson.kt index 27e614b..8a11d5c 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/GenerateModuleJson.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/tasks/GenerateModuleJson.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package eu.cloudnetservice.gradle.juppiter +package eu.cloudnetservice.gradle.juppiter.tasks import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.core.JsonFactory @@ -26,22 +26,23 @@ import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.module.kotlin.registerKotlinModule import eu.cloudnetservice.gradle.juppiter.data.ModuleConfiguration +import eu.cloudnetservice.gradle.juppiter.data.PropertyValueHolder import org.gradle.api.DefaultTask -import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider -import org.gradle.api.tasks.* +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction @CacheableTask abstract class GenerateModuleJson : DefaultTask() { - @get:Input - abstract val fileName: Property - @get:Nested abstract val moduleConfiguration: Property - @get:OutputDirectory - abstract val outputDirectory: DirectoryProperty + @get:OutputFile + abstract val outputFile: RegularFileProperty @TaskAction fun generate() { @@ -79,6 +80,6 @@ abstract class GenerateModuleJson : DefaultTask() { .setSerializationInclusion(JsonInclude.Include.NON_EMPTY).writerWithDefaultPrettyPrinter() val moduleConfiguration = moduleConfiguration.get() - mapper.writeValue(outputDirectory.file(fileName).get().asFile, moduleConfiguration) + mapper.writeValue(outputFile.get().asFile, moduleConfiguration) } } diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/ChecksumHelper.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/ChecksumHelper.kt index 71c7d6e..f025c03 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/ChecksumHelper.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/ChecksumHelper.kt @@ -24,11 +24,12 @@ import java.security.NoSuchAlgorithmException // copied from the CloudNet-Updater to keep consistent when generating checksums object ChecksumHelper { @Throws(IOException::class) - fun fileShaSum(path: File): String = - newSha3256Digest().run { - update(path.readBytes()) - bytesToHex(digest()) - } + fun fileShaSum(path: File): String = sha3256(path) + + fun sha3256(path: File): String = newSha3256Digest().run { + update(path.readBytes()) + bytesToHex(digest()) + } @Throws(NoSuchAlgorithmException::class) private fun newSha3256Digest(): MessageDigest = MessageDigest.getInstance("SHA3-256") diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/ExtractModuleDependencyInformation.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/ExtractModuleDependencyInformation.kt new file mode 100644 index 0000000..ec916a7 --- /dev/null +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/ExtractModuleDependencyInformation.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.gradle.juppiter.util + +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import java.io.File +import java.util.jar.JarFile + +object ExtractModuleDependencyInformation { + private val objectMapper = ObjectMapper() + fun extract(file: File): ExtractedModuleDependency { + val jarFile = JarFile(file) + + // TODO remove, used for testing with old module system + jarFile.getJarEntry("module.json")?.let { jarFile.getInputStream(it) }.use { objectMapper.readTree(it) } + ?.let { it["name"] }?.textValue()?.let { return ExtractedModuleDependency(it) } + + val entry = jarFile.getJarEntry("META-INF/cloudnet-module.json") + ?: throw IllegalArgumentException("File ${file.absolutePath} is not a CloudNet module") + val jsonNode = jarFile.getInputStream(entry).use { objectMapper.readTree(it) }!! + + val schemaNode: JsonNode? = jsonNode["schema"] + if (schemaNode == null || !schemaNode.isTextual) { + throw IllegalArgumentException("File ${file.absolutePath} has a badly formatted cloudnet-module.json") + } + val schema = schemaNode.textValue()!! + return when (schema) { + "v1" -> { + val id = jsonNode["id"]?.textValue() + ?: throw IllegalArgumentException("File ${file.absolutePath} is missing module id") + ExtractedModuleDependency(id) + } + + else -> throw IllegalArgumentException("Unknown schema: $schema") + } + } +} + +data class ExtractedModuleDependency(val id: String) diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/MavenUtility.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/MavenUtility.kt index 3e01af1..2e375fb 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/MavenUtility.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/MavenUtility.kt @@ -17,58 +17,104 @@ package eu.cloudnetservice.gradle.juppiter.util import eu.cloudnetservice.gradle.juppiter.data.ModuleConfiguration -//import eu.cloudnetservice.gradle.juppiter.UnknownDependencyException import org.gradle.api.artifacts.repositories.MavenArtifactRepository +import org.gradle.api.internal.artifacts.repositories.resolver.MavenUniqueSnapshotComponentIdentifier import java.net.HttpURLConnection import java.net.URL +data class MavenDependency(val group: String, val name: String, val version: String, val repositoryName: String) + +data class MavenRepository(val url: String) + //object MavenUtility { -// fun findRepository( -// dependency: ModuleConfiguration.Dependency, -// repositories: Iterable, -// ): ModuleConfiguration.Repository? { -// repositories.forEach { -// val urlInRepository = resolveUrlInRepository(dependency, it) ?: return@forEach -// if (dependency.timestampedVersion != null || dependency.classifier != null) { -// // timestamped version and classifier are not supported by CloudNet module loading currently -// // therefore, we need to hack around this limitation by providing the url directly -// dependency.url = urlInRepository.toExternalForm() -// return null -// } else { -// // CloudNet can download this dependency directly from the maven repository -// val repository = ModuleConfiguration.Repository(it.name) -// repository.url = it.url.toURL().toExternalForm() -// dependency.repo = repository.name -// return repository -// } +// fun findRepository( +// dependency: MavenDependency, +// repositories: Iterable, +// ): MavenRepository? { +// repositories.forEach { +// val urlInRepository = resolveUrlInRepository(dependency, it) ?: return@forEach +// if (dependency.timestampedVersion != null || dependency.classifier != null) { +// // timestamped version and classifier are not supported by CloudNet module loading currently +// // therefore, we need to hack around this limitation by providing the url directly +// dependency.url = urlInRepository.toExternalForm() +// return null +// } else { +// // CloudNet can download this dependency directly from the maven repository +// val repository = ModuleConfiguration.Repository(it.name) +// repository.url = it.url.toURL().toExternalForm() +// dependency.repo = repository.name +// return repository +// } +// } +// +// throw UnknownDependencyException(dependency) // } // -// throw UnknownDependencyException(dependency) -// } +// private fun resolveUrlInRepository( +// dependency: MavenDependency, +// repository: MavenArtifactRepository, +// ): URL? { +// val groupForUrl = dependency.group!!.replace(".", "/") +// val componentVersion = dependency.timestampedVersion ?: dependency.version +// val classifier = if (dependency.classifier != null) "-${dependency.classifier}" else "" +// val componentName = "${dependency.name}-$componentVersion$classifier.jar" +// val urlPath = "$groupForUrl/${dependency.name}/${dependency.version}/$componentName" +// val fullUrl = URL(repository.url.toURL(), urlPath) +// return if (resourceExists(fullUrl)) fullUrl else null +// } // -// private fun resolveUrlInRepository( -// dependency: ModuleConfiguration.Dependency, -// repository: MavenArtifactRepository, -// ): URL? { -// val groupForUrl = dependency.group!!.replace(".", "/") -// val componentVersion = dependency.timestampedVersion ?: dependency.version -// val classifier = if (dependency.classifier != null) "-${dependency.classifier}" else "" -// val componentName = "${dependency.name}-$componentVersion$classifier.jar" -// val urlPath = "$groupForUrl/${dependency.name}/${dependency.version}/$componentName" -// val fullUrl = URL(repository.url.toURL(), urlPath) -// return if (resourceExists(fullUrl)) fullUrl else null -// } +// private fun resourceExists(url: URL): Boolean = +// with(url.openConnection() as HttpURLConnection) { +// useCaches = false +// connectTimeout = 5000 +// requestMethod = "HEAD" +// instanceFollowRedirects = true +// +// setRequestProperty("User-Agent", "CloudNetService/juppiter Repository Resolve") +// connect() +// +// responseCode == 200 +// } +//} + +fun test() { +// if (!this.resolved.getAndSet(true)) { +// name = name ?: project.name +// group = group ?: project.group.toString() +// version = version ?: project.version.toString() // -// private fun resourceExists(url: URL): Boolean = -// with(url.openConnection() as HttpURLConnection) { -// useCaches = false -// connectTimeout = 5000 -// requestMethod = "HEAD" -// instanceFollowRedirects = true +// // other stuff +// author = author ?: "Anonymous" +// website = website ?: "https://cloudnetservice.eu" +// description = description ?: project.description ?: "Just another CloudNet module" // -// setRequestProperty("User-Agent", "CloudNetService/juppiter Repository Resolve") -// connect() +// // dependencies of the module we need to resolve +// libraries.resolvedConfiguration.resolvedArtifacts.forEach { +// val versionId = it.moduleVersion.id +// val dependency = Dependency(it.name) +// dependency.group = versionId.group +// dependency.version = versionId.version +// dependency.classifier = it.classifier +// dependency.checksum = ChecksumHelper.fileShaSum(it.file) // -// responseCode == 200 +// val componentIdentifier = it.id.componentIdentifier +// if (versionId.version.endsWith("-SNAPSHOT") && componentIdentifier is MavenUniqueSnapshotComponentIdentifier) { +// dependency.timestampedVersion = componentIdentifier.timestampedVersion +// } +// +// dependencies.add(dependency) // } -//} +// +// // dependencies of the module that are other modules, so we only need: group, name, version +// moduleDependencies.resolvedConfiguration.firstLevelModuleDependencies +// .map { it.module.id } +// .forEach { +// val dependency = Dependency(it.name) +// dependency.group = it.group +// dependency.version = it.version +// dependency.needsRepoResolve = false +// +// dependencies.add(dependency) +// } +// } +} diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/UnknownDependencyException.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/UnknownDependencyException.kt similarity index 69% rename from src/main/kotlin/eu/cloudnetservice/gradle/juppiter/UnknownDependencyException.kt rename to src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/UnknownDependencyException.kt index db5f1d5..429f750 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/UnknownDependencyException.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/util/UnknownDependencyException.kt @@ -14,10 +14,8 @@ * limitations under the License. */ -package eu.cloudnetservice.gradle.juppiter +package eu.cloudnetservice.gradle.juppiter.util -import eu.cloudnetservice.gradle.juppiter.data.ModuleConfiguration - -//class UnknownDependencyException( -// dep: ModuleConfiguration.Dependency, -//) : Exception("Unable to resolve dependency ${dep.group}:${dep.name}:${dep.version}") +class UnknownDependencyException( + dependencyIdentifier: String, +) : Exception("Unable to resolve dependency $dependencyIdentifier") diff --git a/test/build.gradle.kts b/test/build.gradle.kts index 1313bbc..0bd140c 100644 --- a/test/build.gradle.kts +++ b/test/build.gradle.kts @@ -1,6 +1,4 @@ import eu.cloudnetservice.gradle.juppiter.data.* -import eu.cloudnetservice.gradle.juppiter.putComplex -import eu.cloudnetservice.gradle.juppiter.putSimple /* * Copyright 2019-present CloudNetService team & contributors @@ -43,17 +41,18 @@ moduleJson { this.targetPath = "test/target" this.environments.add("wrapper env ??") }) - dependencies.add(ModuleDependency(objects).apply { - this.id = "bridge" - this.versionRange = "69+" - }) - externalDependencies.add(ModuleExternalDependency(objects).apply { - this.environments.add("* or sth") - this.loader = "was auch immer loader sein soll" - this.optional = true - this.properties.putComplex("test1", Cls("val1")) - this.properties.putComplex("test2", provider { Cls("val2") }) - }) +// dependencies.add(ModuleDependency(objects).apply { +// this.id = "bridge" +// this.versionRange = "69+" +// this.dependencyType = ModuleDependencyType.REQUIRED +// }) +// externalDependencies.add(ModuleExternalDependency(objects).apply { +// this.environments.add("* or sth") +// this.loader = "was auch immer loader sein soll" +// this.optional = true +// this.properties.putComplex("test1", Cls("val1")) +// this.properties.putComplex("test2", provider { Cls("val2") }) +// }) contributors.add(ModuleContributor(objects).apply { this.name = "se big bad noob" this.properties.putSimple("test1", "val1") @@ -85,16 +84,41 @@ tasks.assemble { repositories { mavenCentral() + maven("https://central.sonatype.com/repository/maven-snapshots/") + maven { + name = "papermc" + url = uri("https://repo.papermc.io/repository/maven-public/") + } + + maven("https://repository.derklaro.dev/releases/") + maven("https://repository.derklaro.dev/snapshots/") } +java.toolchain.languageVersion.set(JavaLanguageVersion.of(25)) +//java.disableAutoTargetJvm() + dependencies { + "moduleDependency"("eu.cloudnetservice.cloudnet:bridge-impl:[4.0.0-RC14, 4.0.0-RC16]") // api("com.google.code.gson:gson:2.13.2") - "compileOnlyApi"("com.google.code.gson:gson:[2.13.0, 2.13.5]") + moduleLibrary("com.google.code.gson:gson:[2.13.0, 2.13.5]") +// compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-20260109.190012-50") { +// this.isTransitive = false +// } + moduleLibrary("com.google.guava:guava:33.5.0-jre") +// moduleLibrary("io.papermc.paper:paper-api:1.21.11-R0.1-20260310.030221-86:javadoc") // "commonApi"("com.google.code.gson:gson:2.13.2") } -configurations.compileOnlyApi.get().run { - this.dependencies.forEach { - println(it.version) - } +val c2 = configurations.resolvable("abc") { + this.extendsFrom(configurations.compileOnly) } +//c2.get().run { +// this.allDependencies.forEach { +// println(it.version) +// } +// resolvedConfiguration.resolvedArtifacts.forEach { +// val version = it.moduleVersion.id.version +// println(version) +// println(it.id.componentIdentifier is MavenUniqueSnapshotComponentIdentifier) +// } +//} diff --git a/test/gradle.properties b/test/gradle.properties new file mode 100644 index 0000000..e7f7d7f --- /dev/null +++ b/test/gradle.properties @@ -0,0 +1,28 @@ +# +# Copyright 2019-present CloudNetService team & contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# kotlin configuration +kotlin.code.style=official + +# gradle configuration +org.gradle.caching=true +org.gradle.parallel=true +org.gradle.warning.mode=all +org.gradle.logging.level=warn +org.gradle.configureondemand=true +org.gradle.configuration-cache=true +org.gradle.configuration-cache.problems=fail +org.gradle.jvmargs=-Xmx2G -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 From bb0f8d0299b4c1ed18cb9a4e6314699dc42fb029 Mon Sep 17 00:00:00 2001 From: Lorenz Wrobel <43410952+DasBabyPixel@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:25:58 +0100 Subject: [PATCH 7/8] Seems to be working --- .../gradle/juppiter/JuppiterPlugin.kt | 157 +++++++-------- .../gradle/juppiter/data/ModuleDependency.kt | 3 +- .../juppiter/tasks/PrepareModuleJson.kt | 179 ++++++++++++++++++ test/build.gradle.kts | 3 + 4 files changed, 254 insertions(+), 88 deletions(-) create mode 100644 src/main/kotlin/eu/cloudnetservice/gradle/juppiter/tasks/PrepareModuleJson.kt diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt index cbbcee7..b46d3e8 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/JuppiterPlugin.kt @@ -19,8 +19,8 @@ package eu.cloudnetservice.gradle.juppiter import eu.cloudnetservice.gradle.juppiter.data.* import eu.cloudnetservice.gradle.juppiter.flavor.FlavorExtension import eu.cloudnetservice.gradle.juppiter.tasks.GenerateModuleJson +import eu.cloudnetservice.gradle.juppiter.tasks.PrepareModuleJson import eu.cloudnetservice.gradle.juppiter.util.ChecksumHelper -import eu.cloudnetservice.gradle.juppiter.util.ExtractModuleDependencyInformation import org.gradle.api.Action import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Plugin @@ -28,12 +28,14 @@ import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.ConfigurationContainer import org.gradle.api.artifacts.ResolvedDependency +import org.gradle.api.artifacts.repositories.MavenArtifactRepository import org.gradle.api.plugins.JavaPlugin import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.SourceSetContainer import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.register import org.gradle.kotlin.dsl.withType +import java.io.File class JuppiterPlugin : Plugin { fun ConfigurationContainer.declarable(name: String) = declarable(name) {} @@ -73,7 +75,9 @@ class JuppiterPlugin : Plugin { name, version, classifier, - checksum + checksum, + snapshot, + file ) ) if (!snapshot) { @@ -87,6 +91,7 @@ class JuppiterPlugin : Plugin { override fun apply(target: Project) { target.run { + val objects = objects val libraries = configurations.declarable("moduleLibrary") val moduleDependencies = configurations.declarable("moduleDependency") val librariesOnly = configurations.declarable("moduleLibraryOnly") { @@ -109,104 +114,80 @@ class JuppiterPlugin : Plugin { val flavorExtension = FlavorExtension(this) extensions.add("flavors", flavorExtension) + val prepareModuleJsonTask = tasks.register("prepareModule") { + moduleJson.convention { temporaryDir.resolve("prepared-module.json") } + repositories.convention( + target.repositories.filterIsInstance().map { it.url.toString() }) + + unresolvedModuleDependencies.convention(dependenciesOnlyClasspath.map { configuration -> + configuration.allDependencies.map { dependency -> + // We resolve a detached configuration with our single dependency. + val file = configurations.detachedConfiguration(dependency) + .also { it.isTransitive = false }.incoming.artifacts.resolvedArtifacts.map { set -> set.single() } + .map { it.file }.get() + + val versionRange = dependency.version!! + val optional = false + val type = ModuleDependencyType.REQUIRED + PrepareModuleJson.UnresolvedModuleDependency(versionRange, optional, file, type) + } + }) + unresolvedExternalDependencies.convention(librariesOnlyClasspath.map { configuration -> + configuration.allDependencies.flatMap { dependency -> + + val configuration = configurations.detachedConfiguration(dependency) + + + val intermediates = + configuration.resolvedConfiguration.firstLevelModuleDependencies.single().let { collect(it) } + intermediates.map { e -> + PrepareModuleJson.UnresolvedExternalDependency( + e.snapshot, + e.group, + e.name, + e.version, + e.classifier, + e.environments, + e.optional, + e.file + ) + } + } + }) + + } val generateModuleTask = tasks.register("genModuleJson") { + dependsOn(prepareModuleJsonTask) outputFile.convention( layout.buildDirectory.dir("generated/module-json").map { it.file("cloudnet-module.json") }) moduleConfiguration.convention(moduleExtension) } - moduleExtension.dependencies.addAll(dependenciesOnlyClasspath.map { configuration -> - configuration.allDependencies.map { dependency -> - ModuleDependency(objects).apply { - // We resolve a detached configuration with our single dependency. - val extracted = configurations.detachedConfiguration(dependency) - .also { it.isTransitive = false }.incoming.artifacts.resolvedArtifacts.map { set -> set.single() } - .map { result -> ExtractModuleDependencyInformation.extract(result.file) } - - id.convention(extracted.map { it.id }) - versionRange.convention(dependency.version) - dependencyType.convention(ModuleDependencyType.REQUIRED) + val prepared = prepareModuleJsonTask.flatMap { it.moduleJson }.map { it.asFile } + .map { it.readText() }.map { PrepareModuleJson.PreparedModuleJson.deserialize(it) } + moduleExtension.externalDependencies.addAll(prepared.map { prepared -> + prepared.externalDependencies.map { e -> + ModuleExternalDependency(objects).apply { + this.loader.convention(e.loader) + this.optional.convention(e.optional) + this.environments.convention(e.environments) + e.properties.forEach { (string, any) -> + this.properties.putSimple(string, any) + } } } }) - - moduleExtension.externalDependencies.addAll(librariesOnlyClasspath.map { configuration -> - configuration.allDependencies.flatMap { dependency -> - - val configuration = configurations.detachedConfiguration(dependency) - - - val intermediates = - configuration.resolvedConfiguration.firstLevelModuleDependencies.single().let { collect(it) } - intermediates.map { e -> - ModuleExternalDependency(objects).apply { - this.loader.convention(e.loader) - this.optional.convention(e.optional) - this.environments.convention(e.environments) - this.properties.putSimple("group", e.group) - this.properties.putSimple("name", e.name) - this.properties.putSimple("version", e.version) - e.classifier?.also { this.properties.putSimple("classifier", it) } - e.checksum?.also { this.properties.putSimple("checksum", it) } - } + moduleExtension.dependencies.addAll(prepared.map { prepared -> + prepared.moduleDependencies.map { e -> + ModuleDependency(objects).apply { + this.id.convention(e.id) + this.versionRange.convention(e.versionRange) + this.dependencyType.convention(e.dependencyType) } -// if (isSnapshot(dependency)) { -// configuration.isTransitive = false -// configuration.resolvedConfiguration.firstLevelModuleDependencies.forEach { dep -> -// println(dep) -// } -// configuration.resolvedConfiguration.resolvedArtifacts.map { resolvedArtifact -> -// ModuleExternalDependency(objects).also { externalDependency -> -// externalDependency.loader.convention("maven") -// externalDependency.environments.convention(setOf("*")) -// externalDependency.optional.convention(false) -// externalDependency.properties.putSimple("group", resolvedArtifact.moduleVersion.id.group) -// externalDependency.properties.putSimple("name", resolvedArtifact.name) -// externalDependency.properties.putSimple("version", resolvedArtifact.moduleVersion.id.version) -// resolvedArtifact.classifier?.let { externalDependency.properties.putSimple("classifier", it) } -// } -// } -// } else { -// configuration.resolvedConfiguration.firstLevelModuleDependencies.forEach { dep -> -// println(dep) -// dep.moduleArtifacts.forEach { artifact -> -// println(artifact) -// } -// println(dep.children) -// } -// configuration.resolvedConfiguration.resolvedArtifacts.map { resolvedArtifact -> -// ModuleExternalDependency(objects).also { externalDependency -> -// externalDependency.loader.convention("maven") -// externalDependency.environments.convention(setOf("*")) -// externalDependency.optional.convention(false) -// externalDependency.properties.putSimple("group", resolvedArtifact.moduleVersion.id.group) -// externalDependency.properties.putSimple("name", resolvedArtifact.name) -// externalDependency.properties.putSimple("version", resolvedArtifact.moduleVersion.id.version) -// resolvedArtifact.classifier?.let { externalDependency.properties.putSimple("classifier", it) } -// externalDependency.properties.putSimple("checksum", ChecksumHelper.fileShaSum(resolvedArtifact.file)) -// } -// } -// } } }) - -// afterEvaluate { -// libraries.map { it.allDependencies }.map { -// it.map { -// val c = configurations.detachedConfiguration(it) -// c.isCanBeResolved = true -// c.isCanBeConsumed = false -// c.isCanBeDeclared = false -// -// c.resolvedConfiguration.firstLevelModuleDependencies.forEach { -// println(it) -// } -// } -// }.get() -// } - plugins.withType { extensions.getByType().named(SourceSet.MAIN_SOURCE_SET_NAME) { resources.srcDir(generateModuleTask) @@ -225,5 +206,7 @@ data class IntermediateExternalDependency( val name: String, val version: String, val classifier: String?, - val checksum: String? + val checksum: String?, + val snapshot: Boolean, + val file: File ) diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleDependency.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleDependency.kt index 282b5d0..4520484 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleDependency.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/data/ModuleDependency.kt @@ -38,5 +38,6 @@ class ModuleDependency(objectFactory: ObjectFactory) { enum class ModuleDependencyType { REQUIRED, - SUGGESTED + SUGGESTED, + OPTIONAL } diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/tasks/PrepareModuleJson.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/tasks/PrepareModuleJson.kt new file mode 100644 index 0000000..4471db6 --- /dev/null +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/tasks/PrepareModuleJson.kt @@ -0,0 +1,179 @@ +/* + * Copyright 2019-present CloudNetService team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.cloudnetservice.gradle.juppiter.tasks + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import eu.cloudnetservice.gradle.juppiter.data.ModuleDependencyType +import eu.cloudnetservice.gradle.juppiter.util.ExtractModuleDependencyInformation +import eu.cloudnetservice.gradle.juppiter.util.UnknownDependencyException +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.SetProperty +import org.gradle.api.tasks.* +import java.io.File +import java.net.HttpURLConnection +import java.net.URL + +@CacheableTask +abstract class PrepareModuleJson : DefaultTask() { + @get:Input + abstract val repositories: SetProperty + + @get:Nested + abstract val unresolvedModuleDependencies: SetProperty + + @get:Nested + abstract val unresolvedExternalDependencies: SetProperty + + @get:OutputFile + abstract val moduleJson: RegularFileProperty + + init { + repositories.finalizeValueOnRead() + unresolvedModuleDependencies.finalizeValueOnRead() + unresolvedExternalDependencies.finalizeValueOnRead() + } + + @TaskAction + fun run() { + val repositories = repositories.get() + val externalDependencies = unresolvedExternalDependencies.get().map { dep -> + if (dep.snapshot) { + // We pass all repositories for snapshot dependencies. + // Some dependencies of the snapshots could be on other repositories, there is no clear "source" repository + val properties = HashMap() + properties["group"] = dep.group + properties["name"] = dep.name + properties["version"] = dep.version + dep.classifier?.let { properties["classifier"] = it } + properties["repositories"] = repositories + ResolvedExternalDependency("maven-snapshot", dep.optional, dep.environments, properties) + } else { + repositories.forEach { repository -> + val repositoryUrl = URL(repository) + val depUrl = buildURL(repositoryUrl, dep) + if (!depUrl.hasBackedResource()) { + return@forEach + } + + val properties = HashMap() + properties["group"] = dep.group + properties["name"] = dep.name + properties["version"] = dep.version + dep.classifier?.let { properties["classifier"] = it } + properties["repository"] = repository + properties["url"] = depUrl.toString() + return@map ResolvedExternalDependency("maven", dep.optional, dep.environments, properties) + } + throw UnknownDependencyException("Failed to resolve $dep") + } + }.toSet() + val moduleDependencies = unresolvedModuleDependencies.get().map { dep -> + val id = ExtractModuleDependencyInformation.extract(dep.file).id + ResolvedModuleDependency(id, dep.versionRange, dep.dependencyType) + }.toSet() + val prepared = PreparedModuleJson(moduleDependencies, externalDependencies) + moduleJson.get().asFile.writeText(prepared.serialized()) + } + + private fun URL.hasBackedResource(): Boolean { + return with(openConnection() as HttpURLConnection) { + useCaches = false + connectTimeout = 500 + requestMethod = "HEAD" + instanceFollowRedirects = true + + setRequestProperty("User-Agent", "CloudNetService/juppiter Repository Resolve") + connect() + + responseCode == 200 + } + } + + private fun buildURL(repositoryUrl: URL, dep: UnresolvedExternalDependency): URL { + val group = dep.group.replace(".", "/") + val version = dep.version + val classifier = dep.classifier?.let { "-$it" } ?: "" + val componentName = "${dep.name}-$version$classifier.jar" + val urlPath = "$group/${dep.name}/$version/$componentName" + return URL(repositoryUrl, urlPath) + } + + data class UnresolvedModuleDependency( + @Input + val versionRange: String, + @Input + val optional: Boolean, + @InputFile + @PathSensitive(PathSensitivity.NONE) + val file: File, + @Input + val dependencyType: ModuleDependencyType + ) + + data class UnresolvedExternalDependency( + @Input + val snapshot: Boolean, + @Input + val group: String, + @Input + val name: String, + @Input + val version: String, + @Input + @Optional + val classifier: String?, + @Input + val environments: Set, + @Input + val optional: Boolean, + @InputFile + @PathSensitive(PathSensitivity.NONE) + val file: File + ) + + data class ResolvedModuleDependency( + val id: String, + val versionRange: String, + val dependencyType: ModuleDependencyType + ) + + data class ResolvedExternalDependency( + val loader: String, + val optional: Boolean, + val environments: Set, + // For future maintainers: take great care of serialization + val properties: Map + ) + + data class PreparedModuleJson( + val moduleDependencies: Set, + val externalDependencies: Set + ) { + fun serialized(): String = mapper.writeValueAsString(this) + + companion object { + private val mapper = ObjectMapper().registerKotlinModule() + + fun deserialize(input: String): PreparedModuleJson { + return mapper.readValue(input) + } + } + } +} diff --git a/test/build.gradle.kts b/test/build.gradle.kts index 0bd140c..e809edb 100644 --- a/test/build.gradle.kts +++ b/test/build.gradle.kts @@ -105,10 +105,13 @@ dependencies { // this.isTransitive = false // } moduleLibrary("com.google.guava:guava:33.5.0-jre") + moduleLibrary("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT") // moduleLibrary("io.papermc.paper:paper-api:1.21.11-R0.1-20260310.030221-86:javadoc") // "commonApi"("com.google.code.gson:gson:2.13.2") } +val a = true + val c2 = configurations.resolvable("abc") { this.extendsFrom(configurations.compileOnly) } From 7f6eb6ec23b37c4a29e734132405b12e33dd4e64 Mon Sep 17 00:00:00 2001 From: Lorenz Wrobel <43410952+DasBabyPixel@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:27:50 +0100 Subject: [PATCH 8/8] forgot checksum --- .../cloudnetservice/gradle/juppiter/tasks/PrepareModuleJson.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/tasks/PrepareModuleJson.kt b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/tasks/PrepareModuleJson.kt index 4471db6..99f5607 100644 --- a/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/tasks/PrepareModuleJson.kt +++ b/src/main/kotlin/eu/cloudnetservice/gradle/juppiter/tasks/PrepareModuleJson.kt @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.registerKotlinModule import eu.cloudnetservice.gradle.juppiter.data.ModuleDependencyType +import eu.cloudnetservice.gradle.juppiter.util.ChecksumHelper import eu.cloudnetservice.gradle.juppiter.util.ExtractModuleDependencyInformation import eu.cloudnetservice.gradle.juppiter.util.UnknownDependencyException import org.gradle.api.DefaultTask @@ -79,6 +80,7 @@ abstract class PrepareModuleJson : DefaultTask() { dep.classifier?.let { properties["classifier"] = it } properties["repository"] = repository properties["url"] = depUrl.toString() + properties["checksum"] = "sha3256:${ChecksumHelper.sha3256(dep.file)}" return@map ResolvedExternalDependency("maven", dep.optional, dep.environments, properties) } throw UnknownDependencyException("Failed to resolve $dep")