Merge branch 'task/JAL-3608_property_set_laf_and_tests' into develop
authorJim Procter <jprocter@issues.jalview.org>
Wed, 12 Aug 2020 10:15:54 +0000 (11:15 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Wed, 12 Aug 2020 10:15:54 +0000 (11:15 +0100)
1  2 
build.gradle
src/jalview/bin/Cache.java
src/jalview/bin/Jalview.java

diff --combined build.gradle
@@@ -1,6 -1,3 +1,6 @@@
 +/* Convention for properties.  Read from gradle.properties, use lower_case_underlines for property names.
 + * For properties set within build.gradle, use camelCaseNoSpace.
 + */
  import org.apache.tools.ant.filters.ReplaceTokens
  import org.gradle.internal.os.OperatingSystem
  import org.gradle.plugins.ide.internal.generator.PropertiesPersistableConfigurationObject
@@@ -12,16 -9,7 +12,16 @@@ import java.security.MessageDiges
  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 {
@@@ -29,7 -17,7 +29,7 @@@
      mavenLocal()
    }
    dependencies {
 -    classpath 'org.openclover:clover:4.4.1'
 +    classpath "com.vladsch.flexmark:flexmark-all:0.62.0"
    }
  }
  
@@@ -42,7 -30,6 +42,7 @@@ plugins 
    id 'com.github.johnrengelman.shadow' version '4.0.3'
    id 'com.install4j.gradle' version '8.0.4'
    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'
  }
  
  repositories {
@@@ -95,26 -82,6 +95,26 @@@ 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();
 +  def releasePropFile = findProperty("JALVIEW_RELEASE_FILE");
 +  def defaultReleasePropFile = "${jalviewDirAbsolutePath}/RELEASE";
 +  try {
 +    (new File(releasePropFile!=null ? releasePropFile : defaultReleasePropFile)).withInputStream { 
 +     releaseProps.load(it)
 +    }
 +  } catch (Exception fileLoadError) {
 +    throw new Error("Couldn't load release properties file "+(releasePropFile==null ? defaultReleasePropFile : "from custom location: releasePropFile"),fileLoadError);
 +  }
 +  ////
 +  // Set JALVIEW_VERSION if it is not already set
 +  if (findProperty(JALVIEW_VERSION)==null || "".equals(JALVIEW_VERSION)) {
 +    JALVIEW_VERSION = releaseProps.get("jalview.version")
 +  }
 +  
    // this property set when running Eclipse headlessly
    j2sHeadlessBuildProperty = string("net.sf.j2s.core.headlessbuild")
    // this property set by Eclipse
    J2S_ENABLED = (project.hasProperty('j2s.compiler.status') && project['j2s.compiler.status'] != null && project['j2s.compiler.status'] == "enable")
    if (J2S_ENABLED) {
      println("J2S ENABLED")
 -  }
 -  
 +  } 
    /* *-/
    System.properties.sort { it.key }.each {
      key, val -> println("SYSTEM PROPERTY ${key}='${val}'")
    sourceDir = string("${jalviewDir}/${bareSourceDir}")
    resourceDir = string("${jalviewDir}/${resource_dir}")
    bareTestSourceDir = string(test_source_dir)
 -  testSourceDir = string("${jalviewDir}/${bareTestSourceDir}")
 +  testDir = string("${jalviewDir}/${bareTestSourceDir}")
  
 -  // clover
 -  cloverInstrDir = file("${buildDir}/${cloverSourcesInstrDir}")
 -  cloverDb = string("${buildDir}/clover/clover.db")
    classesDir = string("${jalviewDir}/${classes_dir}")
 -  if (clover.equals("true")) {
 -    use_clover = true
 -    classesDir = string("${buildDir}/${cloverClassesDir}")
 -  } else {
 -    use_clover = false
 -    classesDir = string("${jalviewDir}/${classes_dir}")
 -  }
  
 -  classes = classesDir
 +  // clover
 +  useClover = clover.equals("true")
 +  cloverBuildDir = "${buildDir}/clover"
 +  cloverInstrDir = file("${cloverBuildDir}/clover-instr")
 +  cloverClassesDir = file("${cloverBuildDir}/clover-classes")
 +  cloverReportDir = file("${buildDir}/reports/clover")
 +  cloverTestInstrDir = file("${cloverBuildDir}/clover-test-instr")
 +  cloverTestClassesDir = file("${cloverBuildDir}/clover-test-classes")
 +  //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
      getdownAppBase = string("${bamboo_channelbase}/${bamboo_planKey}${bamboo_getdown_channel_suffix}/${JAVA_VERSION}")
      jvlChannelName += "_${getdownChannelName}"
      // automatically add the test group Not-bamboo for exclusion 
 -    if ("".equals(testngExcludedGroups)) { 
 -      testngExcludedGroups = "Not-bamboo"
 +    if ("".equals(testng_excluded_groups)) { 
 +      testng_excluded_groups = "Not-bamboo"
      }
      install4jExtraScheme = "jalviewb"
      break
      case "RELEASE":
      getdownAppDistDir = getdown_app_dir_release
      reportRsyncCommand = true
 -    // Don't ignore transpile errors for release build
 -    if (jalviewjs_ignore_transpile_errors.equals("true")) {
 -      jalviewjs_ignore_transpile_errors = "false"
 -      println("Setting jalviewjs_ignore_transpile_errors to 'false'")
 -    }
      install4jSuffix = ""
      install4jDSStore = "DS_Store"
      install4jDMGBackgroundImage = "jalview_dmg_background.png"
      getdownChannelName = CHANNEL.toLowerCase()+"/${JALVIEW_VERSION}"
      getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
      getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
 -    if (!file("${ARCHIVEDIR}/${packageDir}").exists()) {
 +    if (!file("${ARCHIVEDIR}/${package_dir}").exists()) {
        throw new GradleException("Must provide an ARCHIVEDIR value to produce an archive distribution")
      } else {
 -      packageDir = string("${ARCHIVEDIR}/${packageDir}")
 -      buildProperties = string("${buildDir}/archive/${build_properties_file}")
 +      package_dir = string("${ARCHIVEDIR}/${package_dir}")
 +      buildProperties = string("${ARCHIVEDIR}/${classes_dir}/${build_properties_file}")
        buildDist = false
      }
      reportRsyncCommand = true
      getdownChannelName = string("archive/${JALVIEW_VERSION}")
      getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
      getdownAppBase = file(getdownWebsiteDir).toURI().toString()
 -    if (!file("${ARCHIVEDIR}/${packageDir}").exists()) {
 +    if (!file("${ARCHIVEDIR}/${package_dir}").exists()) {
        throw new GradleException("Must provide an ARCHIVEDIR value to produce an archive distribution")
      } else {
 -      packageDir = string("${ARCHIVEDIR}/${packageDir}")
 -      buildProperties = string("${buildDir}/archive/${build_properties_file}")
 +      package_dir = string("${ARCHIVEDIR}/${package_dir}")
 +      buildProperties = string("${ARCHIVEDIR}/${classes_dir}/${build_properties_file}")
        buildDist = false
      }
      reportRsyncCommand = true
  
      case "DEVELOP":
      reportRsyncCommand = true
 +    
 +    // DEVELOP-RELEASE is usually associated with a Jalview release series so set the version
 +    JALVIEW_VERSION=JALVIEW_VERSION+"-develop"
 +    
 +    install4jSuffix = "Develop"
      install4jDSStore = "DS_Store-DEVELOP"
      install4jDMGBackgroundImage = "jalview_dmg_background-DEVELOP.png"
      install4jExtraScheme = "jalviewd"
        jalviewjs_ignore_transpile_errors = "false"
        println("Setting jalviewjs_ignore_transpile_errors to 'false'")
      }
 -    JALVIEW_VERSION = "TEST"
 +    JALVIEW_VERSION = JALVIEW_VERSION+"-test"
      install4jSuffix = "Test"
      install4jDSStore = "DS_Store-TEST-RELEASE"
      install4jDMGBackgroundImage = "jalview_dmg_background-TEST.png"
  
      case ~/^SCRATCH(|-[-\w]*)$/:
      getdownChannelName = CHANNEL
 +    JALVIEW_VERSION = JALVIEW_VERSION+"-"+CHANNEL
 +    
      getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
      getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
      reportRsyncCommand = true
      break
  
      case "LOCAL":
 +    JALVIEW_VERSION = "TEST"
      getdownAppBase = file(getdownWebsiteDir).toURI().toString()
      getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
      install4jExtraScheme = "jalviewl"
    modules_compileClasspath = fileTree(dir: "${jalviewDir}/${j11modDir}", include: ["*.jar"])
    modules_runtimeClasspath = modules_compileClasspath
    */
 -  gitHash = string("")
 -  gitBranch = string("")
 +  def details = versionDetails()
 +  gitHash = details.gitHash
 +  gitBranch = details.branchName
  
    println("Using a ${CHANNEL} profile.")
  
  
  
  
 -  buildingHTML = string("${jalviewDir}/${docDir}/building.html")
 -  helpFile = string("${classesDir}/${help_dir}/help.jhm")
 +  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}")
  
