From ae809c9fb4964b863ee7a3a69c3578ee3c62160a Mon Sep 17 00:00:00 2001 From: James Hamilton Date: Tue, 18 Oct 2022 18:32:04 +0200 Subject: [PATCH 1/2] Port Compose Desktop ProGuard support to IntelliJPlugin --- .../org/jetbrains/intellij/IntelliJPlugin.kt | 47 +++++ .../intellij/dsl/ProguardSettings.kt | 25 +++ .../intellij/tasks/AbstractProguardTask.kt | 182 ++++++++++++++++++ .../jetbrains/intellij/tasks/AbstractTask.kt | 50 +++++ .../intellij/utils/ExternalToolRunner.kt | 101 ++++++++++ .../intellij/utils/MultiOutputStream.kt | 35 ++++ .../jetbrains/intellij/utils/cliArgUtils.kt | 45 +++++ .../org/jetbrains/intellij/utils/fileUtils.kt | 110 +++++++++++ .../jetbrains/intellij/utils/gradleUtils.kt | 51 +++++ .../org/jetbrains/intellij/utils/osUtils.kt | 102 ++++++++++ .../resources/proguard/default-config.pro | 1 + 11 files changed, 749 insertions(+) create mode 100644 src/main/kotlin/org/jetbrains/intellij/dsl/ProguardSettings.kt create mode 100644 src/main/kotlin/org/jetbrains/intellij/tasks/AbstractProguardTask.kt create mode 100644 src/main/kotlin/org/jetbrains/intellij/tasks/AbstractTask.kt create mode 100644 src/main/kotlin/org/jetbrains/intellij/utils/ExternalToolRunner.kt create mode 100644 src/main/kotlin/org/jetbrains/intellij/utils/MultiOutputStream.kt create mode 100644 src/main/kotlin/org/jetbrains/intellij/utils/cliArgUtils.kt create mode 100644 src/main/kotlin/org/jetbrains/intellij/utils/fileUtils.kt create mode 100644 src/main/kotlin/org/jetbrains/intellij/utils/gradleUtils.kt create mode 100644 src/main/kotlin/org/jetbrains/intellij/utils/osUtils.kt create mode 100644 src/main/resources/proguard/default-config.pro diff --git a/src/main/kotlin/org/jetbrains/intellij/IntelliJPlugin.kt b/src/main/kotlin/org/jetbrains/intellij/IntelliJPlugin.kt index fbd8410add..4b5082eba5 100644 --- a/src/main/kotlin/org/jetbrains/intellij/IntelliJPlugin.kt +++ b/src/main/kotlin/org/jetbrains/intellij/IntelliJPlugin.kt @@ -94,6 +94,7 @@ import org.jetbrains.intellij.IntelliJPluginConstants.VERIFY_PLUGIN_CONFIGURATIO import org.jetbrains.intellij.IntelliJPluginConstants.VERIFY_PLUGIN_TASK_NAME import org.jetbrains.intellij.IntelliJPluginConstants.VERSION_LATEST import org.jetbrains.intellij.dependency.* +import org.jetbrains.intellij.dsl.ProguardSettings import org.jetbrains.intellij.jbr.JbrResolver import org.jetbrains.intellij.model.MavenMetadata import org.jetbrains.intellij.model.XmlExtractor @@ -103,6 +104,7 @@ import org.jetbrains.intellij.tasks.* import org.jetbrains.intellij.utils.* import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.io.File +import java.io.FileOutputStream import java.net.URL import java.time.LocalDateTime import java.time.format.DateTimeFormatter @@ -114,6 +116,7 @@ abstract class IntelliJPlugin : Plugin { private lateinit var archiveUtils: ArchiveUtils private lateinit var dependenciesDownloader: DependenciesDownloader private lateinit var context: String + private lateinit var proguardSettings: ProguardSettings override fun apply(project: Project) { context = project.logCategory() @@ -123,6 +126,7 @@ abstract class IntelliJPlugin : Plugin { archiveUtils = project.objects.newInstance() dependenciesDownloader = project.objects.newInstance() + proguardSettings = project.objects.newInstance() project.plugins.apply(JavaPlugin::class) project.plugins.apply(IdeaExtPlugin::class) @@ -212,6 +216,7 @@ abstract class IntelliJPlugin : Plugin { configureJarSearchableOptionsTask(project) configureBuildPluginTask(project) configureSignPluginTask(project) + configureProguardTask(project) configurePublishPluginTask(project) configureProcessResources(project) configureInstrumentation(project, extension) @@ -1217,6 +1222,48 @@ abstract class IntelliJPlugin : Plugin { } } + private fun configureProguardTask(project: Project) { + // TODO: how to enable? if block exists? + val runProguard = if (true) { + project.tasks.register("proguard") { + proguardVersion.set(proguardSettings.version) + + // TODO: a more standard way of loading resources? + val defaultConfig = AbstractProguardTask::class.java.getResourceAsStream("/proguard/default-config.pro") + + if (defaultConfig != null) { + val defaultRulesFile = File.createTempFile("default", "pro") + val outputStream = FileOutputStream(defaultRulesFile) + outputStream.write(defaultConfig.readAllBytes()) + outputStream.close() + defaultComposeRulesFile.set(defaultRulesFile) + } + + configurationFiles.from(proguardSettings.configurationFiles) + // ProGuard uses -dontobfuscate option to turn off obfuscation, which is enabled by default + // We want to disable obfuscation by default, because often + // it is not needed, but makes troubleshooting much harder. + // If obfuscation is turned off by default, + // enabling (`isObfuscationEnabled.set(true)`) seems much better, + // than disabling obfuscation disabling (`dontObfuscate.set(false)`). + // That's why a task property is follows ProGuard design, + // when our DSL does the opposite. + dontobfuscate.set(proguardSettings.obfuscate.map { !it }) + maxHeapSize.set(proguardSettings.maxHeapSize) + javaHome.set(System.getProperty("java.home") ?: error("'java.home' system property is not set")) + // TODO: where should the destination dir be? + destinationDir.convention(project.layout.buildDirectory.dir("lib/proguard")) + mainJar.fileProvider(project.provider { + project.tasks.getByPath("jar").outputs.files.singleFile + }) + + // TODO: need to shrink before building & use the shrunk version + // in the build task + dependsOn(BUILD_PLUGIN_TASK_NAME) + } + } else null + } + private fun configureBuildPluginTask(project: Project) { info(context, "Configuring building plugin task") diff --git a/src/main/kotlin/org/jetbrains/intellij/dsl/ProguardSettings.kt b/src/main/kotlin/org/jetbrains/intellij/dsl/ProguardSettings.kt new file mode 100644 index 0000000000..883d4cf6e5 --- /dev/null +++ b/src/main/kotlin/org/jetbrains/intellij/dsl/ProguardSettings.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.intellij.dsl + +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import javax.inject.Inject +import org.jetbrains.intellij.utils.nullableProperty +import org.jetbrains.intellij.utils.notNullProperty + +private const val DEFAULT_PROGUARD_VERSION = "7.2.2" + +abstract class ProguardSettings @Inject constructor( + objects: ObjectFactory, +) { + val version: Property = objects.notNullProperty(DEFAULT_PROGUARD_VERSION) + val maxHeapSize: Property = objects.nullableProperty() + val configurationFiles: ConfigurableFileCollection = objects.fileCollection() + val isEnabled: Property = objects.notNullProperty(false) + val obfuscate: Property = objects.notNullProperty(false) +} diff --git a/src/main/kotlin/org/jetbrains/intellij/tasks/AbstractProguardTask.kt b/src/main/kotlin/org/jetbrains/intellij/tasks/AbstractProguardTask.kt new file mode 100644 index 0000000000..20c9ba8d09 --- /dev/null +++ b/src/main/kotlin/org/jetbrains/intellij/tasks/AbstractProguardTask.kt @@ -0,0 +1,182 @@ +/* + * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.intellij.tasks + +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFile +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.LocalState +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.TaskAction +import org.jetbrains.intellij.utils.ExternalToolRunner +import org.jetbrains.intellij.utils.cliArg +import org.jetbrains.intellij.utils.ioFile +import org.jetbrains.intellij.utils.jvmToolFile +import org.jetbrains.intellij.utils.mangledName +import org.jetbrains.intellij.utils.normalizedPath +import org.jetbrains.intellij.utils.notNullProperty +import org.jetbrains.intellij.utils.nullableProperty +import java.io.File +import java.io.Writer + +// TODO: generalize some options e.g. defaultComposeRulesFile should just be defaultRulesFile +abstract class AbstractProguardTask : AbstractTask() { + + @get:Optional + @get:InputFiles + val inputFiles: ConfigurableFileCollection = objects.fileCollection() + + @get:InputFiles + val libraryJars: ConfigurableFileCollection = objects.fileCollection() + + @get:InputFile + val mainJar: RegularFileProperty = objects.fileProperty() + + @get:Internal + internal val mainJarInDestinationDir: Provider = mainJar.flatMap { + destinationDir.file(it.asFile.name) + } + + @get:InputFiles + val configurationFiles: ConfigurableFileCollection = objects.fileCollection() + + @get:Optional + @get:Input + val dontobfuscate: Property = objects.nullableProperty() + + // todo: DSL for excluding default rules + // also consider pulling coroutines rules from coroutines artifact + // https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro + @get:Optional + @get:InputFile + val defaultComposeRulesFile: RegularFileProperty = objects.fileProperty() + + @get:Input + val proguardVersion: Property = objects.notNullProperty() + + @get:Input + val javaHome: Property = objects.notNullProperty(System.getProperty("java.home")) + + @get:Optional + @get:Input + val mainClass: Property = objects.nullableProperty() + + @get:Internal + val maxHeapSize: Property = objects.nullableProperty() + + @get:OutputDirectory + val destinationDir: DirectoryProperty = objects.directoryProperty() + + @get:LocalState + protected val workingDir: Provider = project.layout.buildDirectory.dir("compose/tmp/$name") + + private val rootConfigurationFile = workingDir.map { it.file("root-config.pro") } + + private val jarsConfigurationFile = workingDir.map { it.file("jars-config.pro") } + + @TaskAction + fun execute() { + val javaHome = File(javaHome.get()) + val proguardFiles = project.configurations.detachedConfiguration( + project.dependencies.create("com.guardsquare:proguard-gradle:${proguardVersion.get()}") + ).files + + cleanDirs(destinationDir, workingDir) + val destinationDir = destinationDir.ioFile.absoluteFile + + // todo: can be cached for a jdk + val jmods = javaHome.resolve("jmods").walk().filter { + it.isFile && it.path.endsWith("jmod", ignoreCase = true) + }.toList() + + val inputToOutputJars = LinkedHashMap() + // avoid mangling mainJar + inputToOutputJars[mainJar.ioFile] = mainJarInDestinationDir.ioFile + for (inputFile in inputFiles) { + if (inputFile.name.endsWith(".jar", ignoreCase = true)) { + inputToOutputJars.putIfAbsent(inputFile, destinationDir.resolve(inputFile.mangledName())) + } else { + inputFile.copyTo(destinationDir.resolve(inputFile.name)) + } + } + + jarsConfigurationFile.ioFile.bufferedWriter().use { writer -> + for ((input, output) in inputToOutputJars.entries) { + writer.writeLn("-injars '${input.normalizedPath()}'") + writer.writeLn("-outjars '${output.normalizedPath()}'") + } + + for (jmod in jmods) { + writer.writeLn("-libraryjars '${jmod.normalizedPath()}'(!**.jar;!module-info.class)") + } + for (libraryJar in libraryJars) { + writer.writeLn("-libraryjars '${libraryJar.canonicalPath}'") + } + // TODO: do this here or in the IntelliJPlugin? + val sourceSets = project.extensions.findByName("sourceSets") as SourceSetContainer + sourceSets.getByName("main").compileClasspath.forEach { + writer.writeLn("-libraryjars '${it.canonicalPath}'") + } + } + + rootConfigurationFile.ioFile.bufferedWriter().use { writer -> + if (dontobfuscate.orNull == true) { + writer.writeLn("-dontobfuscate") + } + + if (mainClass.isPresent) { + writer.writeLn(""" + -keep public class ${mainClass.get()} { + public static void main(java.lang.String[]); + } + """.trimIndent()) + } + + val includeFiles = sequenceOf( + jarsConfigurationFile.ioFile, + defaultComposeRulesFile.ioFile + ) + configurationFiles.files.asSequence() + for (configFile in includeFiles.filterNotNull()) { + writer.writeLn("-include '${configFile.normalizedPath()}'") + } + } + + val javaBinary = jvmToolFile(toolName = "java", javaHome = javaHome) + val args = arrayListOf().apply { + val maxHeapSize = maxHeapSize.orNull + if (maxHeapSize != null) { + add("-Xmx:$maxHeapSize") + } + cliArg("-cp", proguardFiles.map { it.normalizedPath() }.joinToString(File.pathSeparator)) + add("proguard.ProGuard") + // todo: consider separate flag + cliArg("-verbose", verbose) + cliArg("-include", rootConfigurationFile) + } + + runExternalTool( + tool = javaBinary, + args = args, + environment = emptyMap(), + logToConsole = ExternalToolRunner.LogToConsole.Always + ).assertNormalExitValue() + } + + private fun Writer.writeLn(s: String) { + write(s) + write("\n") + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/jetbrains/intellij/tasks/AbstractTask.kt b/src/main/kotlin/org/jetbrains/intellij/tasks/AbstractTask.kt new file mode 100644 index 0000000000..a396f54a05 --- /dev/null +++ b/src/main/kotlin/org/jetbrains/intellij/tasks/AbstractTask.kt @@ -0,0 +1,50 @@ +package org.jetbrains.intellij.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.file.Directory +import org.gradle.api.file.FileSystemLocation +import org.gradle.api.internal.file.FileOperations +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.LocalState +import org.gradle.process.ExecOperations +import org.jetbrains.intellij.utils.ExternalToolRunner +import javax.inject.Inject + +// TODO: AbstractTask from Compose plugin. Keep this or merge into AbstractProguardTask? +abstract class AbstractTask : DefaultTask() { + @get:Inject + protected abstract val objects: ObjectFactory + + @get:Inject + protected abstract val providers: ProviderFactory + @get:Internal + val verbose: Property = objects.property(Boolean::class.java).apply { + set(providers.provider { + logger.isDebugEnabled + }) + } + + @get:Inject + protected abstract val execOperations: ExecOperations + + @get:Inject + protected abstract val fileOperations: FileOperations + + @get:LocalState + protected val logsDir: Provider = project.layout.buildDirectory.dir("intellij/logs/$name") + + @get:Internal + internal val runExternalTool: ExternalToolRunner + get() = ExternalToolRunner(verbose, logsDir, execOperations) + + protected fun cleanDirs(vararg dirs: Provider) { + for (dir in dirs) { + fileOperations.delete(dir) + fileOperations.mkdir(dir) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/jetbrains/intellij/utils/ExternalToolRunner.kt b/src/main/kotlin/org/jetbrains/intellij/utils/ExternalToolRunner.kt new file mode 100644 index 0000000000..44263d6add --- /dev/null +++ b/src/main/kotlin/org/jetbrains/intellij/utils/ExternalToolRunner.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.intellij.utils + +import org.gradle.api.file.Directory +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.process.ExecOperations +import org.gradle.process.ExecResult +import java.io.File +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +internal class ExternalToolRunner( + private val verbose: Property, + private val logsDir: Provider, + private val execOperations: ExecOperations +) { + internal enum class LogToConsole { + Always, + Never, + OnlyWhenVerbose + } + + operator fun invoke( + tool: File, + args: Collection, + environment: Map = emptyMap(), + workingDir: File? = null, + checkExitCodeIsNormal: Boolean = true, + processStdout: Function1? = null, + logToConsole: LogToConsole = LogToConsole.OnlyWhenVerbose + ): ExecResult { + val logsDir = logsDir.ioFile + logsDir.mkdirs() + + val toolName = tool.nameWithoutExtension + val outFile = logsDir.resolve("${toolName}-${currentTimeStamp()}-out.txt") + val errFile = logsDir.resolve("${toolName}-${currentTimeStamp()}-err.txt") + + val result = outFile.outputStream().buffered().use { outFileStream -> + errFile.outputStream().buffered().use { errFileStream -> + execOperations.exec { + val spec = this + spec.executable = tool.absolutePath + spec.args(*args.toTypedArray()) + workingDir?.let { wd -> spec.workingDir(wd) } + spec.environment(environment) + // check exit value later + spec.isIgnoreExitValue = true + + @Suppress("NAME_SHADOWING") + val logToConsole = when (logToConsole) { + LogToConsole.Always -> true + LogToConsole.Never -> false + LogToConsole.OnlyWhenVerbose -> verbose.get() + } + if (logToConsole) { + spec.standardOutput = spec.standardOutput.alsoOutputTo(outFileStream) + spec.errorOutput = spec.errorOutput.alsoOutputTo(errFileStream) + } else { + spec.standardOutput = outFileStream + spec.errorOutput = errFileStream + } + } + } + } + + if (checkExitCodeIsNormal && result.exitValue != 0) { + val errMsg = buildString { + appendLine("External tool execution failed:") + val cmd = (listOf(tool.absolutePath) + args).joinToString(", ") + appendLine("* Command: [$cmd]") + appendLine("* Working dir: [${workingDir?.absolutePath.orEmpty()}]") + appendLine("* Exit code: ${result.exitValue}") + appendLine("* Standard output log: ${outFile.absolutePath}") + appendLine("* Error log: ${errFile.absolutePath}") + } + + error(errMsg) + } + + if (processStdout != null) { + processStdout(outFile.readText()) + } + + if (result.exitValue == 0) { + outFile.delete() + errFile.delete() + } + + return result + } + + private fun currentTimeStamp() = + LocalDateTime.now() + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) +} \ No newline at end of file diff --git a/src/main/kotlin/org/jetbrains/intellij/utils/MultiOutputStream.kt b/src/main/kotlin/org/jetbrains/intellij/utils/MultiOutputStream.kt new file mode 100644 index 0000000000..d391a137ef --- /dev/null +++ b/src/main/kotlin/org/jetbrains/intellij/utils/MultiOutputStream.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.intellij.utils + +import java.io.FilterOutputStream +import java.io.OutputStream + +internal class MultiOutputStream( + mainStream: OutputStream, + private val secondaryStream: OutputStream +) : FilterOutputStream(mainStream) { + override fun write(b: Int) { + super.write(b) + secondaryStream.write(b) + } + + override fun flush() { + super.flush() + secondaryStream.flush() + } + + override fun close() { + try { + super.close() + } finally { + secondaryStream.close() + } + } +} + +internal fun OutputStream.alsoOutputTo(secondaryStream: OutputStream): OutputStream = + MultiOutputStream(this, secondaryStream) \ No newline at end of file diff --git a/src/main/kotlin/org/jetbrains/intellij/utils/cliArgUtils.kt b/src/main/kotlin/org/jetbrains/intellij/utils/cliArgUtils.kt new file mode 100644 index 0000000000..575473ca5a --- /dev/null +++ b/src/main/kotlin/org/jetbrains/intellij/utils/cliArgUtils.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.intellij.utils + +import org.gradle.api.file.FileSystemLocation +import org.gradle.api.provider.Provider +import java.io.File + +internal fun MutableCollection.cliArg( + name: String, + value: T?, + fn: (T) -> String = defaultToString() +) { + if (value is Boolean) { + if (value) add(name) + } else if (value != null) { + add(name) + add(fn(value)) + } +} + +internal fun MutableCollection.cliArg( + name: String, + value: Provider, + fn: (T) -> String = defaultToString() +) { + cliArg(name, value.orNull, fn) +} + +internal fun MutableCollection.javaOption(value: String) { + cliArg("--java-options", "'$value'") +} + +private fun defaultToString(): (T) -> String = + { + val asString = when (it) { + is FileSystemLocation -> it.asFile.normalizedPath() + is File -> it.normalizedPath() + else -> it.toString() + } + "\"$asString\"" + } \ No newline at end of file diff --git a/src/main/kotlin/org/jetbrains/intellij/utils/fileUtils.kt b/src/main/kotlin/org/jetbrains/intellij/utils/fileUtils.kt new file mode 100644 index 0000000000..2c1902f071 --- /dev/null +++ b/src/main/kotlin/org/jetbrains/intellij/utils/fileUtils.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.intellij.utils + +import java.io.* +import java.security.DigestInputStream +import java.security.MessageDigest +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream + +internal fun File.mangledName(): String = + buildString { + append(nameWithoutExtension) + append("-") + append(contentHash()) + val ext = extension + if (ext.isNotBlank()) { + append(".$ext") + } + } + +internal fun File.contentHash(): String { + val md5 = MessageDigest.getInstance("MD5") + if (isDirectory) { + walk() + .filter { it.isFile } + .sortedBy { it.relativeTo(this).path } + .forEach { md5.digestContent(it) } + } else { + md5.digestContent(this) + } + val digest = md5.digest() + return buildString(digest.size * 2) { + for (byte in digest) { + append(Integer.toHexString(0xFF and byte.toInt())) + } + } +} + +private fun MessageDigest.digestContent(file: File) { + file.inputStream().buffered().use { fis -> + DigestInputStream(fis, this).use { ds -> + while (ds.read() != -1) {} + } + } +} + +internal inline fun transformJar( + sourceJar: File, + targetJar: File, + fn: (entry: ZipEntry, zin: ZipInputStream, zout: ZipOutputStream) -> Unit +) { + ZipInputStream(FileInputStream(sourceJar).buffered()).use { zin -> + ZipOutputStream(FileOutputStream(targetJar).buffered()).use { zout -> + for (sourceEntry in generateSequence { zin.nextEntry }) { + fn(sourceEntry, zin, zout) + } + } + } +} + +internal fun copyZipEntry( + entry: ZipEntry, + from: InputStream, + to: ZipOutputStream, +) { + val newEntry = ZipEntry(entry.name).apply { + comment = entry.comment + extra = entry.extra + } + to.withNewEntry(newEntry) { + from.copyTo(to) + } +} + +internal inline fun ZipOutputStream.withNewEntry(zipEntry: ZipEntry, fn: () -> Unit) { + putNextEntry(zipEntry) + fn() + closeEntry() +} + +internal fun InputStream.copyTo(file: File) { + file.outputStream().buffered().use { os -> + copyTo(os) + } +} + +/* +@Internal +internal fun findOutputFileOrDir(dir: File, targetFormat: TargetFormat): File = + when (targetFormat) { + TargetFormat.AppImage -> dir + else -> dir.walk().first { it.isFile && it.name.endsWith(targetFormat.fileExt) } + } +*/ + +internal fun File.checkExistingFile(): File = + apply { + check(isFile) { "'$absolutePath' does not exist" } + } + +internal val File.isJarFile: Boolean + get() = name.endsWith(".jar", ignoreCase = true) && isFile + +internal fun File.normalizedPath() = + if (currentOS == OS.Windows) absolutePath.replace("\\", "\\\\") else absolutePath diff --git a/src/main/kotlin/org/jetbrains/intellij/utils/gradleUtils.kt b/src/main/kotlin/org/jetbrains/intellij/utils/gradleUtils.kt new file mode 100644 index 0000000000..c2b48c6dad --- /dev/null +++ b/src/main/kotlin/org/jetbrains/intellij/utils/gradleUtils.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.intellij.utils + +import org.gradle.api.Task +import org.gradle.api.file.Directory +import org.gradle.api.file.FileSystemLocation +import org.gradle.api.file.RegularFile +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.TaskProvider +import java.io.File + +@SuppressWarnings("UNCHECKED_CAST") +internal inline fun ObjectFactory.nullableProperty(): Property = + property(T::class.java) as Property + +internal inline fun ObjectFactory.notNullProperty(): Property = + property(T::class.java) + +internal inline fun ObjectFactory.notNullProperty(defaultValue: T): Property = + property(T::class.java).value(defaultValue) + +internal inline fun Provider.toProperty(objects: ObjectFactory): Property = + objects.property(T::class.java).value(this) + +internal inline fun Task.provider(noinline fn: () -> T): Provider = + project.provider(fn) + +internal fun Provider.file(relativePath: String): Provider = + map { it.file(relativePath) } + +internal fun Provider.dir(relativePath: String): Provider = + map { it.dir(relativePath) } + +internal inline fun ObjectFactory.new(vararg params: Any): T = + newInstance(T::class.java, *params) + +internal fun TaskProvider.dependsOn(vararg dependencies: Any) { + configure { dependsOn(*dependencies) } +} + +internal val Provider.ioFile: File + get() = get().asFile + +internal val Provider.ioFileOrNull: File? + get() = orNull?.asFile diff --git a/src/main/kotlin/org/jetbrains/intellij/utils/osUtils.kt b/src/main/kotlin/org/jetbrains/intellij/utils/osUtils.kt new file mode 100644 index 0000000000..1d092e57be --- /dev/null +++ b/src/main/kotlin/org/jetbrains/intellij/utils/osUtils.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.intellij.utils + +import org.gradle.api.provider.Provider +import java.io.File + +// From org.jetbrains.compose.desktop.application.tasks.AbstractCheckNativeDistributionRuntime +internal const val MIN_JAVA_RUNTIME_VERSION = 15 + +internal enum class OS(val id: String) { + Linux("linux"), + Windows("windows"), + MacOS("macos") +} + +internal enum class Arch(val id: String) { + X64("x64"), + Arm64("arm64") +} + +internal data class Target(val os: OS, val arch: Arch) { + val id: String + get() = "${os.id}-${arch.id}" +} + +internal val currentTarget by lazy { + Target(currentOS, currentArch) +} + +internal val currentArch by lazy { + val osArch = System.getProperty("os.arch") + when (osArch) { + "x86_64", "amd64" -> Arch.X64 + "aarch64" -> Arch.Arm64 + else -> error("Unsupported OS arch: $osArch") + } +} + +internal val currentOS: OS by lazy { + val os = System.getProperty("os.name") + when { + os.equals("Mac OS X", ignoreCase = true) -> OS.MacOS + os.startsWith("Win", ignoreCase = true) -> OS.Windows + os.startsWith("Linux", ignoreCase = true) -> OS.Linux + else -> error("Unknown OS name: $os") + } +} + +internal fun executableName(nameWithoutExtension: String): String = + if (currentOS == OS.Windows) "$nameWithoutExtension.exe" else nameWithoutExtension + +internal fun javaExecutable(javaHome: String): String = + File(javaHome).resolve("bin/${executableName("java")}").absolutePath + +internal object MacUtils { + val codesign: File by lazy { + File("/usr/bin/codesign").checkExistingFile() + } + + val security: File by lazy { + File("/usr/bin/security").checkExistingFile() + } + + val xcrun: File by lazy { + File("/usr/bin/xcrun").checkExistingFile() + } + + val xcodeBuild: File by lazy { + File("/usr/bin/xcodebuild").checkExistingFile() + } + + val make: File by lazy { + File("/usr/bin/make").checkExistingFile() + } + + val open: File by lazy { + File("/usr/bin/open").checkExistingFile() + } + +} + +internal object UnixUtils { + val git: File by lazy { + File("/usr/bin/git").checkExistingFile() + } +} + +internal fun jvmToolFile(toolName: String, javaHome: Provider): File = + jvmToolFile(toolName, File(javaHome.get())) + +internal fun jvmToolFile(toolName: String, javaHome: File): File { + val jtool = javaHome.resolve("bin/${executableName(toolName)}") + check(jtool.isFile) { + "Invalid JDK: $jtool is not a file! \n" + + "Ensure JAVA_HOME or buildSettings.javaHome is set to JDK $MIN_JAVA_RUNTIME_VERSION or newer" + } + return jtool +} diff --git a/src/main/resources/proguard/default-config.pro b/src/main/resources/proguard/default-config.pro new file mode 100644 index 0000000000..82bb3f78e0 --- /dev/null +++ b/src/main/resources/proguard/default-config.pro @@ -0,0 +1 @@ +-keep class ** extends com.intellij.AbstractBundle { *; } From 8bb2e4ebaa5fb9826431c12012be940ea69c60f9 Mon Sep 17 00:00:00 2001 From: Jakub Chrzanowski Date: Mon, 5 Dec 2022 21:33:01 +0100 Subject: [PATCH 2/2] RunPluginVerifierTask: cleanup --- .../org/jetbrains/intellij/tasks/RunPluginVerifierTask.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/jetbrains/intellij/tasks/RunPluginVerifierTask.kt b/src/main/kotlin/org/jetbrains/intellij/tasks/RunPluginVerifierTask.kt index 3a7745bc7f..6e2b7a849a 100644 --- a/src/main/kotlin/org/jetbrains/intellij/tasks/RunPluginVerifierTask.kt +++ b/src/main/kotlin/org/jetbrains/intellij/tasks/RunPluginVerifierTask.kt @@ -378,9 +378,9 @@ abstract class RunPluginVerifierTask @Inject constructor( if (teamCityOutputFormat.get()) { args.add("-team-city") } - if (subsystemsToCheck.orNull != null) { + subsystemsToCheck.orNull?.let { args.add("-subsystems-to-check") - args.add(subsystemsToCheck.get()) + args.add(it) } if (offline.get()) { args.add("-offline")