|
| 1 | +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers |
| 2 | +// |
| 3 | +// SPDX-License-Identifier: MPL-2.0 |
| 4 | + |
| 5 | +package cc.tweaked.gradle |
| 6 | + |
| 7 | +import org.gradle.api.artifacts.dsl.DependencyHandler |
| 8 | +import org.gradle.api.file.FileSystemLocation |
| 9 | +import org.gradle.api.provider.Property |
| 10 | +import org.gradle.api.provider.Provider |
| 11 | +import org.gradle.api.tasks.JavaExec |
| 12 | +import org.gradle.process.BaseExecSpec |
| 13 | +import org.gradle.process.JavaExecSpec |
| 14 | +import org.gradle.process.ProcessForkOptions |
| 15 | + |
| 16 | +/** |
| 17 | + * Add an annotation processor to all source sets. |
| 18 | + */ |
| 19 | +fun DependencyHandler.annotationProcessorEverywhere(dep: Any) { |
| 20 | + add("compileOnly", dep) |
| 21 | + add("annotationProcessor", dep) |
| 22 | + |
| 23 | + add("clientCompileOnly", dep) |
| 24 | + add("clientAnnotationProcessor", dep) |
| 25 | + |
| 26 | + add("testCompileOnly", dep) |
| 27 | + add("testAnnotationProcessor", dep) |
| 28 | +} |
| 29 | + |
| 30 | +/** |
| 31 | + * A version of [JavaExecSpec.copyTo] which copies *all* properties. |
| 32 | + */ |
| 33 | +fun JavaExec.copyToFull(spec: JavaExec) { |
| 34 | + copyTo(spec) |
| 35 | + |
| 36 | + // Additional Java options |
| 37 | + spec.jvmArgs = jvmArgs // Fabric overrides getJvmArgs so copyTo doesn't do the right thing. |
| 38 | + spec.args = args |
| 39 | + spec.argumentProviders.addAll(argumentProviders) |
| 40 | + spec.mainClass.set(mainClass) |
| 41 | + spec.classpath = classpath |
| 42 | + spec.javaLauncher.set(javaLauncher) |
| 43 | + if (executable != null) spec.setExecutable(executable!!) |
| 44 | + |
| 45 | + // Additional ExecSpec options |
| 46 | + copyToExec(spec) |
| 47 | +} |
| 48 | + |
| 49 | +/** |
| 50 | + * Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo]. |
| 51 | + */ |
| 52 | +fun BaseExecSpec.copyToExec(spec: BaseExecSpec) { |
| 53 | + spec.isIgnoreExitValue = isIgnoreExitValue |
| 54 | + if (standardInput != null) spec.standardInput = standardInput |
| 55 | + if (standardOutput != null) spec.standardOutput = standardOutput |
| 56 | + if (errorOutput != null) spec.errorOutput = errorOutput |
| 57 | +} |
| 58 | + |
| 59 | +/** |
| 60 | + * An alternative to [Nothing] with a more descriptive name. Use to enforce calling a function with named arguments: |
| 61 | + * |
| 62 | + * ```kotlin |
| 63 | + * fun f(vararg unused: UseNamedArgs, arg1: Int, arg2: Int) { |
| 64 | + * // ... |
| 65 | + * } |
| 66 | + * ``` |
| 67 | + */ |
| 68 | +class UseNamedArgs private constructor() |
| 69 | + |
| 70 | +/** |
| 71 | + * An [AutoCloseable] implementation which can be used to combine other [AutoCloseable] instances. |
| 72 | + * |
| 73 | + * Values which implement [AutoCloseable] can be dynamically registered with [CloseScope.add]. When the scope is closed, |
| 74 | + * each value is closed in the opposite order. |
| 75 | + * |
| 76 | + * This is largely intended for cases where it's not appropriate to nest [AutoCloseable.use], for instance when nested |
| 77 | + * would be too deep. |
| 78 | + */ |
| 79 | +class CloseScope : AutoCloseable { |
| 80 | + private val toClose = ArrayDeque<AutoCloseable>() |
| 81 | + |
| 82 | + /** |
| 83 | + * Add a value to be closed when this scope is closed. |
| 84 | + */ |
| 85 | + public fun add(value: AutoCloseable) { |
| 86 | + toClose.addLast(value) |
| 87 | + } |
| 88 | + |
| 89 | + override fun close() { |
| 90 | + close(null) |
| 91 | + } |
| 92 | + |
| 93 | + @PublishedApi |
| 94 | + internal fun close(baseException: Throwable?) { |
| 95 | + var exception = baseException |
| 96 | + |
| 97 | + while (true) { |
| 98 | + var toClose = toClose.removeLastOrNull() ?: break |
| 99 | + try { |
| 100 | + toClose.close() |
| 101 | + } catch (e: Throwable) { |
| 102 | + if (exception == null) { |
| 103 | + exception = e |
| 104 | + } else { |
| 105 | + exception.addSuppressed(e) |
| 106 | + } |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + if (exception != null) throw exception |
| 111 | + } |
| 112 | + |
| 113 | + inline fun <R> use(block: (CloseScope) -> R): R { |
| 114 | + var exception: Throwable? = null |
| 115 | + try { |
| 116 | + return block(this) |
| 117 | + } catch (e: Throwable) { |
| 118 | + exception = e |
| 119 | + throw e |
| 120 | + } finally { |
| 121 | + close(exception) |
| 122 | + } |
| 123 | + } |
| 124 | +} |
| 125 | + |
| 126 | +/** Proxy method to avoid overload ambiguity. */ |
| 127 | +fun <T> Property<T>.setProvider(provider: Provider<out T>) = set(provider) |
| 128 | + |
| 129 | +/** Short-cut method to get the absolute path of a [FileSystemLocation] provider. */ |
| 130 | +fun Provider<out FileSystemLocation>.getAbsolutePath(): String = get().asFile.absolutePath |
| 131 | + |
| 132 | +/** |
| 133 | + * Get the version immediately after the provided version. |
| 134 | + * |
| 135 | + * For example, given "1.2.3", this will return "1.2.4". |
| 136 | + */ |
| 137 | +fun getNextVersion(version: String): String { |
| 138 | + // Split a version like x.y.z-SNAPSHOT into x.y.z and -SNAPSHOT |
| 139 | + val dashIndex = version.indexOf('-') |
| 140 | + val mainVersion = if (dashIndex < 0) version else version.substring(0, dashIndex) |
| 141 | + |
| 142 | + // Find the last component in x.y.z and increment it. |
| 143 | + val lastIndex = mainVersion.lastIndexOf('.') |
| 144 | + if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"") |
| 145 | + val lastVersion = try { |
| 146 | + version.substring(lastIndex + 1).toInt() |
| 147 | + } catch (e: NumberFormatException) { |
| 148 | + throw IllegalArgumentException("Cannot parse version format \"$version\"", e) |
| 149 | + } |
| 150 | + |
| 151 | + // Then append all components together. |
| 152 | + val out = StringBuilder() |
| 153 | + out.append(version, 0, lastIndex + 1) |
| 154 | + out.append(lastVersion + 1) |
| 155 | + if (dashIndex >= 0) out.append(version, dashIndex, version.length) |
| 156 | + return out.toString() |
| 157 | +} |
0 commit comments