@@@ -486,9 -446,10 +486,9 @@@ sourceSets 
        srcDirs += helpParentDir
      }
  
 -    jar.destinationDir = file("${jalviewDir}/${packageDir}")
 +    jar.destinationDir = file("${jalviewDir}/${package_dir}")
  
      compileClasspath = files(sourceSets.main.java.outputDir)
 -    //compileClasspath += files(sourceSets.main.resources.srcDirs)
      compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
  
      runtimeClasspath = compileClasspath
  
    clover {
      java {
 -      srcDirs = [ cloverInstrDir ]
 -      outputDir = file("${buildDir}/${cloverClassesDir}")
 +      srcDirs cloverInstrDir
 +      outputDir = cloverClassesDir
      }
  
      resources {
        srcDirs = sourceSets.main.resources.srcDirs
      }
 -    compileClasspath = configurations.cloverRuntime + files( sourceSets.clover.java.outputDir )
 -    compileClasspath += files(sourceSets.main.java.outputDir)
 -    compileClasspath += sourceSets.main.compileClasspath
 -    compileClasspath += fileTree(dir: "${jalviewDir}/${utilsDir}", include: ["**/*.jar"])
 +
 +    compileClasspath = files( sourceSets.clover.java.outputDir )
 +    //compileClasspath += files( testClassesDir )
      compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
 +    compileClasspath += fileTree(dir: "${jalviewDir}/${clover_lib_dir}", include: ["*.jar"])
 +    compileClasspath += fileTree(dir: "${jalviewDir}/${utils_dir}/testnglibs", include: ["**/*.jar"])
  
      runtimeClasspath = compileClasspath
    }
    test {
      java {
        srcDirs testSourceDir
 -      outputDir = file("${jalviewDir}/${testOutputDir}")
 +      outputDir = file(testClassesDir)
      }
  
      resources {
 -      srcDirs = sourceSets.main.resources.srcDirs
 +      srcDirs = useClover ? sourceSets.clover.resources.srcDirs : sourceSets.main.resources.srcDirs
      }
  
      compileClasspath = files( sourceSets.test.java.outputDir )
 -
 -    if (use_clover) {
 -      compileClasspath = sourceSets.clover.compileClasspath
 -    } else {
 -      compileClasspath += files(sourceSets.main.java.outputDir)
 -    }
 -
 -    compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
 -    compileClasspath += fileTree(dir: "${jalviewDir}/${utilsDir}/testnglibs", include: ["**/*.jar"])
 -    compileClasspath += fileTree(dir: "${jalviewDir}/${utilsDir}/testlibs", include: ["**/*.jar"])
 +    compileClasspath += useClover ? sourceSets.clover.compileClasspath : sourceSets.main.compileClasspath
 +    compileClasspath += fileTree(dir: "${jalviewDir}/${utils_dir}/testnglibs", include: ["**/*.jar"])
  
      runtimeClasspath = compileClasspath
    }
 -}
 -
  
 -// clover bits
 -dependencies {
 -  if (use_clover) {
 -    cloverCompile 'org.openclover:clover:4.4.1'
 -    testCompile 'org.openclover:clover:4.4.1'
 -  }
 -}
 -
 -
 -configurations {
 -  cloverRuntime
 -  cloverRuntime.extendsFrom cloverCompile
  }
  
  
