JAL-3989 Changed to using com.google.common.hash for SHA256 sum, for gradle 7 compati...
[jalview.git] / build.gradle
index c5e9787..76ccefd 100644 (file)
@@ -12,6 +12,7 @@ import java.security.MessageDigest
 import groovy.transform.ExternalizeMethods
 import groovy.util.XmlParser
 import groovy.xml.XmlUtil
+import groovy.json.JsonBuilder
 import com.vladsch.flexmark.util.ast.Node
 import com.vladsch.flexmark.html.HtmlRenderer
 import com.vladsch.flexmark.parser.Parser
@@ -22,6 +23,9 @@ import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension
 import com.vladsch.flexmark.ext.autolink.AutolinkExtension
 import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension
 import com.vladsch.flexmark.ext.toc.TocExtension
+import com.google.common.hash.HashCode
+import com.google.common.hash.Hashing
+import com.google.common.io.Files
 
 buildscript {
   repositories {
@@ -40,9 +44,11 @@ plugins {
   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 '8.0.10'
+  id 'com.install4j.gradle' version '9.0.6'
   id 'com.dorongold.task-tree' version '1.5' // only needed to display task dependency tree with  gradle task1 [task2 ...] taskTree
-  id 'com.palantir.git-version' version '0.12.3'
+  id 'com.palantir.git-version' version '0.13.0' apply false
+//  id 'org.gradle.crypto.checksum' version '1.4.0'
+
 }
 
 repositories {
@@ -52,6 +58,7 @@ repositories {
 }
 
 
+
 // in ext the values are cast to Object. Ensure string values are cast as String (and not GStringImpl) for later use
 def string(Object o) {
   return o == null ? "" : o.toString()
@@ -95,6 +102,7 @@ def overrideProperties(String propsFileName, boolean output = false) {
 ext {
   jalviewDirAbsolutePath = file(jalviewDir).getAbsolutePath()
   jalviewDirRelativePath = jalviewDir
+  date = new Date()
 
   getdownChannelName = CHANNEL.toLowerCase()
   // default to "default". Currently only has different cosmetics for "develop", "release", "default"
@@ -114,7 +122,7 @@ ext {
   // Import releaseProps from the RELEASE file
   // or a file specified via JALVIEW_RELEASE_FILE if defined
   // Expect jalview.version and target release branch in jalview.release        
-  def releaseProps = new Properties();
+  releaseProps = new Properties();
   def releasePropFile = findProperty("JALVIEW_RELEASE_FILE");
   def defaultReleasePropFile = "${jalviewDirAbsolutePath}/RELEASE";
   try {
@@ -189,12 +197,18 @@ ext {
   testClassesDir = useClover ? cloverTestClassesDir : "${jalviewDir}/${test_output_dir}"
 
   getdownWebsiteDir = string("${jalviewDir}/${getdown_website_dir}/${JAVA_VERSION}")
+  getdownArchiveDir = string("${jalviewDir}/${getdown_archive_dir}")
+  getdownFullArchiveDir = null
+  getdownTextLines = []
+  getdownLaunchJvl = null
+  getdownVersionLaunchJvl = null
   buildDist = true
   buildProperties = null
 
   // the following values might be overridden by the CHANNEL switch
   getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
   getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
+  getdownArchiveAppBase = null
   getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher}")
   getdownAppDistDir = getdown_app_dir_alt
   getdownImagesDir = string("${jalviewDir}/${getdown_images_dir}")
@@ -205,12 +219,14 @@ ext {
   install4jDMGDSStore = "${install4j_images_dir}/${install4j_dmg_ds_store}"
   install4jDMGBackgroundImage = "${install4j_images_dir}/${install4j_dmg_background}"
   install4jInstallerName = "${jalview_name} Non-Release Installer"
-  install4jExecutableName = jalview_name.replaceAll("[^\\w]+", "_").toLowerCase()
+  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}")
   install4jBackground = string("${install4j_images_dir}/${install4j_background}")
+  install4jBuildDir = "${install4j_build_dir}/${JAVA_VERSION}"
+  install4jCheckSums = true
   switch (CHANNEL) {
 
     case "BUILD":
@@ -228,9 +244,11 @@ ext {
 
     case [ "RELEASE", "JALVIEWJS-RELEASE" ]:
     getdownAppDistDir = getdown_app_dir_release
+    getdownSetAppBaseProperty = true
     reportRsyncCommand = true
     install4jSuffix = ""
     install4jInstallerName = "${jalview_name} Installer"
+    getdownArchiveAppBase = getdown_archive_base
     break
 
     case "ARCHIVE":
@@ -278,6 +296,7 @@ ext {
 
     case "TEST-RELEASE":
     reportRsyncCommand = true
+    getdownSetAppBaseProperty = true
     // Don't ignore transpile errors for release build
     if (jalviewjs_ignore_transpile_errors.equals("true")) {
       jalviewjs_ignore_transpile_errors = "false"
@@ -315,8 +334,10 @@ ext {
     case [ "LOCAL", "JALVIEWJS" ]:
     JALVIEW_VERSION = "TEST"
     getdownAppBase = file(getdownWebsiteDir).toURI().toString()
+    getdownArchiveAppBase = file("${jalviewDir}/${getdown_archive_dir}").toURI().toString()
     getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
     install4jExtraScheme = "jalviewl"
+    install4jCheckSums = false
     break
 
     default: // something wrong specified
@@ -324,6 +345,7 @@ ext {
     break
 
   }
+  JALVIEW_VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
   // override getdownAppBase if requested
   if (findProperty("getdown_appbase_override") != null) {
     // revert to LOCAL if empty string
@@ -371,6 +393,7 @@ ext {
                                     .replaceAll("_*-_*", "-") // collapse _-_
                                     .toLowerCase()
 
+  getdownWrapperLink = install4jUnixApplicationFolder // e.g. "jalview_local"
   getdownAppDir = string("${getdownWebsiteDir}/${getdownAppDistDir}")
   //getdownJ11libDir = "${getdownWebsiteDir}/${getdown_j11lib_dir}"
   getdownResourceDir = string("${getdownWebsiteDir}/${getdown_resource_dir}")
@@ -381,9 +404,21 @@ ext {
   modules_compileClasspath = fileTree(dir: "${jalviewDir}/${j11modDir}", include: ["*.jar"])
   modules_runtimeClasspath = modules_compileClasspath
   */
-  def details = versionDetails()
-  gitHash = details.gitHash
-  gitBranch = details.branchName
+
+  gitHash = "SOURCE"
+  gitBranch = "Source"
+  try {
+    apply plugin: "com.palantir.git-version"
+    def details = versionDetails()
+    gitHash = details.gitHash
+    gitBranch = details.branchName
+  } catch(org.gradle.api.internal.plugins.PluginApplicationException e) {
+    println("Not in a git repository. Using git values from RELEASE properties file.")
+    gitHash = releaseProps.getProperty("git.hash")
+    gitBranch = releaseProps.getProperty("git.branch")
+  } catch(java.lang.RuntimeException e1) {
+    throw new GradleException("Error with git-version plugin.  Directory '.git' exists but versionDetails() cannot be found.")
+  }
 
   println("Using a ${CHANNEL} profile.")
 
@@ -419,16 +454,16 @@ ext {
     '--add-modules', j11modules
     ]
      */
-  } else if (JAVA_VERSION.equals("12") || JAVA_VERSION.equals("13")) {
-    JAVA_INTEGER_VERSION = JAVA_VERSION
-    libDir = j11libDir
-    libDistDir = j11libDir
-    compile_source_compatibility = JAVA_VERSION
-    compile_target_compatibility = JAVA_VERSION
+  } else if (JAVA_VERSION.equals("17")) {
+    JAVA_INTEGER_VERSION = string("17")
+    libDir = j17libDir
+    libDistDir = j17libDir
+    compile_source_compatibility = 17
+    compile_target_compatibility = 17
     getdownAltJavaMinVersion = string(findProperty("getdown_alt_java11_min_version"))
     getdownAltJavaMaxVersion = string(findProperty("getdown_alt_java11_max_version"))
     getdownAltMultiJavaLocation = string(findProperty("getdown_alt_java11_txt_multi_java_location"))
-    eclipseJavaRuntimeName = string("JavaSE-11")
+    eclipseJavaRuntimeName = string("JavaSE-17")
     /* compile without modules -- using classpath libraries
     additional_compiler_args += [
     '--module-path', modules_compileClasspath.asPath,
@@ -448,11 +483,11 @@ ext {
     jreInstallsDir = System.getProperty("user.home") + jreInstallsDir.substring(1)
   }
   macosJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-mac-x64/jre")
-  macosJavaVMTgz = string("${jreInstallsDir}/tgz/jre-${JAVA_INTEGER_VERSION}-mac-x64.tar.gz")
   windowsJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-windows-x64/jre")
-  windowsJavaVMTgz = string("${jreInstallsDir}/tgz/jre-${JAVA_INTEGER_VERSION}-windows-x64.tar.gz")
   linuxJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-linux-x64/jre")
-  linuxJavaVMTgz = string("${jreInstallsDir}/tgz/jre-${JAVA_INTEGER_VERSION}-linux-x64.tar.gz")
+  macosJavaVMTgz = string("${jreInstallsDir}/tgz/jre_${JAVA_INTEGER_VERSION}_mac_x64.tar.gz")
+  windowsJavaVMTgz = string("${jreInstallsDir}/tgz/jre_${JAVA_INTEGER_VERSION}_windows_x64.tar.gz")
+  linuxJavaVMTgz = string("${jreInstallsDir}/tgz/jre_${JAVA_INTEGER_VERSION}_linux_x64.tar.gz")
   install4jDir = string("${jalviewDir}/${install4j_utils_dir}")
   install4jConfFileName = string("jalview-install4j-conf.install4j")
   install4jConfFile = file("${install4jDir}/${install4jConfFileName}")
@@ -1026,7 +1061,6 @@ cleanTest {
 
 // format is a string like date.format("dd MMMM yyyy")
 def getDate(format) {
-  def date = new Date()
   return date.format(format)
 }
 
@@ -1369,13 +1403,12 @@ task linkCheck(type: JavaExec) {
   args = [ "${helpBuildDir}/${help_dir}", "-nointernet" ]
 
   def outFOS = new FileOutputStream(helpLinksCheckerOutFile, false) // false == don't append
-  def errFOS = outFOS
   standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
     outFOS,
-    standardOutput)
+    System.out)
   errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
     outFOS,
-    errorOutput)
+    System.err)
 
   inputs.dir(helpBuildDir)
   outputs.file(helpLinksCheckerOutFile)
@@ -1410,6 +1443,8 @@ jar {
   def outputDir = "${jalviewDir}/${package_dir}"
   destinationDirectory = file(outputDir)
   archiveFileName = rootProject.name+".jar"
+  duplicatesStrategy "EXCLUDE"
+
 
   exclude "cache*/**"
   exclude "*.jar"
@@ -1475,6 +1510,9 @@ shadowJar {
     attributes "Implementation-Version": JALVIEW_VERSION,
     "Application-Name": install4jApplicationName
   }
+
+  duplicatesStrategy "INCLUDE"
+
   mainClassName = shadow_jar_main_class
   mergeServiceFiles()
   classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
@@ -1490,7 +1528,6 @@ task getdownWebsite() {
   }
 
   def getdownWebsiteResourceFilenames = []
-  def getdownTextString = ""
   def getdownResourceDir = getdownResourceDir
   def getdownResourceFilenames = []
 
@@ -1512,7 +1549,7 @@ task getdownWebsite() {
     }
     getdownWebsiteResourceFilenames += file(channelPropsFile).getName()
 
-    // set some getdown_txt_ properties then go through all properties looking for getdown_txt_...
+    // set some getdownTxt_ properties then go through all properties looking for getdownTxt_...
     def props = project.properties.sort { it.key }
     if (getdownAltJavaMinVersion != null && getdownAltJavaMinVersion.length() > 0) {
       props.put("getdown_txt_java_min_version", getdownAltJavaMinVersion)
@@ -1536,14 +1573,14 @@ task getdownWebsite() {
     props.put("getdown_txt_ui.name", install4jApplicationName)
 
     // start with appbase
-    getdownTextString += "appbase = ${getdownAppBase}\n"
+    getdownTextLines += "appbase = ${getdownAppBase}"
     props.each{ prop, val ->
       if (prop.startsWith("getdown_txt_") && val != null) {
         if (prop.startsWith("getdown_txt_multi_")) {
           def key = prop.substring(18)
           val.split(",").each{ v ->
-            def line = "${key} = ${v}\n"
-            getdownTextString += line
+            def line = "${key} = ${v}"
+            getdownTextLines += line
           }
         } else {
           // file values rationalised
@@ -1563,15 +1600,15 @@ task getdownWebsite() {
             }
           }
           if (! prop.startsWith("getdown_txt_resource")) {
-            def line = prop.substring(12) + " = ${val}\n"
-            getdownTextString += line
+            def line = prop.substring(12) + " = ${val}"
+            getdownTextLines += line
           }
         }
       }
     }
 
     getdownWebsiteResourceFilenames.each{ filename ->
-      getdownTextString += "resource = ${filename}\n"
+      getdownTextLines += "resource = ${filename}"
     }
     getdownResourceFilenames.each{ filename ->
       copy {
@@ -1579,6 +1616,18 @@ task getdownWebsite() {
         into getdownResourceDir
       }
     }
+    
+    def getdownWrapperScripts = [ getdown_bash_wrapper_script, getdown_powershell_wrapper_script, getdown_batch_wrapper_script ]
+    getdownWrapperScripts.each{ script ->
+      def s = file( "${jalviewDir}/utils/getdown/${getdown_wrapper_script_dir}/${script}" )
+      if (s.exists()) {
+        copy {
+          from s
+          into "${getdownWebsiteDir}/${getdown_wrapper_script_dir}"
+        }
+        getdownTextLines += "resource = ${getdown_wrapper_script_dir}/${script}"
+      }
+    }
 
     def codeFiles = []
     fileTree(file(package_dir)).each{ f ->
@@ -1589,10 +1638,12 @@ task getdownWebsite() {
         codeFiles += f
       }
     }
-    codeFiles.sort().each{f ->
+    def jalviewJar = jar.archiveFileName.getOrNull()
+    // put jalview.jar first for CLASSPATH and .properties files reasons
+    codeFiles.sort{a, b -> ( a.getName() == jalviewJar ? -1 : ( b.getName() == jalviewJar ? 1 : a <=> b ) ) }.each{f ->
       def name = f.getName()
-      def line = "code = ${getdownAppDistDir}/${name}\n"
-      getdownTextString += line
+      def line = "code = ${getdownAppDistDir}/${name}"
+      getdownTextLines += line
       copy {
         from f.getPath()
         into getdownAppDir
@@ -1605,8 +1656,8 @@ task getdownWebsite() {
     def j11libFiles = fileTree(dir: "${jalviewDir}/${j11libDir}", include: ["*.jar"]).getFiles()
     j11libFiles.sort().each{f ->
     def name = f.getName()
-    def line = "code = ${getdown_j11lib_dir}/${name}\n"
-    getdownTextString += line
+    def line = "code = ${getdown_j11lib_dir}/${name}"
+    getdownTextLines += line
     copy {
     from f.getPath()
     into getdownJ11libDir
@@ -1616,19 +1667,19 @@ task getdownWebsite() {
      */
 
     // getdown-launcher.jar should not be in main application class path so the main application can move it when updated.  Listed as a resource so it gets updated.
-    //getdownTextString += "class = " + file(getdownLauncher).getName() + "\n"
-    getdownTextString += "resource = ${getdown_launcher_new}\n"
-    getdownTextString += "class = ${main_class}\n"
+    //getdownTextLines += "class = " + file(getdownLauncher).getName()
+    getdownTextLines += "resource = ${getdown_launcher_new}"
+    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) {
-      getdownTextString += "jvmarg = -Dgetdowndistdir=${getdownAppDistDir}\n"
-      getdownTextString += "jvmarg = -Dgetdownappbase=${getdownAppBase}\n"
+      getdownTextLines += "jvmarg = -Dgetdowndistdir=${getdownAppDistDir}"
+      getdownTextLines += "jvmarg = -Dgetdownappbase=${getdownAppBase}"
     }
 
-    def getdown_txt = file("${getdownWebsiteDir}/getdown.txt")
-    getdown_txt.write(getdownTextString)
+    def getdownTxt = file("${getdownWebsiteDir}/getdown.txt")
+    getdownTxt.write(getdownTextLines.join("\n"))
 
-    def getdownLaunchJvl = getdown_launch_jvl_name + ( (jvlChannelName != null && jvlChannelName.length() > 0)?"-${jvlChannelName}":"" ) + ".jvl"
+    getdownLaunchJvl = getdown_launch_jvl_name + ( (jvlChannelName != null && jvlChannelName.length() > 0)?"-${jvlChannelName}":"" ) + ".jvl"
     def launchJvl = file("${getdownWebsiteDir}/${getdownLaunchJvl}")
     launchJvl.write("appbase=${getdownAppBase}")
 
@@ -1651,7 +1702,7 @@ task getdownWebsite() {
     // files going into the getdown website dir: ./install dir and files
     if (! (CHANNEL.startsWith("ARCHIVE") || CHANNEL.startsWith("DEVELOP"))) {
       copy {
-        from getdown_txt
+        from getdownTxt
         from getdownLauncher
         from "${getdownAppDir}/${getdown_build_properties}"
         if (file(getdownLauncher).getName() != getdown_launcher) {
@@ -1669,7 +1720,7 @@ task getdownWebsite() {
 
     // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
     copy {
-      from getdown_txt
+      from getdownTxt
       from launchJvl
       from getdownLauncher
       from "${getdownWebsiteDir}/${getdown_build_properties}"
@@ -1680,7 +1731,7 @@ task getdownWebsite() {
       into getdownFilesDir
     }
 
-    // and ./resources (not all downloaded by getdown)
+    // and ./resource (not all downloaded by getdown)
     copy {
       from getdownResourceDir
       into "${getdownFilesDir}/${getdown_resource_dir}"
@@ -1750,6 +1801,110 @@ task getdown() {
 }
 
 
+task getdownArchiveBuild() {
+  group = "distribution"
+  description = "Put files in the archive dir to go on the website"
+
+  dependsOn getdownWebsite
+
+  def v = "v${JALVIEW_VERSION_UNDERSCORES}"
+  def vDir = "${getdownArchiveDir}/${v}"
+  getdownFullArchiveDir = "${vDir}/getdown"
+  getdownVersionLaunchJvl = "${vDir}/jalview-${v}.jvl"
+
+  def vAltDir = "alt_${v}"
+  def archiveImagesDir = "${jalviewDir}/${channel_properties_dir}/old/images"
+
+  doFirst {
+    if (getdownArchiveAppBase == null) {
+      throw new StopExecutionException("Cannot create getdownArchive for CHANNEL=${CHANNEL}")
+    }
+
+    // cleanup old "old" dir
+    delete getdownArchiveDir
+
+    def getdownArchiveTxt = file("${getdownFullArchiveDir}/getdown.txt")
+    getdownArchiveTxt.getParentFile().mkdirs()
+    def getdownArchiveTextLines = []
+    def getdownFullArchiveAppBase = "${getdownArchiveAppBase}${getdownArchiveAppBase.endsWith("/")?"":"/"}${v}/getdown/"
+
+    // the libdir
+    copy {
+      from "${getdownWebsiteDir}/${getdownAppDistDir}"
+      into "${getdownFullArchiveDir}/${vAltDir}"
+    }
+
+    getdownTextLines.each { line ->
+      line = line.replaceAll("^(?<s>appbase\\s*=\\s*).*", '${s}'+getdownFullArchiveAppBase)
+      line = line.replaceAll("^(?<s>(resource|code)\\s*=\\s*)${getdownAppDistDir}/", '${s}'+vAltDir+"/")
+      line = line.replaceAll("^(?<s>ui.background_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background.png")
+      line = line.replaceAll("^(?<s>ui.instant_background_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background_initialising.png")
+      line = line.replaceAll("^(?<s>ui.error_background\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background_error.png")
+      line = line.replaceAll("^(?<s>ui.progress_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_progress_bar.png")
+      // remove the existing resource = resource/ or bin/ lines
+      if (! line.matches("resource\\s*=\\s*(resource|bin)/.*")) {
+        getdownArchiveTextLines += line
+      }
+    }
+
+    // the resource dir -- add these files as resource lines in getdown.txt
+    copy {
+      from "${archiveImagesDir}"
+      into "${getdownFullArchiveDir}/${getdown_resource_dir}"
+      eachFile { file ->
+        getdownArchiveTextLines += "resource = ${getdown_resource_dir}/${file.getName()}"
+      }
+    }
+
+    getdownArchiveTxt.write(getdownArchiveTextLines.join("\n"))
+
+    def vLaunchJvl = file(getdownVersionLaunchJvl)
+    vLaunchJvl.getParentFile().mkdirs()
+    vLaunchJvl.write("appbase=${getdownFullArchiveAppBase}\n")
+    def vLaunchJvlPath = vLaunchJvl.toPath().toAbsolutePath()
+    def jvlLinkPath = file("${vDir}/jalview.jvl").toPath().toAbsolutePath()
+    // for some reason filepath.relativize(fileInSameDirPath) gives a path to "../" which is wrong
+    //java.nio.file.Files.createSymbolicLink(jvlLinkPath, jvlLinkPath.relativize(vLaunchJvlPath));
+    java.nio.file.Files.createSymbolicLink(jvlLinkPath, java.nio.file.Paths.get(".",vLaunchJvl.getName()));
+
+    // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
+    copy {
+      from getdownLauncher
+      from "${getdownWebsiteDir}/${getdownLaunchJvl}"
+      from "${getdownWebsiteDir}/${getdown_launcher_new}"
+      from "${getdownWebsiteDir}/${channel_props}"
+      if (file(getdownLauncher).getName() != getdown_launcher) {
+        rename(file(getdownLauncher).getName(), getdown_launcher)
+      }
+      into getdownFullArchiveDir
+    }
+
+  }
+}
+
+task getdownArchiveDigest(type: JavaExec) {
+  group = "distribution"
+  description = "Digest the getdown archive folder"
+
+  dependsOn getdownArchiveBuild
+
+  doFirst {
+    classpath = files(getdownLauncher)
+    args getdownFullArchiveDir
+  }
+  main = "com.threerings.getdown.tools.Digester"
+  inputs.dir(getdownFullArchiveDir)
+  outputs.file("${getdownFullArchiveDir}/digest2.txt")
+}
+
+task getdownArchive() {
+  group = "distribution"
+  description = "Build the website archive dir with getdown digest"
+
+  dependsOn getdownArchiveBuild
+  dependsOn getdownArchiveDigest
+}
+
 tasks.withType(JavaCompile) {
        options.encoding = 'UTF-8'
 }
@@ -1759,6 +1914,7 @@ clean {
   doFirst {
     delete getdownWebsiteDir
     delete getdownFilesDir
+    delete getdownArchiveDir
   }
 }
 
@@ -1798,13 +1954,15 @@ task copyInstall4jTemplate {
       }
     }
 
+    // disable install screen for OSX dmg (for 2.11.2.0)
+    install4jConfigXml.'**'.macosArchive.each { macosArchive -> 
+      macosArchive.attributes().remove('executeSetupApp')
+      macosArchive.attributes().remove('setupAppId')
+    }
+
     // turn off checksum creation for LOCAL channel
     def e = install4jConfigXml.application[0]
-    if (CHANNEL == "LOCAL") {
-      e.'@createChecksums' = "false"
-    } else {
-      e.'@createChecksums' = "true"
-    }
+    e.'@createChecksums' = string(install4jCheckSums)
 
     // put file association actions where placeholder action is
     def install4jFileAssociationsText = install4jFileAssociationsFile.text
@@ -1857,12 +2015,24 @@ clean {
   }
 }
 
+task cleanInstallersDataFiles {
+  def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
+  def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
+  def VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
+  def hugoDataJsonFile = file("${jalviewDir}/${install4jBuildDir}/installers-${VERSION_UNDERSCORES}.json")
+  doFirst {
+    delete installersOutputTxt
+    delete installersSha256
+    delete hugoDataJsonFile
+  }
+}
 
-task installers(type: com.install4j.gradle.Install4jTask) {
+task installerFiles(type: com.install4j.gradle.Install4jTask) {
   group = "distribution"
   description = "Create the install4j installers"
   dependsOn getdown
   dependsOn copyInstall4jTemplate
+  dependsOn cleanInstallersDataFiles
 
   projectFile = install4jConfFile
 
@@ -1877,8 +2047,6 @@ task installers(type: com.install4j.gradle.Install4jTask) {
     filesMd5 = filesMd5.substring(0,8)
   }
   def install4jTemplateVersion = "${JALVIEW_VERSION}_F${filesMd5}_C${gitHash}"
-  // make install4jBuildDir relative to jalviewDir
-  def install4jBuildDir = "${install4j_build_dir}/${JAVA_VERSION}"
 
   variables = [
     'JALVIEW_NAME': jalview_name,
@@ -1908,6 +2076,10 @@ task installers(type: com.install4j.gradle.Install4jTask) {
     'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
     'MACOS_DMG_DS_STORE': install4jDMGDSStore,
     'MACOS_DMG_BG_IMAGE': install4jDMGBackgroundImage,
+    'WRAPPER_LINK': getdownWrapperLink,
+    'BASH_WRAPPER_SCRIPT': getdown_bash_wrapper_script,
+    'POWERSHELL_WRAPPER_SCRIPT': getdown_powershell_wrapper_script,
+    'WRAPPER_SCRIPT_BIN_DIR': getdown_wrapper_script_dir,
     'INSTALLER_NAME': install4jInstallerName,
     'INSTALL4J_UTILS_DIR': install4j_utils_dir,
     'GETDOWN_WEBSITE_DIR': getdown_website_dir,
@@ -1957,7 +2129,7 @@ task installers(type: com.install4j.gradle.Install4jTask) {
     println("Using projectFile "+projectFile)
     if (!disableNotarization) { println("Will notarize OSX App DMG") }
   }
-  verbose=true
+  //verbose=true
 
   inputs.dir(getdownWebsiteDir)
   inputs.file(install4jConfFile)
@@ -1967,6 +2139,94 @@ task installers(type: com.install4j.gradle.Install4jTask) {
   outputs.dir("${jalviewDir}/${install4j_build_dir}/${JAVA_VERSION}")
 }
 
+def getDataHash(File myFile) {
+  HashCode hash = Files.asByteSource(myFile).hash(Hashing.sha256())
+  return myFile.exists()
+  ? [
+      "filename" : myFile.getName(),
+      "filesize" : myFile.length(),
+      "sha256" : hash.toString()
+    ]
+  : null
+}
+
+def writeDataJsonFile(File installersOutputTxt, File installersSha256, File hugoDataJsonFile) {
+  if (!installersOutputTxt.exists()) {
+    throw new GradleException("Required input file '${installersOutputTxt.getPath()}' doesn't exist.")
+    return null;
+  }
+  if (install4jCheckSums && (!installersSha256)) {
+    throw new GradleException("Required input file '${installersSha256.getPath()}' doesn't exist.")
+    return null;
+  }
+  def hash = [
+    "channel" : getdownChannelName,
+    "date" : getDate("yyyy-MM-dd HH:mm:ss"),
+    "git-commit" : "${gitHash} [${gitBranch}]",
+    "version" : JALVIEW_VERSION
+  ]
+  def idHash = [:]
+  installersOutputTxt.readLines().each { def line ->
+    if (line.startsWith("#")) {
+      return;
+    }
+    line.replaceAll("\n","")
+    def vals = line.split("\t")
+    def filename = vals[3]
+    def filesize = file(filename).length()
+    filename = filename.replaceAll(/^.*\//, "")
+    hash[vals[0]] = [ "id" : vals[0], "os" : vals[1], "name" : vals[2], "file" : filename, "filesize" : filesize ]
+    idHash."${filename}" = vals[0]
+  }
+  if (install4jCheckSums) {
+    installersSha256.readLines().each { def line ->
+      if (line.startsWith("#")) {
+        return;
+      }
+      line.replaceAll("\n","")
+      def vals = line.split(/\s+\*?/)
+      def filename = vals[1]
+      def innerHash = (hash.(idHash."${filename}"))."sha256" = vals[0]
+    }
+  }
+
+  // executable JAR
+  def jarHash = getDataHash(file(shadowJar.archiveFile))
+  if (jarHash != null) {
+    hash."JAR" = jarHash;
+  }
+
+  // version JVL
+  def jvlHash =getDataHash(file(getdownVersionLaunchJvl))
+  if (jvlHash != null) {
+    hash."JVL" = jvlHash;
+  }
+
+  // source TGZ
+  def tarHash = getDataHash(file(sourceDist.archiveFile))
+  if (tarHash != null) {
+    hash."SOURCE" = tarHash;
+  }
+
+  return hugoDataJsonFile.write(new JsonBuilder(hash).toPrettyString())
+}
+
+task staticMakeInstallersJsonFile {
+  doFirst {
+    def output = findProperty("i4j_output")
+    def sha256 = findProperty("i4j_sha256")
+    def json = findProperty("i4j_json")
+    if (output == null || sha256 == null || json == null) {
+      throw new GradleException("Must provide paths to all of output.txt, sha256sums, and output.json with '-Pi4j_output=... -Pi4j_sha256=... -Pi4j_json=...")
+    }
+    writeDataJsonFile(file(output), file(sha256), file(json))
+  }
+}
+
+task installers {
+  dependsOn installerFiles
+}
+
 
 spotless {
   java {
@@ -1974,6 +2234,22 @@ spotless {
   }
 }
 
+task createSourceReleaseProperties(type: WriteProperties) {
+  group = "distribution"
+  description = "Create the source RELEASE properties file"
+  
+  def sourceTarBuildDir = "${buildDir}/sourceTar"
+  def sourceReleasePropertiesFile = "${sourceTarBuildDir}/RELEASE"
+  outputFile (sourceReleasePropertiesFile)
+
+  doFirst {
+    releaseProps.each{ key, val -> property key, val }
+    property "git.branch", gitBranch
+    property "git.hash", gitHash
+  }
+
+  outputs.file(outputFile)
+}
 
 task sourceDist(type: Tar) {
   group "distribution"
@@ -1981,9 +2257,11 @@ task sourceDist(type: Tar) {
 
   dependsOn createBuildProperties
   dependsOn convertMdFiles
+  dependsOn eclipseAllPreferences
+  dependsOn createSourceReleaseProperties
 
-  def VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
-  def outputFileName = "${project.name}_${VERSION_UNDERSCORES}.tar.gz"
+
+  def outputFileName = "${project.name}_${JALVIEW_VERSION_UNDERSCORES}.tar.gz"
   archiveFileName = outputFileName
   
   compression Compression.GZIP
@@ -2005,6 +2283,7 @@ task sourceDist(type: Tar) {
     "*locales/**",
     "utils/InstallAnywhere",
     "**/*.log",
+    "RELEASE",
   ] 
   def PROCESS_FILES=[
     "AUTHORS",
@@ -2014,7 +2293,6 @@ task sourceDist(type: Tar) {
     "FEATURETODO",
     "LICENSE",
     "**/README",
-    "RELEASE",
     "THIRDPARTYLIBS",
     "TESTNG",
     "build.gradle",
@@ -2029,7 +2307,9 @@ task sourceDist(type: Tar) {
     "**/*.sh",
   ]
   def INCLUDE_FILES=[
-    ".settings/org.eclipse.jdt.core.jalview.prefs",
+    ".classpath",
+    ".settings/org.eclipse.buildship.core.prefs",
+    ".settings/org.eclipse.jdt.core.prefs"
   ]
 
   from(jalviewDir) {
@@ -2076,8 +2356,33 @@ task sourceDist(type: Tar) {
     })
   }
 
+  def sourceTarBuildDir = "${buildDir}/sourceTar"
+  from(sourceTarBuildDir) {
+    // this includes the appended RELEASE properties file
+  }
 }
 
+task makeDataJsonFile {
+  dependsOn installerFiles
+  dependsOn sourceDist
+  dependsOn getdownArchive
+  dependsOn shadowJar
+
+  def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
+  def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
+  def VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
+  def hugoDataJsonFile = file("${jalviewDir}/${hugo_build_dir}/data/installers-${VERSION_UNDERSCORES}.json")
+
+  inputs.file(installersOutputTxt)
+  if (install4jCheckSums) {
+    inputs.file(installersSha256)
+  }
+  outputs.file(hugoDataJsonFile)
+
+  doFirst {
+    writeDataJsonFile(installersOutputTxt, installersSha256, hugoDataJsonFile)
+  }
+}
 
 task helppages {
   dependsOn copyHelp
@@ -2385,6 +2690,10 @@ task jalviewjsSyncAllLibs (type: Sync) {
   preserve {
     include "**"
   }
+
+  // should this be exclude really ?
+  duplicatesStrategy "INCLUDE"
+
   outputs.files outputFiles
   inputs.files inputFiles
 }
@@ -2539,12 +2848,12 @@ DEBUG: ${eclipseDebug}
         new org.apache.tools.ant.util.TeeOutputStream(
           logOutFOS,
           stdout),
-        standardOutput)
+        System.out)
       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
         new org.apache.tools.ant.util.TeeOutputStream(
           logErrFOS,
           stderr),
-        errorOutput)
+        System.err)
     } else {
       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
         logOutFOS,
@@ -2632,7 +2941,7 @@ def jalviewjsCallCore(String name, FileCollection list, String prefixFile, Strin
           new org.apache.tools.ant.util.TeeOutputStream(
             logErrFOS,
             stderr),
-          errorOutput)
+          System.err)
     } else {
       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
         logOutFOS,
@@ -2939,7 +3248,13 @@ task jalviewjsServer {
   def htmlFile = "${jalviewDirAbsolutePath}/${filename}"
   doLast {
 
-    SimpleHttpFileServerFactory factory = new SimpleHttpFileServerFactory()
+    def factory
+    try {
+      def f = Class.forName("org.gradle.plugins.javascript.envjs.http.simple.SimpleHttpFileServerFactory")
+      factory = f.newInstance()
+    } catch (ClassNotFoundException e) {
+      throw new GradleException("Unable to create SimpleHttpFileServerFactory")
+    }
     def port = Integer.valueOf(jalviewjs_server_port)
     def start = port
     def running = false