JAL-4059 JAL-4395 Updated closure-compiler. Added local_eclipse.properties for buildship
[jalview.git] / build.gradle
index c4ed582..df1f5cd 100644 (file)
@@ -14,6 +14,7 @@ import java.util.concurrent.Executors
 import java.util.concurrent.Future
 import java.util.concurrent.ScheduledExecutorService
 import java.util.concurrent.TimeUnit
+import java.nio.file.Path
 import groovy.transform.ExternalizeMethods
 import groovy.util.XmlParser
 import groovy.xml.XmlUtil
@@ -52,7 +53,7 @@ plugins {
   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
@@ -71,39 +72,48 @@ def string(Object o) {
   return o == null ? "" : o.toString()
 }
 
-def overrideProperties(String propsFileName, boolean output = false) {
-  if (propsFileName == null) {
-    return
-  }
+def Properties readPropertiesFile(String propsFileName) {
+  def p = null
   def propsFile = file(propsFileName)
   if (propsFile != null && propsFile.exists()) {
     println("Using properties from file '${propsFileName}'")
     try {
-      def p = new Properties()
+      p = new Properties()
       def localPropsFIS = new FileInputStream(propsFile)
       p.load(localPropsFIS)
       localPropsFIS.close()
-      p.each {
-        key, val -> 
-          def oldval
-          if (project.hasProperty(key)) {
-            oldval = project.findProperty(key)
-            project.setProperty(key, val)
-            if (output) {
-              println("Overriding property '${key}' ('${oldval}') with ${file(propsFile).getName()} value '${val}'")
-            }
-          } else {
-            ext.setProperty(key, val)
-            if (output) {
-              println("Setting ext property '${key}' with ${file(propsFile).getName()}s value '${val}'")
-            }
-          }
-      }
     } catch (Exception e) {
-      println("Exception reading local.properties")
+      println("Exception reading properties file '${propsFileName}'")
       e.printStackTrace()
     }
   }
+  return p
+}
+
+def overrideProperties(String propsFileName, boolean output = false) {
+  if (propsFileName == null) {
+    return
+  }
+  def propsFile = file(propsFileName)
+  if (propsFile != null && propsFile.exists()) {
+    println("Using properties from file '${propsFileName}'")
+    def p = readPropertiesFile(propsFileName)
+    p.each { key, val -> 
+      def oldval
+      if (project.hasProperty(key)) {
+        oldval = project.findProperty(key)
+        project.setProperty(key, val)
+        if (output) {
+          println("Overriding property '${key}' ('${oldval}') with ${file(propsFile).getName()} value '${val}'")
+        }
+      } else {
+        ext.setProperty(key, val)
+        if (output) {
+          println("Setting ext property '${key}' with ${file(propsFile).getName()}s value '${val}'")
+        }
+      }
+    }
+  }
 }
 
 ext {
@@ -122,12 +132,14 @@ ext {
   channelDir = string("${jalviewDir}/${channel_properties_dir}/${channelDirName}")
   channelGradleProperties = string("${channelDir}/channel_gradle.properties")
   channelPropsFile = string("${channelDir}/${resource_dir}/${channel_props}")
+  localProperties = "local.properties"
+  localEclipseProperties = "local_eclipse.properties"
   overrideProperties(channelGradleProperties, false)
   // local build environment properties
   // can be "projectDir/local.properties"
-  overrideProperties("${projectDir}/local.properties", true)
+  overrideProperties("${projectDir}/${localProperties}", true)
   // or "../projectDir_local.properties"
-  overrideProperties(projectDir.getParent() + "/" + projectDir.getName() + "_local.properties", true)
+  overrideProperties(projectDir.getParent() + "/" + projectDir.getName() + "_${localProperties}", true)
 
   ////  
   // Import releaseProps from the RELEASE file
@@ -559,11 +571,12 @@ ext {
   if (IN_ECLIPSE) {
     jalviewjsTransferSiteJsDir = string(jalviewjsSiteDir)
   } else {
-    jalviewjsTransferSiteJsDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_js")
+    jalviewjsTransferSiteJsDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}/sitejs")
   }
-  jalviewjsTransferSiteLibDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_lib")
-  jalviewjsTransferSiteSwingJsDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_swingjs")
-  jalviewjsTransferSiteCoreDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_core")
+  jalviewjsTransferSiteLibDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}/lib")
+  jalviewjsTransferSiteSwingJsDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}/swingjs")
+  jalviewjsTransferSiteMergeDir = string("${jalviewjsBuildDir}/merge/${jalviewjs_site_dir}")
+  jalviewjsTransferSiteCoreDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}/core")
   jalviewjsJalviewCoreHtmlFile = string("")
   jalviewjsJalviewCoreName = string(jalviewjs_core_name)
   jalviewjsCoreClasslists = []
@@ -574,9 +587,12 @@ ext {
   jalviewjsJ2sPlugin = jalviewjs_j2s_plugin
   jalviewjsStderrLaunchFilename = "${jalviewjsSiteDir}/"+(file(jalviewjs_stderr_launch).getName())
 
+  closureCompilerJar = "${jalviewDir}/${jalviewjs_closure_compiler}"
+    
   eclipseWorkspace = null
   eclipseBinary = string("")
   eclipseVersion = string("")
+  eclipseProductVersion = string("")
   eclipseDebug = false
 
   jalviewjsChromiumUserDir = "${jalviewjsBuildDir}/${jalviewjs_chromium_user_dir}"
@@ -779,6 +795,20 @@ eclipse {
             }
           }
         }
+        // local eclipse settings
+        def localEclipsePropertiesFile = file("${jalviewDirAbsolutePath}/${localEclipseProperties}")
+        if (localEclipsePropertiesFile.exists()) {
+          def eclipse_prefs = new Properties()
+          def ins2 = new FileInputStream(localEclipsePropertiesFile)
+          println("Loading Eclipse Preferences from '${localEclipsePropertiesFile}'")
+          eclipse_prefs.load(ins2)
+          ins2.close()
+          eclipse_prefs.forEach { t, v ->
+            props.putAt(t, v)
+          }
+        } else {
+          println("No local Eclipse Preferences file '${localEclipsePropertiesFile}'")
+        }
       }
     }
 
@@ -1783,6 +1813,27 @@ task testTask1(type: Test) {
   }
 }
 
+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
+  }
+}
+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
+  }
+}
+
 /* insert more testTaskNs here -- change N to next digit or other string */
 /*
 task testTaskN(type: Test) {
@@ -1830,7 +1881,9 @@ tasks.withType(Test).matching {t -> t.getName().startsWith("testTask")}.all { te
     showExceptions true
     showCauses true
     showStackTraces true
-
+    if (test_output) {
+      showStandardStreams true
+    }
     info.events = [ TestLogEvent.FAILED ]
   }
 
@@ -2127,22 +2180,56 @@ task cleanDist {
 }
 
 
+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
@@ -2196,9 +2283,9 @@ task getdownImages() {
   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) {
@@ -2310,7 +2397,7 @@ task getdownWebsite() {
           from s
           into "${getdownAppBaseDir}/${getdown_wrapper_script_dir}"
         }
-        getdownTextLines += "resource = ${getdown_wrapper_script_dir}/${script}"
+        getdownTextLines += "xresource = ${getdown_wrapper_script_dir}/${script}"
       }
     }
 
@@ -2452,7 +2539,9 @@ task getdownDigestDir(type: JavaExec) {
 task getdownDigest(type: JavaExec) {
   group = "distribution"
   description = "Digest the getdown website folder"
-  dependsOn getdownWebsite
+
+  dependsOn getdownWebsiteBuild
+
   doFirst {
     classpath = files(getdownLauncher)
   }
@@ -2485,12 +2574,19 @@ task getdown() {
   }
 }
 
+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}"
@@ -3000,6 +3096,7 @@ task sourceDist(type: Tar) {
   into project.name
 
   def EXCLUDE_FILES=[
+    "dist/*",
     "build/*",
     "bin/*",
     "test-output/",
@@ -3179,12 +3276,14 @@ task jalviewjsEnableAltFileProperty(type: WriteProperties) {
 task jalviewjsSetEclipseWorkspace {
   def propKey = "jalviewjs_eclipse_workspace"
   def propVal = null
+  // see if jalviewjs_eclipse_workspace is set by a property
   if (project.hasProperty(propKey)) {
     propVal = project.getProperty(propKey)
     if (propVal.startsWith("~/")) {
       propVal = System.getProperty("user.home") + propVal.substring(1)
     }
   }
+  // else look for an existing build/jalviewjs/eclipse_workspace_location file
   def propsFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_workspace_location_file}"
   def propsFile = file(propsFileName)
   def eclipseWsDir = propVal
@@ -3233,13 +3332,14 @@ task jalviewjsSetEclipseWorkspace {
   }
 
   //inputs.property(propKey, eclipseWsDir) // eclipseWsDir only gets set once this task runs, so will be out-of-date
-  outputs.file(propsFileName)
-  outputs.upToDateWhen { eclipseWorkspace.exists() && propsFile.exists() }
+  //outputs.file(propsFileName) // don't want this to be deleted because of falsely "stale" task
+  outputs.upToDateWhen { eclipseWorkspace.exists() && (propsFile.exists() || !writeProps) }
 }
 
 
 task jalviewjsEclipsePaths {
-  def eclipseProduct
+  def eclipseProductFile
+  def eclipseSetupLog
 
   def eclipseRoot = jalviewjs_eclipse_root
   if (eclipseRoot.startsWith("~/")) {
@@ -3248,32 +3348,49 @@ task jalviewjsEclipsePaths {
   if (OperatingSystem.current().isMacOsX()) {
     eclipseRoot += "/Eclipse.app"
     eclipseBinary = "${eclipseRoot}/Contents/MacOS/eclipse"
-    eclipseProduct = "${eclipseRoot}/Contents/Eclipse/.eclipseproduct"
+    eclipseProductFile = "${eclipseRoot}/Contents/Eclipse/.eclipseproduct"
+    eclipseSetupLog = "${eclipseRoot}/Contents/Eclipse/configuration/org.eclipse.oomph.setup/setup.log"
   } else if (OperatingSystem.current().isWindows()) { // check these paths!!
     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
       eclipseRoot += "/eclipse"
     }
     eclipseBinary = "${eclipseRoot}/eclipse.exe"
-    eclipseProduct = "${eclipseRoot}/.eclipseproduct"
+    eclipseProductFile = "${eclipseRoot}/.eclipseproduct"
+    eclipseSetupLog = "${eclipseRoot}/configuration/org.eclipse.oomph.setup/setup.log"
   } else { // linux or unix
     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
       eclipseRoot += "/eclipse"
-println("eclipseDir exists")
     }
     eclipseBinary = "${eclipseRoot}/eclipse"
-    eclipseProduct = "${eclipseRoot}/.eclipseproduct"
+    eclipseProductFile = "${eclipseRoot}/.eclipseproduct"
+    eclipseSetupLog = "${eclipseRoot}/configuration/org.eclipse.oomph.setup/setup.log"
   }
 
-  eclipseVersion = "4.13" // default
+  eclipseVersion = "unknown" // default
   def assumedVersion = true
-  if (file(eclipseProduct).exists()) {
-    def fis = new FileInputStream(eclipseProduct)
+  if (file(eclipseProductFile).exists()) {
+    def fis = new FileInputStream(eclipseProductFile)
     def props = new Properties()
     props.load(fis)
     eclipseVersion = props.getProperty("version")
     fis.close()
     assumedVersion = false
   }
+  if (file(eclipseSetupLog).exists()) {
+    def productRegex = /(?m)^\[[^\]]+\]\s+Product\s+(org\.eclipse.\S*)/
+    int lineCount = 0
+    file(eclipseSetupLog).eachLine { String line ->
+      def matcher = line =~ productRegex
+      if (matcher.size() > 0) {
+        eclipseProductVersion = matcher[0][1]
+        return true
+      }
+      if (lineCount >= 100) {
+        return true
+      }
+      lineCount++
+    }
+  }
   
   def propKey = "eclipse_debug"
   eclipseDebug = (project.hasProperty(propKey) && project.getProperty(propKey).equals("true"))
@@ -3289,6 +3406,9 @@ println("eclipseDir exists")
 
     if (!assumedVersion) {
       println("ECLIPSE VERSION=${eclipseVersion}")
+      if (eclipseProductVersion.length() != 0) {
+        println("ECLIPSE PRODUCT=${eclipseProductVersion}")
+      }
     }
   }
 }
@@ -3348,41 +3468,37 @@ jalviewjsEclipseCopyDropins.finalizedBy jalviewjsCleanEclipse
 */
 
 