@@@ -643,6 -625,7 +643,6 @@@ eclipse 
      javaRuntimeName = eclipseJavaRuntimeName
  
      // add in jalview project specific properties/preferences into eclipse core preferences
 -    // and also the codestyle XML file
      file {
        withProperties { props ->
          def jalview_prefs = new Properties()
@@@ -741,209 -724,61 +741,209 @@@ eclipseGroovyCorePreferences.mustRunAft
  /* end of eclipse preferences hack */
  
  
 -task cloverInstr {
 -  // only instrument source, we build test classes as normal
 -  inputs.files files (sourceSets.main.allJava,sourceSets.test.allJava) // , fileTree(dir:"$jalviewDir/$testSourceDir", include: ["**/*.java"]))
 -  outputs.dir cloverInstrDir
 +// clover bits
  
 +
 +task cleanClover {
    doFirst {
 -    delete cloverInstrDir
 -    def argsList = [
 -      "--initstring",
 -      cloverDb,
 -      "-d",
 -      cloverInstrDir.getPath(),
 -    ]
 -    argsList.addAll(
 -      inputs.files.files.collect(
 -        { file -> file.absolutePath }
 -      )
 +    delete cloverBuildDir
 +    delete cloverReportDir
 +  }
 +}
 +
 +
 +task cloverInstrJava(type: JavaExec) {
 +  group = "Verification"
 +  description = "Create clover instrumented source java files"
 +
 +  dependsOn cleanClover
 +
 +  inputs.files(sourceSets.main.allJava)
 +  outputs.dir(cloverInstrDir)
 +
 +  //classpath = fileTree(dir: "${jalviewDir}/${clover_lib_dir}", include: ["*.jar"])
 +  classpath = sourceSets.clover.compileClasspath
 +  main = "com.atlassian.clover.CloverInstr"
 +
 +  def argsList = [
 +    "--encoding",
 +    "UTF-8",
 +    "--initstring",
 +    cloverDb,
 +    "--destdir",
 +    cloverInstrDir.getPath(),
 +  ]
 +  def srcFiles = sourceSets.main.allJava.files
 +  argsList.addAll(
 +    srcFiles.collect(
 +      { file -> file.absolutePath }
      )
 -    String[] args = argsList.toArray()
 -    println("About to instrument "+args.length +" files")
 -    com.atlassian.clover.CloverInstr.mainImpl(args)
 +  )
 +  args argsList.toArray()
 +
 +  doFirst {
 +    delete cloverInstrDir
 +    println("Clover: About to instrument "+srcFiles.size() +" files")
    }
  }
  
  
 +task cloverInstrTests(type: JavaExec) {
 +  group = "Verification"
 +  description = "Create clover instrumented source test files"
 +
 +  dependsOn cleanClover
 +
 +  inputs.files(testDir)
 +  outputs.dir(cloverTestInstrDir)
 +
 +  classpath = sourceSets.clover.compileClasspath
 +  main = "com.atlassian.clover.CloverInstr"
 +
 +  def argsList = [
 +    "--encoding",
 +    "UTF-8",
 +    "--initstring",
 +    cloverDb,
 +    "--srcdir",
 +    testDir,
 +    "--destdir",
 +    cloverTestInstrDir.getPath(),
 +  ]
 +  args argsList.toArray()
 +
 +  doFirst {
 +    delete cloverTestInstrDir
 +    println("Clover: About to instrument test files")
 +  }
 +}
 +
 +
 +task cloverInstr {
 +  group = "Verification"
 +  description = "Create clover instrumented all source files"
 +
 +  dependsOn cloverInstrJava
 +  dependsOn cloverInstrTests
 +}
 +
 +
  cloverClasses.dependsOn cloverInstr
  
  
 -task cloverReport {
 +task cloverConsoleReport(type: JavaExec) {
    group = "Verification"
 -  description = "Creates the Clover report"
 -  inputs.dir "${buildDir}/clover"
 -  outputs.dir "${reportsDir}/clover"
 +  description = "Creates clover console report"
 +
    onlyIf {
      file(cloverDb).exists()
    }
 -  doFirst {
 -    def argsList = [
 -      "--initstring",
 -      cloverDb,
 -      "-o",
 -      "${reportsDir}/clover"
 -    ]
 -    String[] args = argsList.toArray()
 -    com.atlassian.clover.reporters.html.HtmlReporter.runReport(args)
  
 -    // and generate ${reportsDir}/clover/clover.xml
 -    args = [
 -      "--initstring",
 -      cloverDb,
 -      "-o",
 -      "${reportsDir}/clover/clover.xml"
 -    ].toArray()
 -    com.atlassian.clover.reporters.xml.XMLReporter.runReport(args)
 +  inputs.dir cloverClassesDir
 +
 +  classpath = sourceSets.clover.runtimeClasspath
 +  main = "com.atlassian.clover.reporters.console.ConsoleReporter"
 +
 +  if (cloverreport_mem.length() > 0) {
 +    maxHeapSize = cloverreport_mem
 +  }
 +  if (cloverreport_jvmargs.length() > 0) {
 +    jvmArgs Arrays.asList(cloverreport_jvmargs.split(" "))
 +  }
 +
 +  def argsList = [
 +    "--alwaysreport",
 +    "--initstring",
 +    cloverDb,
 +    "--unittests"
 +  ]
 +
 +  args argsList.toArray()
 +}
 +
 +
 +task cloverHtmlReport(type: JavaExec) {
 +  group = "Verification"
 +  description = "Creates clover HTML report"
 +
 +  onlyIf {
 +    file(cloverDb).exists()
 +  }
 +
 +  def cloverHtmlDir = cloverReportDir
 +  inputs.dir cloverClassesDir
 +  outputs.dir cloverHtmlDir
 +
 +  classpath = sourceSets.clover.runtimeClasspath
 +  main = "com.atlassian.clover.reporters.html.HtmlReporter"
 +
 +  if (cloverreport_mem.length() > 0) {
 +    maxHeapSize = cloverreport_mem
 +  }
 +  if (cloverreport_jvmargs.length() > 0) {
 +    jvmArgs Arrays.asList(cloverreport_jvmargs.split(" "))
 +  }
 +
 +  def argsList = [
 +    "--alwaysreport",
 +    "--initstring",
 +    cloverDb,
 +    "--outputdir",
 +    cloverHtmlDir
 +  ]
 +
 +  if (cloverreport_html_options.length() > 0) {
 +    argsList += cloverreport_html_options.split(" ")
 +  }
 +
 +  args argsList.toArray()
 +}
 +
 +
 +task cloverXmlReport(type: JavaExec) {
 +  group = "Verification"
 +  description = "Creates clover XML report"
 +
 +  onlyIf {
 +    file(cloverDb).exists()
    }
 +
 +  def cloverXmlFile = "${cloverReportDir}/clover.xml"
 +  inputs.dir cloverClassesDir
 +  outputs.file cloverXmlFile
 +
 +  classpath = sourceSets.clover.runtimeClasspath
 +  main = "com.atlassian.clover.reporters.xml.XMLReporter"
 +
 +  if (cloverreport_mem.length() > 0) {
 +    maxHeapSize = cloverreport_mem
 +  }
 +  if (cloverreport_jvmargs.length() > 0) {
 +    jvmArgs Arrays.asList(cloverreport_jvmargs.split(" "))
 +  }
 +
 +  def argsList = [
 +    "--alwaysreport",
 +    "--initstring",
 +    cloverDb,
 +    "--outfile",
 +    cloverXmlFile
 +  ]
 +
 +  if (cloverreport_xml_options.length() > 0) {
 +    argsList += cloverreport_xml_options.split(" ")
 +  }
 +
 +  args argsList.toArray()
 +}
 +
 +
 +task cloverReport {
 +  group = "Verification"
 +  description = "Creates clover reports"
 +
 +  dependsOn cloverXmlReport
 +  dependsOn cloverHtmlReport
  }
  
  
@@@ -955,29 -790,43 +955,29 @@@ compileCloverJava 
      options.compilerArgs += additional_compiler_args
      print ("Setting target compatibility to "+targetCompatibility+"\n")
    }
 -  classpath += configurations.cloverRuntime
 -}
 -
 -
 -task cleanClover {
 -  doFirst {
 -    delete cloverInstrDir
 -    delete cloverDb
 -  }
 +  //classpath += configurations.cloverRuntime
  }
  // end clover bits
  
  
  compileJava {
 -      
 +  // JBP->BS should the print statement in doFirst refer to compile_target_compatibility ?
    sourceCompatibility = compile_source_compatibility
    targetCompatibility = compile_target_compatibility
    options.compilerArgs = additional_compiler_args
    options.encoding = "UTF-8"
    doFirst {
 -    print ("Setting target compatibility to "+targetCompatibility+"\n")
 +    print ("Setting target compatibility to "+compile_target_compatibility+"\n")
    }
  
  }
  
  
  compileTestJava {
 -  if (use_clover) {
 -    dependsOn compileCloverJava
 -    classpath += configurations.cloverRuntime
 -  } else {
 -    classpath += sourceSets.main.runtimeClasspath
 -  }
 +  sourceCompatibility = compile_source_compatibility
 +  targetCompatibility = compile_target_compatibility
 +  options.compilerArgs = additional_compiler_args
    doFirst {
 -    sourceCompatibility = compile_source_compatibility
 -    targetCompatibility = compile_target_compatibility
 -    options.compilerArgs = additional_compiler_args
      print ("Setting target compatibility to "+targetCompatibility+"\n")
    }
  }
@@@ -1005,10 -854,33 +1005,10 @@@ def getDate(format) 
  }
  
  
 -task setGitVals {
 -  def hashStdOut = new ByteArrayOutputStream()
 -  exec {
 -    commandLine "git", "rev-parse", "--short", "HEAD"
 -    standardOutput = hashStdOut
 -    ignoreExitValue true
 -  }
 -
 -  def branchStdOut = new ByteArrayOutputStream()
 -  exec {
 -    commandLine "git", "rev-parse", "--abbrev-ref", "HEAD"
 -    standardOutput = branchStdOut
 -    ignoreExitValue true
 -  }
 -
 -  gitHash = hashStdOut.toString().trim()
 -  gitBranch = branchStdOut.toString().trim()
 -
 -  outputs.upToDateWhen { false }
 -}
 -
 -
  task createBuildProperties(type: WriteProperties) {
    group = "build"
    description = "Create the ${buildProperties} file"
    
 -  dependsOn setGitVals
    inputs.dir(sourceDir)
    inputs.dir(resourceDir)
    file(buildProperties).getParentFile().mkdirs()
@@@ -1037,116 -909,53 +1037,116 @@@ task cleanBuildingHTML(type: Delete) 
  }
  
  
 -task convertBuildingMD(type: Exec) {
 +def convertMdToHtml (FileTree mdFiles, File cssFile) {
 +  MutableDataSet options = new MutableDataSet()
 +
 +  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)
 +    htmlFile.text = htmlText
 +  }
 +}
 +
 +
 +task convertMdFiles {
    dependsOn cleanBuildingHTML
 -  def buildingMD = "${jalviewDir}/${docDir}/building.md"
 -  def css = "${jalviewDir}/${docDir}/github.css"
 +  def mdFiles = fileTree(dir: "${jalviewDir}/${doc_dir}", 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)
 +  outputs.files(htmlFiles)
  }
  
  
  task syncDocs(type: Sync) {
 -  dependsOn convertBuildingMD
 -  def syncDir = "${classesDir}/${docDir}"
 -  from fileTree("${jalviewDir}/${docDir}")
 +  dependsOn convertMdFiles
 +  def syncDir = "${classesDir}/${doc_dir}"
 +  from fileTree("${jalviewDir}/${doc_dir}")
    into syncDir
 -
  }
  
  
  task copyHelp(type: Copy) {
    def inputDir = helpSourceDir
 -  def outputDir = "${classesDir}/${help_dir}"
 +  def outputDir = "${resourceClassesDir}/${help_dir}"
    from(inputDir) {
      exclude '**/*.gif'
      exclude '**/*.jpg'
  
  
  task syncLib(type: Sync) {
 -  def syncDir = "${classesDir}/${libDistDir}"
 +  def syncDir = "${resourceClassesDir}/${libDistDir}"
    from fileTree("${jalviewDir}/${libDistDir}")
    into syncDir
  }
@@@ -1184,7 -993,7 +1184,7 @@@ task syncResources(type: Sync) 
    dependsOn createBuildProperties
    from resourceDir
    include "**/*.*"
 -  into "${classesDir}"
 +  into "${resourceClassesDir}"
    preserve {
      include "**"
    }
@@@ -1201,17 -1010,18 +1201,17 @@@ task prepare 
  //testReportDirName = "test-reports" // note that test workingDir will be $jalviewDir
  test {
    dependsOn prepare
 -  dependsOn compileJava
 -  if (use_clover) {
 -    dependsOn cloverInstr
 -  }
 +  //dependsOn compileJava ////? DELETE
  
 -  if (use_clover) {
 -    print("Running tests " + (use_clover?"WITH":"WITHOUT") + " clover [clover="+use_clover+"]\n")
 +  if (useClover) {
 +    dependsOn cloverClasses
 +   } else { //?
 +     dependsOn compileJava //?
    }
  
    useTestNG() {
 -    includeGroups testngGroups
 -    excludeGroups testngExcludedGroups
 +    includeGroups testng_groups
 +    excludeGroups testng_excluded_groups
      preserveOrder true
      useDefaultListeners=true
    }
  
    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
+   }
    sourceCompatibility = compile_source_compatibility
    targetCompatibility = compile_target_compatibility
    jvmArgs += additional_compiler_args
  
 +  doFirst {
 +    if (useClover) {
 +      println("Running tests " + (useClover?"WITH":"WITHOUT") + " clover")
 +    }
 +  }
  }
  
  
@@@ -1254,22 -1064,22 +1259,22 @@@ task buildIndices(type: JavaExec) 
  
  task compileLinkCheck(type: JavaCompile) {
    options.fork = true
 -  classpath = files("${jalviewDir}/${utilsDir}")
 -  destinationDir = file("${jalviewDir}/${utilsDir}")
 -  source = fileTree(dir: "${jalviewDir}/${utilsDir}", include: ["HelpLinksChecker.java", "BufferedLineReader.java"])
 +  classpath = files("${jalviewDir}/${utils_dir}")
 +  destinationDir = file("${jalviewDir}/${utils_dir}")
 +  source = fileTree(dir: "${jalviewDir}/${utils_dir}", include: ["HelpLinksChecker.java", "BufferedLineReader.java"])
  
 -  inputs.file("${jalviewDir}/${utilsDir}/HelpLinksChecker.java")
 -  inputs.file("${jalviewDir}/${utilsDir}/HelpLinksChecker.java")
 -  outputs.file("${jalviewDir}/${utilsDir}/HelpLinksChecker.class")
 -  outputs.file("${jalviewDir}/${utilsDir}/BufferedLineReader.class")
 +  inputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.java")
 +  inputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.java")
 +  outputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.class")
 +  outputs.file("${jalviewDir}/${utils_dir}/BufferedLineReader.class")
  }
  
  
  task linkCheck(type: JavaExec) {
    dependsOn prepare, compileLinkCheck
  
 -  def helpLinksCheckerOutFile = file("${jalviewDir}/${utilsDir}/HelpLinksChecker.out")
 -  classpath = files("${jalviewDir}/${utilsDir}")
 +  def helpLinksCheckerOutFile = file("${jalviewDir}/${utils_dir}/HelpLinksChecker.out")
 +  classpath = files("${jalviewDir}/${utils_dir}")
    main = "HelpLinksChecker"
    workingDir = jalviewDir
    args = [ "${classesDir}/${help_dir}", "-nointernet" ]
  // import the pubhtmlhelp target
  ant.properties.basedir = "${jalviewDir}"
  ant.properties.helpBuildDir = "${jalviewDirAbsolutePath}/${classes_dir}/${help_dir}"
 -ant.importBuild "${utilsDir}/publishHelp.xml"
 +ant.importBuild "${utils_dir}/publishHelp.xml"
  
  
  task cleanPackageDir(type: Delete) {
    doFirst {
 -    delete fileTree(dir: "${jalviewDir}/${packageDir}", include: "*.jar")
 +    delete fileTree(dir: "${jalviewDir}/${package_dir}", include: "*.jar")
    }
  }
  
@@@ -1306,13 -1116,13 +1311,13 @@@ jar 
    dependsOn createBuildProperties
  
    manifest {
 -    attributes "Main-Class": mainClass,
 +    attributes "Main-Class": main_class,
      "Permissions": "all-permissions",
      "Application-Name": "Jalview Desktop",
      "Codebase": application_codebase
    }
  
 -  destinationDir = file("${jalviewDir}/${packageDir}")
 +  destinationDir = file("${jalviewDir}/${package_dir}")
    archiveName = rootProject.name+".jar"
  
    exclude "cache*/**"
    exclude "**/*.jar.*"
  
    inputs.dir(classesDir)
 -  outputs.file("${jalviewDir}/${packageDir}/${archiveName}")
 +  outputs.file("${jalviewDir}/${package_dir}/${archiveName}")
  }
  
  
  task copyJars(type: Copy) {
    from fileTree(dir: classesDir, include: "**/*.jar").files
 -  into "${jalviewDir}/${packageDir}"
 +  into "${jalviewDir}/${package_dir}"
  }
  
  
  // doing a Sync instead of Copy as Copy doesn't deal with "outputs" very well
  task syncJars(type: Sync) {
    from fileTree(dir: "${jalviewDir}/${libDistDir}", include: "**/*.jar").files
 -  into "${jalviewDir}/${packageDir}"
 +  into "${jalviewDir}/${package_dir}"
    preserve {
      include jar.archiveName
    }
@@@ -1351,7 -1161,7 +1356,7 @@@ task makeDist 
    dependsOn cleanPackageDir
    dependsOn syncJars
    dependsOn jar
 -  outputs.dir("${jalviewDir}/${packageDir}")
 +  outputs.dir("${jalviewDir}/${package_dir}")
  }
  
  
@@@ -1363,7 -1173,6 +1368,7 @@@ task cleanDist 
  
  shadowJar {
    group = "distribution"
 +  description = "Create a single jar file with all dependency libraries merged. Can be run with java -jar"
    if (buildDist) {
      dependsOn makeDist
    }
    manifest {
      attributes 'Implementation-Version': JALVIEW_VERSION
    }
 -  mainClassName = shadowJarMainClass
 +  mainClassName = shadow_jar_main_class
    mergeServiceFiles()
    classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
    minimize()
@@@ -1465,7 -1274,7 +1470,7 @@@ task getdownWebsite() 
      }
  
      def codeFiles = []
 -    fileTree(file(packageDir)).each{ f ->
 +    fileTree(file(package_dir)).each{ f ->
        if (f.isDirectory()) {
          def files = fileTree(dir: f, include: ["*"]).getFiles()
          codeFiles += files
      // 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 = ${mainClass}\n"
 +    getdownTextString += "class = ${main_class}\n"
  
      def getdown_txt = file("${getdownWebsiteDir}/getdown.txt")
      getdown_txt.write(getdownTextString)
    }
  
    if (buildDist) {
 -    inputs.dir("${jalviewDir}/${packageDir}")
 +    inputs.dir("${jalviewDir}/${package_dir}")
    }
    outputs.dir(getdownWebsiteDir)
    outputs.dir(getdownFilesDir)
  
  // a helper task to allow getdown digest of any dir: `gradle getdownDigestDir -PDIGESTDIR=/path/to/my/random/getdown/dir
  task getdownDigestDir(type: JavaExec) {
 +  group "Help"
 +  description "A task to run a getdown Digest on a dir with getdown.txt. Provide a DIGESTDIR property via -PDIGESTDIR=..."
 +
    def digestDirPropertyName = "DIGESTDIR"
 -  description = "Digest a local dir (-P${digestDirPropertyName}=...) for getdown"
    doFirst {
      classpath = files(getdownLauncher)
      def digestDir = findProperty(digestDirPropertyName)
@@@ -1697,8 -1504,7 +1702,8 @@@ task copyInstall4jTemplate 
      // NB we're deleting the /other/ one!
      // Also remove the examples subdir from non-release versions
      def customizedIdToDelete = "PROGRAM_GROUP_RELEASE"
 -    if (CHANNEL=="RELEASE") {
 +    // 2.11.1.0 NOT releasing with the Examples folder in the Program Group
 +    if (false && CHANNEL=="RELEASE") { // remove 'false && ' to include Examples folder in RELEASE channel
        customizedIdToDelete = "PROGRAM_GROUP_NON_RELEASE"
      } else {
        // remove the examples subdir from Full File Set
@@@ -1739,6 -1545,7 +1744,6 @@@ clean 
  task installers(type: com.install4j.gradle.Install4jTask) {
    group = "distribution"
    description = "Create the install4j installers"
 -  dependsOn setGitVals
    dependsOn getdown
    dependsOn copyInstall4jTemplate
  
@@@ -1837,11 -1644,7 +1842,11 @@@ spotless 
  
  
  task sourceDist(type: Tar) {
 +  group "distribution"
 +  description "Create a source .tar.gz file for distribution"
    
 +  dependsOn convertMdFiles
 +
    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]
@@@ -2616,7 -2419,7 +2621,7 @@@ def jalviewjsPublishCoreTemplate(Strin
        beginToken: '_',
        endToken: '_',
        tokens: [
 -        'MAIN': '"'+mainClass+'"',
 +        'MAIN': '"'+main_class+'"',
          'CODE': "null",
          'NAME': jalviewjsJalviewTemplateName+" [core ${coreName}]",
          'COREKEY': jalviewjs_core_key,
@@@ -2917,7 -2720,7 +2922,7 @@@ task jalviewjsIDE_PrepareSite 
    description "Sync libs and resources to site dir, but not closure cores"
  
    dependsOn jalviewjsIDE_SyncSiteAll
 -  dependsOn cleanJalviewjsTransferSite
 +  //dependsOn cleanJalviewjsTransferSite // not sure why this clean is here -- will slow down a re-run of this task
  }
  
  
@@@ -48,6 -48,10 +48,10 @@@ import java.util.Locale
  import java.util.Properties;
  import java.util.StringTokenizer;
  import java.util.TreeSet;
+ import java.util.regex.Pattern;
+ import javax.swing.LookAndFeel;
+ import javax.swing.UIManager;
  
  import org.apache.log4j.ConsoleAppender;
  import org.apache.log4j.Level;
@@@ -313,9 -317,6 +317,9 @@@ public class Cach
        // lcastor = Logger.getLogger("org.exolab.castor.xml.Marshaller");
        // lcastor.setLevel(Level.toLevel(Cache.getDefault("logs.Castor.Level",
        // Level.INFO.toString())));
 +      // we shouldn't need to do this
 +      org.apache.log4j.Logger.getRootLogger().setLevel(org.apache.log4j.Level.INFO); 
 +
        jalview.bin.Cache.log.setLevel(Level.toLevel(Cache
                .getDefault("logs.Jalview.level", Level.INFO.toString())));
        // laxis.addAppender(ap);
              System.getProperty("installer_template_version"), "\n", null);
      appendIfNotNull(sb, "Launcher version: ",
              System.getProperty("launcher_version"), "\n", null);
-     if (jalview.bin.Cache.getDefault("VERSION", "TEST").equals("DEVELOPMENT")) {
+     LookAndFeel laf = UIManager.getLookAndFeel();
+     String lafName = laf == null?"Not obtained":laf.getName();
+     String lafClass = laf == null?"unknown":laf.getClass().getName();
+     appendIfNotNull(sb, "LookAndFeel: ", lafName+" ("+lafClass+")", "\n", null);
+     // Not displayed in release version ( determined by possible version number regex 9[9.]*9[.-_a9]* )
+     if (Pattern.matches("^\\d[\\d\\.]*\\d[\\.\\-\\w]*$", jalview.bin.Cache.getDefault("VERSION", "TEST"))) {
        appendIfNotNull(sb, "Getdown appdir: ",
                System.getProperty("getdownappdir"), "\n", null);
        appendIfNotNull(sb, "Java home: ", System.getProperty("java.home"),
@@@ -43,8 -43,8 +43,8 @@@ import java.util.logging.ConsoleHandler
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
- import javax.swing.LookAndFeel;
  import javax.swing.UIManager;
+ import javax.swing.UIManager.LookAndFeelInfo;
  
  import com.threerings.getdown.util.LaunchUtil;
  
@@@ -367,49 -367,89 +367,89 @@@ public class Jalvie
  
      desktop = null;
  
-     try
+     // property laf = "crossplatform", "system", "gtk", "metal" or "mac"
+     // If not set (or chosen laf fails), use the normal SystemLaF and if on Mac,
+     // try Quaqua/Vaqua.
+     String lafProp = System.getProperty("laf");
+     String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
+     String laf = "none";
+     if (lafProp != null)
      {
-       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-     } catch (Exception ex)
+       laf = lafProp;
+     }
+     else if (lafSetting != null)
      {
-       System.err.println("Unexpected Look and Feel Exception");
-       ex.printStackTrace();
+       laf = lafSetting;
      }
-     if (Platform.isAMacAndNotJS())
+     boolean lafSet = false;
+     switch (laf)
      {
-       LookAndFeel lookAndFeel = ch.randelshofer.quaqua.QuaquaManager
-               .getLookAndFeel();
-       System.setProperty("com.apple.mrj.application.apple.menu.about.name",
-               "Jalview");
-       System.setProperty("apple.laf.useScreenMenuBar", "true");
-       if (lookAndFeel != null)
+     case "crossplatform":
+       lafSet = setCrossPlatformLookAndFeel();
+       if (!lafSet)
        {
-         try
-         {
-           UIManager.setLookAndFeel(lookAndFeel);
-         } catch (Throwable e)
-         {
-           System.err.println(
-                   "Failed to set QuaQua look and feel: " + e.toString());
-         }
+         System.err.println("Could not set requested laf=" + laf);
        }
-       if (lookAndFeel == null
-               || !(lookAndFeel.getClass().isAssignableFrom(
-                       UIManager.getLookAndFeel().getClass()))
-               || !UIManager.getLookAndFeel().getClass().toString()
-                       .toLowerCase().contains("quaqua"))
+       break;
+     case "system":
+       lafSet = setSystemLookAndFeel();
+       if (!lafSet)
        {
-         try
-         {
-           System.err.println(
-                   "Quaqua LaF not available on this plaform. Using VAqua(4).\nSee https://issues.jalview.org/browse/JAL-2976");
-           UIManager.setLookAndFeel("org.violetlib.aqua.AquaLookAndFeel");
-         } catch (Throwable e)
-         {
-           System.err.println(
-                   "Failed to reset look and feel: " + e.toString());
-         }
+         System.err.println("Could not set requested laf=" + laf);
+       }
+       break;
+     case "gtk":
+       lafSet = setGtkLookAndFeel();
+     {
+       System.err.println("Could not set requested laf=" + laf);
+     }
+       break;
+     case "metal":
+       lafSet = setMetalLookAndFeel();
+     {
+       System.err.println("Could not set requested laf=" + laf);
+     }
+       break;
+     case "nimbus":
+       lafSet = setNimbusLookAndFeel();
+     {
+       System.err.println("Could not set requested laf=" + laf);
+     }
+       break;
+     case "quaqua":
+       lafSet = setQuaquaLookAndFeel();
+     {
+       System.err.println("Could not set requested laf=" + laf);
+     }
+       break;
+     case "vaqua":
+       lafSet = setVaquaLookAndFeel();
+     {
+       System.err.println("Could not set requested laf=" + laf);
+     }
+       break;
+     case "mac":
+       lafSet = setMacLookAndFeel();
+       if (!lafSet)
+       {
+         System.err.println("Could not set requested laf=" + laf);
+       }
+       break;
+     case "none":
+       break;
+     default:
+       System.err.println("Requested laf=" + laf + " not implemented");
+     }
+     if (!lafSet)
+     {
+       setSystemLookAndFeel();
+       if (Platform.isLinux() && !Platform.isJS())
+       {
+         setMetalLookAndFeel();
+       }
+       if (Platform.isAMacAndNotJS())
+       {
+         setMacLookAndFeel();
        }
      }
  
      }
    }
  
+   private static boolean setCrossPlatformLookAndFeel()
+   {
+     return setGenericLookAndFeel(false);
+   }
+   private static boolean setSystemLookAndFeel()
+   {
+     return setGenericLookAndFeel(true);
+   }
+   private static boolean setGenericLookAndFeel(boolean system)
+   {
+     boolean set = false;
+     try
+     {
+       UIManager.setLookAndFeel(
+               system ? UIManager.getSystemLookAndFeelClassName()
+                       : UIManager.getCrossPlatformLookAndFeelClassName());
+       set = true;
+     } catch (Exception ex)
+     {
+       System.err.println("Unexpected Look and Feel Exception");
+       ex.printStackTrace();
+     }
+     return set;
+   }
+   private static boolean setSpecificLookAndFeel(String name,
+           String className, boolean nameStartsWith)
+   {
+     boolean set = false;
+     try
+     {
+       for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
+       {
+         if (info.getName() != null && nameStartsWith
+                 ? info.getName().toLowerCase()
+                         .startsWith(name.toLowerCase())
+                 : info.getName().toLowerCase().equals(name.toLowerCase()))
+         {
+           className = info.getClassName();
+           break;
+         }
+       }
+       UIManager.setLookAndFeel(className);
+       set = true;
+     } catch (Exception ex)
+     {
+       System.err.println("Unexpected Look and Feel Exception");
+       ex.printStackTrace();
+     }
+     return set;
+   }
+   private static boolean setGtkLookAndFeel()
+   {
+     return setSpecificLookAndFeel("gtk",
+             "com.sun.java.swing.plaf.gtk.GTKLookAndFeel", true);
+   }
+   private static boolean setMetalLookAndFeel()
+   {
+     return setSpecificLookAndFeel("metal",
+             "javax.swing.plaf.metal.MetalLookAndFeel", false);
+   }
+   private static boolean setNimbusLookAndFeel()
+   {
+     return setSpecificLookAndFeel("nimbus",
+             "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
+   }
+   private static boolean setQuaquaLookAndFeel()
+   {
+     return setSpecificLookAndFeel("quaqua",
+             ch.randelshofer.quaqua.QuaquaManager.getLookAndFeel().getClass()
+                     .getName(),
+             false);
+   }
+   private static boolean setVaquaLookAndFeel()
+   {
+     return setSpecificLookAndFeel("vaqua",
+             "org.violetlib.aqua.AquaLookAndFeel", false);
+   }
+   private static boolean setMacLookAndFeel()
+   {
+     boolean set = false;
+     System.setProperty("com.apple.mrj.application.apple.menu.about.name",
+             "Jalview");
+     System.setProperty("apple.laf.useScreenMenuBar", "true");
+     set = setQuaquaLookAndFeel();
+     if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
+             .toLowerCase().contains("quaqua"))
+     {
+       set = setVaquaLookAndFeel();
+     }
+     return set;
+   }
    private static void showUsage()
    {
      System.out.println(
                      + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
                      + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
                      + "-groovy FILE\tExecute groovy script in FILE, after all other arguments have been processed (if FILE is the text 'STDIN' then the file will be read from STDIN)\n"
 +                    + "-jvmmempc=PERCENT\tOnly available with standalone executable jar or jalview.bin.Launcher. Limit maximum heap size (memory) to PERCENT% of total physical memory detected. This defaults to 90 if total physical memory can be detected. See https://www.jalview.org/help/html/memory.html for more details.\n"
 +                    + "-jvmmemmax=MAXMEMORY\tOnly available with standalone executable jar or jalview.bin.Launcher. Limit maximum heap size (memory) to MAXMEMORY. MAXMEMORY can be specified in bytes, kilobytes(k), megabytes(m), gigabytes(g) or if you're lucky enough, terabytes(t). This defaults to 32g if total physical memory can be detected, or to 8g if total physical memory cannot be detected. See https://www.jalview.org/help/html/memory.html for more details.\n"
                      + "\n~Read documentation in Application or visit http://www.jalview.org for description of Features and Annotations file~\n\n");
    }