JAL-4023 store distances as doubles
[jalview.git] / build.gradle
index a3ca4b8..b4636bc 100644 (file)
@@ -12,13 +12,25 @@ import java.security.MessageDigest
 import groovy.transform.ExternalizeMethods
 import groovy.util.XmlParser
 import groovy.xml.XmlUtil
-
+import com.vladsch.flexmark.util.ast.Node
+import com.vladsch.flexmark.html.HtmlRenderer
+import com.vladsch.flexmark.parser.Parser
+import com.vladsch.flexmark.util.data.MutableDataSet
+import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension
+import com.vladsch.flexmark.ext.tables.TablesExtension
+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
 
 buildscript {
   repositories {
     mavenCentral()
     mavenLocal()
   }
+  dependencies {
+    classpath "com.vladsch.flexmark:flexmark-all:0.62.0"
+  }
 }
 
 
@@ -28,9 +40,9 @@ 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.4'
+  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 {
@@ -40,29 +52,19 @@ 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()
 }
 
-
-ext {
-  jalviewDirAbsolutePath = file(jalviewDir).getAbsolutePath()
-  jalviewDirRelativePath = jalviewDir
-
-  // local build environment properties
-  // can be "projectDir/local.properties"
-  def localProps = "${projectDir}/local.properties"
-  def propsFile = null;
-  if (file(localProps).exists()) {
-    propsFile = localProps
-  }
-  // or "../projectDir_local.properties"
-  def dirLocalProps = projectDir.getParent() + "/" + projectDir.getName() + "_local.properties"
-  if (file(dirLocalProps).exists()) {
-    propsFile = dirLocalProps
+def overrideProperties(String propsFileName, boolean output = false) {
+  if (propsFileName == null) {
+    return
   }
-  if (propsFile != null) {
+  def propsFile = file(propsFileName)
+  if (propsFile != null && propsFile.exists()) {
+    println("Using properties from file '${propsFileName}'")
     try {
       def p = new Properties()
       def localPropsFIS = new FileInputStream(propsFile)
@@ -70,24 +72,50 @@ ext {
       localPropsFIS.close()
       p.each {
         key, val -> 
-          def oldval = findProperty(key)
-          setProperty(key, val)
-          if (oldval != null) {
-            println("Overriding property '${key}' ('${oldval}') with ${file(propsFile).getName()} value '${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 {
-            println("Setting unknown property '${key}' with ${file(propsFile).getName()}s value '${val}'")
+            ext.setProperty(key, val)
+            if (output) {
+              println("Setting ext property '${key}' with ${file(propsFile).getName()}s value '${val}'")
+            }
           }
       }
     } catch (Exception e) {
-      System.out.println("Exception reading local.properties")
+      println("Exception reading local.properties")
+      e.printStackTrace()
     }
   }
+}
+
+ext {
+  jalviewDirAbsolutePath = file(jalviewDir).getAbsolutePath()
+  jalviewDirRelativePath = jalviewDir
+
+  getdownChannelName = CHANNEL.toLowerCase()
+  // default to "default". Currently only has different cosmetics for "develop", "release", "default"
+  propertiesChannelName = ["develop", "release", "test-release", "jalviewjs", "jalviewjs-release" ].contains(getdownChannelName) ? getdownChannelName : "default"
+  // Import channel_properties
+  channelDir = string("${jalviewDir}/${channel_properties_dir}/${propertiesChannelName}")
+  channelGradleProperties = string("${channelDir}/channel_gradle.properties")
+  channelPropsFile = string("${channelDir}/${resource_dir}/${channel_props}")
+  overrideProperties(channelGradleProperties, false)
+  // local build environment properties
+  // can be "projectDir/local.properties"
+  overrideProperties("${projectDir}/local.properties", true)
+  // or "../projectDir_local.properties"
+  overrideProperties(projectDir.getParent() + "/" + projectDir.getName() + "_local.properties", true)
 
   ////  
   // 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 {
@@ -99,7 +127,7 @@ ext {
   }
   ////
   // Set JALVIEW_VERSION if it is not already set
-  if (findProperty(JALVIEW_VERSION)==null || "".equals(JALVIEW_VERSION)) {
+  if (findProperty("JALVIEW_VERSION")==null || "".equals(JALVIEW_VERSION)) {
     JALVIEW_VERSION = releaseProps.get("jalview.version")
   }
   
@@ -135,6 +163,9 @@ ext {
   }
   */
 
+  // datestamp
+  buildDate = new Date().format("yyyyMMdd")
+
   // essentials
   bareSourceDir = string(source_dir)
   sourceDir = string("${jalviewDir}/${bareSourceDir}")
@@ -155,29 +186,32 @@ ext {
   //cloverTestClassesDir = cloverClassesDir
   cloverDb = string("${cloverBuildDir}/clover.db")
 
-  resourceClassesDir = useClover ? cloverClassesDir : classesDir
-
   testSourceDir = useClover ? cloverTestInstrDir : testDir
   testClassesDir = useClover ? cloverTestClassesDir : "${jalviewDir}/${test_output_dir}"
 
   getdownWebsiteDir = string("${jalviewDir}/${getdown_website_dir}/${JAVA_VERSION}")
   buildDist = true
+  buildProperties = null
 
   // the following values might be overridden by the CHANNEL switch
-  getdownChannelName = CHANNEL.toLowerCase()
   getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
   getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
   getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher}")
   getdownAppDistDir = getdown_app_dir_alt
-  buildProperties = string("${resourceDir}/${build_properties_file}")
+  getdownImagesDir = string("${jalviewDir}/${getdown_images_dir}")
+  getdownSetAppBaseProperty = false // whether to pass the appbase and appdistdir to the application
   reportRsyncCommand = false
   jvlChannelName = CHANNEL.toLowerCase()
   install4jSuffix = CHANNEL.substring(0, 1).toUpperCase() + CHANNEL.substring(1).toLowerCase(); // BUILD -> Build
-  install4jDSStore = "DS_Store-NON-RELEASE"
-  install4jDMGBackgroundImage = "jalview_dmg_background-NON-RELEASE.png"
+  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}")
   switch (CHANNEL) {
 
     case "BUILD":
@@ -193,12 +227,10 @@ ext {
     install4jExtraScheme = "jalviewb"
     break
 
-    case "RELEASE":
+    case [ "RELEASE", "JALVIEWJS-RELEASE" ]:
     getdownAppDistDir = getdown_app_dir_release
     reportRsyncCommand = true
     install4jSuffix = ""
-    install4jDSStore = "DS_Store"
-    install4jDMGBackgroundImage = "jalview_dmg_background.png"
     install4jInstallerName = "${jalview_name} Installer"
     break
 
@@ -236,13 +268,11 @@ ext {
 
     case "DEVELOP":
     reportRsyncCommand = true
-    
+    getdownSetAppBaseProperty = true
     // DEVELOP-RELEASE is usually associated with a Jalview release series so set the version
-    JALVIEW_VERSION=JALVIEW_VERSION+"-develop"
+    JALVIEW_VERSION=JALVIEW_VERSION+"-d${buildDate}"
     
     install4jSuffix = "Develop"
-    install4jDSStore = "DS_Store-DEVELOP"
-    install4jDMGBackgroundImage = "jalview_dmg_background-DEVELOP.png"
     install4jExtraScheme = "jalviewd"
     install4jInstallerName = "${jalview_name} Develop Installer"
     break
@@ -256,8 +286,6 @@ ext {
     }
     JALVIEW_VERSION = JALVIEW_VERSION+"-test"
     install4jSuffix = "Test"
-    install4jDSStore = "DS_Store-TEST-RELEASE"
-    install4jDMGBackgroundImage = "jalview_dmg_background-TEST.png"
     install4jExtraScheme = "jalviewt"
     install4jInstallerName = "${jalview_name} Test Installer"
     break
@@ -281,13 +309,11 @@ ext {
     }
     JALVIEW_VERSION = "TEST"
     install4jSuffix = "Test-Local"
-    install4jDSStore = "DS_Store-TEST-RELEASE"
-    install4jDMGBackgroundImage = "jalview_dmg_background-TEST.png"
     install4jExtraScheme = "jalviewt"
     install4jInstallerName = "${jalview_name} Test Installer"
     break
 
-    case "LOCAL":
+    case [ "LOCAL", "JALVIEWJS" ]:
     JALVIEW_VERSION = "TEST"
     getdownAppBase = file(getdownWebsiteDir).toURI().toString()
     getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
@@ -301,7 +327,16 @@ ext {
   }
   // override getdownAppBase if requested
   if (findProperty("getdown_appbase_override") != null) {
-    getdownAppBase = string(getProperty("getdown_appbase_override"))
+    // revert to LOCAL if empty string
+    if (string(getdown_appbase_override) == "") {
+      getdownAppBase = file(getdownWebsiteDir).toURI().toString()
+      getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
+    } else if (string(getdown_appbase_override).startsWith("file://")) {
+      getdownAppBase = string(getdown_appbase_override)
+      getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
+    } else {
+      getdownAppBase = string(getdown_appbase_override)
+    }
     println("Overriding getdown appbase with '${getdownAppBase}'")
   }
   // sanitise file name for jalview launcher file for this channel
@@ -337,6 +372,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}")
@@ -347,9 +383,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.")
 
@@ -385,16 +433,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,
@@ -414,11 +462,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}")
@@ -427,12 +475,18 @@ ext {
     install4jHomeDir = System.getProperty("user.home") + install4jHomeDir.substring(1)
   }
 
+  resourceBuildDir = string("${buildDir}/resources")
+  resourcesBuildDir = string("${resourceBuildDir}/resources_build")
+  helpBuildDir = string("${resourceBuildDir}/help_build")
+  docBuildDir = string("${resourceBuildDir}/doc_build")
 
-
+  if (buildProperties == null) {
+    buildProperties = string("${resourcesBuildDir}/${build_properties_file}")
+  }
   buildingHTML = string("${jalviewDir}/${doc_dir}/building.html")
-  helpFile = string("${resourceClassesDir}/${help_dir}/help.jhm")
   helpParentDir = string("${jalviewDir}/${help_parent_dir}")
   helpSourceDir = string("${helpParentDir}/${help_dir}")
+  helpFile = string("${helpBuildDir}/${help_dir}/help.jhm")
 
 
   relativeBuildDir = file(jalviewDirAbsolutePath).toPath().relativize(buildDir.toPath())
@@ -451,6 +505,7 @@ ext {
   jalviewjsCoreClasslists = []
   jalviewjsJalviewTemplateName = string(jalviewjs_name)
   jalviewjsJ2sSettingsFileName = string("${jalviewDir}/${jalviewjs_j2s_settings}")
+  jalviewjsJ2sAltSettingsFileName = string("${jalviewDir}/${jalviewjs_j2s_alt_settings}")
   jalviewjsJ2sProps = null
   jalviewjsJ2sPlugin = jalviewjs_j2s_plugin
 
@@ -470,16 +525,14 @@ sourceSets {
     }
 
     resources {
-      srcDirs resourceDir
-      srcDirs += helpParentDir
+      srcDirs = [ resourcesBuildDir, docBuildDir, helpBuildDir ]
     }
 
-    jar.destinationDir = file("${jalviewDir}/${package_dir}")
-
     compileClasspath = files(sourceSets.main.java.outputDir)
     compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
 
     runtimeClasspath = compileClasspath
+    runtimeClasspath += files(sourceSets.main.resources.srcDirs)
   }
 
   clover {
@@ -516,6 +569,7 @@ sourceSets {
     compileClasspath += fileTree(dir: "${jalviewDir}/${utils_dir}/testnglibs", include: ["**/*.jar"])
 
     runtimeClasspath = compileClasspath
+    runtimeClasspath += files(sourceSets.test.resources.srcDirs)
   }
 
 }
@@ -536,14 +590,12 @@ eclipse {
 
   classpath {
     //defaultOutputDir = sourceSets.main.java.outputDir
-    def removeThese = []
-    configurations.each{
-      if (it.isCanBeResolved()) {
-        removeThese += it
+    configurations.each{ c->
+      if (c.isCanBeResolved()) {
+        minusConfigurations += [c]
       }
     }
 
-    minusConfigurations += removeThese
     plusConfigurations = [ ]
     file {
 
@@ -993,89 +1045,146 @@ def getDate(format) {
 }
 
 
-task createBuildProperties(type: WriteProperties) {
-  group = "build"
-  description = "Create the ${buildProperties} file"
-  
-  inputs.dir(sourceDir)
-  inputs.dir(resourceDir)
-  file(buildProperties).getParentFile().mkdirs()
-  outputFile (buildProperties)
-  // taking time specific comment out to allow better incremental builds
-  comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd HH:mm:ss")
-  //comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd")
-  property "BUILD_DATE", getDate("HH:mm:ss dd MMMM yyyy")
-  property "VERSION", JALVIEW_VERSION
-  property "INSTALLATION", INSTALLATION+" git-commit:"+gitHash+" ["+gitBranch+"]"
-  outputs.file(outputFile)
-}
-
+def convertMdToHtml (FileTree mdFiles, File cssFile) {
+  MutableDataSet options = new MutableDataSet()
 
-clean {
-  doFirst {
-    delete buildProperties
+  def extensions = new ArrayList<>()
+  extensions.add(AnchorLinkExtension.create()) 
+  extensions.add(AutolinkExtension.create())
+  extensions.add(StrikethroughExtension.create())
+  extensions.add(TaskListExtension.create())
+  extensions.add(TablesExtension.create())
+  extensions.add(TocExtension.create())
+  
+  options.set(Parser.EXTENSIONS, extensions)
+
+  // set GFM table parsing options
+  options.set(TablesExtension.WITH_CAPTION, false)
+  options.set(TablesExtension.COLUMN_SPANS, false)
+  options.set(TablesExtension.MIN_HEADER_ROWS, 1)
+  options.set(TablesExtension.MAX_HEADER_ROWS, 1)
+  options.set(TablesExtension.APPEND_MISSING_COLUMNS, true)
+  options.set(TablesExtension.DISCARD_EXTRA_COLUMNS, true)
+  options.set(TablesExtension.HEADER_SEPARATOR_COLUMN_MATCH, true)
+  // GFM anchor links
+  options.set(AnchorLinkExtension.ANCHORLINKS_SET_ID, false)
+  options.set(AnchorLinkExtension.ANCHORLINKS_ANCHOR_CLASS, "anchor")
+  options.set(AnchorLinkExtension.ANCHORLINKS_SET_NAME, true)
+  options.set(AnchorLinkExtension.ANCHORLINKS_TEXT_PREFIX, "<span class=\"octicon octicon-link\"></span>")
+
+  Parser parser = Parser.builder(options).build()
+  HtmlRenderer renderer = HtmlRenderer.builder(options).build()
+
+  mdFiles.each { mdFile ->
+    // add table of contents
+    def mdText = "[TOC]\n"+mdFile.text
+
+    // grab the first top-level title
+    def title = null
+    def titleRegex = /(?m)^#(\s+|([^#]))(.*)/
+    def matcher = mdText =~ titleRegex
+    if (matcher.size() > 0) {
+      // matcher[0][2] is the first character of the title if there wasn't any whitespace after the #
+      title = (matcher[0][2] != null ? matcher[0][2] : "")+matcher[0][3]
+    }
+    // or use the filename if none found
+    if (title == null) {
+      title = mdFile.getName()
+    }
+
+    Node document = parser.parse(mdText)
+    String htmlBody = renderer.render(document)
+    def htmlText = '''<html>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <meta http-equiv="Content-Style-Type" content="text/css" />
+    <meta name="generator" content="flexmark" />
+'''
+    htmlText += ((title != null) ? "  <title>${title}</title>" : '' )
+    htmlText += '''
+    <style type="text/css">code{white-space: pre;}</style>
+'''
+    htmlText += ((cssFile != null) ? cssFile.text : '')
+    htmlText += '''</head>
+  <body>
+'''
+    htmlText += htmlBody
+    htmlText += '''
+  </body>
+</html>
+'''
+
+    def htmlFilePath = mdFile.getPath().replaceAll(/\..*?$/, ".html")
+    def htmlFile = file(htmlFilePath)
+    println("Creating ${htmlFilePath}")
+    htmlFile.text = htmlText
+  }
+}
+
+
+task copyDocs(type: Copy) {
+  def inputDir = "${jalviewDir}/${doc_dir}"
+  def outputDir = "${docBuildDir}/${doc_dir}"
+  from(inputDir) {
+    include('**/*.txt')
+    include('**/*.md')
+    include('**/*.html')
+    include('**/*.xml')
+    filter(ReplaceTokens,
+      beginToken: '$$',
+      endToken: '$$',
+      tokens: [
+        'Version-Rel': JALVIEW_VERSION,
+        'Year-Rel': getDate("yyyy")
+      ]
+    )
   }
-}
-
-
-task cleanBuildingHTML(type: Delete) {
-  doFirst {
-    delete buildingHTML
+  from(inputDir) {
+    exclude('**/*.txt')
+    exclude('**/*.md')
+    exclude('**/*.html')
+    exclude('**/*.xml')
   }
+  into outputDir
+
+  inputs.dir(inputDir)
+  outputs.dir(outputDir)
 }
 
 
-task convertBuildingMD(type: Exec) {
-  dependsOn cleanBuildingHTML
-  def buildingMD = "${jalviewDir}/${doc_dir}/building.md"
-  def css = "${jalviewDir}/${doc_dir}/github.css"
+task convertMdFiles {
+  dependsOn copyDocs
+  def mdFiles = fileTree(dir: docBuildDir, include: "**/*.md")
+  def cssFile = file("${jalviewDir}/${flexmark_css}")
 
-  def pandoc = null
-  pandoc_exec.split(",").each {
-    if (file(it.trim()).exists()) {
-      pandoc = it.trim()
-      return true
-    }
+  doLast {
+    convertMdToHtml(mdFiles, cssFile)
   }
 
-  def buildtoolsPandoc = System.getProperty("user.home")+"/buildtools/pandoc/bin/pandoc"
-  if ((pandoc == null || ! file(pandoc).exists()) && file(buildtoolsPandoc).exists()) {
-    pandoc = System.getProperty("user.home")+"/buildtools/pandoc/bin/pandoc"
-  }
+  inputs.files(mdFiles)
+  inputs.file(cssFile)
 
-  doFirst {
-    if (pandoc != null && file(pandoc).exists()) {
-        commandLine pandoc, '-s', '-o', buildingHTML, '--metadata', 'pagetitle="Building Jalview from Source"', '--toc', '-H', css, buildingMD
-    } else {
-        println("Cannot find pandoc. Skipping convert building.md to HTML")
-        throw new StopExecutionException("Cannot find pandoc. Skipping convert building.md to HTML")
-    }
+  def htmlFiles = []
+  mdFiles.each { mdFile ->
+    def htmlFilePath = mdFile.getPath().replaceAll(/\..*?$/, ".html")
+    htmlFiles.add(file(htmlFilePath))
   }
-
-  ignoreExitValue true
-
-  inputs.file(buildingMD)
-  inputs.file(css)
-  outputs.file(buildingHTML)
-}
-
-
-task syncDocs(type: Sync) {
-  //dependsOn convertBuildingMD
-  def syncDir = "${classesDir}/${doc_dir}"
-  from fileTree("${jalviewDir}/${doc_dir}")
-  into syncDir
-
+  outputs.files(htmlFiles)
 }
 
 
 task copyHelp(type: Copy) {
   def inputDir = helpSourceDir
-  def outputDir = "${resourceClassesDir}/${help_dir}"
+  def outputDir = "${helpBuildDir}/${help_dir}"
   from(inputDir) {
-    exclude '**/*.gif'
-    exclude '**/*.jpg'
-    exclude '**/*.png'
+    include('**/*.txt')
+    include('**/*.md')
+    include('**/*.html')
+    include('**/*.hs')
+    include('**/*.xml')
+    include('**/*.jhm')
     filter(ReplaceTokens,
       beginToken: '$$',
       endToken: '$$',
@@ -1086,9 +1195,12 @@ task copyHelp(type: Copy) {
     )
   }
   from(inputDir) {
-    include '**/*.gif'
-    include '**/*.jpg'
-    include '**/*.png'
+    exclude('**/*.txt')
+    exclude('**/*.md')
+    exclude('**/*.html')
+    exclude('**/*.hs')
+    exclude('**/*.xml')
+    exclude('**/*.jhm')
   }
   into outputDir
 
@@ -1098,40 +1210,121 @@ task copyHelp(type: Copy) {
 }
 
 
-task syncLib(type: Sync) {
-  def syncDir = "${resourceClassesDir}/${libDistDir}"
-  from fileTree("${jalviewDir}/${libDistDir}")
-  into syncDir
+task copyResources(type: Copy) {
+  group = "build"
+  description = "Copy (and make text substitutions in) the resources dir to the build area"
+
+  def inputDir = resourceDir
+  def outputDir = resourcesBuildDir
+  from(inputDir) {
+    include('**/*.txt')
+    include('**/*.md')
+    include('**/*.html')
+    include('**/*.xml')
+    filter(ReplaceTokens,
+      beginToken: '$$',
+      endToken: '$$',
+      tokens: [
+        'Version-Rel': JALVIEW_VERSION,
+        'Year-Rel': getDate("yyyy")
+      ]
+    )
+  }
+  from(inputDir) {
+    exclude('**/*.txt')
+    exclude('**/*.md')
+    exclude('**/*.html')
+    exclude('**/*.xml')
+  }
+  into outputDir
+
+  inputs.dir(inputDir)
+  outputs.dir(outputDir)
 }
 
+task copyChannelResources(type: Copy) {
+  dependsOn copyResources
+  group = "build"
+  description = "Copy the channel resources dir to the build resources area"
 
-task syncResources(type: Sync) {
-  dependsOn createBuildProperties
-  from resourceDir
-  include "**/*.*"
-  into "${resourceClassesDir}"
-  preserve {
-    include "**"
+  def inputDir = "${channelDir}/${resource_dir}"
+  def outputDir = resourcesBuildDir
+  from inputDir
+  into outputDir
+
+  inputs.dir(inputDir)
+  outputs.dir(outputDir)
+}
+
+task createBuildProperties(type: WriteProperties) {
+  dependsOn copyResources
+  group = "build"
+  description = "Create the ${buildProperties} file"
+  
+  inputs.dir(sourceDir)
+  inputs.dir(resourcesBuildDir)
+  outputFile (buildProperties)
+  // taking time specific comment out to allow better incremental builds
+  comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd HH:mm:ss")
+  //comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd")
+  property "BUILD_DATE", getDate("HH:mm:ss dd MMMM yyyy")
+  property "VERSION", JALVIEW_VERSION
+  property "INSTALLATION", INSTALLATION+" git-commit:"+gitHash+" ["+gitBranch+"]"
+  if (getdownSetAppBaseProperty) {
+    property "GETDOWNAPPBASE", getdownAppBase
+    property "GETDOWNAPPDISTDIR", getdownAppDistDir
   }
+  outputs.file(outputFile)
+}
+
+
+task buildIndices(type: JavaExec) {
+  dependsOn copyHelp
+  classpath = sourceSets.main.compileClasspath
+  main = "com.sun.java.help.search.Indexer"
+  workingDir = "${helpBuildDir}/${help_dir}"
+  def argDir = "html"
+  args = [ argDir ]
+  inputs.dir("${workingDir}/${argDir}")
+
+  outputs.dir("${classesDir}/doc")
+  outputs.dir("${classesDir}/help")
+  outputs.file("${workingDir}/JavaHelpSearch/DOCS")
+  outputs.file("${workingDir}/JavaHelpSearch/DOCS.TAB")
+  outputs.file("${workingDir}/JavaHelpSearch/OFFSETS")
+  outputs.file("${workingDir}/JavaHelpSearch/POSITIONS")
+  outputs.file("${workingDir}/JavaHelpSearch/SCHEMA")
+  outputs.file("${workingDir}/JavaHelpSearch/TMAP")
 }
 
+task buildResources {
+  dependsOn copyResources
+  dependsOn copyChannelResources
+  dependsOn createBuildProperties
+}
 
 task prepare {
-  dependsOn syncResources
-  dependsOn syncDocs
+  dependsOn buildResources
+  dependsOn copyDocs
   dependsOn copyHelp
+  dependsOn convertMdFiles
+  dependsOn buildIndices
 }
 
 
+compileJava.dependsOn prepare
+run.dependsOn compileJava
+//run.dependsOn prepare
+
+
 //testReportDirName = "test-reports" // note that test workingDir will be $jalviewDir
 test {
   dependsOn prepare
-  //dependsOn compileJava ////? DELETE
 
   if (useClover) {
     dependsOn cloverClasses
    } else { //?
-     dependsOn compileJava //?
+    dependsOn compileJava //?
   }
 
   useTestNG() {
@@ -1144,7 +1337,16 @@ test {
   maxHeapSize = "1024m"
 
   workingDir = jalviewDir
-  //systemProperties 'clover.jar' System.properties.clover.jar
+  def testLaf = project.findProperty("test_laf")
+  if (testLaf != null) {
+    println("Setting Test LaF to '${testLaf}'")
+    systemProperty "laf", testLaf
+  }
+  def testHiDPIScale = project.findProperty("test_HiDPIScale")
+  if (testHiDPIScale != null) {
+    println("Setting Test HiDPI Scale to '${testHiDPIScale}'")
+    systemProperty "sun.java2d.uiScale", testHiDPIScale
+  }
   sourceCompatibility = compile_source_compatibility
   targetCompatibility = compile_target_compatibility
   jvmArgs += additional_compiler_args
@@ -1157,26 +1359,6 @@ test {
 }
 
 
-task buildIndices(type: JavaExec) {
-  dependsOn copyHelp
-  classpath = sourceSets.main.compileClasspath
-  main = "com.sun.java.help.search.Indexer"
-  workingDir = "${classesDir}/${help_dir}"
-  def argDir = "html"
-  args = [ argDir ]
-  inputs.dir("${workingDir}/${argDir}")
-
-  outputs.dir("${classesDir}/doc")
-  outputs.dir("${classesDir}/help")
-  outputs.file("${workingDir}/JavaHelpSearch/DOCS")
-  outputs.file("${workingDir}/JavaHelpSearch/DOCS.TAB")
-  outputs.file("${workingDir}/JavaHelpSearch/OFFSETS")
-  outputs.file("${workingDir}/JavaHelpSearch/POSITIONS")
-  outputs.file("${workingDir}/JavaHelpSearch/SCHEMA")
-  outputs.file("${workingDir}/JavaHelpSearch/TMAP")
-}
-
-
 task compileLinkCheck(type: JavaCompile) {
   options.fork = true
   classpath = files("${jalviewDir}/${utils_dir}")
@@ -1191,30 +1373,31 @@ task compileLinkCheck(type: JavaCompile) {
 
 
 task linkCheck(type: JavaExec) {
-  dependsOn prepare, compileLinkCheck
+  dependsOn prepare
+  dependsOn compileLinkCheck
 
   def helpLinksCheckerOutFile = file("${jalviewDir}/${utils_dir}/HelpLinksChecker.out")
   classpath = files("${jalviewDir}/${utils_dir}")
   main = "HelpLinksChecker"
   workingDir = jalviewDir
-  args = [ "${classesDir}/${help_dir}", "-nointernet" ]
+  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("${classesDir}/${help_dir}")
+  inputs.dir(helpBuildDir)
   outputs.file(helpLinksCheckerOutFile)
 }
 
+
 // import the pubhtmlhelp target
 ant.properties.basedir = "${jalviewDir}"
-ant.properties.helpBuildDir = "${jalviewDirAbsolutePath}/${classes_dir}/${help_dir}"
+ant.properties.helpBuildDir = "${helpBuildDir}/${help_dir}"
 ant.importBuild "${utils_dir}/publishHelp.xml"
 
 
@@ -1226,19 +1409,22 @@ task cleanPackageDir(type: Delete) {
 
 
 jar {
+  dependsOn prepare
   dependsOn linkCheck
-  dependsOn buildIndices
-  dependsOn createBuildProperties
 
   manifest {
     attributes "Main-Class": main_class,
     "Permissions": "all-permissions",
-    "Application-Name": "Jalview Desktop",
-    "Codebase": application_codebase
+    "Application-Name": install4jApplicationName,
+    "Codebase": application_codebase,
+    "Implementation-Version": JALVIEW_VERSION
   }
 
-  destinationDir = file("${jalviewDir}/${package_dir}")
-  archiveName = rootProject.name+".jar"
+  def outputDir = "${jalviewDir}/${package_dir}"
+  destinationDirectory = file(outputDir)
+  archiveFileName = rootProject.name+".jar"
+  duplicatesStrategy "EXCLUDE"
+
 
   exclude "cache*/**"
   exclude "*.jar"
@@ -1246,8 +1432,11 @@ jar {
   exclude "**/*.jar"
   exclude "**/*.jar.*"
 
-  inputs.dir(classesDir)
-  outputs.file("${jalviewDir}/${package_dir}/${archiveName}")
+  inputs.dir(sourceSets.main.java.outputDir)
+  sourceSets.main.resources.srcDirs.each{ dir ->
+    inputs.dir(dir)
+  }
+  outputs.file("${outputDir}/${archiveFileName}")
 }
 
 
@@ -1259,10 +1448,11 @@ task copyJars(type: Copy) {
 
 // doing a Sync instead of Copy as Copy doesn't deal with "outputs" very well
 task syncJars(type: Sync) {
+  dependsOn jar
   from fileTree(dir: "${jalviewDir}/${libDistDir}", include: "**/*.jar").files
   into "${jalviewDir}/${package_dir}"
   preserve {
-    include jar.archiveName
+    include jar.archiveFileName.getOrNull()
   }
 }
 
@@ -1286,6 +1476,7 @@ task cleanDist {
   dependsOn clean
 }
 
+
 shadowJar {
   group = "distribution"
   description = "Create a single jar file with all dependency libraries merged. Can be run with java -jar"
@@ -1296,8 +1487,12 @@ shadowJar {
     include("*.jar")
   }
   manifest {
-    attributes 'Implementation-Version': JALVIEW_VERSION
+    attributes "Implementation-Version": JALVIEW_VERSION,
+    "Application-Name": install4jApplicationName
   }
+
+  duplicatesStrategy "INCLUDE"
+
   mainClassName = shadow_jar_main_class
   mergeServiceFiles()
   classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
@@ -1324,11 +1519,17 @@ task getdownWebsite() {
 
     copy {
       from buildProperties
-      rename(build_properties_file, getdown_build_properties)
+      rename(file(buildProperties).getName(), getdown_build_properties)
       into getdownAppDir
     }
     getdownWebsiteResourceFilenames += "${getdownAppDistDir}/${getdown_build_properties}"
 
+    copy {
+      from channelPropsFile
+      into getdownWebsiteDir
+    }
+    getdownWebsiteResourceFilenames += file(channelPropsFile).getName()
+
     // set some getdown_txt_ properties then go through all properties looking for getdown_txt_...
     def props = project.properties.sort { it.key }
     if (getdownAltJavaMinVersion != null && getdownAltJavaMinVersion.length() > 0) {
@@ -1340,6 +1541,14 @@ task getdownWebsite() {
     if (getdownAltMultiJavaLocation != null && getdownAltMultiJavaLocation.length() > 0) {
       props.put("getdown_txt_multi_java_location", getdownAltMultiJavaLocation)
     }
+    if (getdownImagesDir != null && file(getdownImagesDir).exists()) {
+      props.put("getdown_txt_ui.background_image", "${getdownImagesDir}/${getdown_background_image}")
+      props.put("getdown_txt_ui.instant_background_image", "${getdownImagesDir}/${getdown_instant_background_image}")
+      props.put("getdown_txt_ui.error_background", "${getdownImagesDir}/${getdown_error_background}")
+      props.put("getdown_txt_ui.progress_image", "${getdownImagesDir}/${getdown_progress_image}")
+      props.put("getdown_txt_ui.icon", "${getdownImagesDir}/${getdown_icon}")
+      props.put("getdown_txt_ui.mac_dock_icon", "${getdownImagesDir}/${getdown_mac_dock_icon}")
+    }
 
     props.put("getdown_txt_title", jalview_name)
     props.put("getdown_txt_ui.name", install4jApplicationName)
@@ -1388,6 +1597,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}"
+        }
+        getdownTextString += "resource = ${getdown_wrapper_script_dir}/${script}\n"
+      }
+    }
 
     def codeFiles = []
     fileTree(file(package_dir)).each{ f ->
@@ -1398,7 +1619,9 @@ 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
@@ -1428,6 +1651,11 @@ task getdownWebsite() {
     //getdownTextString += "class = " + file(getdownLauncher).getName() + "\n"
     getdownTextString += "resource = ${getdown_launcher_new}\n"
     getdownTextString += "class = ${main_class}\n"
+    // 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"
+    }
 
     def getdown_txt = file("${getdownWebsiteDir}/getdown.txt")
     getdown_txt.write(getdownTextString)
@@ -1436,12 +1664,14 @@ task getdownWebsite() {
     def launchJvl = file("${getdownWebsiteDir}/${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
     }
 
+    // files going into the getdown website dir: getdown-launcher(-local).jar
     copy {
       from getdownLauncher
       if (file(getdownLauncher).getName() != getdown_launcher) {
@@ -1450,34 +1680,39 @@ task getdownWebsite() {
       into getdownWebsiteDir
     }
 
+    // files going into the getdown website dir: ./install dir and files
     if (! (CHANNEL.startsWith("ARCHIVE") || CHANNEL.startsWith("DEVELOP"))) {
       copy {
         from getdown_txt
         from getdownLauncher
-        from "${getdownWebsiteDir}/${getdown_build_properties}"
+        from "${getdownAppDir}/${getdown_build_properties}"
         if (file(getdownLauncher).getName() != getdown_launcher) {
           rename(file(getdownLauncher).getName(), getdown_launcher)
         }
         into getdownInstallDir
       }
 
+      // and make a copy in the getdown files dir (these are not downloaded by getdown)
       copy {
         from getdownInstallDir
         into getdownFilesInstallDir
       }
     }
 
+    // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
     copy {
       from getdown_txt
       from launchJvl
       from getdownLauncher
       from "${getdownWebsiteDir}/${getdown_build_properties}"
+      from "${getdownWebsiteDir}/${channel_props}"
       if (file(getdownLauncher).getName() != getdown_launcher) {
         rename(file(getdownLauncher).getName(), getdown_launcher)
       }
       into getdownFilesDir
     }
 
+    // and ./resources (not all downloaded by getdown)
     copy {
       from getdownResourceDir
       into "${getdownFilesDir}/${getdown_resource_dir}"
@@ -1595,6 +1830,12 @@ 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") {
@@ -1642,12 +1883,6 @@ task copyInstall4jTemplate {
       }
     }
 
-    // remove the "Uninstall Old Jalview (optional)" symlink from DMG for non-release DS_Stores
-    if (! (CHANNEL == "RELEASE" || CHANNEL == "TEST-RELEASE" ) ) {
-      def symlink = install4jConfigXml.'**'.topLevelFiles.symlink.find { sl -> sl.'@name' == "Uninstall Old Jalview (optional).app" }
-      symlink.parent().remove(symlink)
-    }
-
     // write install4j file
     install4jConfFile.text = XmlUtil.serialize(install4jConfigXml)
   }
@@ -1688,6 +1923,8 @@ task installers(type: com.install4j.gradle.Install4jTask) {
     'JALVIEW_APPLICATION_NAME': install4jApplicationName,
     'JALVIEW_DIR': "../..",
     'OSX_KEYSTORE': OSX_KEYSTORE,
+    'OSX_APPLEID': OSX_APPLEID,
+    'OSX_ALTOOLPASS': OSX_ALTOOLPASS,
     'JSIGN_SH': JSIGN_SH,
     'JRE_DIR': getdown_app_dir_java,
     'INSTALLER_TEMPLATE_VERSION': install4jTemplateVersion,
@@ -1707,8 +1944,12 @@ task installers(type: com.install4j.gradle.Install4jTask) {
     'BUNDLE_ID': install4jBundleId,
     'INTERNAL_ID': install4jInternalId,
     'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
-    'MACOS_DS_STORE': install4jDSStore,
+    '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,
@@ -1724,6 +1965,11 @@ task installers(type: com.install4j.gradle.Install4jTask) {
     'UNIX_APPLICATION_FOLDER': install4jUnixApplicationFolder,
     'EXECUTABLE_NAME': install4jExecutableName,
     'EXTRA_SCHEME': install4jExtraScheme,
+    'MAC_ICONS_FILE': install4jMacIconsFile,
+    'WINDOWS_ICONS_FILE': install4jWindowsIconsFile,
+    'PNG_ICON_FILE': install4jPngIconFile,
+    'BACKGROUND': install4jBackground,
+
   ]
 
   //println("INSTALL4J VARIABLES:")
@@ -1735,15 +1981,25 @@ task installers(type: com.install4j.gradle.Install4jTask) {
   if (install4j_faster.equals("true") || CHANNEL.startsWith("LOCAL")) {
     faster = true
     disableSigning = true
+    disableNotarization = true
   }
 
   if (OSX_KEYPASS) {
     macKeystorePassword = OSX_KEYPASS
+  } 
+  
+  if (OSX_ALTOOLPASS) {
+    appleIdPassword = OSX_ALTOOLPASS
+    disableNotarization = false
+  } else {
+    disableNotarization = true
   }
 
   doFirst {
     println("Using projectFile "+projectFile)
+    if (!disableNotarization) { println("Will notarize OSX App DMG") }
   }
+  //verbose=true
 
   inputs.dir(getdownWebsiteDir)
   inputs.file(install4jConfFile)
@@ -1760,21 +2016,36 @@ 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"
   description "Create a source .tar.gz file for distribution"
-  
-  dependsOn convertBuildingMD
+
+  dependsOn createBuildProperties
+  dependsOn convertMdFiles
+  dependsOn eclipseAllPreferences
+  dependsOn createSourceReleaseProperties
+
 
   def VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
   def outputFileName = "${project.name}_${VERSION_UNDERSCORES}.tar.gz"
-  // cater for buildship < 3.1 [3.0.1 is max version in eclipse 2018-09]
-  try {
-    archiveFileName = outputFileName
-  } catch (Exception e) {
-    archiveName = outputFileName
-  }
+  archiveFileName = outputFileName
   
   compression Compression.GZIP
   
@@ -1795,6 +2066,7 @@ task sourceDist(type: Tar) {
     "*locales/**",
     "utils/InstallAnywhere",
     "**/*.log",
+    "RELEASE",
   ] 
   def PROCESS_FILES=[
     "AUTHORS",
@@ -1804,7 +2076,6 @@ task sourceDist(type: Tar) {
     "FEATURETODO",
     "LICENSE",
     "**/README",
-    "RELEASE",
     "THIRDPARTYLIBS",
     "TESTNG",
     "build.gradle",
@@ -1819,7 +2090,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) {
@@ -1857,6 +2130,19 @@ task sourceDist(type: Tar) {
 //    exclude(EXCLUDE_FILES)
 //    exclude(PROCESS_FILES)
 //  }
+
+  from(file(buildProperties).getParent()) {
+    include(file(buildProperties).getName())
+    rename(file(buildProperties).getName(), "build_properties")
+    filter({ line ->
+      line.replaceAll("^INSTALLATION=.*\$","INSTALLATION=Source Release"+" git-commit\\\\:"+gitHash+" ["+gitBranch+"]")
+    })
+  }
+
+  def sourceTarBuildDir = "${buildDir}/sourceTar"
+  from(sourceTarBuildDir) {
+    // this includes the appended RELEASE properties file
+  }
 }
 
 
@@ -1864,7 +2150,7 @@ task helppages {
   dependsOn copyHelp
   dependsOn pubhtmlhelp
   
-  inputs.dir("${classesDir}/${help_dir}")
+  inputs.dir("${helpBuildDir}/${help_dir}")
   outputs.dir("${buildDir}/distributions/${help_dir}")
 }
 
@@ -1876,6 +2162,33 @@ task j2sSetHeadlessBuild {
 }
 
 
+task jalviewjsEnableAltFileProperty(type: WriteProperties) {
+  group "jalviewjs"
+  description "Enable the alternative J2S Config file for headless build"
+
+  outputFile = jalviewjsJ2sSettingsFileName
+  def j2sPropsFile = file(jalviewjsJ2sSettingsFileName)
+  def j2sProps = new Properties()
+  if (j2sPropsFile.exists()) {
+    try {
+      def j2sPropsFileFIS = new FileInputStream(j2sPropsFile)
+      j2sProps.load(j2sPropsFileFIS)
+      j2sPropsFileFIS.close()
+
+      j2sProps.each { prop, val ->
+        property(prop, val)
+      }
+    } catch (Exception e) {
+      println("Exception reading ${jalviewjsJ2sSettingsFileName}")
+      e.printStackTrace()
+    }
+  }
+  if (! j2sProps.stringPropertyNames().contains(jalviewjs_j2s_alt_file_property_config)) {
+    property(jalviewjs_j2s_alt_file_property_config, jalviewjs_j2s_alt_file_property)
+  }
+}
+
+
 task jalviewjsSetEclipseWorkspace {
   def propKey = "jalviewjs_eclipse_workspace"
   def propVal = null
@@ -2088,7 +2401,7 @@ task jalviewjsTransferUnzipAllLibs {
 
 task jalviewjsCreateJ2sSettings(type: WriteProperties) {
   group "JalviewJS"
-  description "Create the .j2s file from the j2s.* properties"
+  description "Create the alternative j2s file from the j2s.* properties"
 
   jalviewjsJ2sProps = project.properties.findAll { it.key.startsWith("j2s.") }.sort { it.key }
   def siteDirProperty = "j2s.site.directory"
@@ -2107,11 +2420,11 @@ task jalviewjsCreateJ2sSettings(type: WriteProperties) {
       property(siteDirProperty,"${jalviewDirRelativePath}/${jalviewjsTransferSiteJsDir}")
     }
   }
-  outputFile = jalviewjsJ2sSettingsFileName
+  outputFile = jalviewjsJ2sAltSettingsFileName
 
   if (! IN_ECLIPSE) {
     inputs.properties(jalviewjsJ2sProps)
-    outputs.file(jalviewjsJ2sSettingsFileName)
+    outputs.file(jalviewjsJ2sAltSettingsFileName)
   }
 }
 
@@ -2139,13 +2452,19 @@ task jalviewjsSyncAllLibs (type: Sync) {
   preserve {
     include "**"
   }
+
+  // should this be exclude really ?
+  duplicatesStrategy "INCLUDE"
+
   outputs.files outputFiles
   inputs.files inputFiles
 }
 
 
 task jalviewjsSyncResources (type: Sync) {
-  def inputFiles = fileTree(dir: resourceDir)
+  dependsOn buildResources
+
+  def inputFiles = fileTree(dir: resourcesBuildDir)
   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
 
   from inputFiles
@@ -2227,6 +2546,7 @@ task jalviewjsProjectImport(type: Exec) {
   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
   if (!IN_ECLIPSE) {
     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
+    args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
   }
 
   inputs.file("${jalviewDir}/.project")
@@ -2240,6 +2560,9 @@ task jalviewjsTranspile(type: Exec) {
   dependsOn jalviewjsEclipseSetup 
   dependsOn jalviewjsProjectImport
   dependsOn jalviewjsEclipsePaths
+  if (!IN_ECLIPSE) {
+    dependsOn jalviewjsEnableAltFileProperty
+  }
 
   doFirst {
     // do not run a headless transpile when we claim to be in Eclipse
@@ -2259,6 +2582,7 @@ task jalviewjsTranspile(type: Exec) {
   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
   if (!IN_ECLIPSE) {
     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
+    args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
   }
 
   def stdout
@@ -2286,12 +2610,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,
@@ -2379,7 +2703,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,
@@ -2668,11 +2992,7 @@ task jalviewjsSiteTar(type: Tar) {
   description "Creates a tar.gz file for the website"
   dependsOn jalviewjsBuildSite
   def outputFilename = "jalviewjs-site-${JALVIEW_VERSION}.tar.gz"
-  try {
-    archiveFileName = outputFilename
-  } catch (Exception e) {
-    archiveName = outputFilename
-  }
+  archiveFileName = outputFilename
 
   compression Compression.GZIP
 
@@ -2690,7 +3010,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
@@ -2743,7 +3069,7 @@ task cleanJalviewjsAll {
     if (eclipseWorkspace != null && file(eclipseWorkspace.getAbsolutePath()+"/.metadata").exists()) {
       delete file(eclipseWorkspace.getAbsolutePath()+"/.metadata")
     }
-    delete "${jalviewDir}/${jalviewjs_j2s_settings}"
+    delete jalviewjsJ2sAltSettingsFileName
   }
 
   outputs.upToDateWhen( { false } )
@@ -2761,10 +3087,25 @@ task jalviewjsIDE_checkJ2sPlugin {
     if (eclipseHome == null || ! IN_ECLIPSE) {
       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. Skipping J2S Plugin Check.")
     }
-    def eclipseJ2sPlugin = "${eclipseHome}/dropins/${j2sPluginFile.getName()}"
-    def eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
-    if (!eclipseJ2sPluginFile.exists()) {
-      def msg = "Eclipse J2S Plugin is not installed (could not find '${eclipseJ2sPlugin}')\nTry running task jalviewjsIDE_copyJ2sPlugin"
+    def eclipseJ2sPluginDirs = [ "${eclipseHome}/dropins" ]
+    def altPluginsDir = System.properties["org.eclipse.equinox.p2.reconciler.dropins.directory"]
+    if (altPluginsDir != null && file(altPluginsDir).exists()) {
+      eclipseJ2sPluginDirs += altPluginsDir
+    }
+    def foundPlugin = false
+    def j2sPluginFileName = j2sPluginFile.getName()
+    def eclipseJ2sPlugin
+    def eclipseJ2sPluginFile
+    eclipseJ2sPluginDirs.any { dir ->
+      eclipseJ2sPlugin = "${dir}/${j2sPluginFileName}"
+      eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
+      if (eclipseJ2sPluginFile.exists()) {
+        foundPlugin = true
+        return true
+      }
+    }
+    if (!foundPlugin) {
+      def msg = "Eclipse J2S Plugin is not installed (could not find '${j2sPluginFileName}' in\n"+eclipseJ2sPluginDirs.join("\n")+"\n)\nTry running task jalviewjsIDE_copyJ2sPlugin"
       System.err.println(msg)
       throw new StopExecutionException(msg)
     }
@@ -2782,7 +3123,7 @@ task jalviewjsIDE_checkJ2sPlugin {
       System.err.println(msg)
       throw new StopExecutionException(msg)
     } else {
-      def msg = "Eclipse J2S Plugin is the same as '${j2sPlugin}' (this is good)"
+      def msg = "Eclipse J2S Plugin '${eclipseJ2sPlugin}' is the same as '${j2sPlugin}' (this is good)"
       println(msg)
     }
   }