import org.gradle.plugins.ide.eclipse.model.Library
import java.security.MessageDigest
import java.util.regex.Matcher
+import java.util.concurrent.Executors
+import java.util.concurrent.Future
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.TimeUnit
import groovy.transform.ExternalizeMethods
import groovy.util.XmlParser
import groovy.xml.XmlUtil
classpath "com.vladsch.flexmark:flexmark-all:0.62.0"
classpath "org.jsoup:jsoup:1.14.3"
classpath "com.eowise:gradle-imagemagick:0.5.1"
+ classpath 'ru.vyarus:gradle-use-python-plugin:4.0.0'
}
}
id 'application'
id 'eclipse'
id "com.diffplug.gradle.spotless" version "3.28.0"
- id 'com.github.johnrengelman.shadow' version '4.0.3'
+ id 'com.github.johnrengelman.shadow' version '6.0.0'
id 'com.install4j.gradle' version '10.0.3'
id 'com.dorongold.task-tree' version '2.1.1' // only needed to display task dependency tree with gradle task1 [task2 ...] taskTree
id 'com.palantir.git-version' version '0.13.0' apply false
+ id 'ru.vyarus.use-python' version '4.0.0'
}
repositories {
jvlChannelName = CHANNEL.toLowerCase()
install4jSuffix = CHANNEL.substring(0, 1).toUpperCase() + CHANNEL.substring(1).toLowerCase(); // BUILD -> Build
install4jDMGDSStore = "${install4j_images_dir}/${install4j_dmg_ds_store}"
+ install4jDMGDSStoreJSON = "${install4j_images_dir}/${install4j_dmg_ds_store_json}"
install4jDMGBackgroundImageDir = "${install4j_images_dir}"
install4jDMGBackgroundImageBuildDir = "build/imagemagick/install4j"
install4jDMGBackgroundImageFile = "${install4j_dmg_background}"
- install4jInstallerName = "${jalview_name} Non-Release Installer"
+ install4jmacOSArchiveName = "${jalview_name} Non-Release ${JALVIEW_VERSION} Installer"
install4jExecutableName = install4j_executable_name
- install4jExtraScheme = "jalviewx"
+ install4jExtraScheme = "jalviewextra"
install4jMacIconsFile = string("${install4j_images_dir}/${install4j_mac_icons_file}")
install4jWindowsIconsFile = string("${install4j_images_dir}/${install4j_windows_icons_file}")
install4jPngIconFile = string("${install4j_images_dir}/${install4j_png_icon_file}")
install4jBackground = string("${install4j_images_dir}/${install4j_background}")
install4jBuildDir = "${install4j_build_dir}/${JAVA_VERSION}"
+ install4jDMGFixedDSStoreX64 = "build/macos_dmg/${install4j_dmg_ds_store}-x64"
+ install4jDMGFixedDSStoreAarch64 = "build/macos_dmg/${install4j_dmg_ds_store}-aarch64"
+ install4jDMGVolumeIcon = string("${install4j_images_dir}/${install4j_dmg_volume_icon}")
install4jCheckSums = true
applicationName = "${jalview_name}"
getdownSetAppBaseProperty = true
reportRsyncCommand = true
install4jSuffix = ""
- install4jInstallerName = "${jalview_name} Installer"
+ install4jmacOSArchiveName = "Install ${jalview_name} ${JALVIEW_VERSION}"
+ install4jExtraScheme = (CHANNEL=="RELEASE")?"jalviewx":"jalviewjs"
break
case "ARCHIVE":
JALVIEW_VERSION=JALVIEW_VERSION+"-d${suffix}-${buildDate}"
install4jSuffix = "Develop ${suffix}"
install4jExtraScheme = "jalviewd"
- install4jInstallerName = "${jalview_name} Develop ${suffix} Installer"
+ install4jmacOSArchiveName = "Install ${jalview_name} Develop ${suffix} ${JALVIEW_VERSION}"
getdownChannelName = string("develop-${suffix}")
getdownChannelDir = string("${getdown_website_dir}/${getdownChannelName}")
getdownAppBaseDir = string("${jalviewDir}/${getdownChannelDir}/${JAVA_VERSION}")
install4jSuffix = "Develop"
install4jExtraScheme = "jalviewd"
- install4jInstallerName = "${jalview_name} Develop Installer"
+ install4jmacOSArchiveName = "Install ${jalview_name} Develop ${JALVIEW_VERSION}"
backgroundImageText = true
break
JALVIEW_VERSION = JALVIEW_VERSION+"-test"
install4jSuffix = "Test"
install4jExtraScheme = "jalviewt"
- install4jInstallerName = "${jalview_name} Test Installer"
+ install4jmacOSArchiveName = "Install ${jalview_name} Test ${JALVIEW_VERSION}"
backgroundImageText = true
break
JALVIEW_VERSION = "TEST"
install4jSuffix = "Test-Local"
install4jExtraScheme = "jalviewt"
- install4jInstallerName = "${jalview_name} Test Installer"
+ install4jmacOSArchiveName = "Install ${jalview_name} Test ${JALVIEW_VERSION}"
backgroundImageText = true
break
.replaceAll("_+", "_") // collapse __
.replaceAll("_*-_*", "-") // collapse _-_
.toLowerCase()
+ install4jmacOSArchiveX64Name = "${install4jmacOSArchiveName} (Intel)"
+ install4jmacOSArchiveAarch64Name = "${install4jmacOSArchiveName} (Apple Silicon)"
getdownWrapperLink = install4jUnixApplicationFolder // e.g. "jalview_local"
getdownAppDir = string("${getdownAppBaseDir}/${getdownAppDistDir}")
if (install4jHomeDir.startsWith("~/")) {
install4jHomeDir = System.getProperty("user.home") + install4jHomeDir.substring(1)
}
+ install4jmacOSArchiveX64DMGFilename = "${install4jApplicationFolder}-${JALVIEW_VERSION}-macos-x64-java_${JAVA_INTEGER_VERSION}"
+ install4jmacOSArchiveAarch64DMGFilename = "${install4jApplicationFolder}-${JALVIEW_VERSION}-macos-aarch64-java_${JAVA_INTEGER_VERSION}"
+
resourceBuildDir = string("${buildDir}/resources")
resourcesBuildDir = string("${resourceBuildDir}/resources_build")
eclipseBinary = string("")
eclipseVersion = string("")
eclipseDebug = false
-
+
+ jalviewjsChromiumUserDir = "${jalviewjsBuildDir}/${jalviewjs_chromium_user_dir}"
+ jalviewjsChromiumProfileDir = "${ext.jalviewjsChromiumUserDir}/${jalviewjs_chromium_profile_name}"
+
// ENDEXT
}
preserveOrder true
useDefaultListeners=true
}
+ timeout = Duration.ofMinutes(15)
}
/* separated tests */
preserveOrder true
useDefaultListeners=true
}
+ timeout = Duration.ofMinutes(5)
+}
+
+task testTask2(type: Test) {
+ group = "Verification"
+ description = "Tests that need to be isolated from the main test run"
+ useTestNG() {
+ includeGroups name
+ excludeGroups testng_excluded_groups.split(",")
+ preserveOrder true
+ useDefaultListeners=true
+ }
+ timeout = Duration.ofMinutes(5)
+}
+task testTask3(type: Test) {
+ group = "Verification"
+ description = "Tests that need to be isolated from the main test run"
+ useTestNG() {
+ includeGroups name
+ excludeGroups testng_excluded_groups.split(",")
+ preserveOrder true
+ useDefaultListeners=true
+ }
+ timeout = Duration.ofMinutes(5)
}
/* insert more testTaskNs here -- change N to next digit or other string */
}
*/
+
/*
* adapted from https://medium.com/@wasyl/pretty-tests-summary-in-gradle-744804dd676c
* to summarise test results from all Test tasks
showExceptions true
showCauses true
showStackTraces true
-
+ if (test_output) {
+ showStandardStreams true
+ }
info.events = [ TestLogEvent.FAILED ]
}
+ if (OperatingSystem.current().isMacOsX()) {
+ testTask.systemProperty "apple.awt.UIElement", "true"
+ testTask.environment "JAVA_TOOL_OPTIONS", "-Dapple.awt.UIElement=true"
+ }
ignoreFailures = true // Always try to run all tests for all modules
}
+task launcherJar(type: Jar) {
+ manifest {
+ attributes (
+ "Main-Class": shadow_jar_main_class,
+ "Implementation-Version": JALVIEW_VERSION,
+ "Application-Name": applicationName
+ )
+ }
+}
+
shadowJar {
group = "distribution"
description = "Create a single jar file with all dependency libraries merged. Can be run with java -jar"
if (buildDist) {
dependsOn makeDist
}
- from ("${jalviewDir}/${libDistDir}") {
- include("*.jar")
- }
+
+ def jarFiles = fileTree(dir: "${jalviewDir}/${libDistDir}", include: "*.jar", exclude: "regex.jar").getFiles()
+ def groovyJars = jarFiles.findAll {it1 -> file(it1).getName().startsWith("groovy-swing")}
+ def otherJars = jarFiles.findAll {it2 -> !file(it2).getName().startsWith("groovy-swing")}
+ from groovyJars
+ from otherJars
+
manifest {
- attributes "Implementation-Version": JALVIEW_VERSION,
- "Application-Name": applicationName
+ // shadowJar manifest must inheritFrom another Jar task. Can't set attributes here.
+ inheritFrom(project.tasks.launcherJar.manifest)
+ }
+ // we need to include the groovy-swing Include-Package for it to run in the shadowJar
+ doFirst {
+ def jarFileManifests = []
+ groovyJars.each { jarFile ->
+ def mf = zipTree(jarFile).getFiles().find { it.getName().equals("MANIFEST.MF") }
+ if (mf != null) {
+ jarFileManifests += mf
+ }
+ }
+ manifest {
+ from (jarFileManifests) {
+ eachEntry { details ->
+ if (!details.key.equals("Import-Package")) {
+ details.exclude()
+ }
+ }
+ }
+ }
}
duplicatesStrategy "INCLUDE"
+ // this mainClassName is mandatory but gets ignored due to manifest created in doFirst{}. Set the Main-Class as an attribute in launcherJar instead
mainClassName = shadow_jar_main_class
mergeServiceFiles()
classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
dependsOn getdownImagesProcess
}
-task getdownWebsite() {
+task getdownWebsiteBuild() {
group = "distribution"
- description = "Create the getdown minimal app folder, and website folder for this version of jalview. Website folder also used for offline app installer"
+ description = "Create the getdown minimal app folder, and website folder for this version of jalview. Website folder also used for offline app installer. No digest is created."
dependsOn getdownImages
if (buildDist) {
from s
into "${getdownAppBaseDir}/${getdown_wrapper_script_dir}"
}
- getdownTextLines += "resource = ${getdown_wrapper_script_dir}/${script}"
+ getdownTextLines += "xresource = ${getdown_wrapper_script_dir}/${script}"
}
}
task getdownDigest(type: JavaExec) {
group = "distribution"
description = "Digest the getdown website folder"
- dependsOn getdownWebsite
+
+ dependsOn getdownWebsiteBuild
+
doFirst {
classpath = files(getdownLauncher)
}
}
}
+task getdownWebsite {
+ group = "distribution"
+ description = "A task to create the whole getdown channel website dir including digest file"
+
+ dependsOn getdownWebsiteBuild
+ dependsOn getdownDigest
+}
task getdownArchiveBuild() {
group = "distribution"
description = "Put files in the archive dir to go on the website"
- dependsOn getdownWebsite
+ dependsOn getdownWebsiteBuild
def v = "v${JALVIEW_VERSION_UNDERSCORES}"
def vDir = "${getdownArchiveDir}/${v}"
}
}
+ // the wrapper scripts dir
+ if ( file("${getdownAppBaseDir}/${getdown_wrapper_script_dir}").exists() ) {
+ copy {
+ from "${getdownAppBaseDir}/${getdown_wrapper_script_dir}"
+ into "${getdownFullArchiveDir}/${getdown_wrapper_script_dir}"
+ }
+ }
+
getdownArchiveTxt.write(getdownArchiveTextLines.join("\n"))
def vLaunchJvl = file(getdownVersionLaunchJvl)
}
}
+ // delete .VolumeIcon.icns in macos DMG if there isn't one
+ if (!file(install4jDMGVolumeIcon).exists()) {
+ println("No '.VolumeIcon.icns' file found. Removing from install4j file.")
+ install4jConfigXml.'**'.macosArchive.topLevelFiles.each { topLevelFiles ->
+ topLevelFiles.file.each() { file ->
+ if (file.attribute("name") && file.attribute("name").equals(".VolumeIcon.icns")) {
+ println("Removing "+file.toString())
+ topLevelFiles.remove(file)
+ }
+ }
+ }
+ } else {
+ println("Using '.VolumeIcon.icns' file '${install4jDMGVolumeIcon}'")
+ }
+
// disable install screen for OSX dmg (for 2.11.2.0)
install4jConfigXml.'**'.macosArchive.each { macosArchive ->
macosArchive.attributes().remove('executeSetupApp')
}
}
-task install4jDMGBackgroundImage {
+
+python {
+ pip 'ds_store:1.3.1'
+}
+
+task install4jCustomiseDS_StoreX64(type: PythonTask) {
+ inputs.file(install4jDMGDSStore)
+ outputs.file(install4jDMGFixedDSStoreX64)
+ def command_args = [ jalview_customise_ds_store, '--input', install4jDMGDSStore, '--output', install4jDMGFixedDSStoreX64, '--volumename', install4jmacOSArchiveX64Name, '--backgroundfile', install4j_dmg_background_filename, '--dmg', install4jmacOSArchiveX64DMGFilename + ".dmg" ]
+ if (file(install4jDMGDSStoreJSON).exists()) {
+ command_args += [ '--config', install4jDMGDSStoreJSON ]
+ inputs.file(install4jDMGDSStoreJSON)
+ }
+ command = command_args
+ doFirst {
+ println("Running command '${command_args.join(' ')}'")
+ }
+}
+
+task install4jCustomiseDS_StoreAarch64(type: PythonTask) {
+ inputs.file(install4jDMGDSStore)
+ outputs.file(install4jDMGFixedDSStoreAarch64)
+ def command_args = [ jalview_customise_ds_store, '--input', install4jDMGDSStore, '--output', install4jDMGFixedDSStoreAarch64, '--volumename', install4jmacOSArchiveAarch64Name, '--backgroundfile', install4j_dmg_background_filename, '--dmg', install4jmacOSArchiveAarch64DMGFilename + ".dmg" ]
+ if (file(install4jDMGDSStoreJSON).exists()) {
+ command_args += [ '--config', install4jDMGDSStoreJSON ]
+ inputs.file(install4jDMGDSStoreJSON)
+ }
+ command = command_args
+ doFirst {
+ def print_args = []
+ for (int i = 0; i < command_args.size(); i++) {
+ def arg = command_args[i]
+ print_args += (i > 0 && !arg.startsWith("-")) ? "\"${arg}\"" : arg
+ }
+ println("Running command '${print_args.join(' ')}'")
+ }
+}
+
+task install4jCustomiseDS_Store {
+ dependsOn install4jCustomiseDS_StoreX64
+ dependsOn install4jCustomiseDS_StoreAarch64
+}
+
+task install4jDMGProcesses {
dependsOn install4jDMGBackgroundImageProcess
+ dependsOn install4jCustomiseDS_Store
}
task installerFiles(type: com.install4j.gradle.Install4jTask) {
dependsOn getdown
dependsOn copyInstall4jTemplate
dependsOn cleanInstallersDataFiles
- dependsOn install4jDMGBackgroundImage
+ dependsOn install4jDMGProcesses
projectFile = install4jConfFile
+ // run install4j with 4g
+ vmParameters = ["-Xmx4294967296"]
+
// create an md5 for the input files to use as version for install4j conf file
def digest = MessageDigest.getInstance("MD5")
digest.update(
'BUNDLE_ID': install4jBundleId,
'INTERNAL_ID': install4jInternalId,
'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
- 'MACOS_DMG_DS_STORE': install4jDMGDSStore,
+ 'MACOS_X64_DMG_DS_STORE': install4jDMGFixedDSStoreX64,
+ 'MACOS_AARCH64_DMG_DS_STORE': install4jDMGFixedDSStoreAarch64,
'MACOS_DMG_BG_IMAGE': "${install4jDMGBackgroundImageBuildDir}/${install4jDMGBackgroundImageFile}",
+ 'MACOS_DMG_BG_FILENAME': install4j_dmg_background_filename,
'WRAPPER_LINK': getdownWrapperLink,
'BASH_WRAPPER_SCRIPT': getdown_bash_wrapper_script,
'POWERSHELL_WRAPPER_SCRIPT': getdown_powershell_wrapper_script,
'BATCH_WRAPPER_SCRIPT': getdown_batch_wrapper_script,
'WRAPPER_SCRIPT_BIN_DIR': getdown_wrapper_script_dir,
- 'INSTALLER_NAME': install4jInstallerName,
+ 'MACOSARCHIVE_X64_NAME': install4jmacOSArchiveX64Name,
+ 'MACOSARCHIVE_AARCH64_NAME': install4jmacOSArchiveAarch64Name,
'INSTALL4J_UTILS_DIR': install4j_utils_dir,
'GETDOWN_CHANNEL_DIR': getdownChannelDir,
'GETDOWN_FILES_DIR': getdown_files_dir,
'WINDOWS_ICONS_FILE': install4jWindowsIconsFile,
'PNG_ICON_FILE': install4jPngIconFile,
'BACKGROUND': install4jBackground,
+ 'MACOSARCHIVE_X64_DMG_FILENAME': install4jmacOSArchiveX64DMGFilename,
+ 'MACOSARCHIVE_AARCH64_DMG_FILENAME': install4jmacOSArchiveAarch64DMGFilename,
+ 'MACOSARCHIVE_VOLUMEICON': install4jDMGVolumeIcon,
]
def varNameMap = [
into project.name
def EXCLUDE_FILES=[
+ "dist/*",
"build/*",
"bin/*",
"test-output/",
}
-task jalviewjs {
- group "JalviewJS"
- description "Build the site"
- dependsOn jalviewjsBuildSite
-}
-
-
task jalviewjsCopyStderrLaunchFile(type: Copy) {
from file(jalviewjs_stderr_launch)
into jalviewjsSiteDir
outputs.file jalviewjsStderrLaunchFilename
}
+task cleanJalviewjsChromiumUserDir {
+ doFirst {
+ delete jalviewjsChromiumUserDir
+ }
+ outputs.dir jalviewjsChromiumUserDir
+ // always run when depended on
+ outputs.upToDateWhen { !file(jalviewjsChromiumUserDir).exists() }
+}
+
task jalviewjsChromiumProfile {
- def profileDir = "${jalviewjsBuildDir}/${jalviewjs_chromium_user_dir}/${jalviewjs_chromium_profile_name}"
- def firstRun = file("${profileDir}/First Run")
+ dependsOn cleanJalviewjsChromiumUserDir
+ mustRunAfter cleanJalviewjsChromiumUserDir
+
+ def firstRun = file("${jalviewjsChromiumUserDir}/First Run")
doFirst {
- mkdir profileDir
+ mkdir jalviewjsChromiumProfileDir
firstRun.text = ""
}
-
outputs.file firstRun
}
-task jalviewjsLaunchTest(type: Exec) {
+task jalviewjsLaunchTest {
group "Test"
description "Check JalviewJS opens in a browser"
dependsOn jalviewjsBuildSite
dependsOn jalviewjsCopyStderrLaunchFile
dependsOn jalviewjsChromiumProfile
- def chromiumBinary = jalviewjs_chromium_binary
+ def macOS = OperatingSystem.current().isMacOsX()
+ def chromiumBinary = macOS ? jalviewjs_macos_chromium_binary : jalviewjs_chromium_binary
if (chromiumBinary.startsWith("~/")) {
chromiumBinary = System.getProperty("user.home") + chromiumBinary.substring(1)
}
+ def stdout
+ def stderr
doFirst {
- def exec = file(chromiumBinary)
- if (!exec.exists()) {
- throw new GradleException("Could not find chromium binary '${chromiumBinary}'. Cannot run task ${name}.")
+ def timeoutms = Integer.valueOf(jalviewjs_chromium_overall_timeout) * 1000
+
+ def binary = file(chromiumBinary)
+ if (!binary.exists()) {
+ throw new StopExecutionException("Could not find chromium binary '${chromiumBinary}'. Cannot run task ${name}.")
}
- }
+ stdout = new ByteArrayOutputStream()
+ stderr = new ByteArrayOutputStream()
+ def execStdout
+ def execStderr
+ if (jalviewjs_j2s_to_console.equals("true")) {
+ execStdout = new org.apache.tools.ant.util.TeeOutputStream(
+ stdout,
+ System.out)
+ execStderr = new org.apache.tools.ant.util.TeeOutputStream(
+ stderr,
+ System.err)
+ } else {
+ execStdout = stdout
+ execStderr = stderr
+ }
+ def execArgs = [
+ "--no-sandbox", // --no-sandbox IS USED BY THE THORIUM APPIMAGE ON THE BUILDSERVER
+ "--headless=new",
+ "--disable-gpu",
+ "--timeout=${timeoutms}",
+ "--virtual-time-budget=${timeoutms}",
+ "--user-data-dir=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_chromium_user_dir}",
+ "--profile-directory=${jalviewjs_chromium_profile_name}",
+ "--allow-file-access-from-files",
+ "--enable-logging=stderr",
+ "file://${jalviewDirAbsolutePath}/${jalviewjsStderrLaunchFilename}"
+ ]
+
+ if (true || macOS) {
+ ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
+ Future f1 = executor.submit(
+ () -> {
+ exec {
+ standardOutput = execStdout
+ errorOutput = execStderr
+ executable(chromiumBinary)
+ args(execArgs)
+ println "COMMAND: '"+commandLine.join(" ")+"'"
+ }
+ executor.shutdownNow()
+ }
+ )
+
+ def noChangeBytes = 0
+ def noChangeIterations = 0
+ executor.scheduleAtFixedRate(
+ () -> {
+ String stderrString = stderr.toString()
+ // shutdown the task if we have a success string
+ if (stderrString.contains(jalviewjs_desktop_init_string)) {
+ f1.cancel()
+ Thread.sleep(1000)
+ executor.shutdownNow()
+ }
+ // if no change in stderr for 10s then also end
+ if (noChangeIterations >= jalviewjs_chromium_idle_timeout) {
+ executor.shutdownNow()
+ }
+ if (stderrString.length() == noChangeBytes) {
+ noChangeIterations++
+ } else {
+ noChangeBytes = stderrString.length()
+ noChangeIterations = 0
+ }
+ },
+ 1, 1, TimeUnit.SECONDS)
+
+ executor.schedule(new Runnable(){
+ public void run(){
+ f1.cancel()
+ executor.shutdownNow()
+ }
+ }, timeoutms, TimeUnit.MILLISECONDS)
- executable(chromiumBinary)
- args([
- "--headless=new",
- "--timeout=60000",
- "--virtual-time-budget=60000",
- "--user-data-dir=${jalviewjsBuildDir}/${jalviewjs_chromium_user_dir}",
- "--profile-directory=${jalviewjs_chromium_profile_name}",
- "--allow-file-access-from-files",
- "--enable-logging=stderr",
- jalviewjsStderrLaunchFilename
- ])
-
- /*
- standardOutput = new ByteArrayOutputStream()
- errorOutput = new ByteArrayOutputStream()
+ executor.awaitTermination(timeoutms+10000, TimeUnit.MILLISECONDS)
+ executor.shutdownNow()
+ }
+
+ }
doLast {
- println("Chrome STDOUT: ")
- println(standardOutput.toString())
- println("Chrome STDERR: ")
- println(errorOutput.toString())
-
def found = false
- def stderr = errorOutput.toString()
- stderr.eachLine { line ->
+ stderr.toString().eachLine { line ->
if (line.contains(jalviewjs_desktop_init_string)) {
println("Found line '"+line+"'")
found = true
throw new GradleException("Could not find evidence of Desktop launch in JalviewJS.")
}
}
- */
+}
+
+
+task jalviewjs {
+ group "JalviewJS"
+ description "Build the JalviewJS site and run the launch test"
+ dependsOn jalviewjsBuildSite
+ dependsOn jalviewjsLaunchTest
}