|
|
@ -1,13 +1,11 @@ |
|
|
|
package org.jetbrains.compose.desktop.application.tasks |
|
|
|
package org.jetbrains.compose.desktop.application.tasks |
|
|
|
|
|
|
|
|
|
|
|
import org.gradle.api.file.ConfigurableFileCollection |
|
|
|
import org.gradle.api.file.* |
|
|
|
import org.gradle.api.file.Directory |
|
|
|
|
|
|
|
import org.gradle.api.file.DirectoryProperty |
|
|
|
|
|
|
|
import org.gradle.api.file.RegularFileProperty |
|
|
|
|
|
|
|
import org.gradle.api.provider.ListProperty |
|
|
|
import org.gradle.api.provider.ListProperty |
|
|
|
import org.gradle.api.provider.Property |
|
|
|
import org.gradle.api.provider.Property |
|
|
|
import org.gradle.api.provider.Provider |
|
|
|
import org.gradle.api.provider.Provider |
|
|
|
import org.gradle.api.tasks.* |
|
|
|
import org.gradle.api.tasks.* |
|
|
|
|
|
|
|
import org.gradle.api.tasks.Optional |
|
|
|
import org.gradle.process.ExecResult |
|
|
|
import org.gradle.process.ExecResult |
|
|
|
import org.gradle.process.ExecSpec |
|
|
|
import org.gradle.process.ExecSpec |
|
|
|
import org.gradle.work.ChangeType |
|
|
|
import org.gradle.work.ChangeType |
|
|
@ -15,10 +13,19 @@ import org.gradle.work.InputChanges |
|
|
|
import org.jetbrains.compose.desktop.application.dsl.MacOSSigningSettings |
|
|
|
import org.jetbrains.compose.desktop.application.dsl.MacOSSigningSettings |
|
|
|
import org.jetbrains.compose.desktop.application.dsl.TargetFormat |
|
|
|
import org.jetbrains.compose.desktop.application.dsl.TargetFormat |
|
|
|
import org.jetbrains.compose.desktop.application.internal.* |
|
|
|
import org.jetbrains.compose.desktop.application.internal.* |
|
|
|
|
|
|
|
import org.jetbrains.compose.desktop.application.internal.files.MacJarSignFileCopyingProcessor |
|
|
|
|
|
|
|
import org.jetbrains.compose.desktop.application.internal.files.SimpleFileCopyingProcessor |
|
|
|
|
|
|
|
import org.jetbrains.compose.desktop.application.internal.files.fileHash |
|
|
|
import org.jetbrains.compose.desktop.application.internal.validation.ValidatedMacOSSigningSettings |
|
|
|
import org.jetbrains.compose.desktop.application.internal.validation.ValidatedMacOSSigningSettings |
|
|
|
import org.jetbrains.compose.desktop.application.internal.validation.validate |
|
|
|
import org.jetbrains.compose.desktop.application.internal.validation.validate |
|
|
|
import java.io.File |
|
|
|
import java.io.File |
|
|
|
|
|
|
|
import java.io.Serializable |
|
|
|
|
|
|
|
import java.util.* |
|
|
|
import javax.inject.Inject |
|
|
|
import javax.inject.Inject |
|
|
|
|
|
|
|
import kotlin.collections.HashMap |
|
|
|
|
|
|
|
import kotlin.collections.HashSet |
|
|
|
|
|
|
|
import java.io.ObjectInputStream |
|
|
|
|
|
|
|
import java.io.ObjectOutputStream |
|
|
|
|
|
|
|
|
|
|
|
abstract class AbstractJPackageTask @Inject constructor( |
|
|
|
abstract class AbstractJPackageTask @Inject constructor( |
|
|
|
@get:Input |
|
|
|
@get:Input |
|
|
@ -166,12 +173,28 @@ abstract class AbstractJPackageTask @Inject constructor( |
|
|
|
@get:LocalState |
|
|
|
@get:LocalState |
|
|
|
protected val signDir: Provider<Directory> = project.layout.buildDirectory.dir("compose/tmp/sign") |
|
|
|
protected val signDir: Provider<Directory> = project.layout.buildDirectory.dir("compose/tmp/sign") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@get:Internal |
|
|
|
|
|
|
|
private val libsDir: Provider<Directory> = workingDir.map { |
|
|
|
|
|
|
|
it.dir("libs") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@get:Internal |
|
|
|
|
|
|
|
private val libsMappingFile: Provider<RegularFile> = workingDir.map { |
|
|
|
|
|
|
|
it.file("libs-mapping.txt") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@get:Internal |
|
|
|
|
|
|
|
private val libsMapping = FilesMapping() |
|
|
|
|
|
|
|
|
|
|
|
override fun makeArgs(tmpDir: File): MutableList<String> = super.makeArgs(tmpDir).apply { |
|
|
|
override fun makeArgs(tmpDir: File): MutableList<String> = super.makeArgs(tmpDir).apply { |
|
|
|
if (targetFormat == TargetFormat.AppImage || appImage.orNull == null) { |
|
|
|
if (targetFormat == TargetFormat.AppImage || appImage.orNull == null) { |
|
|
|
// Args, that can only be used, when creating an app image or an installer w/o --app-image parameter |
|
|
|
// Args, that can only be used, when creating an app image or an installer w/o --app-image parameter |
|
|
|
cliArg("--input", tmpDir) |
|
|
|
cliArg("--input", tmpDir) |
|
|
|
cliArg("--runtime-image", runtimeImage) |
|
|
|
cliArg("--runtime-image", runtimeImage) |
|
|
|
cliArg("--main-jar", launcherMainJar.ioFile.name) |
|
|
|
|
|
|
|
|
|
|
|
val mappedJar = libsMapping[launcherMainJar.ioFile] |
|
|
|
|
|
|
|
?: error("Main jar was not processed correctly: ${launcherMainJar.ioFile}") |
|
|
|
|
|
|
|
cliArg("--main-jar", mappedJar) |
|
|
|
cliArg("--main-class", launcherMainClass) |
|
|
|
cliArg("--main-class", launcherMainClass) |
|
|
|
|
|
|
|
|
|
|
|
when (currentOS) { |
|
|
|
when (currentOS) { |
|
|
@ -241,10 +264,45 @@ abstract class AbstractJPackageTask @Inject constructor( |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
override fun prepareWorkingDir(inputChanges: InputChanges) { |
|
|
|
private fun invalidateMappedLibs( |
|
|
|
val workingDir = workingDir.ioFile |
|
|
|
inputChanges: InputChanges |
|
|
|
|
|
|
|
): Set<File> { |
|
|
|
|
|
|
|
val outdatedLibs = HashSet<File>() |
|
|
|
|
|
|
|
val libsDirFile = libsDir.ioFile |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun invalidateAllLibs() { |
|
|
|
|
|
|
|
outdatedLibs.addAll(files.files) |
|
|
|
|
|
|
|
outdatedLibs.add(launcherMainJar.ioFile) |
|
|
|
|
|
|
|
|
|
|
|
// todo: parallel processing |
|
|
|
logger.debug("Clearing all files in working dir: $libsDirFile") |
|
|
|
|
|
|
|
fileOperations.delete(libsDirFile) |
|
|
|
|
|
|
|
libsDirFile.mkdirs() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (inputChanges.isIncremental) { |
|
|
|
|
|
|
|
val allChanges = inputChanges.getFileChanges(files).asSequence() + |
|
|
|
|
|
|
|
inputChanges.getFileChanges(launcherMainJar) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
for (change in allChanges) { |
|
|
|
|
|
|
|
libsMapping.remove(change.file)?.let { fileOperations.delete(it) } |
|
|
|
|
|
|
|
if (change.changeType != ChangeType.REMOVED) { |
|
|
|
|
|
|
|
outdatedLibs.add(change.file) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
|
|
logger.debug("Could remove outdated libs incrementally: ${e.stacktraceToString()}") |
|
|
|
|
|
|
|
invalidateAllLibs() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
invalidateAllLibs() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return outdatedLibs |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun prepareWorkingDir(inputChanges: InputChanges) { |
|
|
|
|
|
|
|
val libsDirFile = libsDir.ioFile |
|
|
|
val fileProcessor = |
|
|
|
val fileProcessor = |
|
|
|
withValidatedMacOSSigning { signing -> |
|
|
|
withValidatedMacOSSigning { signing -> |
|
|
|
val tmpDirForSign = signDir.ioFile |
|
|
|
val tmpDirForSign = signDir.ioFile |
|
|
@ -258,35 +316,17 @@ abstract class AbstractJPackageTask @Inject constructor( |
|
|
|
) |
|
|
|
) |
|
|
|
} ?: SimpleFileCopyingProcessor |
|
|
|
} ?: SimpleFileCopyingProcessor |
|
|
|
|
|
|
|
|
|
|
|
if (inputChanges.isIncremental) { |
|
|
|
val outdatedLibs = invalidateMappedLibs(inputChanges) |
|
|
|
logger.debug("Updating working dir incrementally: $workingDir") |
|
|
|
for (sourceFile in outdatedLibs) { |
|
|
|
val allChanges = inputChanges.getFileChanges(files).asSequence() + |
|
|
|
assert(sourceFile.exists()) { "Lib file does not exist: $sourceFile" } |
|
|
|
inputChanges.getFileChanges(launcherMainJar) |
|
|
|
|
|
|
|
allChanges.forEach { fileChange -> |
|
|
|
|
|
|
|
val sourceFile = fileChange.file |
|
|
|
|
|
|
|
val targetFile = workingDir.resolve(sourceFile.name) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (fileChange.changeType == ChangeType.REMOVED) { |
|
|
|
|
|
|
|
fileOperations.delete(targetFile) |
|
|
|
|
|
|
|
logger.debug("Deleted: $targetFile") |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
fileProcessor.copy(sourceFile, targetFile) |
|
|
|
|
|
|
|
logger.debug("Updated: $targetFile") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
logger.debug("Updating working dir non-incrementally: $workingDir") |
|
|
|
|
|
|
|
fileOperations.delete(workingDir) |
|
|
|
|
|
|
|
fileOperations.mkdir(workingDir) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
files.forEach { sourceFile -> |
|
|
|
val targetFileName = |
|
|
|
val targetFile = workingDir.resolve(sourceFile.name) |
|
|
|
if (sourceFile.isJarFile) |
|
|
|
if (targetFile.exists()) { |
|
|
|
"${sourceFile.nameWithoutExtension}-${fileHash(sourceFile)}.jar" |
|
|
|
// todo: handle possible clashes |
|
|
|
else sourceFile.name |
|
|
|
logger.warn("w: File already exists: $targetFile") |
|
|
|
val targetFile = libsDirFile.resolve(targetFileName) |
|
|
|
} |
|
|
|
|
|
|
|
fileProcessor.copy(sourceFile, targetFile) |
|
|
|
fileProcessor.copy(sourceFile, targetFile) |
|
|
|
} |
|
|
|
libsMapping[sourceFile] = targetFile |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -309,4 +349,69 @@ abstract class AbstractJPackageTask @Inject constructor( |
|
|
|
val outputFile = findOutputFileOrDir(destinationDir.ioFile, targetFormat) |
|
|
|
val outputFile = findOutputFileOrDir(destinationDir.ioFile, targetFormat) |
|
|
|
logger.lifecycle("The distribution is written to ${outputFile.canonicalPath}") |
|
|
|
logger.lifecycle("The distribution is written to ${outputFile.canonicalPath}") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun initState() { |
|
|
|
|
|
|
|
val mappingFile = libsMappingFile.ioFile |
|
|
|
|
|
|
|
if (mappingFile.exists()) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
libsMapping.loadFrom(mappingFile) |
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
|
|
fileOperations.delete(mappingFile) |
|
|
|
|
|
|
|
throw e |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
logger.debug("Loaded libs mapping from $mappingFile") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun saveStateAfterFinish() { |
|
|
|
|
|
|
|
val mappingFile = libsMappingFile.ioFile |
|
|
|
|
|
|
|
libsMapping.saveTo(mappingFile) |
|
|
|
|
|
|
|
logger.debug("Saved libs mapping to $mappingFile") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Serializable is only needed to avoid breaking configuration cache: |
|
|
|
|
|
|
|
// https://docs.gradle.org/current/userguide/configuration_cache.html#config_cache:requirements |
|
|
|
|
|
|
|
private class FilesMapping : Serializable { |
|
|
|
|
|
|
|
private var mapping = HashMap<File, File>() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
operator fun get(key: File): File? = |
|
|
|
|
|
|
|
mapping[key] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
operator fun set(key: File, value: File) { |
|
|
|
|
|
|
|
mapping[key] = value |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun remove(key: File): File? = |
|
|
|
|
|
|
|
mapping.remove(key) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun loadFrom(mappingFile: File) { |
|
|
|
|
|
|
|
mappingFile.readLines().forEach { line -> |
|
|
|
|
|
|
|
if (line.isNotBlank()) { |
|
|
|
|
|
|
|
val (k, v) = line.split(File.pathSeparatorChar) |
|
|
|
|
|
|
|
mapping[File(k)] = File(v) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun saveTo(mappingFile: File) { |
|
|
|
|
|
|
|
mappingFile.parentFile.mkdirs() |
|
|
|
|
|
|
|
mappingFile.bufferedWriter().use { writer -> |
|
|
|
|
|
|
|
mapping.entries |
|
|
|
|
|
|
|
.sortedBy { (k, _) -> k.absolutePath } |
|
|
|
|
|
|
|
.forEach { (k, v) -> |
|
|
|
|
|
|
|
writer.append(k.absolutePath) |
|
|
|
|
|
|
|
writer.append(File.pathSeparatorChar) |
|
|
|
|
|
|
|
writer.appendln(v.absolutePath) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun writeObject(stream: ObjectOutputStream) { |
|
|
|
|
|
|
|
stream.writeObject(mapping) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun readObject(stream: ObjectInputStream) { |
|
|
|
|
|
|
|
mapping = stream.readObject() as HashMap<File, File> |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |