Merge branch 'releases/Release_2_11_4_Branch'
[jalview.git] / build.gradle
index 4aa019c..1cd0565 100644 (file)
@@ -42,7 +42,7 @@ buildscript {
   dependencies {
     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'
   }
 }
 
@@ -52,14 +52,14 @@ 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.install4j.gradle' version '10.0.3'
+  id 'com.github.johnrengelman.shadow' version '6.0.0'
+  id 'com.install4j.gradle' version '10.0.8'
   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 {
-  jcenter()
   mavenCentral()
   mavenLocal()
 }
@@ -233,17 +233,18 @@ ext {
   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"
-  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}")
+  install4jExtraScheme = "jalviewextra"
   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}"
@@ -251,9 +252,16 @@ ext {
 
     case "BUILD":
     // TODO: get bamboo build artifact URL for getdown artifacts
-    getdown_channel_base = bamboo_channelbase
     getdownChannelName = string("${bamboo_planKey}/${JAVA_VERSION}")
-    getdownAppBase = string("${bamboo_channelbase}/${bamboo_planKey}${bamboo_getdown_channel_suffix}/${JAVA_VERSION}")
+
+    /**
+     * This URL doesn't work
+     * if (bamboo_buildResultsUrl && bamboo_buildResultsKey) {
+     *   getdownAppBase = "${bamboo_buildResultsKey}/${bamboo_buildResultsArtifactPath}"
+     * } else {
+     */
+    getdownAppBase = "${bamboo_channelbase}/${bamboo_planKey}/${bamboo_getdown_channel_suffix}"
+    /* } */
     jvlChannelName += "_${getdownChannelName}"
     // automatically add the test group Not-bamboo for exclusion 
     if ("".equals(testng_excluded_groups)) { 
@@ -268,7 +276,8 @@ ext {
     getdownSetAppBaseProperty = true
     reportRsyncCommand = true
     install4jSuffix = ""
-    install4jInstallerName = "${jalview_name} Installer"
+    install4jmacOSArchiveName = "Install ${jalview_name} ${JALVIEW_VERSION}"
+    install4jExtraScheme = (CHANNEL=="RELEASE")?"jalviewx":"jalviewjs"
     break
 
     case "ARCHIVE":
@@ -310,7 +319,7 @@ ext {
     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}")
@@ -328,7 +337,7 @@ ext {
     
     install4jSuffix = "Develop"
     install4jExtraScheme = "jalviewd"
-    install4jInstallerName = "${jalview_name} Develop Installer"
+    install4jmacOSArchiveName = "Install ${jalview_name} Develop ${JALVIEW_VERSION}"
     backgroundImageText = true
     break
 
@@ -343,7 +352,7 @@ ext {
     JALVIEW_VERSION = JALVIEW_VERSION+"-test"
     install4jSuffix = "Test"
     install4jExtraScheme = "jalviewt"
-    install4jInstallerName = "${jalview_name} Test Installer"
+    install4jmacOSArchiveName = "Install ${jalview_name} Test ${JALVIEW_VERSION}"
     backgroundImageText = true
     break
 
@@ -367,7 +376,7 @@ ext {
     JALVIEW_VERSION = "TEST"
     install4jSuffix = "Test-Local"
     install4jExtraScheme = "jalviewt"
-    install4jInstallerName = "${jalview_name} Test Installer"
+    install4jmacOSArchiveName = "Install ${jalview_name} Test ${JALVIEW_VERSION}"
     backgroundImageText = true
     break
 
@@ -433,6 +442,8 @@ ext {
                                     .replaceAll("_+", "_") // collapse __
                                     .replaceAll("_*-_*", "-") // collapse _-_
                                     .toLowerCase()
+  install4jmacOSArchiveX64Name = "${install4jmacOSArchiveName} (Intel)"
+  install4jmacOSArchiveAarch64Name = "${install4jmacOSArchiveName} (Apple Silicon)"
 
   getdownWrapperLink = install4jUnixApplicationFolder // e.g. "jalview_local"
   getdownAppDir = string("${getdownAppBaseDir}/${getdownAppDistDir}")
@@ -530,6 +541,9 @@ ext {
   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")
@@ -1308,6 +1322,25 @@ def mdFileComponents(File mdFile, def dateOnly=false) {
   return dateOnly ? map["date"] : [map, content]
 }
 
+def setReleaseAndWhatsNew(File whatsnewMdFile, File releaseMdFile) {
+  if (CHANNEL != "") {
+    // we may have a version string that has additional bits vs the actual release version
+    if (CHANNEL != "RELEASE")
+    {
+      var nearestVersion = "${JALVIEW_VERSION_UNDERSCORES}"
+      println "Stripping rc/test/etc for ${nearestVersion}"
+      nearestVersion = nearestVersion.replaceAll(~/-?(d|rc|test|develop|dev).*/,"")
+      var nearestMd = file("${jalviewDir}/${whatsnew_dir}/whatsnew-${nearestVersion}.md")
+      println "Looking in ${nearestMd}"
+      if (nearestMd.exists()) {
+        whatsnewMdFile = nearestMd
+        releaseMdFile = file("${jalviewDir}/${releases_dir}/release-${nearestVersion}.md")
+      }
+    }
+  }
+  return [whatsnewMdFile,releaseMdFile]
+}
+
 task hugoTemplates {
   group "website"
   description "Create partially populated md pages for hugo website build"
@@ -1327,16 +1360,18 @@ task hugoTemplates {
     def givenDate = null
     def givenChannel = null
     def givenVersion = null
-    if (CHANNEL == "RELEASE") {
+    
+    if (CHANNEL!="") {
+      (whatsnewMdFile, releaseMdFile) = setReleaseAndWhatsNew(whatsnewMdFile, releaseMdFile)
+
       def (map, content) = mdFileComponents(releaseMdFile)
       givenDate = map.date
       givenChannel = map.channel
       givenVersion = map.version
       changes = content
-      if (givenVersion != null && givenVersion != JALVIEW_VERSION) {
+      if (CHANNEL=="RELEASE" && givenVersion != null && givenVersion != JALVIEW_VERSION) {
         throw new GradleException("'version' header (${givenVersion}) found in ${releaseMdFile} does not match JALVIEW_VERSION (${JALVIEW_VERSION})")
       }
-
       if (whatsnewMdFile.exists())
         whatsnew = whatsnewMdFile.text
     }
@@ -1503,6 +1538,8 @@ task releasesTemplates {
     def releaseMdFile = file("${releasesMdDir}/release-${JALVIEW_VERSION_UNDERSCORES}.md")
     def whatsnewMdFile = file("${whatsnewMdDir}/whatsnew-${JALVIEW_VERSION_UNDERSCORES}.md")
 
+    (whatsnewMdFile,releaseMdFile) = setReleaseAndWhatsNew(whatsnewMdFile, releaseMdFile)
+    
     if (CHANNEL == "RELEASE") {
       if (!releaseMdFile.exists()) {
         throw new GradleException("File ${releaseMdFile} must be created for RELEASE")
@@ -1769,6 +1806,7 @@ task testTask0(type: Test) {
     preserveOrder true
     useDefaultListeners=true
   }
+  timeout = Duration.ofMinutes(15)
 }
 
 /* separated tests */
@@ -1781,6 +1819,7 @@ task testTask1(type: Test) {
     preserveOrder true
     useDefaultListeners=true
   }
+  timeout = Duration.ofMinutes(5)
 }
 
 task testTask2(type: Test) {
@@ -1792,6 +1831,7 @@ task testTask2(type: Test) {
     preserveOrder true
     useDefaultListeners=true
   }
+  timeout = Duration.ofMinutes(5)
 }
 task testTask3(type: Test) {
   group = "Verification"
@@ -1802,6 +1842,7 @@ task testTask3(type: Test) {
     preserveOrder true
     useDefaultListeners=true
   }
+  timeout = Duration.ofMinutes(5)
 }
 
 /* insert more testTaskNs here -- change N to next digit or other string */
@@ -1818,6 +1859,7 @@ task testTaskN(type: Test) {
 }
 */
 
+
 /*
  * adapted from https://medium.com/@wasyl/pretty-tests-summary-in-gradle-744804dd676c
  * to summarise test results from all Test tasks
@@ -1851,8 +1893,9 @@ tasks.withType(Test).matching {t -> t.getName().startsWith("testTask")}.all { te
     showExceptions true
     showCauses true
     showStackTraces true
-    showStandardStreams true
-
+    if (test_output) {
+      showStandardStreams true
+    }
     info.events = [ TestLogEvent.FAILED ]
   }
 
@@ -2149,22 +2192,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
@@ -2218,9 +2295,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) {
@@ -2228,6 +2305,7 @@ task getdownWebsite() {
   }
 
   def getdownWebsiteResourceFilenames = []
+  def getdownWebsitePResourceFilenames = []
   def getdownResourceDir = getdownResourceDir
   def getdownResourceFilenames = []
 
@@ -2256,7 +2334,7 @@ task getdownWebsite() {
     }
     getdownWebsiteResourceFilenames += file(channelPropsFile).getName()
 
-    // set some getdownTxt_ properties then go through all properties looking for getdownTxt_...
+    // set some getdownTxt_ properties then go through all properties looking for getdown_txt_...
     def props = project.properties.sort { it.key }
     if (getdownAltJavaMinVersion != null && getdownAltJavaMinVersion.length() > 0) {
       props.put("getdown_txt_java_min_version", getdownAltJavaMinVersion)
@@ -2302,7 +2380,11 @@ task getdownWebsite() {
             }
             if (r.exists()) {
               val = "${getdown_resource_dir}/" + r.getName()
-              getdownWebsiteResourceFilenames += val
+              if (prop.startsWith("getdown_txt_ui.")) {
+                getdownWebsitePResourceFilenames += val
+              } else {
+                getdownWebsiteResourceFilenames += val
+              }
               getdownResourceFilenames += r.getPath()
             }
           }
@@ -2314,6 +2396,9 @@ task getdownWebsite() {
       }
     }
 
+    getdownWebsitePResourceFilenames.each{ filename ->
+      getdownTextLines += "presource = ${filename}"
+    }
     getdownWebsiteResourceFilenames.each{ filename ->
       getdownTextLines += "resource = ${filename}"
     }
@@ -2323,8 +2408,15 @@ task getdownWebsite() {
         into getdownResourceDir
       }
     }
-    
-    def getdownWrapperScripts = [ getdown_bash_wrapper_script, getdown_powershell_wrapper_script, getdown_batch_wrapper_script ]
+
+    def getdownWrapperScripts = [
+      getdown_bash_wrapper_script,
+      getdown_powershell_wrapper_script,
+      getdown_bash_update_script,
+      getdown_powershell_update_script,
+    ]
+    def run_powershell = file( "${jalviewDir}/utils/getdown/${getdown_wrapper_script_dir}/${getdown_run_powershell}" )
+    def run_other_script = file( "${jalviewDir}/utils/getdown/${getdown_wrapper_script_dir}/${getdown_run_other_script}" )
     getdownWrapperScripts.each{ script ->
       def s = file( "${jalviewDir}/utils/getdown/${getdown_wrapper_script_dir}/${script}" )
       if (s.exists()) {
@@ -2332,7 +2424,32 @@ task getdownWebsite() {
           from s
           into "${getdownAppBaseDir}/${getdown_wrapper_script_dir}"
         }
-        getdownTextLines += "resource = ${getdown_wrapper_script_dir}/${script}"
+        getdownTextLines += "xresource = ${getdown_wrapper_script_dir}/${script}"
+      }
+      def ext = script.toLowerCase(Locale.ROOT).substring(script.lastIndexOf(".") + 1)
+      if ("ps1".equals(ext)) {
+        def base = script.take(script.lastIndexOf("."))
+        def newbase = "update".equals(base) ? "${install4jUnixApplicationFolder}_update" : install4jUnixApplicationFolder
+        if (!newbase.equals(base)) {
+          copy {
+            from run_other_script
+            rename(run_other_script.getName(), "${newbase}.${ext}")
+            into "${getdownAppBaseDir}/${getdown_wrapper_script_dir}"
+            getdownTextLines += "xresource = ${getdown_wrapper_script_dir}/${newbase}.${ext}"
+            filter(ReplaceTokens,
+              beginToken: '__',
+              endToken: '__',
+              tokens: [
+                'OTHERSCRIPT': script
+              ]
+            )          }
+        }
+        copy {
+          from run_powershell
+          rename(run_powershell.getName(), "${newbase}.bat")
+          into "${getdownAppBaseDir}/${getdown_wrapper_script_dir}"
+          getdownTextLines += "xresource = ${getdown_wrapper_script_dir}/${newbase}.bat"
+        }
       }
     }
 
@@ -2379,8 +2496,7 @@ task getdownWebsite() {
     getdownTextLines += "class = ${main_class}"
     // Not setting these properties in general so that getdownappbase and getdowndistdir will default to release version in jalview.bin.Cache
     if (getdownSetAppBaseProperty) {
-      getdownTextLines += "jvmarg = -Dgetdowndistdir=${getdownAppDistDir}"
-      getdownTextLines += "jvmarg = -Dgetdownappbase=${getdownAppBase}"
+      getdownTextLines += "jvmarg = -Dlauncher.distdir=${getdownAppDistDir}"
     }
 
     def getdownTxt = file("${getdownAppBaseDir}/getdown.txt")
@@ -2388,7 +2504,10 @@ task getdownWebsite() {
 
     getdownLaunchJvl = getdown_launch_jvl_name + ( (jvlChannelName != null && jvlChannelName.length() > 0)?"-${jvlChannelName}":"" ) + ".jvl"
     def launchJvl = file("${getdownAppBaseDir}/${getdownLaunchJvl}")
-    launchJvl.write("appbase=${getdownAppBase}")
+    def launch_lines = []
+    launch_lines.add("appbase = ${getdownAppBase}")
+    launch_lines.add("jvl_replace = true")
+    launchJvl.write(launch_lines.join("\n"))
 
     // files going into the getdown website dir: getdown-launcher.jar
     copy {
@@ -2474,7 +2593,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)
   }
@@ -2507,12 +2628,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}"
@@ -2559,6 +2687,14 @@ task getdownArchiveBuild() {
       }
     }
 
+    // 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)
@@ -2631,8 +2767,6 @@ install4j {
     install4jHomeDir = "/Applications/install4j.app/Contents/Resources/app"
   }
   installDir(file(install4jHomeDir))
-
-  mediaTypes = Arrays.asList(install4j_media_types.split(","))
 }
 
 
@@ -2657,6 +2791,21 @@ task copyInstall4jTemplate {
       }
     }
 
+    // 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')
@@ -2771,8 +2920,109 @@ task install4jDMGBackgroundImageProcess {
   }
 }
 
-task install4jDMGBackgroundImage {
+
+python {
+  pip 'ds_store:1.3.0'
+}
+
+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",
+    '--appname', "${applicationName}.app",
+  ]
+  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",
+    '--appname', "${applicationName}.app",
+  ]
+  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 install4jDMGVmoptionsFile(type: Copy) {
+  def inputDir = "${jalviewDir}/${install4j_utils_dir}"
+  def outputDir = "${jalviewDir}/${install4j_build_dir}/tmp"
+
+  def installDateTime = getDate("yyyy-MM-dd HH:mm:ss") + " (build time)"
+
+  from(inputDir) {
+    include(string("${install4j_default_vmoptions}"))
+    rename(string("${install4j_default_vmoptions}"), string("${install4j_default_vmoptions}.X64"))
+
+    filter(ReplaceTokens,
+      beginToken: '__',
+      endToken: '__',
+      tokens: [
+        'INSTALLERFILENAME': string("${install4jmacOSArchiveX64DMGFilename}.dmg"),
+        'INSTALLDATETIME': installDateTime
+      ]
+    )
+
+  }
+
+  from(inputDir) {
+    include(string("${install4j_default_vmoptions}"))
+    rename(string("${install4j_default_vmoptions}"), string("${install4j_default_vmoptions}.AARCH64"))
+
+    filter(ReplaceTokens,
+      beginToken: '__',
+      endToken: '__',
+      tokens: [
+        'INSTALLERFILENAME': string("${install4jmacOSArchiveAarch64DMGFilename}.dmg"),
+        'INSTALLDATETIME': installDateTime
+      ]
+    )
+  }
+
+  into outputDir
+
+  inputs.file("${inputDir}/${install4j_default_vmoptions}")
+  outputs.file("${outputDir}/${install4j_default_vmoptions}.X64")
+  outputs.file("${outputDir}/${install4j_default_vmoptions}.AARCH64")
+}
+
+task install4jDMGProcesses {
   dependsOn install4jDMGBackgroundImageProcess
+  dependsOn install4jCustomiseDS_Store
+  dependsOn install4jDMGVmoptionsFile
 }
 
 task installerFiles(type: com.install4j.gradle.Install4jTask) {
@@ -2781,10 +3031,13 @@ 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(
@@ -2817,14 +3070,18 @@ task installerFiles(type: com.install4j.gradle.Install4jTask) {
     '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,
+    'BASH_UPDATE_SCRIPT': getdown_bash_update_script,
+    'POWERSHELL_UPDATE_SCRIPT': getdown_powershell_update_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,
@@ -2833,18 +3090,34 @@ task installerFiles(type: com.install4j.gradle.Install4jTask) {
     'GETDOWN_ALT_DIR': getdown_app_dir_alt,
     'GETDOWN_INSTALL_DIR': getdown_install_dir,
     'INFO_PLIST_FILE_ASSOCIATIONS_FILE': install4j_info_plist_file_associations,
-    'BUILD_DIR': install4jBuildDir,
+    'BUILD_DIR': install4j_build_dir,
     'APPLICATION_CATEGORIES': install4j_application_categories,
     'APPLICATION_FOLDER': install4jApplicationFolder,
     'UNIX_APPLICATION_FOLDER': install4jUnixApplicationFolder,
     'EXECUTABLE_NAME': install4jExecutableName,
     'EXTRA_SCHEME': install4jExtraScheme,
-    'MAC_ICONS_FILE': install4jMacIconsFile,
-    'WINDOWS_ICONS_FILE': install4jWindowsIconsFile,
-    'PNG_ICON_FILE': install4jPngIconFile,
+    'ICONS_DIR': install4j_images_dir,
+    'MAC_ICONS_FILE': install4j_mac_icons_file,
+    'WINDOWS_ICONS_FILE': install4j_windows_icons_file,
+    'PNG_ICON_FILE': install4j_png_icon_file,
     'BACKGROUND': install4jBackground,
+    'MACOSARCHIVE_X64_DMG_FILENAME': install4jmacOSArchiveX64DMGFilename,
+    'MACOSARCHIVE_AARCH64_DMG_FILENAME': install4jmacOSArchiveAarch64DMGFilename,
+    'MACOSARCHIVE_VOLUMEICON': install4jDMGVolumeIcon,
+    'INSTALLER_ICON': "${getdownImagesDir}/${install4j_installer_icon}",
+    'INSTALLER_MAC_ICON': "${getdownImagesDir}/${install4j_installer_mac_icon}",
+    'INSTALLER_WINDOWS_ICON': "${getdownImagesDir}/${install4j_installer_windows_icon}",
+    'TITLE_ICON': "${getdownImagesDir}/${install4j_title_icon}",
+    'LOG_FILE': "${install4jUnixApplicationFolder}.log",
   ]
 
+  if (project.hasProperty("install4j_build_ids")) {
+    buildIds = Arrays.asList(install4j_build_ids.split(","))
+  } else if (project.hasProperty("install4j_media_types")) {
+    buildSelected = true
+    mediaTypes = Arrays.asList(install4j_media_types.split(","))
+  }
+
   def varNameMap = [
     'mac': 'MACOS',
     'windows': 'WINDOWS',
@@ -2871,7 +3144,6 @@ task installerFiles(type: com.install4j.gradle.Install4jTask) {
   //variables.each{k,v->println("${k}=${v}")}
 
   destination = "${jalviewDir}/${install4jBuildDir}"
-  buildSelected = true
 
   if (install4j_faster.equals("true") || CHANNEL.startsWith("LOCAL")) {
     faster = true
@@ -3022,6 +3294,7 @@ task sourceDist(type: Tar) {
   into project.name
 
   def EXCLUDE_FILES=[
+    "dist/*",
     "build/*",
     "bin/*",
     "test-output/",