JAL-3989 add in issue shortcodes to the archive version page changes list
[jalview.git] / build.gradle
index 8bf9f78..79fb2d9 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,10 @@ 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
+import org.jsoup.Jsoup
 
 buildscript {
   repositories {
@@ -30,6 +35,7 @@ buildscript {
   }
   dependencies {
     classpath "com.vladsch.flexmark:flexmark-all:0.62.0"
+    classpath "org.jsoup:jsoup:1.14.3"
   }
 }
 
@@ -42,7 +48,7 @@ plugins {
   id 'com.github.johnrengelman.shadow' version '4.0.3'
   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
 }
 
 repositories {
@@ -96,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"
@@ -115,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,18 +196,20 @@ ext {
   testSourceDir = useClover ? cloverTestInstrDir : testDir
   testClassesDir = useClover ? cloverTestClassesDir : "${jalviewDir}/${test_output_dir}"
 
-  getdownWebsiteDir = string("${jalviewDir}/${getdown_website_dir}/${JAVA_VERSION}")
+  getdownChannelDir = string("${getdown_website_dir}/${propertiesChannelName}")
+  getdownAppBaseDir = string("${jalviewDir}/${getdownChannelDir}/${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
+  getdownArchiveAppBase = getdown_archive_base
   getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher}")
   getdownAppDistDir = getdown_app_dir_alt
   getdownImagesDir = string("${jalviewDir}/${getdown_images_dir}")
@@ -217,6 +226,10 @@ ext {
   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
+
+  applicationName = "${jalview_name}"
   switch (CHANNEL) {
 
     case "BUILD":
@@ -238,7 +251,6 @@ ext {
     reportRsyncCommand = true
     install4jSuffix = ""
     install4jInstallerName = "${jalview_name} Installer"
-    getdownArchiveAppBase = getdown_archive_base
     break
 
     case "ARCHIVE":
@@ -259,7 +271,7 @@ ext {
     case "ARCHIVELOCAL":
     getdownChannelName = string("archive/${JALVIEW_VERSION}")
     getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
-    getdownAppBase = file(getdownWebsiteDir).toURI().toString()
+    getdownAppBase = file(getdownAppBaseDir).toURI().toString()
     if (!file("${ARCHIVEDIR}/${package_dir}").exists()) {
       throw new GradleException("Must provide an ARCHIVEDIR value to produce an archive distribution")
     } else {
@@ -323,10 +335,11 @@ ext {
 
     case [ "LOCAL", "JALVIEWJS" ]:
     JALVIEW_VERSION = "TEST"
-    getdownAppBase = file(getdownWebsiteDir).toURI().toString()
+    getdownAppBase = file(getdownAppBaseDir).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
@@ -335,11 +348,13 @@ ext {
 
   }
   JALVIEW_VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
+  hugoDataJsonFile = file("${jalviewDir}/${hugo_build_dir}/${hugo_data_installers_dir}/installers-${JALVIEW_VERSION_UNDERSCORES}.json")
+  hugoArchiveMdFile = file("${jalviewDir}/${hugo_build_dir}/${hugo_version_archive_dir}/Version-${JALVIEW_VERSION_UNDERSCORES}/_index.md")
   // override getdownAppBase if requested
   if (findProperty("getdown_appbase_override") != null) {
     // revert to LOCAL if empty string
     if (string(getdown_appbase_override) == "") {
-      getdownAppBase = file(getdownWebsiteDir).toURI().toString()
+      getdownAppBase = file(getdownAppBaseDir).toURI().toString()
       getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
     } else if (string(getdown_appbase_override).startsWith("file://")) {
       getdownAppBase = string(getdown_appbase_override)
@@ -353,11 +368,10 @@ ext {
   jvlChannelName = jvlChannelName.replaceAll("[^\\w\\-]+", "_")
   // install4j application and folder names
   if (install4jSuffix == "") {
-    install4jApplicationName = "${jalview_name}"
     install4jBundleId = "${install4j_bundle_id}"
     install4jWinApplicationId = install4j_release_win_application_id
   } else {
-    install4jApplicationName = "${jalview_name} ${install4jSuffix}"
+    applicationName = "${jalview_name} ${install4jSuffix}"
     install4jBundleId = "${install4j_bundle_id}-" + install4jSuffix.toLowerCase()
     // add int hash of install4jSuffix to the last part of the application_id
     def id = install4j_release_win_application_id
@@ -367,15 +381,15 @@ ext {
   }
   // sanitise folder and id names
   // install4jApplicationFolder = e.g. "Jalview Build"
-  install4jApplicationFolder = install4jApplicationName
+  install4jApplicationFolder = applicationName
                                     .replaceAll("[\"'~:/\\\\\\s]", "_") // replace all awkward filename chars " ' ~ : / \
                                     .replaceAll("_+", "_") // collapse __
-  install4jInternalId = install4jApplicationName
+  install4jInternalId = applicationName
                                     .replaceAll(" ","_")
                                     .replaceAll("[^\\w\\-\\.]", "_") // replace other non [alphanumeric,_,-,.]
                                     .replaceAll("_+", "") // collapse __
                                     //.replaceAll("_*-_*", "-") // collapse _-_
-  install4jUnixApplicationFolder = install4jApplicationName
+  install4jUnixApplicationFolder = applicationName
                                     .replaceAll(" ","_")
                                     .replaceAll("[^\\w\\-\\.]", "_") // replace other non [alphanumeric,_,-,.]
                                     .replaceAll("_+", "_") // collapse __
@@ -383,19 +397,31 @@ ext {
                                     .toLowerCase()
 
   getdownWrapperLink = install4jUnixApplicationFolder // e.g. "jalview_local"
-  getdownAppDir = string("${getdownWebsiteDir}/${getdownAppDistDir}")
-  //getdownJ11libDir = "${getdownWebsiteDir}/${getdown_j11lib_dir}"
-  getdownResourceDir = string("${getdownWebsiteDir}/${getdown_resource_dir}")
-  getdownInstallDir = string("${getdownWebsiteDir}/${getdown_install_dir}")
+  getdownAppDir = string("${getdownAppBaseDir}/${getdownAppDistDir}")
+  //getdownJ11libDir = "${getdownAppBaseDir}/${getdown_j11lib_dir}"
+  getdownResourceDir = string("${getdownAppBaseDir}/${getdown_resource_dir}")
+  getdownInstallDir = string("${getdownAppBaseDir}/${getdown_install_dir}")
   getdownFilesDir = string("${jalviewDir}/${getdown_files_dir}/${JAVA_VERSION}/")
   getdownFilesInstallDir = string("${getdownFilesDir}/${getdown_install_dir}")
   /* compile without modules -- using classpath libraries
   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.")
 
@@ -1038,7 +1064,6 @@ cleanTest {
 
 // format is a string like date.format("dd MMMM yyyy")
 def getDate(format) {
-  def date = new Date()
   return date.format(format)
 }
 
@@ -1413,7 +1438,7 @@ jar {
   manifest {
     attributes "Main-Class": main_class,
     "Permissions": "all-permissions",
-    "Application-Name": install4jApplicationName,
+    "Application-Name": applicationName,
     "Codebase": application_codebase,
     "Implementation-Version": JALVIEW_VERSION
   }
@@ -1486,7 +1511,7 @@ shadowJar {
   }
   manifest {
     attributes "Implementation-Version": JALVIEW_VERSION,
-    "Application-Name": install4jApplicationName
+    "Application-Name": applicationName
   }
 
   duplicatesStrategy "INCLUDE"
@@ -1511,7 +1536,7 @@ task getdownWebsite() {
 
   doFirst {
     // clean the getdown website and files dir before creating getdown folders
-    delete getdownWebsiteDir
+    delete getdownAppBaseDir
     delete getdownFilesDir
 
     copy {
@@ -1523,7 +1548,7 @@ task getdownWebsite() {
 
     copy {
       from channelPropsFile
-      into getdownWebsiteDir
+      into getdownAppBaseDir
     }
     getdownWebsiteResourceFilenames += file(channelPropsFile).getName()
 
@@ -1548,7 +1573,7 @@ task getdownWebsite() {
     }
 
     props.put("getdown_txt_title", jalview_name)
-    props.put("getdown_txt_ui.name", install4jApplicationName)
+    props.put("getdown_txt_ui.name", applicationName)
 
     // start with appbase
     getdownTextLines += "appbase = ${getdownAppBase}"
@@ -1601,7 +1626,7 @@ task getdownWebsite() {
       if (s.exists()) {
         copy {
           from s
-          into "${getdownWebsiteDir}/${getdown_wrapper_script_dir}"
+          into "${getdownAppBaseDir}/${getdown_wrapper_script_dir}"
         }
         getdownTextLines += "resource = ${getdown_wrapper_script_dir}/${script}"
       }
@@ -1654,18 +1679,18 @@ task getdownWebsite() {
       getdownTextLines += "jvmarg = -Dgetdownappbase=${getdownAppBase}"
     }
 
-    def getdownTxt = file("${getdownWebsiteDir}/getdown.txt")
+    def getdownTxt = file("${getdownAppBaseDir}/getdown.txt")
     getdownTxt.write(getdownTextLines.join("\n"))
 
     getdownLaunchJvl = getdown_launch_jvl_name + ( (jvlChannelName != null && jvlChannelName.length() > 0)?"-${jvlChannelName}":"" ) + ".jvl"
-    def launchJvl = file("${getdownWebsiteDir}/${getdownLaunchJvl}")
+    def launchJvl = file("${getdownAppBaseDir}/${getdownLaunchJvl}")
     launchJvl.write("appbase=${getdownAppBase}")
 
     // files going into the getdown website dir: getdown-launcher.jar
     copy {
       from getdownLauncher
       rename(file(getdownLauncher).getName(), getdown_launcher_new)
-      into getdownWebsiteDir
+      into getdownAppBaseDir
     }
 
     // files going into the getdown website dir: getdown-launcher(-local).jar
@@ -1674,7 +1699,7 @@ task getdownWebsite() {
       if (file(getdownLauncher).getName() != getdown_launcher) {
         rename(file(getdownLauncher).getName(), getdown_launcher)
       }
-      into getdownWebsiteDir
+      into getdownAppBaseDir
     }
 
     // files going into the getdown website dir: ./install dir and files
@@ -1701,8 +1726,8 @@ task getdownWebsite() {
       from getdownTxt
       from launchJvl
       from getdownLauncher
-      from "${getdownWebsiteDir}/${getdown_build_properties}"
-      from "${getdownWebsiteDir}/${channel_props}"
+      from "${getdownAppBaseDir}/${getdown_build_properties}"
+      from "${getdownAppBaseDir}/${channel_props}"
       if (file(getdownLauncher).getName() != getdown_launcher) {
         rename(file(getdownLauncher).getName(), getdown_launcher)
       }
@@ -1719,7 +1744,7 @@ task getdownWebsite() {
   if (buildDist) {
     inputs.dir("${jalviewDir}/${package_dir}")
   }
-  outputs.dir(getdownWebsiteDir)
+  outputs.dir(getdownAppBaseDir)
   outputs.dir(getdownFilesDir)
 }
 
@@ -1750,9 +1775,9 @@ task getdownDigest(type: JavaExec) {
     classpath = files(getdownLauncher)
   }
   main = "com.threerings.getdown.tools.Digester"
-  args getdownWebsiteDir
-  inputs.dir(getdownWebsiteDir)
-  outputs.file("${getdownWebsiteDir}/digest2.txt")
+  args getdownAppBaseDir
+  inputs.dir(getdownAppBaseDir)
+  outputs.file("${getdownAppBaseDir}/digest2.txt")
 }
 
 
@@ -1762,7 +1787,7 @@ task getdown() {
   dependsOn getdownDigest
   doLast {
     if (reportRsyncCommand) {
-      def fromDir = getdownWebsiteDir + (getdownWebsiteDir.endsWith('/')?'':'/')
+      def fromDir = getdownAppBaseDir + (getdownAppBaseDir.endsWith('/')?'':'/')
       def toDir = "${getdown_rsync_dest}/${getdownDir}" + (getdownDir.endsWith('/')?'':'/')
       println "LIKELY RSYNC COMMAND:"
       println "mkdir -p '$toDir'\nrsync -avh --delete '$fromDir' '$toDir'"
@@ -1788,15 +1813,12 @@ task getdownArchiveBuild() {
   def v = "v${JALVIEW_VERSION_UNDERSCORES}"
   def vDir = "${getdownArchiveDir}/${v}"
   getdownFullArchiveDir = "${vDir}/getdown"
-  def vLaunchVersionJvl = "${vDir}/jalview-${v}.jvl"
+  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
 
@@ -1807,7 +1829,7 @@ task getdownArchiveBuild() {
 
     // the libdir
     copy {
-      from "${getdownWebsiteDir}/${getdownAppDistDir}"
+      from "${getdownAppBaseDir}/${getdownAppDistDir}"
       into "${getdownFullArchiveDir}/${vAltDir}"
     }
 
@@ -1835,7 +1857,7 @@ task getdownArchiveBuild() {
 
     getdownArchiveTxt.write(getdownArchiveTextLines.join("\n"))
 
-    def vLaunchJvl = file(vLaunchVersionJvl)
+    def vLaunchJvl = file(getdownVersionLaunchJvl)
     vLaunchJvl.getParentFile().mkdirs()
     vLaunchJvl.write("appbase=${getdownFullArchiveAppBase}\n")
     def vLaunchJvlPath = vLaunchJvl.toPath().toAbsolutePath()
@@ -1847,9 +1869,9 @@ task getdownArchiveBuild() {
     // 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}"
+      from "${getdownAppBaseDir}/${getdownLaunchJvl}"
+      from "${getdownAppBaseDir}/${getdown_launcher_new}"
+      from "${getdownAppBaseDir}/${channel_props}"
       if (file(getdownLauncher).getName() != getdown_launcher) {
         rename(file(getdownLauncher).getName(), getdown_launcher)
       }
@@ -1889,7 +1911,7 @@ tasks.withType(JavaCompile) {
 
 clean {
   doFirst {
-    delete getdownWebsiteDir
+    delete getdownAppBaseDir
     delete getdownFilesDir
     delete getdownArchiveDir
   }
@@ -1939,11 +1961,7 @@ task copyInstall4jTemplate {
 
     // 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
@@ -1996,12 +2014,23 @@ clean {
   }
 }
 
+task cleanInstallersDataFiles {
+  def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
+  def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
+  def hugoDataJsonFile = file("${jalviewDir}/${install4jBuildDir}/installers-${JALVIEW_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
 
@@ -2016,12 +2045,10 @@ 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,
-    'JALVIEW_APPLICATION_NAME': install4jApplicationName,
+    'JALVIEW_APPLICATION_NAME': applicationName,
     'JALVIEW_DIR': "../..",
     'OSX_KEYSTORE': OSX_KEYSTORE,
     'OSX_APPLEID': OSX_APPLEID,
@@ -2053,7 +2080,7 @@ task installers(type: com.install4j.gradle.Install4jTask) {
     'WRAPPER_SCRIPT_BIN_DIR': getdown_wrapper_script_dir,
     'INSTALLER_NAME': install4jInstallerName,
     'INSTALL4J_UTILS_DIR': install4j_utils_dir,
-    'GETDOWN_WEBSITE_DIR': getdown_website_dir,
+    'GETDOWN_CHANNEL_DIR': getdownChannelDir,
     'GETDOWN_FILES_DIR': getdown_files_dir,
     'GETDOWN_RESOURCE_DIR': getdown_resource_dir,
     'GETDOWN_DIST_DIR': getdownAppDistDir,
@@ -2102,7 +2129,7 @@ task installers(type: com.install4j.gradle.Install4jTask) {
   }
   //verbose=true
 
-  inputs.dir(getdownWebsiteDir)
+  inputs.dir(getdownAppBaseDir)
   inputs.file(install4jConfFile)
   inputs.file("${install4jDir}/${install4j_info_plist_file_associations}")
   inputs.dir(macosJavaVMDir)
@@ -2110,6 +2137,84 @@ 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()
+  ? [
+      "file" : myFile.getName(),
+      "filesize" : myFile.length(),
+      "sha256" : hash.toString()
+    ]
+  : null
+}
+
+def writeDataJsonFile(File installersOutputTxt, File installersSha256, File dataJsonFile) {
+  def hash = [
+    "channel" : getdownChannelName,
+    "date" : getDate("yyyy-MM-dd HH:mm:ss"),
+    "git-commit" : "${gitHash} [${gitBranch}]",
+    "version" : JALVIEW_VERSION
+  ]
+  // install4j installer files
+  if (installersOutputTxt.exists()) {
+    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.exists()) {
+      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]
+      }
+    }
+  }
+
+  [
+    "JAR": shadowJar.archiveFile, // executable JAR
+    "JVL": getdownVersionLaunchJvl, // version JVL
+    "SOURCE": sourceDist.archiveFile // source TGZ
+  ].each { key, value ->
+    def file = file(value)
+    if (file.exists()) {
+      def fileHash = getDataHash(file)
+      if (fileHash != null) {
+        hash."${key}" = fileHash;
+      }
+    }
+  }
+  return dataJsonFile.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 {
@@ -2117,6 +2222,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"
@@ -2124,6 +2245,9 @@ task sourceDist(type: Tar) {
 
   dependsOn createBuildProperties
   dependsOn convertMdFiles
+  dependsOn eclipseAllPreferences
+  dependsOn createSourceReleaseProperties
+
 
   def outputFileName = "${project.name}_${JALVIEW_VERSION_UNDERSCORES}.tar.gz"
   archiveFileName = outputFileName
@@ -2147,6 +2271,7 @@ task sourceDist(type: Tar) {
     "*locales/**",
     "utils/InstallAnywhere",
     "**/*.log",
+    "RELEASE",
   ] 
   def PROCESS_FILES=[
     "AUTHORS",
@@ -2156,7 +2281,6 @@ task sourceDist(type: Tar) {
     "FEATURETODO",
     "LICENSE",
     "**/README",
-    "RELEASE",
     "THIRDPARTYLIBS",
     "TESTNG",
     "build.gradle",
@@ -2171,7 +2295,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) {
@@ -2195,7 +2321,9 @@ task sourceDist(type: Tar) {
     exclude ("utils/InstallAnywhere")
 
     exclude (getdown_files_dir)
-    exclude (getdown_website_dir)
+    // getdown_website_dir and getdown_archive_dir moved to build/website/docroot/getdown
+    //exclude (getdown_website_dir)
+    //exclude (getdown_archive_dir)
 
     // exluding these as not using jars as modules yet
     exclude ("${j11modDir}/**/*.jar")
@@ -2218,6 +2346,200 @@ task sourceDist(type: Tar) {
     })
   }
 
+  def sourceTarBuildDir = "${buildDir}/sourceTar"
+  from(sourceTarBuildDir) {
+    // this includes the appended RELEASE properties file
+  }
+}
+
+task dataInstallersJson {
+  group "website"
+  description "Create the installers-VERSION.json data file for installer files created"
+
+  mustRunAfter installers
+  mustRunAfter shadowJar
+  mustRunAfter sourceDist
+  mustRunAfter getdownArchive
+
+  def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
+  def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
+
+  if (installersOutputTxt.exists()) {
+    inputs.file(installersOutputTxt)
+  }
+  if (install4jCheckSums && installersSha256.exists()) {
+    inputs.file(installersSha256)
+  }
+  [
+    shadowJar.archiveFile, // executable JAR
+    getdownVersionLaunchJvl, // version JVL
+    sourceDist.archiveFile // source TGZ
+  ].each { fileName ->
+    if (file(fileName).exists()) {
+      inputs.file(fileName)
+    }
+  }
+
+  outputs.file(hugoDataJsonFile)
+
+  doFirst {
+    writeDataJsonFile(installersOutputTxt, installersSha256, hugoDataJsonFile)
+  }
+}
+
+def hugoTemplateSubstitutions(String input) {
+  def output = input
+  output = output.replaceAll("__DATE__", getDate("yyyy-MM-dd"))
+  output = output.replaceAll("__CHANNEL__", propertiesChannelName)
+  output = output.replaceAll("__APPLICATION_NAME__", applicationName)
+  output = output.replaceAll("__GIT_HASH__", gitHash)
+  output = output.replaceAll("__GIT_BRANCH__", gitBranch)
+  output = output.replaceAll("__VERSION__", JALVIEW_VERSION)
+  output = output.replaceAll("__JAVA_VERSION__", JAVA_VERSION)
+  output = output.replaceAll("__VERSION_UNDERSCORES__", JALVIEW_VERSION_UNDERSCORES)
+  return output
+}
+
+task hugoTemplates {
+  group "website"
+  description "Create partially populated md pages for hugo website build"
+
+  def hugoTemplatesDir = file("${jalviewDir}/${hugo_templates_dir}")
+  def hugoBuildDir = "${jalviewDir}/${hugo_build_dir}"
+  def templateFiles = fileTree(dir: hugoTemplatesDir)
+
+  doFirst {
+    // specific release template for version archive
+    def summary = "${applicationName} ${JALVIEW_VERSION}"
+    def changes = ""
+    def oldDate = null
+    if (CHANNEL == "RELEASE") {
+      def releasesHtmlFile = file("${helpSourceDir}/${releases_html}")
+      //HTMLPARSE
+      def html = releasesHtmlFile.text
+      def doc = Jsoup.parse(html)
+      def table = doc.select("table").first()
+      def headings = table.select("tr").first().select("th").collect { it.text() }
+      def releaseRow = null
+      def trs = table.select("tr")
+      trs.any { tr ->
+        def tds = tr.select("td")
+        if (tds.size() == 0)
+          return false
+        def releaseTd = tds.first()
+        if (releaseTd.text().startsWith("${JALVIEW_VERSION} ")) {
+          releaseRow = tr
+          return true
+        }
+      }
+
+      if (releaseRow != null && headings != null && headings.size() == 3) {
+        def releaseTd = releaseRow.select("td").first()
+        def spaceIndex = releaseTd.text().indexOf(" ")
+        if (spaceIndex >= 0) {
+          oldDate = new Date().parse("dd/MM/yyyy", releaseTd.text().substring(spaceIndex+1))
+        }
+        def releaseCells = releaseRow.select("td")
+        if (releaseCells.size() == 3) {
+          def title1 = headings[1]
+          def title2 = headings[2]
+
+          def lastDotIndex = JALVIEW_VERSION.lastIndexOf(".")
+          if (lastDotIndex > 0) {
+            def patchRelease = JALVIEW_VERSION.substring(lastDotIndex+1) as Integer
+            def patchReleaseString = null
+            if (patchRelease == 0) {
+                patchReleaseString = "first minor"
+            } else if (patchRelease == 1) {
+                patchReleaseString = "first patch"
+            } else if (patchRelease == 2) {
+                patchReleaseString = "second patch"
+            } else if (patchRelease == 3) {
+                patchReleaseString = "third patch"
+            } else if (patchRelease == 4) {
+                patchReleaseString = "fourth patch"
+            } else if (patchRelease == 5) {
+                patchReleaseString = "fifth patch"
+            } else if (patchRelease == 6) {
+                patchReleaseString = "sixth patch"
+            } else if (patchRelease == 7) {
+                patchReleaseString = "seventh patch"
+            } else if (patchRelease > 13 && (patchRelease % 10 == 1)) {
+                patchReleaseString += "st"
+            } else if (patchRelease > 13 && (patchRelease % 10 == 2)) {
+                patchReleaseString += "nd"
+            } else if (patchRelease > 13 && (patchRelease % 10 == 3)) {
+                patchReleaseString += "rd"
+            } else if (patchRelease != null) {
+                patchReleaseString += "th"
+            }
+            summary += (patchReleaseString != null) ? " is the ${patchReleaseString} release in the ${JALVIEW_VERSION.substring(0,lastDotIndex)} series." : ""
+          }
+
+          [1,2].each { col ->
+            if (headings[col] != null && headings[col].size() > 0) {
+              def noheadings = true
+              releaseCells[col].children().each { e ->
+                if (e.tagName().toLowerCase() == "ul") {
+                  e.select("li").each { li ->
+                    def issues = []
+                    def mdItem = "- "
+                    li.childNodes().any {n ->
+                      if (n.nodeName().equals("#comment")) {
+                        mdItem += "${n} "
+                        issues = n.getData().trim().split(/[,\s]+/)
+                        return true
+                      }
+                    }
+                    mdItem += li.text()
+                    issues.each { jal ->
+                      mdItem += " {{< jal issue=\"${jal}\" >}}"
+                    }
+                    if (noheadings) {
+                      changes += "\n### ${headings[1]}\n\n"
+                      noheadings = false
+                    }
+                    changes += "${mdItem}\n"
+                  }
+                } else if (e.tag() == "em") {
+                  changes += "\n#### ${e.text()}\n\n"
+                  noheadings = false
+                }
+              }
+            }
+          }
+
+        }
+      }
+    }
+
+    templateFiles.each{ templateFile ->
+      def newFileName = string(hugoTemplateSubstitutions(templateFile.getName()))
+      def relPath = hugoTemplatesDir.toPath().relativize(templateFile.toPath()).getParent()
+      def newRelPathName = hugoTemplateSubstitutions( relPath.toString() )
+
+      def outPathName = string("${hugoBuildDir}/$newRelPathName")
+      
+      copy {
+        from templateFile
+        rename(templateFile.getName(), newFileName)
+        into outPathName
+      }
+
+      def newFile = file("${outPathName}/${newFileName}".toString())
+      def content = newFile.text
+      content = content.replaceAll("__SUMMARY__", summary)
+      content = content.replaceAll("__CHANGES__", changes)
+      if (oldDate != null) {
+        content = content.replaceAll("__DATE__", oldDate.format("yyyy-MM-dd"))
+      }
+      newFile.text = hugoTemplateSubstitutions(content)
+    }
+  }
+
+  inputs.dir(hugoTemplatesDir)
+  inputs.property("JALVIEW_VERSION", { JALVIEW_VERSION })
+  inputs.property("CHANNEL", { CHANNEL })
 }