-task jalviewjsTransferUnzipSwingJs {
-  def file_zip = "${jalviewDir}/${jalviewjs_swingjs_zip}"
+task jalviewjsTransferUnzipSwingJs(type: Copy) {
+  def swingJsZipFile = "${jalviewDir}/${jalviewjs_swingjs_zip}"
+  from zipTree( "${jalviewDir}/${jalviewjs_swingjs_zip}" )
+  into "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
 
-  doLast {
-    copy {
-      from zipTree(file_zip)
-      into "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
-    }
-  }
-
-  inputs.file file_zip
-  outputs.dir "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
+  inputs.file swingJsZipFile
 }
 
 
-task jalviewjsTransferUnzipLib {
-  def zipFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_libjs_dir}", include: "*.zip")
+task jalviewjsTransferUnzipLib(type: Copy) {
+  def zipFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_libjs_dir}", include: "*.zip").sort()
 
-  doLast {
-    zipFiles.each { file_zip -> 
-      copy {
-        from zipTree(file_zip)
-        into "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
+  zipFiles.each { file_zip ->
+    from zipTree(file_zip)
+
+    // The following replace() is needed due to a mismatch in Jmol calls to
+    // colorPtToFFRGB$javajs_util_T3d when only colorPtToFFRGB$javajs_util_T3 is defined
+    // in the SwingJS.zip (github or the one distributed with JSmol)
+    if (file_zip.getName().startsWith("Jmol-SwingJS")) {
+      filter { line ->
+        def l = ""
+        while(!line.equals(l)) {
+          line = line.replace('colorPtToFFRGB$javajs_util_T3d', 'colorPtToFFRGB$javajs_util_T3')
+          l = line
+        }
+        return line
       }
     }
-  }
 
-  inputs.files zipFiles
-  outputs.dir "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
-}
-
-
-task jalviewjsTransferUnzipAllLibs {
-  dependsOn jalviewjsTransferUnzipSwingJs
-  dependsOn jalviewjsTransferUnzipLib
+  }
+  into "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
 }
 
 
@@ -3411,7 +3527,6 @@ task jalviewjsCreateJ2sSettings(type: WriteProperties) {
 
   if (! IN_ECLIPSE) {
     inputs.properties(jalviewjsJ2sProps)
-    outputs.file(jalviewjsJ2sAltSettingsFileName)
   }
 }
 
@@ -3423,46 +3538,81 @@ task jalviewjsEclipseSetup {
 }
 
 
-task jalviewjsSyncAllLibs (type: Sync) {
-  dependsOn jalviewjsTransferUnzipAllLibs
-  def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteLibDir}")
-  inputFiles += fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
+task jalviewjsSyncLibs (type: Sync) {
+  dependsOn jalviewjsTransferUnzipLib
+  dependsOn jalviewjsTransferUnzipSwingJs
+
+  def inputDir = file("${jalviewDir}/${jalviewjsTransferSiteLibDir}")
+  def inputFiles = fileTree(dir: inputDir)
   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
 
   from inputFiles
   into outputDir
   def outputFiles = []
-  rename { filename ->
-    outputFiles += "${outputDir}/${filename}"
-    null
+  inputFiles.each{ file ->
+    def rfile = inputDir.toPath().relativize(file.toPath())
+    def ofile = new File("${outputDir}/${rfile}")
+    outputFiles += "${ofile}"
   }
   preserve {
-    include "**"
+    include "**/*"
+  }
+
+  duplicatesStrategy "EXCLUDE"
+
+  outputs.files outputFiles
+  inputs.files inputFiles
+}
+
+task jalviewjsSyncSwingJS (type: Sync) {
+  dependsOn jalviewjsTransferUnzipSwingJs
+  mustRunAfter jalviewjsSyncLibs
+
+  def inputDir = file("${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
+  def inputFiles = fileTree(dir: inputDir)
+  def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
+
+  from inputFiles
+  into outputDir
+  def outputFiles = []
+  inputFiles.each{ file ->
+    def rfile = inputDir.toPath().relativize(file.toPath())
+    def ofile = new File("${outputDir}/${rfile}")
+    outputFiles += "${ofile}"
+  }
+  preserve {
+    include "**/*"
   }
 
-  // should this be exclude really ?
   duplicatesStrategy "INCLUDE"
 
   outputs.files outputFiles
   inputs.files inputFiles
 }
 
+task jalviewjsSyncAllLibs {
+  dependsOn jalviewjsSyncLibs
+  dependsOn jalviewjsSyncSwingJS
+}
+
 
 task jalviewjsSyncResources (type: Sync) {
   dependsOn buildResources
 
-  def inputFiles = fileTree(dir: resourcesBuildDir)
+  def inputDir = file(resourcesBuildDir)
+  def inputFiles = fileTree(dir: inputDir)
   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
 
   from inputFiles
   into outputDir
   def outputFiles = []
-  rename { filename ->
-    outputFiles += "${outputDir}/${filename}"
-    null
+  inputFiles.each{ file ->
+    def rfile = inputDir.toPath().relativize(file.toPath())
+    def ofile = new File("${outputDir}/${rfile}")
+    outputFiles += "${ofile}"
   }
   preserve {
-    include "**"
+    include "**/*"
   }
   outputs.files outputFiles
   inputs.files inputFiles
@@ -3470,19 +3620,22 @@ task jalviewjsSyncResources (type: Sync) {
 
 
 task jalviewjsSyncSiteResources (type: Sync) {
-  def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_site_resource_dir}")
+  def inputDir = file("${jalviewDir}/${jalviewjs_site_resource_dir}")
+  def inputFiles = fileTree(dir: inputDir)
   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
 
   from inputFiles
   into outputDir
   def outputFiles = []
-  rename { filename ->
-    outputFiles += "${outputDir}/${filename}"
-    null
+  inputFiles.each{ file ->
+    def rfile = inputDir.toPath().relativize(file.toPath())
+    def ofile = new File("${outputDir}/${rfile}")
+    outputFiles += "${ofile}"
   }
   preserve {
-    include "**"
+    include "**/*"
   }
+
   outputs.files outputFiles
   inputs.files inputFiles
 }
@@ -3490,19 +3643,24 @@ task jalviewjsSyncSiteResources (type: Sync) {
 
 task jalviewjsSyncBuildProperties (type: Sync) {
   dependsOn createBuildProperties
-  def inputFiles = [file(buildProperties)]
+
+  def f = file(buildProperties)
+  def inputDir = f.getParentFile()
+  def inputFiles = [f]
   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
 
   from inputFiles
   into outputDir
   def outputFiles = []
-  rename { filename ->
-    outputFiles += "${outputDir}/${filename}"
-    null
+  inputFiles.each{ file ->
+    def rfile = inputDir.toPath().relativize(file.toPath())
+    def ofile = new File("${outputDir}/${rfile}")
+    outputFiles += "${ofile}"
   }
   preserve {
-    include "**"
+    include "**/*"
   }
+
   outputs.files outputFiles
   inputs.files inputFiles
 }
@@ -3524,7 +3682,8 @@ task jalviewjsProjectImport(type: Exec) {
   }
 
   //def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview/org.eclipse.jdt.core"
-  def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview"
+  def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/${eclipse_project_name}"
+
   executable(eclipseBinary)
   args(["-nosplash", "--launcher.suppressErrors", "-application", "com.seeq.eclipse.importprojects.headlessimport", "-data", eclipseWorkspace.getPath(), "-import", jalviewDirAbsolutePath])
   if (eclipseDebug) {
@@ -3534,19 +3693,22 @@ task jalviewjsProjectImport(type: Exec) {
   if (!IN_ECLIPSE) {
     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
+    inputs.file("${jalviewjsJ2sAltSettingsFileName}")
   }
 
-  inputs.file("${jalviewDir}/.project")
-  outputs.upToDateWhen { 
-    file(projdir).exists()
-  }
+  outputs.upToDateWhen( {
+    if (IN_ECLIPSE) {
+      return true
+    }
+    def projDirExists = file(projdir).exists()
+    return projDirExists
+  } )
 }
 
-
+// jalviewjs_eclipse_workspace_location_file
 task jalviewjsTranspile(type: Exec) {
-  dependsOn jalviewjsEclipseSetup 
   dependsOn jalviewjsProjectImport
-  dependsOn jalviewjsEclipsePaths
+
   if (!IN_ECLIPSE) {
     dependsOn jalviewjsEnableAltFileProperty
   }
@@ -3581,11 +3743,12 @@ task jalviewjsTranspile(type: Exec) {
     def logOutFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}"
     def logOutFile = file(logOutFileName)
     logOutFile.createNewFile()
-    logOutFile.text = """ROOT: ${jalviewjs_eclipse_root}
-BINARY: ${eclipseBinary}
-VERSION: ${eclipseVersion}
-WORKSPACE: ${eclipseWorkspace}
-DEBUG: ${eclipseDebug}
+    def info = """ROOT: ${jalviewjs_eclipse_root}
+ECLIPSE BINARY: ${eclipseBinary}
+ECLIPSE VERSION: ${eclipseVersion}
+ECLIPSE PRODUCT: ${eclipseProductVersion}
+ECLIPSE WORKSPACE: ${eclipseWorkspace}
+ECLIPSE DEBUG: ${eclipseDebug}
 ----
 """
     def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
@@ -3611,12 +3774,58 @@ DEBUG: ${eclipseDebug}
         logErrFOS,
         stderr)
     }
+    standardOutput.write(string(info).getBytes("UTF-8"))
   }
 
   doLast {
-    if (stdout.toString().contains("Error processing ")) {
+    def transpileError = false
+    def j2sIsActive = false
+    def j2sBuildStarting = false
+    def compilingLines = 0
+    def j2sBuildingJavascript = false
+    def j2sBuildingJavascriptRegex = /(?m)^J2S building JavaScript for (\d+) files/
+    def numFiles = 0
+    def transpilingLines = 0
+    stdout.toString().eachLine { String line ->
+      if (line.startsWith("J2S isActive true")) {
+        j2sIsActive = true
+      }
+      if (line.startsWith("J2S buildStarting")) {
+        j2sBuildStarting = true
+      }
+      if (line =~ / Compiling /) {
+        compilingLines++
+      }
+      if (!j2sBuildingJavascript) {
+        def matcher = line =~ j2sBuildingJavascriptRegex
+        if (matcher.size() > 0) {
+          numFiles = Integer.valueOf(matcher[0][1])
+          j2sBuildingJavascript = true
+        }
+      }
+      if (line.startsWith("J2S transpiling ")) {
+        transpilingLines++
+      }
+      if (line.contains("Error processing ")) {
+        transpileError = true
+      }
+    }
+    
+    println("J2S IS ACTIVE=${j2sIsActive}")
+    println("J2S BUILD STARTING=${j2sBuildStarting}")
+    println("J2S BUILDING JAVASCRIPT=${j2sBuildingJavascript}")
+    println("NUM FILES=${numFiles}")
+    println("COMPILING LINES=${compilingLines}")
+    println("TRANSPILING LINES=${transpilingLines}")
+    println("TRANSPILE ERROR=${transpileError}")
+    
+    if (!j2sIsActive
+        || transpileError
+        || (j2sBuildStarting && transpilingLines == 0)
+        || (transpilingLines < compilingLines)
+        || (transpilingLines != numFiles)
+        ) {
       // j2s did not complete transpile
-      //throw new TaskExecutionException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
       if (jalviewjs_ignore_transpile_errors.equals("true")) {
         println("IGNORING TRANSPILE ERRORS")
         println("See eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
@@ -3626,9 +3835,127 @@ DEBUG: ${eclipseDebug}
     }
   }
 
+  if (IN_ECLIPSE) {
+    inputs.file(jalviewjsJ2sSettingsFileName)
+  } else {
+    inputs.file(jalviewjsJ2sAltSettingsFileName)
+  }
   inputs.dir("${jalviewDir}/${sourceDir}")
-  outputs.dir("${jalviewDir}/${jalviewjsTransferSiteJsDir}")
-  outputs.upToDateWhen( { file("${jalviewDir}/${jalviewjsTransferSiteJsDir}${jalviewjs_server_resource}").exists() } )
+
+  def inputJavaDir = file("${jalviewDir}/${sourceDir}")
+  def inputJavaFiles = fileTree(dir: inputJavaDir)
+  def outputJsDir = "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
+  def outputJsFiles = []
+  inputJavaFiles.each{ file ->
+    def rfile = inputJavaDir.toPath().relativize(file.toPath())
+    def ofile = new File("${outputJsDir}/${rfile}")
+    def ofilenamejs = ofile.getPath()
+    if (ofilenamejs.endsWith(".java")) {
+      ofilenamejs = ofilenamejs.substring(0,ofilenamejs.length()-4)+"js"
+    }
+    outputJsFiles += "${ofilenamejs}"
+  }
+
+  outputs.files outputJsFiles
+  outputs.file("${jalviewDir}/${jalviewjsTransferSiteJsDir}${jalviewjs_server_resource}")
+}
+
+
+task jalviewjsTransferSiteMergeSiteJsDir (type: Copy) {
+  dependsOn jalviewjsTranspile
+
+  def inputDir = file("${jalviewDir}/${jalviewjsTransferSiteJsDir}")
+  def outputDir = "${jalviewDir}/${jalviewjsTransferSiteMergeDir}"
+  into outputDir
+  from {
+    def inputFiles = fileTree(dir: inputDir)
+    return inputFiles
+  }
+
+  includeEmptyDirs = false
+  exclude "**/*.html"
+  exclude "**/*.htm"
+
+  // should this be exclude really ? No, swingjs dir should be transferred last (and overwrite)
+  duplicatesStrategy "INCLUDE"
+
+  // SiteJs files should take priority and write over existing files if different
+  // so we define the output files
+  outputs.upToDateWhen(
+    {
+      def transpiledFiles = jalviewjsTransferSiteMergeSiteJsDir.getOutputs().getFiles()
+      def inputFiles = fileTree(dir: inputDir)
+      if (inputFiles.size() < transpiledFiles.size()) {
+        return false
+      }
+      def retVal = ! inputFiles.any { file ->
+        def rfile = inputDir.toPath().relativize(file.toPath())
+        def ofile = new File("${outputDir}/${rfile}")
+        if (!ofile.exists() || ofile.lastModified() < file.lastModified()) {
+          return true // this is NOTted to false
+        }
+      }
+
+      return retVal
+    }
+  )
+
+  inputs.files jalviewjsTranspile
+}
+
+task jalviewjsTransferSiteMergeLibDir (type: Copy) {
+  dependsOn jalviewjsTransferUnzipLib
+
+  def outputDir = "${jalviewDir}/${jalviewjsTransferSiteMergeDir}"
+
+  // This takes the outputs of jalviewjsTransferUnzipLib
+  from jalviewjsTransferUnzipLib
+  into outputDir
+
+  includeEmptyDirs = false
+  exclude "**/*.html"
+  exclude "**/*.htm"
+
+  // don't overwrite files in the destination
+  // Note, this closure gets run at run stage not config stage
+  eachFile {
+    if (it.getRelativePath().getFile(file(outputDir)).exists()) {
+      it.exclude()
+    }
+  }
+
+  duplicatesStrategy "INCLUDE"
+}
+
+task jalviewjsTransferSiteMergeSwingJsDir (type: Copy) {
+  dependsOn jalviewjsTransferUnzipSwingJs
+
+  def outputDir = "${jalviewDir}/${jalviewjsTransferSiteMergeDir}"
+
+  // This takes the outputs of jalviewjsTransferUnzipSwingJs
+  from jalviewjsTransferUnzipSwingJs
+  into outputDir
+
+  includeEmptyDirs = false
+  exclude "**/*.html"
+  exclude "**/*.htm"
+
+  // DO overwrite files in the destination
+  
+  // should this be exclude really ? No, swingjs dir should be transferred last (and overwrite)
+  duplicatesStrategy "INCLUDE"
+}
+
+// we run after SiteJs and exclude overwriting files
+jalviewjsTransferSiteMergeLibDir.mustRunAfter jalviewjsTransferSiteMergeSiteJsDir
+jalviewjsTransferSiteMergeLibDir.mustRunAfter jalviewjsTransferSiteMergeSwingJsDir
+// we run this last, overwriting files from sitejs and lib, to ensure a consistent SwingJS
+jalviewjsTransferSiteMergeSwingJsDir.mustRunAfter jalviewjsTransferSiteMergeSiteJsDir
+
+task jalviewjsTransferSiteMergeDirs {
+  dependsOn jalviewjsTransferSiteMergeLibDir
+  dependsOn jalviewjsTransferSiteMergeSwingJsDir
+  dependsOn jalviewjsTransferSiteMergeSiteJsDir
 }
 
 
@@ -3646,6 +3973,7 @@ def jalviewjsCallCore(String name, FileCollection list, String prefixFile, Strin
 
   def coreTop = file(prefixFile)
   def coreBottom = file(suffixFile)
+  def missingFiles = []
   coreFile.getParentFile().mkdirs()
   coreFile.createNewFile()
   coreFile.write( coreTop.getText("UTF-8") )
@@ -3659,6 +3987,7 @@ def jalviewjsCallCore(String name, FileCollection list, String prefixFile, Strin
       msg = "...file '"+f.getPath()+"' does not exist, skipping"
       println(msg)
       logOutFile.append(msg+"\n")
+      missingFiles += f
     }
   }
   coreFile.append( coreBottom.getText("UTF-8") )
@@ -3670,11 +3999,11 @@ def jalviewjsCallCore(String name, FileCollection list, String prefixFile, Strin
   def logErrFOS = logOutFOS
 
   javaexec {
-    classpath = files(["${jalviewDir}/${jalviewjs_closure_compiler}"])
+    classpath = files([closureCompilerJar])
     main = "com.google.javascript.jscomp.CommandLineRunner"
     jvmArgs = [ "-Dfile.encoding=UTF-8" ]
-    args = [ "--compilation_level", "SIMPLE_OPTIMIZATIONS", "--warning_level", "QUIET", "--charset", "UTF-8", "--js", jsfile, "--js_output_file", zjsfile ]
-    maxHeapSize = "2g"
+    args = [ "--compilation_level", jalviewjs_closure_compiler_optimization_level, "--warning_level", "QUIET", "--charset", "UTF-8", "--js", jsfile, "--js_output_file", zjsfile ]
+    maxHeapSize = "4g"
 
     msg = "\nRunning '"+commandLine.join(' ')+"'\n"
     println(msg)
@@ -3701,21 +4030,25 @@ def jalviewjsCallCore(String name, FileCollection list, String prefixFile, Strin
     }
   }
   msg = "--"
+  if (missingFiles.size() > 0) {
+    msg += "\n!!! These files were listed but missing:\n"
+    missingFiles.each { file -> msg += "!!!  " + file.getPath() + "\n" }
+    msg = "--"
+  }
   println(msg)
   logOutFile.append(msg+"\n")
 }
 
 
-task jalviewjsBuildAllCores {
+task jalviewjsBuildCore {
   group "JalviewJS"
   description "Build the core js lib closures listed in the classlists dir"
-  dependsOn jalviewjsTranspile
-  dependsOn jalviewjsTransferUnzipSwingJs
+  dependsOn jalviewjsTransferSiteMergeDirs
 
-  def j2sDir = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${jalviewjs_j2s_subdir}"
-  def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_j2s_subdir}"
-  def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteLibDir}/${jalviewjs_j2s_subdir}"
-  def jsDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_js_subdir}"
+  def j2sDir = "${jalviewDir}/${jalviewjsTransferSiteMergeDir}/${jalviewjs_j2s_subdir}"
+  def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteMergeDir}/${jalviewjs_j2s_subdir}"
+  def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteMergeDir}/${jalviewjs_j2s_subdir}"
+  def jsDir = "${jalviewDir}/${jalviewjsTransferSiteMergeDir}/${jalviewjs_js_subdir}"
   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
   def prefixFile = "${jsDir}/core/coretop2.js"
   def suffixFile = "${jsDir}/core/corebottom2.js"
@@ -3734,8 +4067,7 @@ task jalviewjsBuildAllCores {
     ]
   }
 
-  // _jmol and _jalview cores. Add any other peculiar classlist.txt files here
-  //classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jmol}"), 'name': "_jvjmol" ]
+  // _jalview core
   classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jalview}"), 'name': jalviewjsJalviewCoreName ]
 
   jalviewjsCoreClasslists = []
@@ -3776,65 +4108,18 @@ task jalviewjsBuildAllCores {
     outputs.file(zjsfile)
   }
   
-  // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
-  def stevesoftClasslistName = "_stevesoft"
-  def stevesoftClasslist = [
-    'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
-    'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
-    'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
-    'name': stevesoftClasslistName
-  ]
-  jalviewjsCoreClasslists += stevesoftClasslist
-  inputs.files(stevesoftClasslist['list'])
-  outputs.file(stevesoftClasslist['jsfile'])
-  outputs.file(stevesoftClasslist['zjsfile'])
-
-  // _all core
-  def allClasslistName = "_all"
-  def allJsFiles = fileTree(dir: j2sDir, include: "**/*.js")
-  allJsFiles += fileTree(
-    dir: libJ2sDir,
-    include: "**/*.js",
-    excludes: [
-      // these exlusions are files that the closure-compiler produces errors for. Should fix them
-      "**/org/jmol/jvxl/readers/IsoIntersectFileReader.js",
-      "**/org/jmol/export/JSExporter.js"
-    ]
-  )
-  allJsFiles += fileTree(
-    dir: swingJ2sDir,
-    include: "**/*.js",
-    excludes: [
-      // these exlusions are files that the closure-compiler produces errors for. Should fix them
-      "**/sun/misc/Unsafe.js",
-      "**/swingjs/jquery/jquery-editable-select.js",
-      "**/swingjs/jquery/j2sComboBox.js",
-      "**/sun/misc/FloatingDecimal.js"
-    ]
-  )
-  def allClasslist = [
-    'jsfile': "${outputDir}/core${allClasslistName}.js",
-    'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
-    'list': allJsFiles,
-    'name': allClasslistName
-  ]
-  // not including this version of "all" core at the moment
-  //jalviewjsCoreClasslists += allClasslist
-  inputs.files(allClasslist['list'])
-  outputs.file(allClasslist['jsfile'])
-  outputs.file(allClasslist['zjsfile'])
-
   doFirst {
     def logOutFile = file("${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_closure_stdout}")
     logOutFile.getParentFile().mkdirs()
     logOutFile.createNewFile()
-    logOutFile.write(getDate("yyyy-MM-dd HH:mm:ss")+" jalviewjsBuildAllCores\n----\n")
+    logOutFile.write(getDate("yyyy-MM-dd HH:mm:ss")+" jalviewjsBuildCore\n----\n")
 
     jalviewjsCoreClasslists.each {
       jalviewjsCallCore(it.name, it.list, prefixFile, suffixFile, it.jsfile, it.zjsfile, logOutFile, jalviewjs_j2s_to_console.equals("true"))
     }
   }
 
+  inputs.file(closureCompilerJar)
 }
 
 
@@ -3864,7 +4149,8 @@ def jalviewjsPublishCoreTemplate(String coreName, String templateName, File inpu
 
 
 task jalviewjsPublishCoreTemplates {
-  dependsOn jalviewjsBuildAllCores
+  dependsOn jalviewjsBuildCore
+
   def inputFileName = "${jalviewDir}/${j2s_coretemplate_html}"
   def inputFile = file(inputFileName)
   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
@@ -3887,29 +4173,43 @@ task jalviewjsPublishCoreTemplates {
 
 
 task jalviewjsSyncCore (type: Sync) {
-  dependsOn jalviewjsBuildAllCores
+  dependsOn jalviewjsBuildCore
   dependsOn jalviewjsPublishCoreTemplates
-  def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
+
+  def inputDir = file("${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
+  def inputFiles = fileTree(dir: inputDir)
   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
 
   from inputFiles
   into outputDir
   def outputFiles = []
-  rename { filename ->
-    outputFiles += "${outputDir}/${filename}"
-    null
+  inputFiles.each{ file ->
+    def rfile = inputDir.toPath().relativize(file.toPath())
+    def ofile = new File("${outputDir}/${rfile}")
+    outputFiles += "${ofile}"
   }
   preserve {
-    include "**"
+    include "**/*"
   }
+
   outputs.files outputFiles
   inputs.files inputFiles
 }
 
 
 // this Copy version of TransferSiteJs will delete anything else in the target dir
+task jalviewjsCopyTransferSiteMergeDir(type: Copy) {
+  dependsOn jalviewjsTransferSiteMergeDirs
+
+  from "${jalviewDir}/${jalviewjsTransferSiteMergeDir}"
+  into "${jalviewDir}/${jalviewjsSiteDir}"
+}
+
+
+// this Copy version of TransferSiteJs will delete anything else in the target dir
 task jalviewjsCopyTransferSiteJs(type: Copy) {
   dependsOn jalviewjsTranspile
+
   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
   into "${jalviewDir}/${jalviewjsSiteDir}"
 }
@@ -3921,7 +4221,7 @@ task jalviewjsSyncTransferSiteJs(type: Sync) {
   include "**/*.*"
   into "${jalviewDir}/${jalviewjsSiteDir}"
   preserve {
-    include "**"
+    include "**/*"
   }
 }
 
@@ -3940,7 +4240,7 @@ jalviewjsSyncBuildProperties.mustRunAfter jalviewjsSyncTransferSiteJs
 task jalviewjsPrepareSite {
   group "JalviewJS"
   description "Prepares the website folder including unzipping files and copying resources"
-  dependsOn jalviewjsSyncAllLibs
+  //dependsOn jalviewjsSyncAllLibs // now using jalviewjsCopyTransferSiteMergeDir
   dependsOn jalviewjsSyncResources
   dependsOn jalviewjsSyncSiteResources
   dependsOn jalviewjsSyncBuildProperties
@@ -3951,7 +4251,7 @@ task jalviewjsPrepareSite {
 task jalviewjsBuildSite {
   group "JalviewJS"
   description "Builds the whole website including transpiled code"
-  dependsOn jalviewjsCopyTransferSiteJs
+  dependsOn jalviewjsCopyTransferSiteMergeDir
   dependsOn jalviewjsPrepareSite
 }
 
@@ -4281,33 +4581,37 @@ task jalviewjsLaunchTest {
       execStdout = stdout
       execStderr = stderr
     }
+    // macOS seems okay now with timeout arguments
     def execArgs = [
+      "--virtual-time-budget=${timeoutms}",
+      "--timeout=${timeoutms}",
+    ]
+    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()
+
+    java.lang.Runnable runChrome = () -> {
+        exec {
+          standardOutput = execStdout
+          errorOutput = execStderr
+          executable(chromiumBinary)
+          args(execArgs)
+          println "COMMAND: '"+commandLine.join(" ")+"'"
         }
-      )
+      }
+
+    if (macOS) {
+    // we create our own timeout executor as --timeout doesn't work on macOS
+      ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
+
+      Future f1 = executor.submit( runChrome )
 
       def noChangeBytes = 0
       def noChangeIterations = 0
@@ -4316,10 +4620,10 @@ task jalviewjsLaunchTest {
           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)
+            f1.cancel(true)
+            Thread.sleep(100)
             executor.shutdownNow()
-          }
+          } else
           // if no change in stderr for 10s then also end
           if (noChangeIterations >= jalviewjs_chromium_idle_timeout) {
             executor.shutdownNow()
@@ -4331,17 +4635,21 @@ task jalviewjsLaunchTest {
             noChangeIterations = 0
           }
         },
-        1, 1, TimeUnit.SECONDS)
+        200, 200, TimeUnit.MILLISECONDS)
 
       executor.schedule(new Runnable(){
         public void run(){
-          f1.cancel()
+          f1.cancel(true)
           executor.shutdownNow()
         }
       }, timeoutms, TimeUnit.MILLISECONDS)
 
-      executor.awaitTermination(timeoutms+10000, TimeUnit.MILLISECONDS)
+      executor.awaitTermination(timeoutms+200, TimeUnit.MILLISECONDS)
+      f1.cancel(true)
       executor.shutdownNow()
+    } else {
+      // just run chrome and rely on --virtual-time-budget and --timeout
+      runChrome.run()
     }
 
   }