From 3c49fc2e9a7bc8094d32843c7878083eaf8d63de Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Thu, 16 Jul 2020 18:40:15 +0300 Subject: [PATCH] Refactor CreateDmg and BundleMacosJdk: use Property<..>, migrate to Kotlin --- .github/workflows/gradle.yml | 20 +++ README.md | 74 ++++++-- build.gradle.kts | 24 ++- .../itemis/mps/gradle/BundleMacosJdk.groovy | 66 ------- .../itemis/mps/gradle/BundledScripts.groovy | 26 --- .../de/itemis/mps/gradle/CreateDmg.groovy | 106 ------------ .../itemis/mps/gradle/BaseBundleMacOsTask.kt | 42 +++++ .../de/itemis/mps/gradle/BundleMacosJdk.kt | 48 ++++++ .../de/itemis/mps/gradle/BundledScripts.kt | 28 +++ .../kotlin/de/itemis/mps/gradle/CreateDmg.kt | 86 ++++++++++ .../itemis/mps/gradle/test/CreateDmgTest.kt | 161 ++++++++++++++++++ .../de/itemis/mps/gradle/test/Info.plist.xml | 121 +++++++++++++ 12 files changed, 585 insertions(+), 217 deletions(-) delete mode 100644 src/main/groovy/de/itemis/mps/gradle/BundleMacosJdk.groovy delete mode 100644 src/main/groovy/de/itemis/mps/gradle/BundledScripts.groovy delete mode 100644 src/main/groovy/de/itemis/mps/gradle/CreateDmg.groovy create mode 100644 src/main/kotlin/de/itemis/mps/gradle/BaseBundleMacOsTask.kt create mode 100644 src/main/kotlin/de/itemis/mps/gradle/BundleMacosJdk.kt create mode 100644 src/main/kotlin/de/itemis/mps/gradle/BundledScripts.kt create mode 100644 src/main/kotlin/de/itemis/mps/gradle/CreateDmg.kt create mode 100644 src/test/kotlin/de/itemis/mps/gradle/test/CreateDmgTest.kt create mode 100644 src/test/resources/de/itemis/mps/gradle/test/Info.plist.xml diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 7783d03d..ee914fa2 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -12,6 +12,26 @@ jobs: strategy: matrix: java: [1.8, 11, 13] + name: 'ubuntu (JDK ${{ matrix.java }}) ' + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew test + + build-macos: + runs-on: macos-latest + strategy: + matrix: + java: [1.8, 11] + + name: 'macOS (JDK ${{ matrix.java }}) ' steps: - uses: actions/checkout@v2 diff --git a/README.md b/README.md index 2bb03c3a..e69ccb7a 100644 --- a/README.md +++ b/README.md @@ -81,22 +81,48 @@ created by an MPS-generated Ant script), a JDK, and a background image. ### Usage -``` +Groovy: + +```gradle task buildDmg(type: de.itemis.mps.gradle.CreateDmg) { - rcpArtifact file('path/to/RCP.tgz') + rcpArtifact = file('path/to/RCP.tgz') jdkDependency "com.jetbrains.jdk:jdk:${jdkVersion}:osx_x64@tgz" // -or - - jdk file('path/to/jdk.tgz') + jdk = file('path/to/jdk.zip') + + backgroundImage = file('path/to/background.png') + dmgFile = file('output.dmg') + + signKeyChain = file("/path/to/my.keychain-db") + + signKeyChainPassword = "my.keychain-db-password" + + signIdentity = "my Application ID Name" +} +``` + +Kotlin: + +```kotlin +import de.itemis.mps.gradle.CreateDmg - backgroundImage file('path/to/background.png') - dmgFile file('output.dmg') +val buildDmg by tasks.registering(CreateDmg::class) { + rcpArtifact.set(file("path/to/RCP.tgz")) - signKeyChain file("/path/to/my.keychain-db") + jdkDependency("com.jetbrains.jdk:jdk:${jdkVersion}:osx_x64@tgz") + // -or- + jdk.set(layout.file(provider { jdkConfiguration.singleFile })) + // -or- + jdk.set(file("path/to/jdk.zip")) - signKeyChainPassword "my.keychain-db-password" + backgroundImage.set(file("path/to/background.png")) + dmgFile.set(file("output.dmg")) - signIdentity "my Application ID Name" + // Below properties must be all present or they all must be absent + signKeyChain.set(file("/path/to/my.keychain-db")) + signKeyChainPassword.set("my.keychain-db-password") + signIdentity.set("my Application ID Name") } ``` @@ -104,7 +130,7 @@ Parameters: * `rcpArtifact` - the path to the RCP artifact produced by a build script. * `jdkDependency` - the coordinates of a JDK in case it's available in a repository and can be resolved as a Gradle dependency. -* `jdk` - the path to a JDK .tgz file. +* `jdk` - the path to a JDK .zip file. * `backgroundImage` - the path to the background image. * `dmgFile` - the path and file name of the output DMG image. Must end with `.dmg`. @@ -129,15 +155,35 @@ task. ### Usage -``` +Groovy: + +```gradle task bundleMacosJdk(type: de.itemis.mps.gradle.BundleMacosJdk) { - rcpArtifact file('path/to/RCP.tgz') + rcpArtifact = file('path/to/RCP.tgz') jdkDependency "com.jetbrains.jdk:jdk:${jdkVersion}:osx_x64@tgz" // -or - - jdk file('path/to/jdk.tgz') + jdk = file('path/to/jdk.tgz') + + outputFile = file('output.tar.gz') +} +``` + +Kotlin: + +```kotlin +import de.itemis.mps.gradle.BundleMacosJdk + +val buildDmg by tasks.registering(BundleMacosJdk::class) { + rcpArtifact.set(file("path/to/RCP.tgz")) + + jdkDependency("com.jetbrains.jdk:jdk:${jdkVersion}:osx_x64@tgz") + // -or- + jdk.set(layout.file(provider { jdkConfiguration.singleFile })) + // -or- + jdk.set(file("path/to/jdk.tgz")) - outputFile file('output.tar.gz') + outputFile.set(file("output.dmg")) } ``` @@ -145,7 +191,7 @@ Parameters: * `rcpArtifact` - the path to the RCP artifact produced by a build script. * `jdkDependency` - the coordinates of a JDK in case it's available in a repository and can be resolved as a Gradle dependency. -* `jdk` - the path to a JDK .tgz file. +* `jdk` - the path to a JDK .zip file. * `outputFile` - the path and file name of the output gzipped tar archive. ### Operation diff --git a/build.gradle.kts b/build.gradle.kts index 8837bb03..72a12e7c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.net.URI +import org.gradle.api.tasks.testing.logging.TestExceptionFormat buildscript { configurations.classpath { @@ -20,7 +21,7 @@ plugins { } val versionMajor = 1 -val versionMinor = 4 +val versionMinor = 5 group = "de.itemis.mps" @@ -133,10 +134,23 @@ dependencies { testRuntimeOnly(files(tasks["createClasspathManifest"])) } -tasks.withType { - kotlinOptions.jvmTarget = "1.8" - kotlinOptions.apiVersion = kotlinApiVersion - kotlinOptions.allWarningsAsErrors = true +tasks.withType().configureEach { + kotlinOptions { + jvmTarget = "1.8" + apiVersion = kotlinApiVersion + allWarningsAsErrors = true + } +} + +tasks.withType().configureEach { + useJUnit() + testLogging { + exceptionFormat = TestExceptionFormat.FULL + } +} + +kotlinDslPluginOptions { + experimentalWarning.set(false) } diff --git a/src/main/groovy/de/itemis/mps/gradle/BundleMacosJdk.groovy b/src/main/groovy/de/itemis/mps/gradle/BundleMacosJdk.groovy deleted file mode 100644 index e56dcc8e..00000000 --- a/src/main/groovy/de/itemis/mps/gradle/BundleMacosJdk.groovy +++ /dev/null @@ -1,66 +0,0 @@ -package de.itemis.mps.gradle - -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.artifacts.Dependency -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction - -class BundleMacosJdk extends DefaultTask { - @InputFile - File rcpArtifact - - @InputFile - File jdk - - @OutputFile - File outputFile - - def setRcpArtifact(Object file) { - this.rcpArtifact = project.file(file) - } - - def setJdk(Object file) { - this.jdk = project.file(file) - } - - /** - * Sets the {@link #jdk} property from a dependency, given as either a {@link Dependency} object or in dependency - * notation. - */ - def setJdkDependency(Object jdkDependency) { - Dependency dep = project.dependencies.create(jdkDependency) - def files = project.configurations.detachedConfiguration(dep).resolve() - if (files.size() != 1) { - throw new GradleException( - "Expected a single file for jdkDependency '$jdkDependency', got ${files.size()} files") - } - this.jdk = files.first() - } - - def setOutputFile(Object file) { - this.outputFile = project.file(file) - } - - @TaskAction - def build() { - File scriptsDir = File.createTempDir() - File tmpDir = File.createTempDir() - try { - String scriptName = 'bundle_macos_jdk.sh' - BundledScripts.extractScriptsToDir(scriptsDir, scriptName) - project.exec { - executable new File(scriptsDir, scriptName) - args rcpArtifact, tmpDir, jdk, outputFile - workingDir scriptsDir - } - } finally { - // Do not use File.deleteDir() because it follows symlinks! - // (e.g. the symlink to /Applications inside tmpDir) - project.exec { - commandLine 'rm', '-rf', scriptsDir, tmpDir - } - } - } -} diff --git a/src/main/groovy/de/itemis/mps/gradle/BundledScripts.groovy b/src/main/groovy/de/itemis/mps/gradle/BundledScripts.groovy deleted file mode 100644 index 6ca62f1e..00000000 --- a/src/main/groovy/de/itemis/mps/gradle/BundledScripts.groovy +++ /dev/null @@ -1,26 +0,0 @@ -package de.itemis.mps.gradle - -import org.gradle.api.GradleException - -import java.nio.file.Files -import java.nio.file.attribute.PosixFilePermissions - -class BundledScripts { - static void extractScriptsToDir(File dir, String... scriptNames) { - def rwxPermissions = PosixFilePermissions.fromString("rwx------") - - for (name in scriptNames) { - File file = new File(dir, name) - if (!file.parentFile.isDirectory() && ! file.parentFile.mkdirs()) { - throw new GradleException("Could not create directory " + file.parentFile) - } - InputStream resourceStream = BundledScripts.class.getResourceAsStream(name) - if (resourceStream == null) { - throw new IllegalArgumentException("Resource ${name} was not found") - } - - resourceStream.withStream { is -> file.newOutputStream().withStream { os -> os << is } } - Files.setPosixFilePermissions(file.toPath(), rwxPermissions) - } - } -} diff --git a/src/main/groovy/de/itemis/mps/gradle/CreateDmg.groovy b/src/main/groovy/de/itemis/mps/gradle/CreateDmg.groovy deleted file mode 100644 index b0dc1957..00000000 --- a/src/main/groovy/de/itemis/mps/gradle/CreateDmg.groovy +++ /dev/null @@ -1,106 +0,0 @@ -package de.itemis.mps.gradle - -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.artifacts.Dependency -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.Optional - -class CreateDmg extends DefaultTask { - @InputFile - File rcpArtifact - - @InputFile - File backgroundImage - - @InputFile - File jdk - - @OutputFile - File dmgFile - - @Optional @Input - String signKeyChainPassword - - @Optional @Input - String signIdentity - - @InputFile @Optional - File signKeyChain - - def setSignKeyChain(Object file) { - this.signKeyChain = project.file(file) - } - - def setRcpArtifact(Object file) { - this.rcpArtifact = project.file(file) - } - - def setBackgroundImage(Object file) { - this.backgroundImage = project.file(file) - } - - def setJdk(Object file) { - this.jdk = project.file(file) - } - - /** - * Sets the {@link #jdk} property from a dependency, given as either a {@link Dependency} object or in dependency - * notation. - */ - def setJdkDependency(Object jdkDependency) { - Dependency dep = project.dependencies.create(jdkDependency) - def files = project.configurations.detachedConfiguration(dep).resolve() - if (files.size() != 1) { - throw new GradleException( - "Expected a single file for jdkDependency '$jdkDependency', got ${files.size()} files") - } - this.jdk = files.first() - } - - def setDmgFile(Object file) { - this.dmgFile = project.file(file) - if (dmgFile != null && !dmgFile.name.endsWith(".dmg")) { - throw new GradleException("Value of dmgFile must end with .dmg but was $dmgFile") - } - } - - @TaskAction - def build() { - String[] scripts = ['mpssign.sh', 'mpsdmg.sh', 'mpsdmg.pl', - 'Mac/Finder/DSStore/BuddyAllocator.pm', 'Mac/Finder/DSStore.pm'] - File scriptsDir = File.createTempDir() - File dmgDir = File.createTempDir() - def signingInfo = [signKeyChainPassword, signKeyChain, signIdentity] - try { - BundledScripts.extractScriptsToDir(scriptsDir, scripts) - project.exec { - executable new File(scriptsDir, 'mpssign.sh') - - if(signingInfo.every {it != null}) { - args '-r', rcpArtifact, '-o', dmgDir, '-j', jdk, '-p', signKeyChainPassword, '-k', signKeyChain, '-i', signIdentity - }else if (signingInfo.every {it == null}){ - args '-r', rcpArtifact, '-o', dmgDir, '-j', jdk - }else{ - throw new IllegalArgumentException("Not all signing paramters set. signKeyChain: ${getSigningInfo[1]}, signIdentity: ${getSigningInfo[2]} and signKeyChainPassword needs to be set. ") - } - workingDir scriptsDir - } - project.exec { - executable new File(scriptsDir, 'mpsdmg.sh') - args dmgDir, dmgFile, backgroundImage - workingDir scriptsDir - } - } finally { - // Do not use File.deleteDir() because it follows symlinks! - // (e.g. the symlink to /Applications inside dmgDir) - project.exec { - commandLine 'rm', '-rf', scriptsDir, dmgDir - } - } - } - -} diff --git a/src/main/kotlin/de/itemis/mps/gradle/BaseBundleMacOsTask.kt b/src/main/kotlin/de/itemis/mps/gradle/BaseBundleMacOsTask.kt new file mode 100644 index 00000000..db2c2192 --- /dev/null +++ b/src/main/kotlin/de/itemis/mps/gradle/BaseBundleMacOsTask.kt @@ -0,0 +1,42 @@ +package de.itemis.mps.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.file.ProjectLayout +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.kotlin.dsl.property +import java.io.File + +abstract class BaseBundleMacOsTask( + objects: ObjectFactory, + private val providers: ProviderFactory, + private val layout: ProjectLayout +) : DefaultTask() { + @InputFile + val rcpArtifact = objects.fileProperty() + + @InputFile + val jdk = objects.fileProperty() + + fun jdkDependency(dependencyNotation: String) { + val jdkConfig = project.configurations.detachedConfiguration( + project.dependencies.create(dependencyNotation) + ).apply { + description = "JDK for $name" + } + jdk.set(layout.file(providers.provider { jdkConfig.singleFile })) + } + + @Input + val cleanTemporaryDirectories = objects.property().convention(true) + + protected fun rmdirs(vararg directory: File) { + // Do not use File.deleteDir() because it follows symlinks! + // (e.g. the symlink to /Applications inside tmpDir) + project.exec { + commandLine("rm", "-rf", *directory) + } + } +} diff --git a/src/main/kotlin/de/itemis/mps/gradle/BundleMacosJdk.kt b/src/main/kotlin/de/itemis/mps/gradle/BundleMacosJdk.kt new file mode 100644 index 00000000..0eac131e --- /dev/null +++ b/src/main/kotlin/de/itemis/mps/gradle/BundleMacosJdk.kt @@ -0,0 +1,48 @@ +package de.itemis.mps.gradle + +import org.gradle.api.file.ProjectLayout +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import java.io.File +import javax.inject.Inject + +open class BundleMacosJdk @Inject constructor( + objects: ObjectFactory, + providers: ProviderFactory, + layout: ProjectLayout +) : BaseBundleMacOsTask(objects, providers, layout) { + @OutputFile + val outputFile = objects.fileProperty() + .convention(layout.buildDirectory.file("bundleRcp/$name/$name.tgz")) + + @Internal + val scriptsDir = layout.buildDirectory.dir("bundleRcp/$name/scripts") + + @Internal + val tmpDir = layout.buildDirectory.dir("bundleRcp/$name/tmp") + + @TaskAction + fun run() { + val scriptsDir = scriptsDir.get().asFile + val tmpDir = tmpDir.get().asFile + try { + // Cleanup temporary directory so stale files do not affect the current task execution + rmdirs(tmpDir) + tmpDir.mkdirs() + val scriptName = "bundle_macos_jdk.sh" + BundledScripts.extractScriptsToDir(scriptsDir, scriptName) + project.exec { + executable = "./$scriptName" + workingDir = scriptsDir + args(rcpArtifact.get().asFile, tmpDir, jdk.get().asFile, outputFile.get().asFile) + } + } finally { + if (cleanTemporaryDirectories.get()) { + rmdirs(scriptsDir, tmpDir) + } + } + } +} diff --git a/src/main/kotlin/de/itemis/mps/gradle/BundledScripts.kt b/src/main/kotlin/de/itemis/mps/gradle/BundledScripts.kt new file mode 100644 index 00000000..64cfb999 --- /dev/null +++ b/src/main/kotlin/de/itemis/mps/gradle/BundledScripts.kt @@ -0,0 +1,28 @@ +package de.itemis.mps.gradle + +import org.gradle.api.GradleException +import java.io.File +import java.nio.file.Files +import java.nio.file.attribute.PosixFilePermissions + +internal object BundledScripts { + fun extractScriptsToDir(dir: File, vararg scriptNames: String) { + val rwxPermissions = PosixFilePermissions.fromString("rwx------") + + for (name in scriptNames) { + val file = File(dir, name) + if (!file.parentFile.isDirectory && !file.parentFile.mkdirs()) { + throw GradleException("Could not create directory " + file.parentFile) + } + val resourceStream = BundledScripts::class.java.getResourceAsStream(name) + ?: throw GradleException("Resource $name was not found") + + resourceStream.use { resource -> + file.outputStream().use { output -> + resource.copyTo(output) + } + } + Files.setPosixFilePermissions(file.toPath(), rwxPermissions) + } + } +} diff --git a/src/main/kotlin/de/itemis/mps/gradle/CreateDmg.kt b/src/main/kotlin/de/itemis/mps/gradle/CreateDmg.kt new file mode 100644 index 00000000..1f675f03 --- /dev/null +++ b/src/main/kotlin/de/itemis/mps/gradle/CreateDmg.kt @@ -0,0 +1,86 @@ +package de.itemis.mps.gradle + +import org.gradle.api.GradleException +import org.gradle.api.file.ProjectLayout +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.* +import org.gradle.kotlin.dsl.property +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FilterOutputStream +import javax.inject.Inject + +open class CreateDmg @Inject constructor( + objects: ObjectFactory, + providers: ProviderFactory, + layout: ProjectLayout +) : BaseBundleMacOsTask(objects, providers, layout) { + @InputFile + val backgroundImage = objects.fileProperty() + + @OutputFile + val dmgFile = objects.fileProperty() + .convention(layout.buildDirectory.file("createDmg/$name/$name.dmg")) + + @Input + @Optional + val signKeyChainPassword = objects.property() + + @Input + @Optional + val signIdentity = objects.property() + + @InputFile + @Optional + val signKeyChain = objects.fileProperty() + + @Internal + val scriptsDir = layout.buildDirectory.dir("createDmg/$name/scripts") + + @Internal + val dmgDir = layout.buildDirectory.dir("createDmg/$name/dmg") + + @TaskAction + fun run() { + val dmgFile = dmgFile.get().asFile + + if (!dmgFile.path.endsWith(".dmg")) { + throw GradleException("Value of dmgFile must end with .dmg but was $dmgFile") + } + + val scripts = arrayOf("mpssign.sh", "mpsdmg.sh", "mpsdmg.pl", + "Mac/Finder/DSStore/BuddyAllocator.pm", "Mac/Finder/DSStore.pm") + val signingInfo = arrayOf(signKeyChainPassword, signKeyChain, signIdentity) + val scriptsDir = scriptsDir.get().asFile + val dmgDir = dmgDir.get().asFile + try { + // Cleanup temporary directory so stale files do not affect the current task execution + rmdirs(dmgDir) + dmgDir.mkdirs() + BundledScripts.extractScriptsToDir(scriptsDir, *scripts) + project.exec { + executable = "./mpssign.sh" + workingDir = scriptsDir + args("-r", rcpArtifact.get().asFile, "-o", dmgDir, "-j", jdk.get().asFile) + + if (signingInfo.all { it.isPresent }) { + args("-p", signKeyChainPassword.get()) + args("-k", signKeyChain.get().asFile) + args("-i", signIdentity.get()) + } else if (signingInfo.any { it.isPresent }) { + throw IllegalArgumentException("Not all signing parameters are set. signKeyChain: ${signKeyChain.orNull}, signIdentity: ${signIdentity.orNull} and signKeyChainPassword needs to be set. ") + } + } + project.exec { + executable = "./mpsdmg.sh" + workingDir = scriptsDir + args(dmgDir, dmgFile, backgroundImage.get().asFile) + } + } finally { + if (cleanTemporaryDirectories.get()) { + rmdirs(scriptsDir, dmgDir) + } + } + } +} diff --git a/src/test/kotlin/de/itemis/mps/gradle/test/CreateDmgTest.kt b/src/test/kotlin/de/itemis/mps/gradle/test/CreateDmgTest.kt new file mode 100644 index 00000000..8229a70d --- /dev/null +++ b/src/test/kotlin/de/itemis/mps/gradle/test/CreateDmgTest.kt @@ -0,0 +1,161 @@ +package de.itemis.mps.gradle.test + +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Assert.assertEquals +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import java.io.File +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +const val JBR_REPOSITORY = """ + repositories { + ivy { + url = uri("https://dl.bintray.com/jetbrains/intellij-jdk/") + content { + includeGroupByRegex("com.jetbrains.jdk") + } + patternLayout { + artifact("[module]-[revision]-[classifier].[ext]") + } + metadataSources { // skip downloading ivy.xml + artifact() + } + } + } +""" + +class CreateDmgTest { + @Rule + @JvmField + val testProjectDir: TemporaryFolder = TemporaryFolder() + private lateinit var settingsFile: File + private lateinit var buildFile: File + private lateinit var backgroundImage: File + private lateinit var rcpArchive: File + private lateinit var gradleRunner: GradleRunner + val jbrDependency = "com.jetbrains.jdk:jbr:11_0_4:osx-x64-b546.1@tar.gz" + + @Before + fun setup() { + assumeTrue( + "This test uses macOS-specific commands, so it requires macOS for execution", + System.getProperty("os.name").toLowerCase().contains("mac") + ) + gradleRunner = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withPluginClasspath() + .forwardOutput() + settingsFile = testProjectDir.newFile("settings.gradle.kts").apply { + val cp = gradleRunner.pluginClasspath.joinToString { "\"${it.absolutePath}\"" } + writeText(""" + rootProject.name = "create-dmg-test" + buildscript { + dependencies { + classpath(files($cp)) + } + } + """.trimIndent()) + } + buildFile = testProjectDir.newFile("build.gradle.kts") + val resources = testProjectDir.newFolder("resources") + backgroundImage = File(resources, "background.png").apply { + writeText("not a png file, however background is required") + } + // Generate a minimal RCP + val appName = "hello world" + rcpArchive = File(resources, "$appName.zip").apply { + outputStream().use { os -> + ZipOutputStream(os).use { zos -> + zos.putNextEntry(ZipEntry("$appName.app/Contents/Info.plist")) + CreateDmgTest::class.java.getResourceAsStream("Info.plist.xml").use { + it.copyTo(zos) + } + zos.putNextEntry(ZipEntry("$appName.app/Contents/Resources/")) + // This is a "mps launcher" binary, and mpssign.sh sets executable bit, + // so we create a dummy file here + zos.putNextEntry(ZipEntry("$appName.app/Contents/MacOS/mps")) + // required for chmod + zos.putNextEntry(ZipEntry("$appName.app/Contents/bin/printenv.py")) + // required for chmod + zos.putNextEntry(ZipEntry("$appName.app/Contents/bin/fsnotifier")) + // required for chmod + zos.putNextEntry(ZipEntry("$appName.app/Contents/bin/restarter")) + } + } + } + } + + @Test + fun `CreateDmg, non signed dmg, kotlin dsl`() { + buildFile.writeText(""" + import de.itemis.mps.gradle.CreateDmg + + $JBR_REPOSITORY + + val createDmg by tasks.registering(CreateDmg::class) { + rcpArtifact.set(file("resources/${rcpArchive.name}")) + jdkDependency("$jbrDependency") + backgroundImage.set(file("resources/${backgroundImage.name}")) + dmgFile.set(layout.buildDirectory.file("distributions/output.dmg")) + } + """.trimIndent()) + + val result = gradleRunner + .withArguments("-i", "-s", "createDmg", "--warning-mode", "all") + .build() + + assertEquals("createDmg task outcome", TaskOutcome.SUCCESS, result.task(":createDmg")?.outcome) + } + + @Test + fun `CreateDmg, non signed dmg, groovy dsl`() { + buildFile.writeText(""" + import de.itemis.mps.gradle.CreateDmg + + $JBR_REPOSITORY + + tasks.register('createDmg', CreateDmg.class) { + rcpArtifact = file("resources/${rcpArchive.name}") + jdkDependency "$jbrDependency" + backgroundImage = file("resources/${backgroundImage.name}") + dmgFile = layout.buildDirectory.file("distributions/output.dmg") + } + """.trimIndent()) + + // Rename file, so Groovy DSL is used + buildFile.renameTo(File(buildFile.parent, "build.gradle")) + + val result = gradleRunner + .withArguments("-i", "-s", "createDmg", "--warning-mode", "all") + .build() + + assertEquals("createDmg task outcome", TaskOutcome.SUCCESS, result.task(":createDmg")?.outcome) + } + + @Test + fun bundleMacosJdk() { + buildFile.writeText(""" + import de.itemis.mps.gradle.BundleMacosJdk + + $JBR_REPOSITORY + + val bundleJdk by tasks.registering(BundleMacosJdk::class) { + rcpArtifact.set(file("resources/${rcpArchive.name}")) + jdkDependency("$jbrDependency") + outputFile.set(layout.buildDirectory.file("distributions/output.tgz")) + } + """.trimIndent()) + + val result = gradleRunner + .withArguments("-i", "-s", "bundleJdk", "--warning-mode", "all") + .build() + + assertEquals("bundleJdk task outcome", TaskOutcome.SUCCESS, result.task(":bundleJdk")?.outcome) + } +} + diff --git a/src/test/resources/de/itemis/mps/gradle/test/Info.plist.xml b/src/test/resources/de/itemis/mps/gradle/test/Info.plist.xml new file mode 100644 index 00000000..dcc59da2 --- /dev/null +++ b/src/test/resources/de/itemis/mps/gradle/test/Info.plist.xml @@ -0,0 +1,121 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + mpr + + CFBundleTypeIconFile + mps.icns + CFBundleTypeName + JetBrains MPS Project + CFBundleTypeRole + Editor + + + CFBundleTypeExtensions + + * + + CFBundleTypeName + All documents + CFBundleTypeOSTypes + + **** + + CFBundleTypeRole + Editor + LSTypeIsPackage + + + + CFBundleExecutable + mps + CFBundleIconFile + mps.icns + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + MPS + CFBundlePackageType + APPL + CFBundleIdentifier + com.jetbrains.intellij + CFBundleSignature + ???? + CFBundleGetInfoString + JetBrains MPS $version$ + CFBundleShortVersionString + $version$ + CFBundleVersion + $build$ + LSApplicationCategoryType + public.app-category.developer-tools + + NSHighResolutionCapable + + + NSSupportsAutomaticGraphicsSwitching + + + LSArchitecturePriority + + x86_64 + i386 + + LSRequiresNativeExecution + YES + LSMinimumSystemVersion + 10.6 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + Stacktrace + CFBundleURLSchemes + + + + + JVMOptions + + ClassPath + $APP_PACKAGE/Contents/lib/branding.jar:$APP_PACKAGE/Contents/lib/mps-boot.jar:$APP_PACKAGE/Contents/lib/mps-boot-util.jar:$APP_PACKAGE/Contents/lib/boot.jar:$APP_PACKAGE/Contents/lib/bootstrap.jar:$APP_PACKAGE/Contents/lib/util.jar:$APP_PACKAGE/Contents/lib/jdom.jar:$APP_PACKAGE/Contents/lib/log4j.jar:$APP_PACKAGE/Contents/lib/extensions.jar:$APP_PACKAGE/Contents/lib/trove4j.jar + JVMVersion + 1.8* + MainClass + jetbrains.mps.Launcher + Properties + + apple.laf.useScreenMenuBar + true + com.apple.mrj.application.live-resize + false + + idea.paths.selector + MPS + + idea.executable + mps + + idea.java.redist + NoJavaDistribution + idea.home.path + $APP_PACKAGE/Contents + + + VMOptions + -XX:+UseCompressedOops + WorkingDirectory + $APP_PACKAGE/Contents/bin + + +