JAL-3878 update branch from 2.12 merge from 2.11.2
[jalview.git] / build.gradle
1 /* Convention for properties.  Read from gradle.properties, use lower_case_underlines for property names.
2  * For properties set within build.gradle, use camelCaseNoSpace.
3  */
4 import org.apache.tools.ant.filters.ReplaceTokens
5 import org.gradle.internal.os.OperatingSystem
6 import org.gradle.plugins.ide.internal.generator.PropertiesPersistableConfigurationObject
7 import org.gradle.api.internal.PropertiesTransformer
8 import org.gradle.util.ConfigureUtil
9 import org.gradle.plugins.ide.eclipse.model.Output
10 import org.gradle.plugins.ide.eclipse.model.Library
11 import java.security.MessageDigest
12 import java.util.regex.Matcher
13 import groovy.transform.ExternalizeMethods
14 import groovy.util.XmlParser
15 import groovy.xml.XmlUtil
16 import groovy.json.JsonBuilder
17 import com.vladsch.flexmark.util.ast.Node
18 import com.vladsch.flexmark.html.HtmlRenderer
19 import com.vladsch.flexmark.parser.Parser
20 import com.vladsch.flexmark.util.data.MutableDataSet
21 import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension
22 import com.vladsch.flexmark.ext.tables.TablesExtension
23 import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension
24 import com.vladsch.flexmark.ext.autolink.AutolinkExtension
25 import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension
26 import com.vladsch.flexmark.ext.toc.TocExtension
27 import com.google.common.hash.HashCode
28 import com.google.common.hash.Hashing
29 import com.google.common.io.Files
30 import org.jsoup.Jsoup
31 import org.jsoup.nodes.Element
32
33 buildscript {
34   repositories {
35     mavenCentral()
36     mavenLocal()
37   }
38   dependencies {
39     classpath "com.vladsch.flexmark:flexmark-all:0.62.0"
40     classpath "org.jsoup:jsoup:1.14.3"
41     classpath "com.eowise:gradle-imagemagick:0.5.1"
42   }
43 }
44
45
46 plugins {
47   id 'java'
48   id 'application'
49   id 'eclipse'
50   id "com.diffplug.gradle.spotless" version "3.28.0"
51   id 'com.github.johnrengelman.shadow' version '4.0.3'
52   id 'com.install4j.gradle' version '9.0.6'
53   id 'com.dorongold.task-tree' version '2.1.0' // only needed to display task dependency tree with  gradle task1 [task2 ...] taskTree
54   id 'com.palantir.git-version' version '0.13.0' apply false
55 }
56
57 repositories {
58   jcenter()
59   mavenCentral()
60   mavenLocal()
61 }
62
63
64
65 // in ext the values are cast to Object. Ensure string values are cast as String (and not GStringImpl) for later use
66 def string(Object o) {
67   return o == null ? "" : o.toString()
68 }
69
70 def overrideProperties(String propsFileName, boolean output = false) {
71   if (propsFileName == null) {
72     return
73   }
74   def propsFile = file(propsFileName)
75   if (propsFile != null && propsFile.exists()) {
76     println("Using properties from file '${propsFileName}'")
77     try {
78       def p = new Properties()
79       def localPropsFIS = new FileInputStream(propsFile)
80       p.load(localPropsFIS)
81       localPropsFIS.close()
82       p.each {
83         key, val -> 
84           def oldval
85           if (project.hasProperty(key)) {
86             oldval = project.findProperty(key)
87             project.setProperty(key, val)
88             if (output) {
89               println("Overriding property '${key}' ('${oldval}') with ${file(propsFile).getName()} value '${val}'")
90             }
91           } else {
92             ext.setProperty(key, val)
93             if (output) {
94               println("Setting ext property '${key}' with ${file(propsFile).getName()}s value '${val}'")
95             }
96           }
97       }
98     } catch (Exception e) {
99       println("Exception reading local.properties")
100       e.printStackTrace()
101     }
102   }
103 }
104
105 ext {
106   jalviewDirAbsolutePath = file(jalviewDir).getAbsolutePath()
107   jalviewDirRelativePath = jalviewDir
108   date = new Date()
109
110   getdownChannelName = CHANNEL.toLowerCase()
111   // default to "default". Currently only has different cosmetics for "develop", "release", "default"
112   propertiesChannelName = ["develop", "release", "test-release", "jalviewjs", "jalviewjs-release" ].contains(getdownChannelName) ? getdownChannelName : "default"
113   channelDirName = propertiesChannelName
114   // Import channel_properties
115   if (getdownChannelName.startsWith("develop-")) {
116     channelDirName = "develop-SUFFIX"
117   }
118   channelDir = string("${jalviewDir}/${channel_properties_dir}/${channelDirName}")
119   channelGradleProperties = string("${channelDir}/channel_gradle.properties")
120   channelPropsFile = string("${channelDir}/${resource_dir}/${channel_props}")
121   overrideProperties(channelGradleProperties, false)
122   // local build environment properties
123   // can be "projectDir/local.properties"
124   overrideProperties("${projectDir}/local.properties", true)
125   // or "../projectDir_local.properties"
126   overrideProperties(projectDir.getParent() + "/" + projectDir.getName() + "_local.properties", true)
127
128   ////  
129   // Import releaseProps from the RELEASE file
130   // or a file specified via JALVIEW_RELEASE_FILE if defined
131   // Expect jalview.version and target release branch in jalview.release        
132   releaseProps = new Properties();
133   def releasePropFile = findProperty("JALVIEW_RELEASE_FILE");
134   def defaultReleasePropFile = "${jalviewDirAbsolutePath}/RELEASE";
135   try {
136     (new File(releasePropFile!=null ? releasePropFile : defaultReleasePropFile)).withInputStream { 
137      releaseProps.load(it)
138     }
139   } catch (Exception fileLoadError) {
140     throw new Error("Couldn't load release properties file "+(releasePropFile==null ? defaultReleasePropFile : "from custom location: releasePropFile"),fileLoadError);
141   }
142   ////
143   // Set JALVIEW_VERSION if it is not already set
144   if (findProperty("JALVIEW_VERSION")==null || "".equals(JALVIEW_VERSION)) {
145     JALVIEW_VERSION = releaseProps.get("jalview.version")
146   }
147   println("JALVIEW_VERSION is set to '${JALVIEW_VERSION}'")
148   
149   // this property set when running Eclipse headlessly
150   j2sHeadlessBuildProperty = string("net.sf.j2s.core.headlessbuild")
151   // this property set by Eclipse
152   eclipseApplicationProperty = string("eclipse.application")
153   // CHECK IF RUNNING FROM WITHIN ECLIPSE
154   def eclipseApplicationPropertyVal = System.properties[eclipseApplicationProperty]
155   IN_ECLIPSE = eclipseApplicationPropertyVal != null && eclipseApplicationPropertyVal.startsWith("org.eclipse.ui.")
156   // BUT WITHOUT THE HEADLESS BUILD PROPERTY SET
157   if (System.properties[j2sHeadlessBuildProperty].equals("true")) {
158     println("Setting IN_ECLIPSE to ${IN_ECLIPSE} as System.properties['${j2sHeadlessBuildProperty}'] == '${System.properties[j2sHeadlessBuildProperty]}'")
159     IN_ECLIPSE = false
160   }
161   if (IN_ECLIPSE) {
162     println("WITHIN ECLIPSE IDE")
163   } else {
164     println("HEADLESS BUILD")
165   }
166   
167   J2S_ENABLED = (project.hasProperty('j2s.compiler.status') && project['j2s.compiler.status'] != null && project['j2s.compiler.status'] == "enable")
168   if (J2S_ENABLED) {
169     println("J2S ENABLED")
170   } 
171   /* *-/
172   System.properties.sort { it.key }.each {
173     key, val -> println("SYSTEM PROPERTY ${key}='${val}'")
174   }
175   /-* *-/
176   if (false && IN_ECLIPSE) {
177     jalviewDir = jalviewDirAbsolutePath
178   }
179   */
180
181   // datestamp
182   buildDate = new Date().format("yyyyMMdd")
183
184   // essentials
185   bareSourceDir = string(source_dir)
186   sourceDir = string("${jalviewDir}/${bareSourceDir}")
187   resourceDir = string("${jalviewDir}/${resource_dir}")
188   bareTestSourceDir = string(test_source_dir)
189   testDir = string("${jalviewDir}/${bareTestSourceDir}")
190
191   classesDir = string("${jalviewDir}/${classes_dir}")
192
193   // clover
194   useClover = clover.equals("true")
195   cloverBuildDir = "${buildDir}/clover"
196   cloverInstrDir = file("${cloverBuildDir}/clover-instr")
197   cloverClassesDir = file("${cloverBuildDir}/clover-classes")
198   cloverReportDir = file("${buildDir}/reports/clover")
199   cloverTestInstrDir = file("${cloverBuildDir}/clover-test-instr")
200   cloverTestClassesDir = file("${cloverBuildDir}/clover-test-classes")
201   //cloverTestClassesDir = cloverClassesDir
202   cloverDb = string("${cloverBuildDir}/clover.db")
203
204   testSourceDir = useClover ? cloverTestInstrDir : testDir
205   testClassesDir = useClover ? cloverTestClassesDir : "${jalviewDir}/${test_output_dir}"
206
207   channelSuffix = ""
208   backgroundImageText = BACKGROUNDIMAGETEXT
209   getdownChannelDir = string("${getdown_website_dir}/${propertiesChannelName}")
210   getdownAppBaseDir = string("${jalviewDir}/${getdownChannelDir}/${JAVA_VERSION}")
211   getdownArchiveDir = string("${jalviewDir}/${getdown_archive_dir}")
212   getdownFullArchiveDir = null
213   getdownTextLines = []
214   getdownLaunchJvl = null
215   getdownVersionLaunchJvl = null
216   buildDist = true
217   buildProperties = null
218
219   // the following values might be overridden by the CHANNEL switch
220   getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
221   getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
222   getdownArchiveAppBase = getdown_archive_base
223   getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher}")
224   getdownAppDistDir = getdown_app_dir_alt
225   getdownImagesDir = string("${jalviewDir}/${getdown_images_dir}")
226   getdownImagesBuildDir = string("${buildDir}/imagemagick/getdown")
227   getdownSetAppBaseProperty = false // whether to pass the appbase and appdistdir to the application
228   reportRsyncCommand = false
229   jvlChannelName = CHANNEL.toLowerCase()
230   install4jSuffix = CHANNEL.substring(0, 1).toUpperCase() + CHANNEL.substring(1).toLowerCase(); // BUILD -> Build
231   install4jDMGDSStore = "${install4j_images_dir}/${install4j_dmg_ds_store}"
232   install4jDMGBackgroundImageDir = "${install4j_images_dir}"
233   install4jDMGBackgroundImageBuildDir = "build/imagemagick/install4j"
234   install4jDMGBackgroundImageFile = "${install4j_dmg_background}"
235   install4jInstallerName = "${jalview_name} Non-Release Installer"
236   install4jExecutableName = install4j_executable_name
237   install4jExtraScheme = "jalviewx"
238   install4jMacIconsFile = string("${install4j_images_dir}/${install4j_mac_icons_file}")
239   install4jWindowsIconsFile = string("${install4j_images_dir}/${install4j_windows_icons_file}")
240   install4jPngIconFile = string("${install4j_images_dir}/${install4j_png_icon_file}")
241   install4jBackground = string("${install4j_images_dir}/${install4j_background}")
242   install4jBuildDir = "${install4j_build_dir}/${JAVA_VERSION}"
243   install4jCheckSums = true
244
245   applicationName = "${jalview_name}"
246   switch (CHANNEL) {
247
248     case "BUILD":
249     // TODO: get bamboo build artifact URL for getdown artifacts
250     getdown_channel_base = bamboo_channelbase
251     getdownChannelName = string("${bamboo_planKey}/${JAVA_VERSION}")
252     getdownAppBase = string("${bamboo_channelbase}/${bamboo_planKey}${bamboo_getdown_channel_suffix}/${JAVA_VERSION}")
253     jvlChannelName += "_${getdownChannelName}"
254     // automatically add the test group Not-bamboo for exclusion 
255     if ("".equals(testng_excluded_groups)) { 
256       testng_excluded_groups = "Not-bamboo"
257     }
258     install4jExtraScheme = "jalviewb"
259     backgroundImageText = true
260     break
261
262     case [ "RELEASE", "JALVIEWJS-RELEASE" ]:
263     getdownAppDistDir = getdown_app_dir_release
264     getdownSetAppBaseProperty = true
265     reportRsyncCommand = true
266     install4jSuffix = ""
267     install4jInstallerName = "${jalview_name} Installer"
268     break
269
270     case "ARCHIVE":
271     getdownChannelName = CHANNEL.toLowerCase()+"/${JALVIEW_VERSION}"
272     getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
273     getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
274     if (!file("${ARCHIVEDIR}/${package_dir}").exists()) {
275       throw new GradleException("Must provide an ARCHIVEDIR value to produce an archive distribution")
276     } else {
277       package_dir = string("${ARCHIVEDIR}/${package_dir}")
278       buildProperties = string("${ARCHIVEDIR}/${classes_dir}/${build_properties_file}")
279       buildDist = false
280     }
281     reportRsyncCommand = true
282     install4jExtraScheme = "jalviewa"
283     break
284
285     case "ARCHIVELOCAL":
286     getdownChannelName = string("archive/${JALVIEW_VERSION}")
287     getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
288     getdownAppBase = file(getdownAppBaseDir).toURI().toString()
289     if (!file("${ARCHIVEDIR}/${package_dir}").exists()) {
290       throw new GradleException("Must provide an ARCHIVEDIR value to produce an archive distribution [did not find '${ARCHIVEDIR}/${package_dir}']")
291     } else {
292       package_dir = string("${ARCHIVEDIR}/${package_dir}")
293       buildProperties = string("${ARCHIVEDIR}/${classes_dir}/${build_properties_file}")
294       buildDist = false
295     }
296     reportRsyncCommand = true
297     getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
298     install4jSuffix = "Archive"
299     install4jExtraScheme = "jalviewa"
300     break
301
302     case ~/^DEVELOP-([\.\-\w]*)$/:
303     def suffix = Matcher.lastMatcher[0][1]
304     reportRsyncCommand = true
305     getdownSetAppBaseProperty = true
306     JALVIEW_VERSION=JALVIEW_VERSION+"-d${suffix}-${buildDate}"
307     install4jSuffix = "Develop ${suffix}"
308     install4jExtraScheme = "jalviewd"
309     install4jInstallerName = "${jalview_name} Develop ${suffix} Installer"
310     getdownChannelName = string("develop-${suffix}")
311     getdownChannelDir = string("${getdown_website_dir}/${getdownChannelName}")
312     getdownAppBaseDir = string("${jalviewDir}/${getdownChannelDir}/${JAVA_VERSION}")
313     getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
314     getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
315     channelSuffix = string(suffix)
316     backgroundImageText = true
317     break
318
319     case "DEVELOP":
320     reportRsyncCommand = true
321     getdownSetAppBaseProperty = true
322     // DEVELOP-RELEASE is usually associated with a Jalview release series so set the version
323     JALVIEW_VERSION=JALVIEW_VERSION+"-d${buildDate}"
324     
325     install4jSuffix = "Develop"
326     install4jExtraScheme = "jalviewd"
327     install4jInstallerName = "${jalview_name} Develop Installer"
328     backgroundImageText = true
329     break
330
331     case "TEST-RELEASE":
332     reportRsyncCommand = true
333     getdownSetAppBaseProperty = true
334     // Don't ignore transpile errors for release build
335     if (jalviewjs_ignore_transpile_errors.equals("true")) {
336       jalviewjs_ignore_transpile_errors = "false"
337       println("Setting jalviewjs_ignore_transpile_errors to 'false'")
338     }
339     JALVIEW_VERSION = JALVIEW_VERSION+"-test"
340     install4jSuffix = "Test"
341     install4jExtraScheme = "jalviewt"
342     install4jInstallerName = "${jalview_name} Test Installer"
343     backgroundImageText = true
344     break
345
346     case ~/^SCRATCH(|-[-\w]*)$/:
347     getdownChannelName = CHANNEL
348     JALVIEW_VERSION = JALVIEW_VERSION+"-"+CHANNEL
349     
350     getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
351     getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
352     reportRsyncCommand = true
353     install4jSuffix = "Scratch"
354     break
355
356     case "TEST-LOCAL":
357     if (!file("${LOCALDIR}").exists()) {
358       throw new GradleException("Must provide a LOCALDIR value to produce a local distribution")
359     } else {
360       getdownAppBase = file(file("${LOCALDIR}").getAbsolutePath()).toURI().toString()
361       getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
362     }
363     JALVIEW_VERSION = "TEST"
364     install4jSuffix = "Test-Local"
365     install4jExtraScheme = "jalviewt"
366     install4jInstallerName = "${jalview_name} Test Installer"
367     backgroundImageText = true
368     break
369
370     case [ "LOCAL", "JALVIEWJS" ]:
371     JALVIEW_VERSION = "TEST"
372     getdownAppBase = file(getdownAppBaseDir).toURI().toString()
373     getdownArchiveAppBase = file("${jalviewDir}/${getdown_archive_dir}").toURI().toString()
374     getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
375     install4jExtraScheme = "jalviewl"
376     install4jCheckSums = false
377     break
378
379     default: // something wrong specified
380     throw new GradleException("CHANNEL must be one of BUILD, RELEASE, ARCHIVE, DEVELOP, TEST-RELEASE, SCRATCH-..., LOCAL [default]")
381     break
382
383   }
384   JALVIEW_VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
385   hugoDataJsonFile = file("${jalviewDir}/${hugo_build_dir}/${hugo_data_installers_dir}/installers-${JALVIEW_VERSION_UNDERSCORES}.json")
386   hugoArchiveMdFile = file("${jalviewDir}/${hugo_build_dir}/${hugo_version_archive_dir}/Version-${JALVIEW_VERSION_UNDERSCORES}/_index.md")
387   // override getdownAppBase if requested
388   if (findProperty("getdown_appbase_override") != null) {
389     // revert to LOCAL if empty string
390     if (string(getdown_appbase_override) == "") {
391       getdownAppBase = file(getdownAppBaseDir).toURI().toString()
392       getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
393     } else if (string(getdown_appbase_override).startsWith("file://")) {
394       getdownAppBase = string(getdown_appbase_override)
395       getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
396     } else {
397       getdownAppBase = string(getdown_appbase_override)
398     }
399     println("Overriding getdown appbase with '${getdownAppBase}'")
400   }
401   // sanitise file name for jalview launcher file for this channel
402   jvlChannelName = jvlChannelName.replaceAll("[^\\w\\-]+", "_")
403   // install4j application and folder names
404   if (install4jSuffix == "") {
405     install4jBundleId = "${install4j_bundle_id}"
406     install4jWinApplicationId = install4j_release_win_application_id
407   } else {
408     applicationName = "${jalview_name} ${install4jSuffix}"
409     install4jBundleId = "${install4j_bundle_id}-" + install4jSuffix.toLowerCase()
410     // add int hash of install4jSuffix to the last part of the application_id
411     def id = install4j_release_win_application_id
412     def idsplitreverse = id.split("-").reverse()
413     idsplitreverse[0] = idsplitreverse[0].toInteger() + install4jSuffix.hashCode()
414     install4jWinApplicationId = idsplitreverse.reverse().join("-")
415   }
416   // sanitise folder and id names
417   // install4jApplicationFolder = e.g. "Jalview Build"
418   install4jApplicationFolder = applicationName
419                                     .replaceAll("[\"'~:/\\\\\\s]", "_") // replace all awkward filename chars " ' ~ : / \
420                                     .replaceAll("_+", "_") // collapse __
421   install4jInternalId = applicationName
422                                     .replaceAll(" ","_")
423                                     .replaceAll("[^\\w\\-\\.]", "_") // replace other non [alphanumeric,_,-,.]
424                                     .replaceAll("_+", "") // collapse __
425                                     //.replaceAll("_*-_*", "-") // collapse _-_
426   install4jUnixApplicationFolder = applicationName
427                                     .replaceAll(" ","_")
428                                     .replaceAll("[^\\w\\-\\.]", "_") // replace other non [alphanumeric,_,-,.]
429                                     .replaceAll("_+", "_") // collapse __
430                                     .replaceAll("_*-_*", "-") // collapse _-_
431                                     .toLowerCase()
432
433   getdownWrapperLink = install4jUnixApplicationFolder // e.g. "jalview_local"
434   getdownAppDir = string("${getdownAppBaseDir}/${getdownAppDistDir}")
435   //getdownJ11libDir = "${getdownAppBaseDir}/${getdown_j11lib_dir}"
436   getdownResourceDir = string("${getdownAppBaseDir}/${getdown_resource_dir}")
437   getdownInstallDir = string("${getdownAppBaseDir}/${getdown_install_dir}")
438   getdownFilesDir = string("${jalviewDir}/${getdown_files_dir}/${JAVA_VERSION}/")
439   getdownFilesInstallDir = string("${getdownFilesDir}/${getdown_install_dir}")
440   /* compile without modules -- using classpath libraries
441   modules_compileClasspath = fileTree(dir: "${jalviewDir}/${j11modDir}", include: ["*.jar"])
442   modules_runtimeClasspath = modules_compileClasspath
443   */
444   gitHash = "SOURCE"
445   gitBranch = "Source"
446   try {
447     apply plugin: "com.palantir.git-version"
448     def details = versionDetails()
449     gitHash = details.gitHash
450     gitBranch = details.branchName
451   } catch(org.gradle.api.internal.plugins.PluginApplicationException e) {
452     println("Not in a git repository. Using git values from RELEASE properties file.")
453     gitHash = releaseProps.getProperty("git.hash")
454     gitBranch = releaseProps.getProperty("git.branch")
455   } catch(java.lang.RuntimeException e1) {
456     throw new GradleException("Error with git-version plugin.  Directory '.git' exists but versionDetails() cannot be found.")
457   }
458
459   println("Using a ${CHANNEL} profile.")
460
461   additional_compiler_args = []
462   // configure classpath/args for j8/j11 compilation
463   if (JAVA_VERSION.equals("1.8")) {
464     JAVA_INTEGER_VERSION = string("8")
465     //libDir = j8libDir
466     libDir = j11libDir
467     libDistDir = j8libDir
468     compile_source_compatibility = 1.8
469     compile_target_compatibility = 1.8
470     // these are getdown.txt properties defined dependent on the JAVA_VERSION
471     getdownAltJavaMinVersion = string(findProperty("getdown_alt_java8_min_version"))
472     getdownAltJavaMaxVersion = string(findProperty("getdown_alt_java8_max_version"))
473     // this property is assigned below and expanded to multiple lines in the getdown task
474     getdownAltMultiJavaLocation = string(findProperty("getdown_alt_java8_txt_multi_java_location"))
475     // this property is for the Java library used in eclipse
476     eclipseJavaRuntimeName = string("JavaSE-1.8")
477   } else if (JAVA_VERSION.equals("11")) {
478     JAVA_INTEGER_VERSION = string("11")
479     libDir = j11libDir
480     libDistDir = j11libDir
481     compile_source_compatibility = 11
482     compile_target_compatibility = 11
483     getdownAltJavaMinVersion = string(findProperty("getdown_alt_java11_min_version"))
484     getdownAltJavaMaxVersion = string(findProperty("getdown_alt_java11_max_version"))
485     getdownAltMultiJavaLocation = string(findProperty("getdown_alt_java11_txt_multi_java_location"))
486     eclipseJavaRuntimeName = string("JavaSE-11")
487     /* compile without modules -- using classpath libraries
488     additional_compiler_args += [
489     '--module-path', modules_compileClasspath.asPath,
490     '--add-modules', j11modules
491     ]
492      */
493   } else if (JAVA_VERSION.equals("17")) {
494     JAVA_INTEGER_VERSION = string("17")
495     libDir = j17libDir
496     libDistDir = j17libDir
497     compile_source_compatibility = 17
498     compile_target_compatibility = 17
499     getdownAltJavaMinVersion = string(findProperty("getdown_alt_java11_min_version"))
500     getdownAltJavaMaxVersion = string(findProperty("getdown_alt_java11_max_version"))
501     getdownAltMultiJavaLocation = string(findProperty("getdown_alt_java11_txt_multi_java_location"))
502     eclipseJavaRuntimeName = string("JavaSE-17")
503     /* compile without modules -- using classpath libraries
504     additional_compiler_args += [
505     '--module-path', modules_compileClasspath.asPath,
506     '--add-modules', j11modules
507     ]
508      */
509   } else {
510     throw new GradleException("JAVA_VERSION=${JAVA_VERSION} not currently supported by Jalview")
511   }
512
513
514   // for install4j
515   JAVA_MIN_VERSION = JAVA_VERSION
516   JAVA_MAX_VERSION = JAVA_VERSION
517   def jreInstallsDir = string(jre_installs_dir)
518   if (jreInstallsDir.startsWith("~/")) {
519     jreInstallsDir = System.getProperty("user.home") + jreInstallsDir.substring(1)
520   }
521   macosJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-mac-x64/jre")
522   windowsJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-windows-x64/jre")
523   linuxJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-linux-x64/jre")
524   macosJavaVMTgz = string("${jreInstallsDir}/tgz/jre_${JAVA_INTEGER_VERSION}_mac_x64.tar.gz")
525   windowsJavaVMTgz = string("${jreInstallsDir}/tgz/jre_${JAVA_INTEGER_VERSION}_windows_x64.tar.gz")
526   linuxJavaVMTgz = string("${jreInstallsDir}/tgz/jre_${JAVA_INTEGER_VERSION}_linux_x64.tar.gz")
527   install4jDir = string("${jalviewDir}/${install4j_utils_dir}")
528   install4jConfFileName = string("jalview-install4j-conf.install4j")
529   install4jConfFile = file("${install4jDir}/${install4jConfFileName}")
530   install4jHomeDir = install4j_home_dir
531   if (install4jHomeDir.startsWith("~/")) {
532     install4jHomeDir = System.getProperty("user.home") + install4jHomeDir.substring(1)
533   }
534
535   resourceBuildDir = string("${buildDir}/resources")
536   resourcesBuildDir = string("${resourceBuildDir}/resources_build")
537   helpBuildDir = string("${resourceBuildDir}/help_build")
538   docBuildDir = string("${resourceBuildDir}/doc_build")
539
540   if (buildProperties == null) {
541     buildProperties = string("${resourcesBuildDir}/${build_properties_file}")
542   }
543   buildingHTML = string("${jalviewDir}/${doc_dir}/building.html")
544   helpParentDir = string("${jalviewDir}/${help_parent_dir}")
545   helpSourceDir = string("${helpParentDir}/${help_dir}")
546   helpFile = string("${helpBuildDir}/${help_dir}/help.jhm")
547
548   convertBinary = null
549   convertBinaryExpectedLocation = imagemagick_convert
550   if (convertBinaryExpectedLocation.startsWith("~/")) {
551     convertBinaryExpectedLocation = System.getProperty("user.home") + convertBinaryExpectedLocation.substring(1)
552   }
553   if (file(convertBinaryExpectedLocation).exists()) {
554     convertBinary = convertBinaryExpectedLocation
555   }
556
557   relativeBuildDir = file(jalviewDirAbsolutePath).toPath().relativize(buildDir.toPath())
558   jalviewjsBuildDir = string("${relativeBuildDir}/jalviewjs")
559   jalviewjsSiteDir = string("${jalviewjsBuildDir}/${jalviewjs_site_dir}")
560   if (IN_ECLIPSE) {
561     jalviewjsTransferSiteJsDir = string(jalviewjsSiteDir)
562   } else {
563     jalviewjsTransferSiteJsDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_js")
564   }
565   jalviewjsTransferSiteLibDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_lib")
566   jalviewjsTransferSiteSwingJsDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_swingjs")
567   jalviewjsTransferSiteCoreDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_core")
568   jalviewjsJalviewCoreHtmlFile = string("")
569   jalviewjsJalviewCoreName = string(jalviewjs_core_name)
570   jalviewjsCoreClasslists = []
571   jalviewjsJalviewTemplateName = string(jalviewjs_name)
572   jalviewjsJ2sSettingsFileName = string("${jalviewDir}/${jalviewjs_j2s_settings}")
573   jalviewjsJ2sAltSettingsFileName = string("${jalviewDir}/${jalviewjs_j2s_alt_settings}")
574   jalviewjsJ2sProps = null
575   jalviewjsJ2sPlugin = jalviewjs_j2s_plugin
576
577   eclipseWorkspace = null
578   eclipseBinary = string("")
579   eclipseVersion = string("")
580   eclipseDebug = false
581   // ENDEXT
582 }
583
584
585 sourceSets {
586   main {
587     java {
588       srcDirs sourceDir
589       outputDir = file(classesDir)
590     }
591
592     resources {
593       srcDirs = [ resourcesBuildDir, docBuildDir, helpBuildDir ]
594     }
595
596     compileClasspath = files(sourceSets.main.java.outputDir)
597     compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
598
599     runtimeClasspath = compileClasspath
600     runtimeClasspath += files(sourceSets.main.resources.srcDirs)
601   }
602
603   clover {
604     java {
605       srcDirs cloverInstrDir
606       outputDir = cloverClassesDir
607     }
608
609     resources {
610       srcDirs = sourceSets.main.resources.srcDirs
611     }
612
613     compileClasspath = files( sourceSets.clover.java.outputDir )
614     //compileClasspath += files( testClassesDir )
615     compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
616     compileClasspath += fileTree(dir: "${jalviewDir}/${clover_lib_dir}", include: ["*.jar"])
617     compileClasspath += fileTree(dir: "${jalviewDir}/${utils_dir}/testnglibs", include: ["**/*.jar"])
618
619     runtimeClasspath = compileClasspath
620   }
621
622   test {
623     java {
624       srcDirs testSourceDir
625       outputDir = file(testClassesDir)
626     }
627
628     resources {
629       srcDirs = useClover ? sourceSets.clover.resources.srcDirs : sourceSets.main.resources.srcDirs
630     }
631
632     compileClasspath = files( sourceSets.test.java.outputDir )
633     compileClasspath += useClover ? sourceSets.clover.compileClasspath : sourceSets.main.compileClasspath
634     compileClasspath += fileTree(dir: "${jalviewDir}/${utils_dir}/testnglibs", include: ["**/*.jar"])
635
636     runtimeClasspath = compileClasspath
637     runtimeClasspath += files(sourceSets.test.resources.srcDirs)
638   }
639
640 }
641
642
643 // eclipse project and settings files creation, also used by buildship
644 eclipse {
645   project {
646     name = eclipse_project_name
647
648     natures 'org.eclipse.jdt.core.javanature',
649     'org.eclipse.jdt.groovy.core.groovyNature',
650     'org.eclipse.buildship.core.gradleprojectnature'
651
652     buildCommand 'org.eclipse.jdt.core.javabuilder'
653     buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder'
654   }
655
656   classpath {
657     //defaultOutputDir = sourceSets.main.java.outputDir
658     configurations.each{ c->
659       if (c.isCanBeResolved()) {
660         minusConfigurations += [c]
661       }
662     }
663
664     plusConfigurations = [ ]
665     file {
666
667       whenMerged { cp ->
668         def removeTheseToo = []
669         HashMap<String, Boolean> alreadyAddedSrcPath = new HashMap<>();
670         cp.entries.each { entry ->
671           // This conditional removes all src classpathentries that a) have already been added or b) aren't "src" or "test".
672           // e.g. this removes the resources dir being copied into bin/main, bin/test AND bin/clover
673           // we add the resources and help/help dirs in as libs afterwards (see below)
674           if (entry.kind == 'src') {
675             if (alreadyAddedSrcPath.getAt(entry.path) || !(entry.path == bareSourceDir || entry.path == bareTestSourceDir)) {
676               removeTheseToo += entry
677             } else {
678               alreadyAddedSrcPath.putAt(entry.path, true)
679             }
680           }
681
682         }
683         cp.entries.removeAll(removeTheseToo)
684
685         //cp.entries += new Output("${eclipse_bin_dir}/main")
686         if (file(helpParentDir).isDirectory()) {
687           cp.entries += new Library(fileReference(helpParentDir))
688         }
689         if (file(resourceDir).isDirectory()) {
690           cp.entries += new Library(fileReference(resourceDir))
691         }
692
693         HashMap<String, Boolean> alreadyAddedLibPath = new HashMap<>();
694
695         sourceSets.main.compileClasspath.findAll { it.name.endsWith(".jar") }.any {
696           //don't want to add outputDir as eclipse is using its own output dir in bin/main
697           if (it.isDirectory() || ! it.exists()) {
698             // don't add dirs to classpath, especially if they don't exist
699             return false // groovy "continue" in .any closure
700           }
701           def itPath = it.toString()
702           if (itPath.startsWith("${jalviewDirAbsolutePath}/")) {
703             // make relative path
704             itPath = itPath.substring(jalviewDirAbsolutePath.length()+1)
705           }
706           if (alreadyAddedLibPath.get(itPath)) {
707             //println("Not adding duplicate entry "+itPath)
708           } else {
709             //println("Adding entry "+itPath)
710             cp.entries += new Library(fileReference(itPath))
711             alreadyAddedLibPath.put(itPath, true)
712           }
713         }
714
715         sourceSets.test.compileClasspath.findAll { it.name.endsWith(".jar") }.any {
716           //no longer want to add outputDir as eclipse is using its own output dir in bin/main
717           if (it.isDirectory() || ! it.exists()) {
718             // don't add dirs to classpath
719             return false // groovy "continue" in .any closure
720           }
721
722           def itPath = it.toString()
723           if (itPath.startsWith("${jalviewDirAbsolutePath}/")) {
724             itPath = itPath.substring(jalviewDirAbsolutePath.length()+1)
725           }
726           if (alreadyAddedLibPath.get(itPath)) {
727             // don't duplicate
728           } else {
729             def lib = new Library(fileReference(itPath))
730             lib.entryAttributes["test"] = "true"
731             cp.entries += lib
732             alreadyAddedLibPath.put(itPath, true)
733           }
734         }
735
736       } // whenMerged
737
738     } // file
739
740     containers 'org.eclipse.buildship.core.gradleclasspathcontainer'
741
742   } // classpath
743
744   jdt {
745     // for the IDE, use java 11 compatibility
746     sourceCompatibility = compile_source_compatibility
747     targetCompatibility = compile_target_compatibility
748     javaRuntimeName = eclipseJavaRuntimeName
749
750     // add in jalview project specific properties/preferences into eclipse core preferences
751     file {
752       withProperties { props ->
753         def jalview_prefs = new Properties()
754         def ins = new FileInputStream("${jalviewDirAbsolutePath}/${eclipse_extra_jdt_prefs_file}")
755         jalview_prefs.load(ins)
756         ins.close()
757         jalview_prefs.forEach { t, v ->
758           if (props.getAt(t) == null) {
759             props.putAt(t, v)
760           }
761         }
762         // codestyle file -- overrides previous formatter prefs
763         def csFile = file("${jalviewDirAbsolutePath}/${eclipse_codestyle_file}")
764         if (csFile.exists()) {
765           XmlParser parser = new XmlParser()
766           def profiles = parser.parse(csFile)
767           def profile = profiles.'profile'.find { p -> (p.'@kind' == "CodeFormatterProfile" && p.'@name' == "Jalview") }
768           if (profile != null) {
769             profile.'setting'.each { s ->
770               def id = s.'@id'
771               def value = s.'@value'
772               if (id != null && value != null) {
773                 props.putAt(id, value)
774               }
775             }
776           }
777         }
778       }
779     }
780
781   } // jdt
782
783   if (IN_ECLIPSE) {
784     // Don't want these to be activated if in headless build
785     synchronizationTasks "eclipseSynchronizationTask"
786     //autoBuildTasks "eclipseAutoBuildTask"
787
788   }
789 }
790
791
792 /* hack to change eclipse prefs in .settings files other than org.eclipse.jdt.core.prefs */
793 // Class to allow updating arbitrary properties files
794 class PropertiesFile extends PropertiesPersistableConfigurationObject {
795   public PropertiesFile(PropertiesTransformer t) { super(t); }
796   @Override protected void load(Properties properties) { }
797   @Override protected void store(Properties properties) { }
798   @Override protected String getDefaultResourceName() { return ""; }
799   // This is necessary, because PropertiesPersistableConfigurationObject fails
800   // if no default properties file exists.
801   @Override public void loadDefaults() { load(new StringBufferInputStream("")); }
802 }
803
804 // Task to update arbitrary properties files (set outputFile)
805 class PropertiesFileTask extends PropertiesGeneratorTask<PropertiesFile> {
806   private final PropertiesFileContentMerger file;
807   public PropertiesFileTask() { file = new PropertiesFileContentMerger(getTransformer()); }
808   protected PropertiesFile create() { return new PropertiesFile(getTransformer()); }
809   protected void configure(PropertiesFile props) {
810     file.getBeforeMerged().execute(props); file.getWhenMerged().execute(props);
811   }
812   public void file(Closure closure) { ConfigureUtil.configure(closure, file); }
813 }
814
815 task eclipseUIPreferences(type: PropertiesFileTask) {
816   description = "Generate Eclipse additional settings"
817   def filename = "org.eclipse.jdt.ui.prefs"
818   outputFile = "$projectDir/.settings/${filename}" as File
819   file {
820     withProperties {
821       it.load new FileInputStream("$projectDir/utils/eclipse/${filename}" as String)
822     }
823   }
824 }
825
826 task eclipseGroovyCorePreferences(type: PropertiesFileTask) {
827   description = "Generate Eclipse additional settings"
828   def filename = "org.eclipse.jdt.groovy.core.prefs"
829   outputFile = "$projectDir/.settings/${filename}" as File
830   file {
831     withProperties {
832       it.load new FileInputStream("$projectDir/utils/eclipse/${filename}" as String)
833     }
834   }
835 }
836
837 task eclipseAllPreferences {
838   dependsOn eclipseJdt
839   dependsOn eclipseUIPreferences
840   dependsOn eclipseGroovyCorePreferences
841 }
842
843 eclipseUIPreferences.mustRunAfter eclipseJdt
844 eclipseGroovyCorePreferences.mustRunAfter eclipseJdt
845
846 /* end of eclipse preferences hack */
847
848
849 // clover bits
850
851
852 task cleanClover {
853   doFirst {
854     delete cloverBuildDir
855     delete cloverReportDir
856   }
857 }
858
859
860 task cloverInstrJava(type: JavaExec) {
861   group = "Verification"
862   description = "Create clover instrumented source java files"
863
864   dependsOn cleanClover
865
866   inputs.files(sourceSets.main.allJava)
867   outputs.dir(cloverInstrDir)
868
869   //classpath = fileTree(dir: "${jalviewDir}/${clover_lib_dir}", include: ["*.jar"])
870   classpath = sourceSets.clover.compileClasspath
871   main = "com.atlassian.clover.CloverInstr"
872
873   def argsList = [
874     "--encoding",
875     "UTF-8",
876     "--initstring",
877     cloverDb,
878     "--destdir",
879     cloverInstrDir.getPath(),
880   ]
881   def srcFiles = sourceSets.main.allJava.files
882   argsList.addAll(
883     srcFiles.collect(
884       { file -> file.absolutePath }
885     )
886   )
887   args argsList.toArray()
888
889   doFirst {
890     delete cloverInstrDir
891     println("Clover: About to instrument "+srcFiles.size() +" files")
892   }
893 }
894
895
896 task cloverInstrTests(type: JavaExec) {
897   group = "Verification"
898   description = "Create clover instrumented source test files"
899
900   dependsOn cleanClover
901
902   inputs.files(testDir)
903   outputs.dir(cloverTestInstrDir)
904
905   classpath = sourceSets.clover.compileClasspath
906   main = "com.atlassian.clover.CloverInstr"
907
908   def argsList = [
909     "--encoding",
910     "UTF-8",
911     "--initstring",
912     cloverDb,
913     "--srcdir",
914     testDir,
915     "--destdir",
916     cloverTestInstrDir.getPath(),
917   ]
918   args argsList.toArray()
919
920   doFirst {
921     delete cloverTestInstrDir
922     println("Clover: About to instrument test files")
923   }
924 }
925
926
927 task cloverInstr {
928   group = "Verification"
929   description = "Create clover instrumented all source files"
930
931   dependsOn cloverInstrJava
932   dependsOn cloverInstrTests
933 }
934
935
936 cloverClasses.dependsOn cloverInstr
937
938
939 task cloverConsoleReport(type: JavaExec) {
940   group = "Verification"
941   description = "Creates clover console report"
942
943   onlyIf {
944     file(cloverDb).exists()
945   }
946
947   inputs.dir cloverClassesDir
948
949   classpath = sourceSets.clover.runtimeClasspath
950   main = "com.atlassian.clover.reporters.console.ConsoleReporter"
951
952   if (cloverreport_mem.length() > 0) {
953     maxHeapSize = cloverreport_mem
954   }
955   if (cloverreport_jvmargs.length() > 0) {
956     jvmArgs Arrays.asList(cloverreport_jvmargs.split(" "))
957   }
958
959   def argsList = [
960     "--alwaysreport",
961     "--initstring",
962     cloverDb,
963     "--unittests"
964   ]
965
966   args argsList.toArray()
967 }
968
969
970 task cloverHtmlReport(type: JavaExec) {
971   group = "Verification"
972   description = "Creates clover HTML report"
973
974   onlyIf {
975     file(cloverDb).exists()
976   }
977
978   def cloverHtmlDir = cloverReportDir
979   inputs.dir cloverClassesDir
980   outputs.dir cloverHtmlDir
981
982   classpath = sourceSets.clover.runtimeClasspath
983   main = "com.atlassian.clover.reporters.html.HtmlReporter"
984
985   if (cloverreport_mem.length() > 0) {
986     maxHeapSize = cloverreport_mem
987   }
988   if (cloverreport_jvmargs.length() > 0) {
989     jvmArgs Arrays.asList(cloverreport_jvmargs.split(" "))
990   }
991
992   def argsList = [
993     "--alwaysreport",
994     "--initstring",
995     cloverDb,
996     "--outputdir",
997     cloverHtmlDir
998   ]
999
1000   if (cloverreport_html_options.length() > 0) {
1001     argsList += cloverreport_html_options.split(" ")
1002   }
1003
1004   args argsList.toArray()
1005 }
1006
1007
1008 task cloverXmlReport(type: JavaExec) {
1009   group = "Verification"
1010   description = "Creates clover XML report"
1011
1012   onlyIf {
1013     file(cloverDb).exists()
1014   }
1015
1016   def cloverXmlFile = "${cloverReportDir}/clover.xml"
1017   inputs.dir cloverClassesDir
1018   outputs.file cloverXmlFile
1019
1020   classpath = sourceSets.clover.runtimeClasspath
1021   main = "com.atlassian.clover.reporters.xml.XMLReporter"
1022
1023   if (cloverreport_mem.length() > 0) {
1024     maxHeapSize = cloverreport_mem
1025   }
1026   if (cloverreport_jvmargs.length() > 0) {
1027     jvmArgs Arrays.asList(cloverreport_jvmargs.split(" "))
1028   }
1029
1030   def argsList = [
1031     "--alwaysreport",
1032     "--initstring",
1033     cloverDb,
1034     "--outfile",
1035     cloverXmlFile
1036   ]
1037
1038   if (cloverreport_xml_options.length() > 0) {
1039     argsList += cloverreport_xml_options.split(" ")
1040   }
1041
1042   args argsList.toArray()
1043 }
1044
1045
1046 task cloverReport {
1047   group = "Verification"
1048   description = "Creates clover reports"
1049
1050   dependsOn cloverXmlReport
1051   dependsOn cloverHtmlReport
1052 }
1053
1054
1055 compileCloverJava {
1056
1057   doFirst {
1058     sourceCompatibility = compile_source_compatibility
1059     targetCompatibility = compile_target_compatibility
1060     options.compilerArgs += additional_compiler_args
1061     print ("Setting target compatibility to "+targetCompatibility+"\n")
1062   }
1063   //classpath += configurations.cloverRuntime
1064 }
1065 // end clover bits
1066
1067
1068 compileJava {
1069   // JBP->BS should the print statement in doFirst refer to compile_target_compatibility ?
1070   sourceCompatibility = compile_source_compatibility
1071   targetCompatibility = compile_target_compatibility
1072   options.compilerArgs = additional_compiler_args
1073   options.encoding = "UTF-8"
1074   doFirst {
1075     print ("Setting target compatibility to "+compile_target_compatibility+"\n")
1076   }
1077
1078 }
1079
1080
1081 compileTestJava {
1082   sourceCompatibility = compile_source_compatibility
1083   targetCompatibility = compile_target_compatibility
1084   options.compilerArgs = additional_compiler_args
1085   doFirst {
1086     print ("Setting target compatibility to "+targetCompatibility+"\n")
1087   }
1088 }
1089
1090
1091 clean {
1092   doFirst {
1093     delete sourceSets.main.java.outputDir
1094   }
1095 }
1096
1097
1098 cleanTest {
1099   dependsOn cleanClover
1100   doFirst {
1101     delete sourceSets.test.java.outputDir
1102   }
1103 }
1104
1105
1106 // format is a string like date.format("dd MMMM yyyy")
1107 def getDate(format) {
1108   return date.format(format)
1109 }
1110
1111
1112 def convertMdToHtml (FileTree mdFiles, File cssFile) {
1113   MutableDataSet options = new MutableDataSet()
1114
1115   def extensions = new ArrayList<>()
1116   extensions.add(AnchorLinkExtension.create()) 
1117   extensions.add(AutolinkExtension.create())
1118   extensions.add(StrikethroughExtension.create())
1119   extensions.add(TaskListExtension.create())
1120   extensions.add(TablesExtension.create())
1121   extensions.add(TocExtension.create())
1122   
1123   options.set(Parser.EXTENSIONS, extensions)
1124
1125   // set GFM table parsing options
1126   options.set(TablesExtension.WITH_CAPTION, false)
1127   options.set(TablesExtension.COLUMN_SPANS, false)
1128   options.set(TablesExtension.MIN_HEADER_ROWS, 1)
1129   options.set(TablesExtension.MAX_HEADER_ROWS, 1)
1130   options.set(TablesExtension.APPEND_MISSING_COLUMNS, true)
1131   options.set(TablesExtension.DISCARD_EXTRA_COLUMNS, true)
1132   options.set(TablesExtension.HEADER_SEPARATOR_COLUMN_MATCH, true)
1133   // GFM anchor links
1134   options.set(AnchorLinkExtension.ANCHORLINKS_SET_ID, false)
1135   options.set(AnchorLinkExtension.ANCHORLINKS_ANCHOR_CLASS, "anchor")
1136   options.set(AnchorLinkExtension.ANCHORLINKS_SET_NAME, true)
1137   options.set(AnchorLinkExtension.ANCHORLINKS_TEXT_PREFIX, "<span class=\"octicon octicon-link\"></span>")
1138
1139   Parser parser = Parser.builder(options).build()
1140   HtmlRenderer renderer = HtmlRenderer.builder(options).build()
1141
1142   mdFiles.each { mdFile ->
1143     // add table of contents
1144     def mdText = "[TOC]\n"+mdFile.text
1145
1146     // grab the first top-level title
1147     def title = null
1148     def titleRegex = /(?m)^#(\s+|([^#]))(.*)/
1149     def matcher = mdText =~ titleRegex
1150     if (matcher.size() > 0) {
1151       // matcher[0][2] is the first character of the title if there wasn't any whitespace after the #
1152       title = (matcher[0][2] != null ? matcher[0][2] : "")+matcher[0][3]
1153     }
1154     // or use the filename if none found
1155     if (title == null) {
1156       title = mdFile.getName()
1157     }
1158
1159     Node document = parser.parse(mdText)
1160     String htmlBody = renderer.render(document)
1161     def htmlText = '''<html>
1162 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1163 <html xmlns="http://www.w3.org/1999/xhtml">
1164   <head>
1165     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
1166     <meta http-equiv="Content-Style-Type" content="text/css" />
1167     <meta name="generator" content="flexmark" />
1168 '''
1169     htmlText += ((title != null) ? "  <title>${title}</title>" : '' )
1170     htmlText += '''
1171     <style type="text/css">code{white-space: pre;}</style>
1172 '''
1173     htmlText += ((cssFile != null) ? cssFile.text : '')
1174     htmlText += '''</head>
1175   <body>
1176 '''
1177     htmlText += htmlBody
1178     htmlText += '''
1179   </body>
1180 </html>
1181 '''
1182
1183     def htmlFilePath = mdFile.getPath().replaceAll(/\..*?$/, ".html")
1184     def htmlFile = file(htmlFilePath)
1185     println("Creating ${htmlFilePath}")
1186     htmlFile.text = htmlText
1187   }
1188 }
1189
1190
1191 task copyDocs(type: Copy) {
1192   def inputDir = "${jalviewDir}/${doc_dir}"
1193   def outputDir = "${docBuildDir}/${doc_dir}"
1194   from(inputDir) {
1195     include('**/*.txt')
1196     include('**/*.md')
1197     include('**/*.html')
1198     include('**/*.xml')
1199     filter(ReplaceTokens,
1200       beginToken: '$$',
1201       endToken: '$$',
1202       tokens: [
1203         'Version-Rel': JALVIEW_VERSION,
1204         'Year-Rel': getDate("yyyy")
1205       ]
1206     )
1207   }
1208   from(inputDir) {
1209     exclude('**/*.txt')
1210     exclude('**/*.md')
1211     exclude('**/*.html')
1212     exclude('**/*.xml')
1213   }
1214   into outputDir
1215
1216   inputs.dir(inputDir)
1217   outputs.dir(outputDir)
1218 }
1219
1220
1221 task convertMdFiles {
1222   dependsOn copyDocs
1223   def mdFiles = fileTree(dir: docBuildDir, include: "**/*.md")
1224   def cssFile = file("${jalviewDir}/${flexmark_css}")
1225
1226   doLast {
1227     convertMdToHtml(mdFiles, cssFile)
1228   }
1229
1230   inputs.files(mdFiles)
1231   inputs.file(cssFile)
1232
1233   def htmlFiles = []
1234   mdFiles.each { mdFile ->
1235     def htmlFilePath = mdFile.getPath().replaceAll(/\..*?$/, ".html")
1236     htmlFiles.add(file(htmlFilePath))
1237   }
1238   outputs.files(htmlFiles)
1239 }
1240
1241
1242 def hugoTemplateSubstitutions(String input, Map extras=null) {
1243   def replacements = [
1244     DATE: getDate("yyyy-MM-dd"),
1245     CHANNEL: propertiesChannelName,
1246     APPLICATION_NAME: applicationName,
1247     GIT_HASH: gitHash,
1248     GIT_BRANCH: gitBranch,
1249     VERSION: JALVIEW_VERSION,
1250     JAVA_VERSION: JAVA_VERSION,
1251     VERSION_UNDERSCORES: JALVIEW_VERSION_UNDERSCORES,
1252     DRAFT: "false",
1253     JVL_HEADER: ""
1254   ]
1255   def output = input
1256   if (extras != null) {
1257     extras.each{ k, v ->
1258       output = output.replaceAll("__${k}__", ((v == null)?"":v))
1259     }
1260   }
1261   replacements.each{ k, v ->
1262     output = output.replaceAll("__${k}__", ((v == null)?"":v))
1263   }
1264   return output
1265 }
1266
1267 def mdFileComponents(File mdFile, def dateOnly=false) {
1268   def map = [:]
1269   def content = ""
1270   if (mdFile.exists()) {
1271     def inFrontMatter = false
1272     def firstLine = true
1273     mdFile.eachLine { line ->
1274       if (line.matches("---")) {
1275         def prev = inFrontMatter
1276         inFrontMatter = firstLine
1277         if (inFrontMatter != prev)
1278           return false
1279       }
1280       if (inFrontMatter) {
1281         def m = null
1282         if (m = line =~ /^date:\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/) {
1283           map["date"] = new Date().parse("yyyy-MM-dd HH:mm:ss", m[0][1])
1284         } else if (m = line =~ /^date:\s*(\d{4}-\d{2}-\d{2})/) {
1285           map["date"] = new Date().parse("yyyy-MM-dd", m[0][1])
1286         } else if (m = line =~ /^channel:\s*(\S+)/) {
1287           map["channel"] = m[0][1]
1288         } else if (m = line =~ /^version:\s*(\S+)/) {
1289           map["version"] = m[0][1]
1290         } else if (m = line =~ /^\s*([^:]+)\s*:\s*(\S.*)/) {
1291           map[ m[0][1] ] = m[0][2]
1292         }
1293         if (dateOnly && map["date"] != null) {
1294           return false
1295         }
1296       } else {
1297         if (dateOnly)
1298           return false
1299         content += line+"\n"
1300       }
1301       firstLine = false
1302     }
1303   }
1304   return dateOnly ? map["date"] : [map, content]
1305 }
1306
1307 task hugoTemplates {
1308   group "website"
1309   description "Create partially populated md pages for hugo website build"
1310
1311   def hugoTemplatesDir = file("${jalviewDir}/${hugo_templates_dir}")
1312   def hugoBuildDir = "${jalviewDir}/${hugo_build_dir}"
1313   def templateFiles = fileTree(dir: hugoTemplatesDir)
1314   def releaseMdFile = file("${jalviewDir}/${releases_dir}/release-${JALVIEW_VERSION_UNDERSCORES}.md")
1315   def whatsnewMdFile = file("${jalviewDir}/${whatsnew_dir}/whatsnew-${JALVIEW_VERSION_UNDERSCORES}.md")
1316   def oldJvlFile = file("${jalviewDir}/${hugo_old_jvl}")
1317   def jalviewjsFile = file("${jalviewDir}/${hugo_jalviewjs}")
1318
1319   doFirst {
1320     // specific release template for version archive
1321     def changes = ""
1322     def whatsnew = null
1323     def givenDate = null
1324     def givenChannel = null
1325     def givenVersion = null
1326     if (CHANNEL == "RELEASE") {
1327       def (map, content) = mdFileComponents(releaseMdFile)
1328       givenDate = map.date
1329       givenChannel = map.channel
1330       givenVersion = map.version
1331       changes = content
1332       if (givenVersion != null && givenVersion != JALVIEW_VERSION) {
1333         throw new GradleException("'version' header (${givenVersion}) found in ${releaseMdFile} does not match JALVIEW_VERSION (${JALVIEW_VERSION})")
1334       }
1335
1336       if (whatsnewMdFile.exists())
1337         whatsnew = whatsnewMdFile.text
1338     }
1339
1340     def oldJvl = oldJvlFile.exists() ? oldJvlFile.collect{it} : []
1341     def jalviewjsLink = jalviewjsFile.exists() ? jalviewjsFile.collect{it} : []
1342
1343     def changesHugo = null
1344     if (changes != null) {
1345       changesHugo = '<div class="release_notes">\n\n'
1346       def inSection = false
1347       changes.eachLine { line ->
1348         def m = null
1349         if (m = line =~ /^##([^#].*)$/) {
1350           if (inSection) {
1351             changesHugo += "</div>\n\n"
1352           }
1353           def section = m[0][1].trim()
1354           section = section.toLowerCase()
1355           section = section.replaceAll(/ +/, "_")
1356           section = section.replaceAll(/[^a-z0-9_\-]/, "")
1357           changesHugo += "<div class=\"${section}\">\n\n"
1358           inSection = true
1359         } else if (m = line =~ /^(\s*-\s*)<!--([^>]+)-->(.*?)(<br\/?>)?\s*$/) {
1360           def comment = m[0][2].trim()
1361           if (comment != "") {
1362             comment = comment.replaceAll('"', "&quot;")
1363             def issuekeys = []
1364             comment.eachMatch(/JAL-\d+/) { jal -> issuekeys += jal }
1365             def newline = m[0][1]
1366             if (comment.trim() != "")
1367               newline += "{{<comment>}}${comment}{{</comment>}}  "
1368             newline += m[0][3].trim()
1369             if (issuekeys.size() > 0)
1370               newline += "  {{< jal issue=\"${issuekeys.join(",")}\" alt=\"${comment}\" >}}"
1371             if (m[0][4] != null)
1372               newline += m[0][4]
1373             line = newline
1374           }
1375         }
1376         changesHugo += line+"\n"
1377       }
1378       if (inSection) {
1379         changesHugo += "\n</div>\n\n"
1380       }
1381       changesHugo += '</div>'
1382     }
1383
1384     templateFiles.each{ templateFile ->
1385       def newFileName = string(hugoTemplateSubstitutions(templateFile.getName()))
1386       def relPath = hugoTemplatesDir.toPath().relativize(templateFile.toPath()).getParent()
1387       def newRelPathName = hugoTemplateSubstitutions( relPath.toString() )
1388
1389       def outPathName = string("${hugoBuildDir}/$newRelPathName")
1390
1391       copy {
1392         from templateFile
1393         rename(templateFile.getName(), newFileName)
1394         into outPathName
1395       }
1396
1397       def newFile = file("${outPathName}/${newFileName}".toString())
1398       def content = newFile.text
1399       newFile.text = hugoTemplateSubstitutions(content,
1400         [
1401           WHATSNEW: whatsnew,
1402           CHANGES: changesHugo,
1403           DATE: givenDate == null ? "" : givenDate.format("yyyy-MM-dd"),
1404           DRAFT: givenDate == null ? "true" : "false",
1405           JALVIEWJSLINK: jalviewjsLink.contains(JALVIEW_VERSION) ? "true" : "false",
1406           JVL_HEADER: oldJvl.contains(JALVIEW_VERSION) ? "jvl: true" : ""
1407         ]
1408       )
1409     }
1410
1411   }
1412
1413   inputs.file(oldJvlFile)
1414   inputs.dir(hugoTemplatesDir)
1415   inputs.property("JALVIEW_VERSION", { JALVIEW_VERSION })
1416   inputs.property("CHANNEL", { CHANNEL })
1417 }
1418
1419 def getMdDate(File mdFile) {
1420   return mdFileComponents(mdFile, true)
1421 }
1422
1423 def getMdSections(String content) {
1424   def sections = [:]
1425   def sectionContent = ""
1426   def sectionName = null
1427   content.eachLine { line ->
1428     def m = null
1429     if (m = line =~ /^##([^#].*)$/) {
1430       if (sectionName != null) {
1431         sections[sectionName] = sectionContent
1432         sectionName = null
1433         sectionContent = ""
1434       }
1435       sectionName = m[0][1].trim()
1436       sectionName = sectionName.toLowerCase()
1437       sectionName = sectionName.replaceAll(/ +/, "_")
1438       sectionName = sectionName.replaceAll(/[^a-z0-9_\-]/, "")
1439     } else if (sectionName != null) {
1440       sectionContent += line+"\n"
1441     }
1442   }
1443   if (sectionContent != null) {
1444     sections[sectionName] = sectionContent
1445   }
1446   return sections
1447 }
1448 task copyHelp(type: Copy) {
1449   def inputDir = helpSourceDir
1450   def outputDir = "${helpBuildDir}/${help_dir}"
1451   from(inputDir) {
1452     include('**/*.txt')
1453     include('**/*.md')
1454     include('**/*.html')
1455     include('**/*.hs')
1456     include('**/*.xml')
1457     include('**/*.jhm')
1458     filter(ReplaceTokens,
1459       beginToken: '$$',
1460       endToken: '$$',
1461       tokens: [
1462         'Version-Rel': JALVIEW_VERSION,
1463         'Year-Rel': getDate("yyyy")
1464       ]
1465     )
1466   }
1467   from(inputDir) {
1468     exclude('**/*.txt')
1469     exclude('**/*.md')
1470     exclude('**/*.html')
1471     exclude('**/*.hs')
1472     exclude('**/*.xml')
1473     exclude('**/*.jhm')
1474   }
1475   into outputDir
1476
1477   inputs.dir(inputDir)
1478   outputs.files(helpFile)
1479   outputs.dir(outputDir)
1480 }
1481
1482
1483 task releasesTemplates {
1484   group "help"
1485   description "Recreate whatsNew.html and releases.html from markdown files and templates in help"
1486
1487   dependsOn copyHelp
1488
1489   def releasesTemplateFile = file("${jalviewDir}/${releases_template}")
1490   def whatsnewTemplateFile = file("${jalviewDir}/${whatsnew_template}")
1491   def releasesHtmlFile = file("${helpBuildDir}/${help_dir}/${releases_html}")
1492   def whatsnewHtmlFile = file("${helpBuildDir}/${help_dir}/${whatsnew_html}")
1493   def releasesMdDir = "${jalviewDir}/${releases_dir}"
1494   def whatsnewMdDir = "${jalviewDir}/${whatsnew_dir}"
1495
1496   doFirst {
1497     def releaseMdFile = file("${releasesMdDir}/release-${JALVIEW_VERSION_UNDERSCORES}.md")
1498     def whatsnewMdFile = file("${whatsnewMdDir}/whatsnew-${JALVIEW_VERSION_UNDERSCORES}.md")
1499
1500     if (CHANNEL == "RELEASE") {
1501       if (!releaseMdFile.exists()) {
1502         throw new GradleException("File ${releaseMdFile} must be created for RELEASE")
1503       }
1504       if (!whatsnewMdFile.exists()) {
1505         throw new GradleException("File ${whatsnewMdFile} must be created for RELEASE")
1506       }
1507     }
1508
1509     def releaseFiles = fileTree(dir: releasesMdDir, include: "release-*.md")
1510     def releaseFilesDates = releaseFiles.collectEntries {
1511       [(it): getMdDate(it)]
1512     }
1513     releaseFiles = releaseFiles.sort { a,b -> releaseFilesDates[a].compareTo(releaseFilesDates[b]) }
1514
1515     def releasesTemplate = releasesTemplateFile.text
1516     def m = releasesTemplate =~ /(?s)__VERSION_LOOP_START__(.*)__VERSION_LOOP_END__/
1517     def versionTemplate = m[0][1]
1518
1519     MutableDataSet options = new MutableDataSet()
1520
1521     def extensions = new ArrayList<>()
1522     options.set(Parser.EXTENSIONS, extensions)
1523     options.set(Parser.HTML_BLOCK_COMMENT_ONLY_FULL_LINE, true)
1524
1525     Parser parser = Parser.builder(options).build()
1526     HtmlRenderer renderer = HtmlRenderer.builder(options).build()
1527
1528     def actualVersions = releaseFiles.collect { rf ->
1529       def (rfMap, rfContent) = mdFileComponents(rf)
1530       return rfMap.version
1531     }
1532     def versionsHtml = ""
1533     def linkedVersions = []
1534     releaseFiles.reverse().each { rFile ->
1535       def (rMap, rContent) = mdFileComponents(rFile)
1536
1537       def versionLink = ""
1538       def partialVersion = ""
1539       def firstPart = true
1540       rMap.version.split("\\.").each { part ->
1541         def displayPart = ( firstPart ? "" : "." ) + part
1542         partialVersion += displayPart
1543         if (
1544             linkedVersions.contains(partialVersion)
1545             || ( actualVersions.contains(partialVersion) && partialVersion != rMap.version )
1546             ) {
1547           versionLink += displayPart
1548         } else {
1549           versionLink += "<a id=\"Jalview.${partialVersion}\">${displayPart}</a>"
1550           linkedVersions += partialVersion
1551         }
1552         firstPart = false
1553       }
1554       def displayDate = releaseFilesDates[rFile].format("dd/MM/yyyy")
1555
1556       def lm = null
1557       def rContentProcessed = ""
1558       rContent.eachLine { line ->
1559         if (lm = line =~ /^(\s*-)(\s*<!--[^>]*?-->)(.*)$/) {
1560           line = "${lm[0][1]}${lm[0][3]}${lm[0][2]}"
1561       } else if (lm = line =~ /^###([^#]+.*)$/) {
1562           line = "_${lm[0][1].trim()}_"
1563         }
1564         rContentProcessed += line + "\n"
1565       }
1566
1567       def rContentSections = getMdSections(rContentProcessed)
1568       def rVersion = versionTemplate
1569       if (rVersion != "") {
1570         def rNewFeatures = rContentSections["new_features"]
1571         def rIssuesResolved = rContentSections["issues_resolved"]
1572         Node newFeaturesNode = parser.parse(rNewFeatures)
1573         String newFeaturesHtml = renderer.render(newFeaturesNode)
1574         Node issuesResolvedNode = parser.parse(rIssuesResolved)
1575         String issuesResolvedHtml = renderer.render(issuesResolvedNode)
1576         rVersion = hugoTemplateSubstitutions(rVersion,
1577           [
1578             VERSION: rMap.version,
1579             VERSION_LINK: versionLink,
1580             DISPLAY_DATE: displayDate,
1581             NEW_FEATURES: newFeaturesHtml,
1582             ISSUES_RESOLVED: issuesResolvedHtml
1583           ]
1584         )
1585         versionsHtml += rVersion
1586       }
1587     }
1588
1589     releasesTemplate = releasesTemplate.replaceAll("(?s)__VERSION_LOOP_START__.*__VERSION_LOOP_END__", versionsHtml)
1590     releasesTemplate = hugoTemplateSubstitutions(releasesTemplate)
1591     releasesHtmlFile.text = releasesTemplate
1592
1593     if (whatsnewMdFile.exists()) {
1594       def wnDisplayDate = releaseFilesDates[releaseMdFile] != null ? releaseFilesDates[releaseMdFile].format("dd MMMM yyyy") : ""
1595       def whatsnewMd = hugoTemplateSubstitutions(whatsnewMdFile.text)
1596       Node whatsnewNode = parser.parse(whatsnewMd)
1597       String whatsnewHtml = renderer.render(whatsnewNode)
1598       whatsnewHtml = whatsnewTemplateFile.text.replaceAll("__WHATS_NEW__", whatsnewHtml)
1599       whatsnewHtmlFile.text = hugoTemplateSubstitutions(whatsnewHtml,
1600         [
1601             VERSION: JALVIEW_VERSION,
1602           DISPLAY_DATE: wnDisplayDate
1603         ]
1604       )
1605     } else if (gradle.taskGraph.hasTask(":linkCheck")) {
1606       whatsnewHtmlFile.text = "Development build " + getDate("yyyy-MM-dd HH:mm:ss")
1607     }
1608
1609   }
1610
1611   inputs.file(releasesTemplateFile)
1612   inputs.file(whatsnewTemplateFile)
1613   inputs.dir(releasesMdDir)
1614   inputs.dir(whatsnewMdDir)
1615   outputs.file(releasesHtmlFile)
1616   outputs.file(whatsnewHtmlFile)
1617 }
1618 task copyResources(type: Copy) {
1619   group = "build"
1620   description = "Copy (and make text substitutions in) the resources dir to the build area"
1621
1622   def inputDir = resourceDir
1623   def outputDir = resourcesBuildDir
1624   from(inputDir) {
1625     include('**/*.txt')
1626     include('**/*.md')
1627     include('**/*.html')
1628     include('**/*.xml')
1629     filter(ReplaceTokens,
1630       beginToken: '$$',
1631       endToken: '$$',
1632       tokens: [
1633         'Version-Rel': JALVIEW_VERSION,
1634         'Year-Rel': getDate("yyyy")
1635       ]
1636     )
1637   }
1638   from(inputDir) {
1639     exclude('**/*.txt')
1640     exclude('**/*.md')
1641     exclude('**/*.html')
1642     exclude('**/*.xml')
1643   }
1644   into outputDir
1645
1646   inputs.dir(inputDir)
1647   outputs.dir(outputDir)
1648 }
1649
1650 task copyChannelResources(type: Copy) {
1651   dependsOn copyResources
1652   group = "build"
1653   description = "Copy the channel resources dir to the build resources area"
1654
1655   def inputDir = "${channelDir}/${resource_dir}"
1656   def outputDir = resourcesBuildDir
1657   from(inputDir) {
1658     include(channel_props)
1659     filter(ReplaceTokens,
1660       beginToken: '__',
1661       endToken: '__',
1662       tokens: [
1663         'SUFFIX': channelSuffix
1664       ]
1665     )
1666   }
1667   from(inputDir) {
1668     exclude(channel_props)
1669   }
1670   into outputDir
1671
1672   inputs.dir(inputDir)
1673   outputs.dir(outputDir)
1674 }
1675
1676 task createBuildProperties(type: WriteProperties) {
1677   dependsOn copyResources
1678   group = "build"
1679   description = "Create the ${buildProperties} file"
1680   
1681   inputs.dir(sourceDir)
1682   inputs.dir(resourcesBuildDir)
1683   outputFile (buildProperties)
1684   // taking time specific comment out to allow better incremental builds
1685   comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd HH:mm:ss")
1686   //comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd")
1687   property "BUILD_DATE", getDate("HH:mm:ss dd MMMM yyyy")
1688   property "VERSION", JALVIEW_VERSION
1689   property "INSTALLATION", INSTALLATION+" git-commit:"+gitHash+" ["+gitBranch+"]"
1690   property "JAVA_COMPILE_VERSION", JAVA_INTEGER_VERSION
1691   if (getdownSetAppBaseProperty) {
1692     property "GETDOWNAPPBASE", getdownAppBase
1693     property "GETDOWNAPPDISTDIR", getdownAppDistDir
1694   }
1695   outputs.file(outputFile)
1696 }
1697
1698
1699 task buildIndices(type: JavaExec) {
1700   dependsOn copyHelp
1701   classpath = sourceSets.main.compileClasspath
1702   main = "com.sun.java.help.search.Indexer"
1703   workingDir = "${helpBuildDir}/${help_dir}"
1704   def argDir = "html"
1705   args = [ argDir ]
1706   inputs.dir("${workingDir}/${argDir}")
1707
1708   outputs.dir("${classesDir}/doc")
1709   outputs.dir("${classesDir}/help")
1710   outputs.file("${workingDir}/JavaHelpSearch/DOCS")
1711   outputs.file("${workingDir}/JavaHelpSearch/DOCS.TAB")
1712   outputs.file("${workingDir}/JavaHelpSearch/OFFSETS")
1713   outputs.file("${workingDir}/JavaHelpSearch/POSITIONS")
1714   outputs.file("${workingDir}/JavaHelpSearch/SCHEMA")
1715   outputs.file("${workingDir}/JavaHelpSearch/TMAP")
1716 }
1717
1718 task buildResources {
1719   dependsOn copyResources
1720   dependsOn copyChannelResources
1721   dependsOn createBuildProperties
1722 }
1723
1724 task prepare {
1725   dependsOn buildResources
1726   dependsOn copyDocs
1727   dependsOn copyHelp
1728   dependsOn releasesTemplates
1729   dependsOn convertMdFiles
1730   dependsOn buildIndices
1731 }
1732
1733
1734 compileJava.dependsOn prepare
1735 run.dependsOn compileJava
1736 //run.dependsOn prepare
1737
1738
1739 //testReportDirName = "test-reports" // note that test workingDir will be $jalviewDir
1740 test {
1741   dependsOn prepare
1742
1743   if (useClover) {
1744     dependsOn cloverClasses
1745    } else { //?
1746     dependsOn compileJava //?
1747   }
1748
1749   useTestNG() {
1750     includeGroups testng_groups
1751     excludeGroups testng_excluded_groups
1752     preserveOrder true
1753     useDefaultListeners=true
1754     // Uncomment to show stdout and stderr for tests in gradle build output
1755     // testLogging.showStandardStreams = true
1756   }
1757
1758   maxHeapSize = "1024m"
1759
1760   workingDir = jalviewDir
1761   def testLaf = project.findProperty("test_laf")
1762   if (testLaf != null) {
1763     println("Setting Test LaF to '${testLaf}'")
1764     systemProperty "laf", testLaf
1765   }
1766   def testHiDPIScale = project.findProperty("test_HiDPIScale")
1767   if (testHiDPIScale != null) {
1768     println("Setting Test HiDPI Scale to '${testHiDPIScale}'")
1769     systemProperty "sun.java2d.uiScale", testHiDPIScale
1770   }
1771   sourceCompatibility = compile_source_compatibility
1772   targetCompatibility = compile_target_compatibility
1773   jvmArgs += additional_compiler_args
1774
1775   doFirst {
1776     if (useClover) {
1777       println("Running tests " + (useClover?"WITH":"WITHOUT") + " clover")
1778     }
1779   }
1780 }
1781
1782
1783 task compileLinkCheck(type: JavaCompile) {
1784   options.fork = true
1785   classpath = files("${jalviewDir}/${utils_dir}")
1786   destinationDir = file("${jalviewDir}/${utils_dir}")
1787   source = fileTree(dir: "${jalviewDir}/${utils_dir}", include: ["HelpLinksChecker.java", "BufferedLineReader.java"])
1788
1789   inputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.java")
1790   inputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.java")
1791   outputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.class")
1792   outputs.file("${jalviewDir}/${utils_dir}/BufferedLineReader.class")
1793 }
1794
1795
1796 task linkCheck(type: JavaExec) {
1797   dependsOn prepare
1798   dependsOn compileLinkCheck
1799
1800   def helpLinksCheckerOutFile = file("${jalviewDir}/${utils_dir}/HelpLinksChecker.out")
1801   classpath = files("${jalviewDir}/${utils_dir}")
1802   main = "HelpLinksChecker"
1803   workingDir = "${helpBuildDir}"
1804   args = [ "${helpBuildDir}/${help_dir}", "-nointernet" ]
1805
1806   def outFOS = new FileOutputStream(helpLinksCheckerOutFile, false) // false == don't append
1807   standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
1808     outFOS,
1809     System.out)
1810   errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
1811     outFOS,
1812     System.err)
1813
1814   inputs.dir(helpBuildDir)
1815   outputs.file(helpLinksCheckerOutFile)
1816 }
1817
1818
1819 // import the pubhtmlhelp target
1820 ant.properties.basedir = "${jalviewDir}"
1821 ant.properties.helpBuildDir = "${helpBuildDir}/${help_dir}"
1822 ant.importBuild "${utils_dir}/publishHelp.xml"
1823
1824
1825 task cleanPackageDir(type: Delete) {
1826   doFirst {
1827     delete fileTree(dir: "${jalviewDir}/${package_dir}", include: "*.jar")
1828   }
1829 }
1830
1831
1832 jar {
1833   dependsOn prepare
1834   dependsOn linkCheck
1835
1836   manifest {
1837     attributes "Main-Class": main_class,
1838     "Permissions": "all-permissions",
1839     "Application-Name": applicationName,
1840     "Codebase": application_codebase,
1841     "Implementation-Version": JALVIEW_VERSION
1842   }
1843
1844   def outputDir = "${jalviewDir}/${package_dir}"
1845   destinationDirectory = file(outputDir)
1846   archiveFileName = rootProject.name+".jar"
1847   duplicatesStrategy "EXCLUDE"
1848
1849
1850   exclude "cache*/**"
1851   exclude "*.jar"
1852   exclude "*.jar.*"
1853   exclude "**/*.jar"
1854   exclude "**/*.jar.*"
1855
1856   inputs.dir(sourceSets.main.java.outputDir)
1857   sourceSets.main.resources.srcDirs.each{ dir ->
1858     inputs.dir(dir)
1859   }
1860   outputs.file("${outputDir}/${archiveFileName}")
1861 }
1862
1863
1864 task copyJars(type: Copy) {
1865   from fileTree(dir: classesDir, include: "**/*.jar").files
1866   into "${jalviewDir}/${package_dir}"
1867 }
1868
1869
1870 // doing a Sync instead of Copy as Copy doesn't deal with "outputs" very well
1871 task syncJars(type: Sync) {
1872   dependsOn jar
1873   from fileTree(dir: "${jalviewDir}/${libDistDir}", include: "**/*.jar").files
1874   into "${jalviewDir}/${package_dir}"
1875   preserve {
1876     include jar.archiveFileName.getOrNull()
1877   }
1878 }
1879
1880
1881 task makeDist {
1882   group = "build"
1883   description = "Put all required libraries in dist"
1884   // order of "cleanPackageDir", "copyJars", "jar" important!
1885   jar.mustRunAfter cleanPackageDir
1886   syncJars.mustRunAfter cleanPackageDir
1887   dependsOn cleanPackageDir
1888   dependsOn syncJars
1889   dependsOn jar
1890   outputs.dir("${jalviewDir}/${package_dir}")
1891 }
1892
1893
1894 task cleanDist {
1895   dependsOn cleanPackageDir
1896   dependsOn cleanTest
1897   dependsOn clean
1898 }
1899
1900
1901 shadowJar {
1902   group = "distribution"
1903   description = "Create a single jar file with all dependency libraries merged. Can be run with java -jar"
1904   if (buildDist) {
1905     dependsOn makeDist
1906   }
1907   from ("${jalviewDir}/${libDistDir}") {
1908     include("*.jar")
1909   }
1910   manifest {
1911     attributes "Implementation-Version": JALVIEW_VERSION,
1912     "Application-Name": applicationName
1913   }
1914
1915   duplicatesStrategy "INCLUDE"
1916
1917   mainClassName = shadow_jar_main_class
1918   mergeServiceFiles()
1919   classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
1920   minimize()
1921 }
1922
1923 task getdownImagesCopy() {
1924   inputs.dir getdownImagesDir
1925   outputs.dir getdownImagesBuildDir
1926
1927   doFirst {
1928     copy {
1929       from(getdownImagesDir) {
1930         include("*getdown*.png")
1931       }
1932       into getdownImagesBuildDir
1933     }
1934   }
1935 }
1936
1937 task getdownImagesProcess() {
1938   dependsOn getdownImagesCopy
1939
1940   doFirst {
1941     if (backgroundImageText) {
1942       if (convertBinary == null) {
1943         throw new StopExecutionException("No ImageMagick convert binary installed at '${convertBinaryExpectedLocation}'")
1944       }
1945       if (!project.hasProperty("getdown_background_image_text_suffix_cmd")) {
1946         throw new StopExecutionException("No property 'getdown_background_image_text_suffix_cmd' defined. See channel_gradle.properties for channel ${CHANNEL}")
1947       }
1948       fileTree(dir: getdownImagesBuildDir, include: "*background*.png").getFiles().each { file ->
1949         exec {
1950           executable convertBinary
1951           args = [
1952             file.getPath(),
1953             '-font', getdown_background_image_text_font,
1954             '-fill', getdown_background_image_text_colour,
1955             '-draw', sprintf(getdown_background_image_text_suffix_cmd, channelSuffix),
1956             '-draw', sprintf(getdown_background_image_text_commit_cmd, "git-commit: ${gitHash}"),
1957             '-draw', sprintf(getdown_background_image_text_date_cmd, getDate("yyyy-MM-dd HH:mm:ss")),
1958             file.getPath()
1959           ]
1960         }
1961       }
1962     }
1963   }
1964 }
1965
1966 task getdownImages() {
1967   dependsOn getdownImagesProcess
1968 }
1969
1970 task getdownWebsite() {
1971   group = "distribution"
1972   description = "Create the getdown minimal app folder, and website folder for this version of jalview. Website folder also used for offline app installer"
1973
1974   dependsOn getdownImages
1975   if (buildDist) {
1976     dependsOn makeDist
1977   }
1978
1979   def getdownWebsiteResourceFilenames = []
1980   def getdownResourceDir = getdownResourceDir
1981   def getdownResourceFilenames = []
1982
1983   doFirst {
1984     // clean the getdown website and files dir before creating getdown folders
1985     delete getdownAppBaseDir
1986     delete getdownFilesDir
1987
1988     copy {
1989       from buildProperties
1990       rename(file(buildProperties).getName(), getdown_build_properties)
1991       into getdownAppDir
1992     }
1993     getdownWebsiteResourceFilenames += "${getdownAppDistDir}/${getdown_build_properties}"
1994
1995     copy {
1996       from channelPropsFile
1997       filter(ReplaceTokens,
1998         beginToken: '__',
1999         endToken: '__',
2000         tokens: [
2001           'SUFFIX': channelSuffix
2002         ]
2003       )
2004       into getdownAppBaseDir
2005     }
2006     getdownWebsiteResourceFilenames += file(channelPropsFile).getName()
2007
2008     // set some getdownTxt_ properties then go through all properties looking for getdownTxt_...
2009     def props = project.properties.sort { it.key }
2010     if (getdownAltJavaMinVersion != null && getdownAltJavaMinVersion.length() > 0) {
2011       props.put("getdown_txt_java_min_version", getdownAltJavaMinVersion)
2012     }
2013     if (getdownAltJavaMaxVersion != null && getdownAltJavaMaxVersion.length() > 0) {
2014       props.put("getdown_txt_java_max_version", getdownAltJavaMaxVersion)
2015     }
2016     if (getdownAltMultiJavaLocation != null && getdownAltMultiJavaLocation.length() > 0) {
2017       props.put("getdown_txt_multi_java_location", getdownAltMultiJavaLocation)
2018     }
2019     if (getdownImagesBuildDir != null && file(getdownImagesBuildDir).exists()) {
2020       props.put("getdown_txt_ui.background_image", "${getdownImagesBuildDir}/${getdown_background_image}")
2021       props.put("getdown_txt_ui.instant_background_image", "${getdownImagesBuildDir}/${getdown_instant_background_image}")
2022       props.put("getdown_txt_ui.error_background", "${getdownImagesBuildDir}/${getdown_error_background}")
2023       props.put("getdown_txt_ui.progress_image", "${getdownImagesBuildDir}/${getdown_progress_image}")
2024       props.put("getdown_txt_ui.icon", "${getdownImagesBuildDir}/${getdown_icon}")
2025       props.put("getdown_txt_ui.mac_dock_icon", "${getdownImagesBuildDir}/${getdown_mac_dock_icon}")
2026     }
2027
2028     props.put("getdown_txt_title", jalview_name)
2029     props.put("getdown_txt_ui.name", applicationName)
2030
2031     // start with appbase
2032     getdownTextLines += "appbase = ${getdownAppBase}"
2033     props.each{ prop, val ->
2034       if (prop.startsWith("getdown_txt_") && val != null) {
2035         if (prop.startsWith("getdown_txt_multi_")) {
2036           def key = prop.substring(18)
2037           val.split(",").each{ v ->
2038             def line = "${key} = ${v}"
2039             getdownTextLines += line
2040           }
2041         } else {
2042           // file values rationalised
2043           if (val.indexOf('/') > -1 || prop.startsWith("getdown_txt_resource")) {
2044             def r = null
2045             if (val.indexOf('/') == 0) {
2046               // absolute path
2047               r = file(val)
2048             } else if (val.indexOf('/') > 0) {
2049               // relative path (relative to jalviewDir)
2050               r = file( "${jalviewDir}/${val}" )
2051             }
2052             if (r.exists()) {
2053               val = "${getdown_resource_dir}/" + r.getName()
2054               getdownWebsiteResourceFilenames += val
2055               getdownResourceFilenames += r.getPath()
2056             }
2057           }
2058           if (! prop.startsWith("getdown_txt_resource")) {
2059             def line = prop.substring(12) + " = ${val}"
2060             getdownTextLines += line
2061           }
2062         }
2063       }
2064     }
2065
2066     getdownWebsiteResourceFilenames.each{ filename ->
2067       getdownTextLines += "resource = ${filename}"
2068     }
2069     getdownResourceFilenames.each{ filename ->
2070       copy {
2071         from filename
2072         into getdownResourceDir
2073       }
2074     }
2075     
2076     def getdownWrapperScripts = [ getdown_bash_wrapper_script, getdown_powershell_wrapper_script, getdown_batch_wrapper_script ]
2077     getdownWrapperScripts.each{ script ->
2078       def s = file( "${jalviewDir}/utils/getdown/${getdown_wrapper_script_dir}/${script}" )
2079       if (s.exists()) {
2080         copy {
2081           from s
2082           into "${getdownAppBaseDir}/${getdown_wrapper_script_dir}"
2083         }
2084         getdownTextLines += "resource = ${getdown_wrapper_script_dir}/${script}"
2085       }
2086     }
2087
2088     def codeFiles = []
2089     fileTree(file(package_dir)).each{ f ->
2090       if (f.isDirectory()) {
2091         def files = fileTree(dir: f, include: ["*"]).getFiles()
2092         codeFiles += files
2093       } else if (f.exists()) {
2094         codeFiles += f
2095       }
2096     }
2097     def jalviewJar = jar.archiveFileName.getOrNull()
2098     // put jalview.jar first for CLASSPATH and .properties files reasons
2099     codeFiles.sort{a, b -> ( a.getName() == jalviewJar ? -1 : ( b.getName() == jalviewJar ? 1 : a <=> b ) ) }.each{f ->
2100       def name = f.getName()
2101       def line = "code = ${getdownAppDistDir}/${name}"
2102       getdownTextLines += line
2103       copy {
2104         from f.getPath()
2105         into getdownAppDir
2106       }
2107     }
2108
2109     // NOT USING MODULES YET, EVERYTHING SHOULD BE IN dist
2110     /*
2111     if (JAVA_VERSION.equals("11")) {
2112     def j11libFiles = fileTree(dir: "${jalviewDir}/${j11libDir}", include: ["*.jar"]).getFiles()
2113     j11libFiles.sort().each{f ->
2114     def name = f.getName()
2115     def line = "code = ${getdown_j11lib_dir}/${name}"
2116     getdownTextLines += line
2117     copy {
2118     from f.getPath()
2119     into getdownJ11libDir
2120     }
2121     }
2122     }
2123      */
2124
2125     // 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.
2126     //getdownTextLines += "class = " + file(getdownLauncher).getName()
2127     getdownTextLines += "resource = ${getdown_launcher_new}"
2128     getdownTextLines += "class = ${main_class}"
2129     // Not setting these properties in general so that getdownappbase and getdowndistdir will default to release version in jalview.bin.Cache
2130     if (getdownSetAppBaseProperty) {
2131       getdownTextLines += "jvmarg = -Dgetdowndistdir=${getdownAppDistDir}"
2132       getdownTextLines += "jvmarg = -Dgetdownappbase=${getdownAppBase}"
2133     }
2134
2135     def getdownTxt = file("${getdownAppBaseDir}/getdown.txt")
2136     getdownTxt.write(getdownTextLines.join("\n"))
2137
2138     getdownLaunchJvl = getdown_launch_jvl_name + ( (jvlChannelName != null && jvlChannelName.length() > 0)?"-${jvlChannelName}":"" ) + ".jvl"
2139     def launchJvl = file("${getdownAppBaseDir}/${getdownLaunchJvl}")
2140     launchJvl.write("appbase=${getdownAppBase}")
2141
2142     // files going into the getdown website dir: getdown-launcher.jar
2143     copy {
2144       from getdownLauncher
2145       rename(file(getdownLauncher).getName(), getdown_launcher_new)
2146       into getdownAppBaseDir
2147     }
2148
2149     // files going into the getdown website dir: getdown-launcher(-local).jar
2150     copy {
2151       from getdownLauncher
2152       if (file(getdownLauncher).getName() != getdown_launcher) {
2153         rename(file(getdownLauncher).getName(), getdown_launcher)
2154       }
2155       into getdownAppBaseDir
2156     }
2157
2158     // files going into the getdown website dir: ./install dir and files
2159     if (! (CHANNEL.startsWith("ARCHIVE") || CHANNEL.startsWith("DEVELOP"))) {
2160       copy {
2161         from getdownTxt
2162         from getdownLauncher
2163         from "${getdownAppDir}/${getdown_build_properties}"
2164         if (file(getdownLauncher).getName() != getdown_launcher) {
2165           rename(file(getdownLauncher).getName(), getdown_launcher)
2166         }
2167         into getdownInstallDir
2168       }
2169
2170       // and make a copy in the getdown files dir (these are not downloaded by getdown)
2171       copy {
2172         from getdownInstallDir
2173         into getdownFilesInstallDir
2174       }
2175     }
2176
2177     // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
2178     copy {
2179       from getdownTxt
2180       from launchJvl
2181       from getdownLauncher
2182       from "${getdownAppBaseDir}/${getdown_build_properties}"
2183       from "${getdownAppBaseDir}/${channel_props}"
2184       if (file(getdownLauncher).getName() != getdown_launcher) {
2185         rename(file(getdownLauncher).getName(), getdown_launcher)
2186       }
2187       into getdownFilesDir
2188     }
2189
2190     // and ./resource (not all downloaded by getdown)
2191     copy {
2192       from getdownResourceDir
2193       into "${getdownFilesDir}/${getdown_resource_dir}"
2194     }
2195   }
2196
2197   if (buildDist) {
2198     inputs.dir("${jalviewDir}/${package_dir}")
2199   }
2200   outputs.dir(getdownAppBaseDir)
2201   outputs.dir(getdownFilesDir)
2202 }
2203
2204
2205 // a helper task to allow getdown digest of any dir: `gradle getdownDigestDir -PDIGESTDIR=/path/to/my/random/getdown/dir
2206 task getdownDigestDir(type: JavaExec) {
2207   group "Help"
2208   description "A task to run a getdown Digest on a dir with getdown.txt. Provide a DIGESTDIR property via -PDIGESTDIR=..."
2209
2210   def digestDirPropertyName = "DIGESTDIR"
2211   doFirst {
2212     classpath = files(getdownLauncher)
2213     def digestDir = findProperty(digestDirPropertyName)
2214     if (digestDir == null) {
2215       throw new GradleException("Must provide a DIGESTDIR value to produce an alternative getdown digest")
2216     }
2217     args digestDir
2218   }
2219   main = "com.threerings.getdown.tools.Digester"
2220 }
2221
2222
2223 task getdownDigest(type: JavaExec) {
2224   group = "distribution"
2225   description = "Digest the getdown website folder"
2226   dependsOn getdownWebsite
2227   doFirst {
2228     classpath = files(getdownLauncher)
2229   }
2230   main = "com.threerings.getdown.tools.Digester"
2231   args getdownAppBaseDir
2232   inputs.dir(getdownAppBaseDir)
2233   outputs.file("${getdownAppBaseDir}/digest2.txt")
2234 }
2235
2236
2237 task getdown() {
2238   group = "distribution"
2239   description = "Create the minimal and full getdown app folder for installers and website and create digest file"
2240   dependsOn getdownDigest
2241   doLast {
2242     if (reportRsyncCommand) {
2243       def fromDir = getdownAppBaseDir + (getdownAppBaseDir.endsWith('/')?'':'/')
2244       def toDir = "${getdown_rsync_dest}/${getdownDir}" + (getdownDir.endsWith('/')?'':'/')
2245       println "LIKELY RSYNC COMMAND:"
2246       println "mkdir -p '$toDir'\nrsync -avh --delete '$fromDir' '$toDir'"
2247       if (RUNRSYNC == "true") {
2248         exec {
2249           commandLine "mkdir", "-p", toDir
2250         }
2251         exec {
2252           commandLine "rsync", "-avh", "--delete", fromDir, toDir
2253         }
2254       }
2255     }
2256   }
2257 }
2258
2259
2260 task getdownArchiveBuild() {
2261   group = "distribution"
2262   description = "Put files in the archive dir to go on the website"
2263
2264   dependsOn getdownWebsite
2265
2266   def v = "v${JALVIEW_VERSION_UNDERSCORES}"
2267   def vDir = "${getdownArchiveDir}/${v}"
2268   getdownFullArchiveDir = "${vDir}/getdown"
2269   getdownVersionLaunchJvl = "${vDir}/jalview-${v}.jvl"
2270
2271   def vAltDir = "alt_${v}"
2272   def archiveImagesDir = "${jalviewDir}/${channel_properties_dir}/old/images"
2273
2274   doFirst {
2275     // cleanup old "old" dir
2276     delete getdownArchiveDir
2277
2278     def getdownArchiveTxt = file("${getdownFullArchiveDir}/getdown.txt")
2279     getdownArchiveTxt.getParentFile().mkdirs()
2280     def getdownArchiveTextLines = []
2281     def getdownFullArchiveAppBase = "${getdownArchiveAppBase}${getdownArchiveAppBase.endsWith("/")?"":"/"}${v}/getdown/"
2282
2283     // the libdir
2284     copy {
2285       from "${getdownAppBaseDir}/${getdownAppDistDir}"
2286       into "${getdownFullArchiveDir}/${vAltDir}"
2287     }
2288
2289     getdownTextLines.each { line ->
2290       line = line.replaceAll("^(?<s>appbase\\s*=\\s*).*", '${s}'+getdownFullArchiveAppBase)
2291       line = line.replaceAll("^(?<s>(resource|code)\\s*=\\s*)${getdownAppDistDir}/", '${s}'+vAltDir+"/")
2292       line = line.replaceAll("^(?<s>ui.background_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background.png")
2293       line = line.replaceAll("^(?<s>ui.instant_background_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background_initialising.png")
2294       line = line.replaceAll("^(?<s>ui.error_background\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background_error.png")
2295       line = line.replaceAll("^(?<s>ui.progress_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_progress_bar.png")
2296       // remove the existing resource = resource/ or bin/ lines
2297       if (! line.matches("resource\\s*=\\s*(resource|bin)/.*")) {
2298         getdownArchiveTextLines += line
2299       }
2300     }
2301
2302     // the resource dir -- add these files as resource lines in getdown.txt
2303     copy {
2304       from "${archiveImagesDir}"
2305       into "${getdownFullArchiveDir}/${getdown_resource_dir}"
2306       eachFile { file ->
2307         getdownArchiveTextLines += "resource = ${getdown_resource_dir}/${file.getName()}"
2308       }
2309     }
2310
2311     getdownArchiveTxt.write(getdownArchiveTextLines.join("\n"))
2312
2313     def vLaunchJvl = file(getdownVersionLaunchJvl)
2314     vLaunchJvl.getParentFile().mkdirs()
2315     vLaunchJvl.write("appbase=${getdownFullArchiveAppBase}\n")
2316     def vLaunchJvlPath = vLaunchJvl.toPath().toAbsolutePath()
2317     def jvlLinkPath = file("${vDir}/jalview.jvl").toPath().toAbsolutePath()
2318     // for some reason filepath.relativize(fileInSameDirPath) gives a path to "../" which is wrong
2319     //java.nio.file.Files.createSymbolicLink(jvlLinkPath, jvlLinkPath.relativize(vLaunchJvlPath));
2320     java.nio.file.Files.createSymbolicLink(jvlLinkPath, java.nio.file.Paths.get(".",vLaunchJvl.getName()));
2321
2322     // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
2323     copy {
2324       from getdownLauncher
2325       from "${getdownAppBaseDir}/${getdownLaunchJvl}"
2326       from "${getdownAppBaseDir}/${getdown_launcher_new}"
2327       from "${getdownAppBaseDir}/${channel_props}"
2328       if (file(getdownLauncher).getName() != getdown_launcher) {
2329         rename(file(getdownLauncher).getName(), getdown_launcher)
2330       }
2331       into getdownFullArchiveDir
2332     }
2333
2334   }
2335 }
2336
2337 task getdownArchiveDigest(type: JavaExec) {
2338   group = "distribution"
2339   description = "Digest the getdown archive folder"
2340
2341   dependsOn getdownArchiveBuild
2342
2343   doFirst {
2344     classpath = files(getdownLauncher)
2345     args getdownFullArchiveDir
2346   }
2347   main = "com.threerings.getdown.tools.Digester"
2348   inputs.dir(getdownFullArchiveDir)
2349   outputs.file("${getdownFullArchiveDir}/digest2.txt")
2350 }
2351
2352 task getdownArchive() {
2353   group = "distribution"
2354   description = "Build the website archive dir with getdown digest"
2355
2356   dependsOn getdownArchiveBuild
2357   dependsOn getdownArchiveDigest
2358 }
2359 tasks.withType(JavaCompile) {
2360         options.encoding = 'UTF-8'
2361 }
2362
2363
2364 clean {
2365   doFirst {
2366     delete getdownAppBaseDir
2367     delete getdownFilesDir
2368     delete getdownArchiveDir
2369   }
2370 }
2371
2372
2373 install4j {
2374   if (file(install4jHomeDir).exists()) {
2375     // good to go!
2376   } else if (file(System.getProperty("user.home")+"/buildtools/install4j").exists()) {
2377     install4jHomeDir = System.getProperty("user.home")+"/buildtools/install4j"
2378   } else if (file("/Applications/install4j.app/Contents/Resources/app").exists()) {
2379     install4jHomeDir = "/Applications/install4j.app/Contents/Resources/app"
2380   }
2381   installDir(file(install4jHomeDir))
2382
2383   mediaTypes = Arrays.asList(install4j_media_types.split(","))
2384 }
2385
2386
2387 task copyInstall4jTemplate {
2388   def install4jTemplateFile = file("${install4jDir}/${install4j_template}")
2389   def install4jFileAssociationsFile = file("${install4jDir}/${install4j_installer_file_associations}")
2390   inputs.file(install4jTemplateFile)
2391   inputs.file(install4jFileAssociationsFile)
2392   inputs.property("CHANNEL", { CHANNEL })
2393   outputs.file(install4jConfFile)
2394
2395   doLast {
2396     def install4jConfigXml = new XmlParser().parse(install4jTemplateFile)
2397
2398     // turn off code signing if no OSX_KEYPASS
2399     if (OSX_KEYPASS == "") {
2400       install4jConfigXml.'**'.codeSigning.each { codeSigning ->
2401         codeSigning.'@macEnabled' = "false"
2402       }
2403       install4jConfigXml.'**'.windows.each { windows ->
2404         windows.'@runPostProcessor' = "false"
2405       }
2406     }
2407
2408     // disable install screen for OSX dmg (for 2.11.2.0)
2409     install4jConfigXml.'**'.macosArchive.each { macosArchive -> 
2410       macosArchive.attributes().remove('executeSetupApp')
2411       macosArchive.attributes().remove('setupAppId')
2412     }
2413     // turn off checksum creation for LOCAL channel
2414     def e = install4jConfigXml.application[0]
2415     e.'@createChecksums' = string(install4jCheckSums)
2416
2417     // put file association actions where placeholder action is
2418     def install4jFileAssociationsText = install4jFileAssociationsFile.text
2419     def fileAssociationActions = new XmlParser().parseText("<actions>${install4jFileAssociationsText}</actions>")
2420     install4jConfigXml.'**'.action.any { a -> // .any{} stops after the first one that returns true
2421       if (a.'@name' == 'EXTENSIONS_REPLACED_BY_GRADLE') {
2422         def parent = a.parent()
2423         parent.remove(a)
2424         fileAssociationActions.each { faa ->
2425             parent.append(faa)
2426         }
2427         // don't need to continue in .any loop once replacements have been made
2428         return true
2429       }
2430     }
2431
2432     // use Windows Program Group with Examples folder for RELEASE, and Program Group without Examples for everything else
2433     // NB we're deleting the /other/ one!
2434     // Also remove the examples subdir from non-release versions
2435     def customizedIdToDelete = "PROGRAM_GROUP_RELEASE"
2436     // 2.11.1.0 NOT releasing with the Examples folder in the Program Group
2437     if (false && CHANNEL=="RELEASE") { // remove 'false && ' to include Examples folder in RELEASE channel
2438       customizedIdToDelete = "PROGRAM_GROUP_NON_RELEASE"
2439     } else {
2440       // remove the examples subdir from Full File Set
2441       def files = install4jConfigXml.files[0]
2442       def fileset = files.filesets.fileset.find { fs -> fs.'@customizedId' == "FULL_FILE_SET" }
2443       def root = files.roots.root.find { r -> r.'@fileset' == fileset.'@id' }
2444       def mountPoint = files.mountPoints.mountPoint.find { mp -> mp.'@root' == root.'@id' }
2445       def dirEntry = files.entries.dirEntry.find { de -> de.'@mountPoint' == mountPoint.'@id' && de.'@subDirectory' == "examples" }
2446       dirEntry.parent().remove(dirEntry)
2447     }
2448     install4jConfigXml.'**'.action.any { a ->
2449       if (a.'@customizedId' == customizedIdToDelete) {
2450         def parent = a.parent()
2451         parent.remove(a)
2452         return true
2453       }
2454     }
2455
2456     // write install4j file
2457     install4jConfFile.text = XmlUtil.serialize(install4jConfigXml)
2458   }
2459 }
2460
2461
2462 clean {
2463   doFirst {
2464     delete install4jConfFile
2465   }
2466 }
2467
2468 task cleanInstallersDataFiles {
2469   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
2470   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
2471   def hugoDataJsonFile = file("${jalviewDir}/${install4jBuildDir}/installers-${JALVIEW_VERSION_UNDERSCORES}.json")
2472   doFirst {
2473     delete installersOutputTxt
2474     delete installersSha256
2475     delete hugoDataJsonFile
2476   }
2477 }
2478
2479 task install4jDMGBackgroundImageCopy {
2480   inputs.file "${install4jDMGBackgroundImageDir}/${install4jDMGBackgroundImageFile}"
2481   outputs.dir "${install4jDMGBackgroundImageBuildDir}"
2482   doFirst {
2483     copy {
2484       from(install4jDMGBackgroundImageDir) {
2485         include(install4jDMGBackgroundImageFile)
2486       }
2487       into install4jDMGBackgroundImageBuildDir
2488     }
2489   }
2490 }
2491
2492 task install4jDMGBackgroundImageProcess {
2493   dependsOn install4jDMGBackgroundImageCopy
2494
2495   doFirst {
2496     if (backgroundImageText) {
2497       if (convertBinary == null) {
2498         throw new StopExecutionException("No ImageMagick convert binary installed at '${convertBinaryExpectedLocation}'")
2499       }
2500       if (!project.hasProperty("install4j_background_image_text_suffix_cmd")) {
2501         throw new StopExecutionException("No property 'install4j_background_image_text_suffix_cmd' defined. See channel_gradle.properties for channel ${CHANNEL}")
2502       }
2503       fileTree(dir: install4jDMGBackgroundImageBuildDir, include: "*.png").getFiles().each { file ->
2504         exec {
2505           executable convertBinary
2506           args = [
2507             file.getPath(),
2508             '-font', install4j_background_image_text_font,
2509             '-fill', install4j_background_image_text_colour,
2510             '-draw', sprintf(install4j_background_image_text_suffix_cmd, channelSuffix),
2511             '-draw', sprintf(install4j_background_image_text_commit_cmd, "git-commit: ${gitHash}"),
2512             '-draw', sprintf(install4j_background_image_text_date_cmd, getDate("yyyy-MM-dd HH:mm:ss")),
2513             file.getPath()
2514           ]
2515         }
2516       }
2517     }
2518   }
2519 }
2520
2521 task install4jDMGBackgroundImage {
2522   dependsOn install4jDMGBackgroundImageProcess
2523 }
2524
2525 task installerFiles(type: com.install4j.gradle.Install4jTask) {
2526   group = "distribution"
2527   description = "Create the install4j installers"
2528   dependsOn getdown
2529   dependsOn copyInstall4jTemplate
2530   dependsOn cleanInstallersDataFiles
2531   dependsOn install4jDMGBackgroundImage
2532
2533   projectFile = install4jConfFile
2534
2535   // create an md5 for the input files to use as version for install4j conf file
2536   def digest = MessageDigest.getInstance("MD5")
2537   digest.update(
2538     (file("${install4jDir}/${install4j_template}").text + 
2539     file("${install4jDir}/${install4j_info_plist_file_associations}").text +
2540     file("${install4jDir}/${install4j_installer_file_associations}").text).bytes)
2541   def filesMd5 = new BigInteger(1, digest.digest()).toString(16)
2542   if (filesMd5.length() >= 8) {
2543     filesMd5 = filesMd5.substring(0,8)
2544   }
2545   def install4jTemplateVersion = "${JALVIEW_VERSION}_F${filesMd5}_C${gitHash}"
2546
2547   variables = [
2548     'JALVIEW_NAME': jalview_name,
2549     'JALVIEW_APPLICATION_NAME': applicationName,
2550     'JALVIEW_DIR': "../..",
2551     'OSX_KEYSTORE': OSX_KEYSTORE,
2552     'OSX_APPLEID': OSX_APPLEID,
2553     'OSX_ALTOOLPASS': OSX_ALTOOLPASS,
2554     'JSIGN_SH': JSIGN_SH,
2555     'JRE_DIR': getdown_app_dir_java,
2556     'INSTALLER_TEMPLATE_VERSION': install4jTemplateVersion,
2557     'JALVIEW_VERSION': JALVIEW_VERSION,
2558     'JAVA_MIN_VERSION': JAVA_MIN_VERSION,
2559     'JAVA_MAX_VERSION': JAVA_MAX_VERSION,
2560     'JAVA_VERSION': JAVA_VERSION,
2561     'JAVA_INTEGER_VERSION': JAVA_INTEGER_VERSION,
2562     'VERSION': JALVIEW_VERSION,
2563     'MACOS_JAVA_VM_DIR': macosJavaVMDir,
2564     'WINDOWS_JAVA_VM_DIR': windowsJavaVMDir,
2565     'LINUX_JAVA_VM_DIR': linuxJavaVMDir,
2566     'MACOS_JAVA_VM_TGZ': macosJavaVMTgz,
2567     'WINDOWS_JAVA_VM_TGZ': windowsJavaVMTgz,
2568     'LINUX_JAVA_VM_TGZ': linuxJavaVMTgz,
2569     'COPYRIGHT_MESSAGE': install4j_copyright_message,
2570     'BUNDLE_ID': install4jBundleId,
2571     'INTERNAL_ID': install4jInternalId,
2572     'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
2573     'MACOS_DMG_DS_STORE': install4jDMGDSStore,
2574     'MACOS_DMG_BG_IMAGE': "${install4jDMGBackgroundImageBuildDir}/${install4jDMGBackgroundImageFile}",
2575     'WRAPPER_LINK': getdownWrapperLink,
2576     'BASH_WRAPPER_SCRIPT': getdown_bash_wrapper_script,
2577     'POWERSHELL_WRAPPER_SCRIPT': getdown_powershell_wrapper_script,
2578     'WRAPPER_SCRIPT_BIN_DIR': getdown_wrapper_script_dir,
2579     'INSTALLER_NAME': install4jInstallerName,
2580     'INSTALL4J_UTILS_DIR': install4j_utils_dir,
2581     'GETDOWN_CHANNEL_DIR': getdownChannelDir,
2582     'GETDOWN_FILES_DIR': getdown_files_dir,
2583     'GETDOWN_RESOURCE_DIR': getdown_resource_dir,
2584     'GETDOWN_DIST_DIR': getdownAppDistDir,
2585     'GETDOWN_ALT_DIR': getdown_app_dir_alt,
2586     'GETDOWN_INSTALL_DIR': getdown_install_dir,
2587     'INFO_PLIST_FILE_ASSOCIATIONS_FILE': install4j_info_plist_file_associations,
2588     'BUILD_DIR': install4jBuildDir,
2589     'APPLICATION_CATEGORIES': install4j_application_categories,
2590     'APPLICATION_FOLDER': install4jApplicationFolder,
2591     'UNIX_APPLICATION_FOLDER': install4jUnixApplicationFolder,
2592     'EXECUTABLE_NAME': install4jExecutableName,
2593     'EXTRA_SCHEME': install4jExtraScheme,
2594     'MAC_ICONS_FILE': install4jMacIconsFile,
2595     'WINDOWS_ICONS_FILE': install4jWindowsIconsFile,
2596     'PNG_ICON_FILE': install4jPngIconFile,
2597     'BACKGROUND': install4jBackground,
2598
2599   ]
2600
2601   //println("INSTALL4J VARIABLES:")
2602   //variables.each{k,v->println("${k}=${v}")}
2603
2604   destination = "${jalviewDir}/${install4jBuildDir}"
2605   buildSelected = true
2606
2607   if (install4j_faster.equals("true") || CHANNEL.startsWith("LOCAL")) {
2608     faster = true
2609     disableSigning = true
2610     disableNotarization = true
2611   }
2612
2613   if (OSX_KEYPASS) {
2614     macKeystorePassword = OSX_KEYPASS
2615   } 
2616   
2617   if (OSX_ALTOOLPASS) {
2618     appleIdPassword = OSX_ALTOOLPASS
2619     disableNotarization = false
2620   } else {
2621     disableNotarization = true
2622   }
2623
2624   doFirst {
2625     println("Using projectFile "+projectFile)
2626     if (!disableNotarization) { println("Will notarize OSX App DMG") }
2627   }
2628   //verbose=true
2629
2630   inputs.dir(getdownAppBaseDir)
2631   inputs.file(install4jConfFile)
2632   inputs.file("${install4jDir}/${install4j_info_plist_file_associations}")
2633   inputs.dir(macosJavaVMDir)
2634   inputs.dir(windowsJavaVMDir)
2635   outputs.dir("${jalviewDir}/${install4j_build_dir}/${JAVA_VERSION}")
2636 }
2637
2638 def getDataHash(File myFile) {
2639   HashCode hash = Files.asByteSource(myFile).hash(Hashing.sha256())
2640   return myFile.exists()
2641   ? [
2642       "file" : myFile.getName(),
2643       "filesize" : myFile.length(),
2644       "sha256" : hash.toString()
2645     ]
2646   : null
2647 }
2648
2649 def writeDataJsonFile(File installersOutputTxt, File installersSha256, File dataJsonFile) {
2650   def hash = [
2651     "channel" : getdownChannelName,
2652     "date" : getDate("yyyy-MM-dd HH:mm:ss"),
2653     "git-commit" : "${gitHash} [${gitBranch}]",
2654     "version" : JALVIEW_VERSION
2655   ]
2656   // install4j installer files
2657   if (installersOutputTxt.exists()) {
2658     def idHash = [:]
2659     installersOutputTxt.readLines().each { def line ->
2660       if (line.startsWith("#")) {
2661         return;
2662       }
2663       line.replaceAll("\n","")
2664       def vals = line.split("\t")
2665       def filename = vals[3]
2666       def filesize = file(filename).length()
2667       filename = filename.replaceAll(/^.*\//, "")
2668       hash[vals[0]] = [ "id" : vals[0], "os" : vals[1], "name" : vals[2], "file" : filename, "filesize" : filesize ]
2669       idHash."${filename}" = vals[0]
2670     }
2671     if (install4jCheckSums && installersSha256.exists()) {
2672       installersSha256.readLines().each { def line ->
2673         if (line.startsWith("#")) {
2674           return;
2675         }
2676         line.replaceAll("\n","")
2677         def vals = line.split(/\s+\*?/)
2678         def filename = vals[1]
2679         def innerHash = (hash.(idHash."${filename}"))."sha256" = vals[0]
2680       }
2681     }
2682   }
2683
2684   [
2685     "JAR": shadowJar.archiveFile, // executable JAR
2686     "JVL": getdownVersionLaunchJvl, // version JVL
2687     "SOURCE": sourceDist.archiveFile // source TGZ
2688   ].each { key, value ->
2689     def file = file(value)
2690     if (file.exists()) {
2691       def fileHash = getDataHash(file)
2692       if (fileHash != null) {
2693         hash."${key}" = fileHash;
2694       }
2695     }
2696   }
2697   return dataJsonFile.write(new JsonBuilder(hash).toPrettyString())
2698 }
2699
2700 task staticMakeInstallersJsonFile {
2701   doFirst {
2702     def output = findProperty("i4j_output")
2703     def sha256 = findProperty("i4j_sha256")
2704     def json = findProperty("i4j_json")
2705     if (output == null || sha256 == null || json == null) {
2706       throw new GradleException("Must provide paths to all of output.txt, sha256sums, and output.json with '-Pi4j_output=... -Pi4j_sha256=... -Pi4j_json=...")
2707     }
2708     writeDataJsonFile(file(output), file(sha256), file(json))
2709   }
2710 }
2711
2712 task installers {
2713   dependsOn installerFiles
2714 }
2715
2716 spotless {
2717   java {
2718     eclipse().configFile(eclipse_codestyle_file)
2719   }
2720 }
2721
2722 task createSourceReleaseProperties(type: WriteProperties) {
2723   group = "distribution"
2724   description = "Create the source RELEASE properties file"
2725   
2726   def sourceTarBuildDir = "${buildDir}/sourceTar"
2727   def sourceReleasePropertiesFile = "${sourceTarBuildDir}/RELEASE"
2728   outputFile (sourceReleasePropertiesFile)
2729
2730   doFirst {
2731     releaseProps.each{ key, val -> property key, val }
2732     property "git.branch", gitBranch
2733     property "git.hash", gitHash
2734   }
2735
2736   outputs.file(outputFile)
2737 }
2738
2739 task sourceDist(type: Tar) {
2740   group "distribution"
2741   description "Create a source .tar.gz file for distribution"
2742
2743   dependsOn createBuildProperties
2744   dependsOn convertMdFiles
2745   dependsOn eclipseAllPreferences
2746   dependsOn createSourceReleaseProperties
2747
2748
2749   def outputFileName = "${project.name}_${JALVIEW_VERSION_UNDERSCORES}.tar.gz"
2750   archiveFileName = outputFileName
2751   
2752   compression Compression.GZIP
2753   
2754   into project.name
2755
2756   def EXCLUDE_FILES=[
2757     "build/*",
2758     "bin/*",
2759     "test-output/",
2760     "test-reports",
2761     "tests",
2762     "clover*/*",
2763     ".*",
2764     "benchmarking/*",
2765     "**/.*",
2766     "*.class",
2767     "**/*.class","$j11modDir/**/*.jar","appletlib","**/*locales",
2768     "*locales/**",
2769     "utils/InstallAnywhere",
2770     "**/*.log",
2771     "RELEASE",
2772   ] 
2773   def PROCESS_FILES=[
2774     "AUTHORS",
2775     "CITATION",
2776     "FEATURETODO",
2777     "JAVA-11-README",
2778     "FEATURETODO",
2779     "LICENSE",
2780     "**/README",
2781     "THIRDPARTYLIBS",
2782     "TESTNG",
2783     "build.gradle",
2784     "gradle.properties",
2785     "**/*.java",
2786     "**/*.html",
2787     "**/*.xml",
2788     "**/*.gradle",
2789     "**/*.groovy",
2790     "**/*.properties",
2791     "**/*.perl",
2792     "**/*.sh",
2793   ]
2794   def INCLUDE_FILES=[
2795     ".classpath",
2796     ".settings/org.eclipse.buildship.core.prefs",
2797     ".settings/org.eclipse.jdt.core.prefs"
2798   ]
2799
2800   from(jalviewDir) {
2801     exclude (EXCLUDE_FILES)
2802     include (PROCESS_FILES)
2803     filter(ReplaceTokens,
2804       beginToken: '$$',
2805       endToken: '$$',
2806       tokens: [
2807         'Version-Rel': JALVIEW_VERSION,
2808         'Year-Rel': getDate("yyyy")
2809       ]
2810     )
2811   }
2812   from(jalviewDir) {
2813     exclude (EXCLUDE_FILES)
2814     exclude (PROCESS_FILES)
2815     exclude ("appletlib")
2816     exclude ("**/*locales")
2817     exclude ("*locales/**")
2818     exclude ("utils/InstallAnywhere")
2819
2820     exclude (getdown_files_dir)
2821     // getdown_website_dir and getdown_archive_dir moved to build/website/docroot/getdown
2822     //exclude (getdown_website_dir)
2823     //exclude (getdown_archive_dir)
2824
2825     // exluding these as not using jars as modules yet
2826     exclude ("${j11modDir}/**/*.jar")
2827   }
2828   from(jalviewDir) {
2829     include(INCLUDE_FILES)
2830   }
2831 //  from (jalviewDir) {
2832 //    // explicit includes for stuff that seemed to not get included
2833 //    include(fileTree("test/**/*."))
2834 //    exclude(EXCLUDE_FILES)
2835 //    exclude(PROCESS_FILES)
2836 //  }
2837
2838   from(file(buildProperties).getParent()) {
2839     include(file(buildProperties).getName())
2840     rename(file(buildProperties).getName(), "build_properties")
2841     filter({ line ->
2842       line.replaceAll("^INSTALLATION=.*\$","INSTALLATION=Source Release"+" git-commit\\\\:"+gitHash+" ["+gitBranch+"]")
2843     })
2844   }
2845
2846   def sourceTarBuildDir = "${buildDir}/sourceTar"
2847   from(sourceTarBuildDir) {
2848     // this includes the appended RELEASE properties file
2849   }
2850 }
2851
2852 task dataInstallersJson {
2853   group "website"
2854   description "Create the installers-VERSION.json data file for installer files created"
2855
2856   mustRunAfter installers
2857   mustRunAfter shadowJar
2858   mustRunAfter sourceDist
2859   mustRunAfter getdownArchive
2860
2861   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
2862   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
2863
2864   if (installersOutputTxt.exists()) {
2865     inputs.file(installersOutputTxt)
2866   }
2867   if (install4jCheckSums && installersSha256.exists()) {
2868     inputs.file(installersSha256)
2869   }
2870   [
2871     shadowJar.archiveFile, // executable JAR
2872     getdownVersionLaunchJvl, // version JVL
2873     sourceDist.archiveFile // source TGZ
2874   ].each { fileName ->
2875     if (file(fileName).exists()) {
2876       inputs.file(fileName)
2877     }
2878   }
2879
2880   outputs.file(hugoDataJsonFile)
2881
2882   doFirst {
2883     writeDataJsonFile(installersOutputTxt, installersSha256, hugoDataJsonFile)
2884   }
2885 }
2886
2887 task helppages {
2888   group "help"
2889   description "Copies all help pages to build dir. Runs ant task 'pubhtmlhelp'."
2890   dependsOn copyHelp
2891   dependsOn pubhtmlhelp
2892   
2893   inputs.dir("${helpBuildDir}/${help_dir}")
2894   outputs.dir("${buildDir}/distributions/${help_dir}")
2895 }
2896
2897
2898 task j2sSetHeadlessBuild {
2899   doFirst {
2900     IN_ECLIPSE = false
2901   }
2902 }
2903
2904
2905 task jalviewjsEnableAltFileProperty(type: WriteProperties) {
2906   group "jalviewjs"
2907   description "Enable the alternative J2S Config file for headless build"
2908
2909   outputFile = jalviewjsJ2sSettingsFileName
2910   def j2sPropsFile = file(jalviewjsJ2sSettingsFileName)
2911   def j2sProps = new Properties()
2912   if (j2sPropsFile.exists()) {
2913     try {
2914       def j2sPropsFileFIS = new FileInputStream(j2sPropsFile)
2915       j2sProps.load(j2sPropsFileFIS)
2916       j2sPropsFileFIS.close()
2917
2918       j2sProps.each { prop, val ->
2919         property(prop, val)
2920       }
2921     } catch (Exception e) {
2922       println("Exception reading ${jalviewjsJ2sSettingsFileName}")
2923       e.printStackTrace()
2924     }
2925   }
2926   if (! j2sProps.stringPropertyNames().contains(jalviewjs_j2s_alt_file_property_config)) {
2927     property(jalviewjs_j2s_alt_file_property_config, jalviewjs_j2s_alt_file_property)
2928   }
2929 }
2930
2931
2932 task jalviewjsSetEclipseWorkspace {
2933   def propKey = "jalviewjs_eclipse_workspace"
2934   def propVal = null
2935   if (project.hasProperty(propKey)) {
2936     propVal = project.getProperty(propKey)
2937     if (propVal.startsWith("~/")) {
2938       propVal = System.getProperty("user.home") + propVal.substring(1)
2939     }
2940   }
2941   def propsFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_workspace_location_file}"
2942   def propsFile = file(propsFileName)
2943   def eclipseWsDir = propVal
2944   def props = new Properties()
2945
2946   def writeProps = true
2947   if (( eclipseWsDir == null || !file(eclipseWsDir).exists() ) && propsFile.exists()) {
2948     def ins = new FileInputStream(propsFileName)
2949     props.load(ins)
2950     ins.close()
2951     if (props.getProperty(propKey, null) != null) {
2952       eclipseWsDir = props.getProperty(propKey)
2953       writeProps = false
2954     }
2955   }
2956
2957   if (eclipseWsDir == null || !file(eclipseWsDir).exists()) {
2958     def tempDir = File.createTempDir()
2959     eclipseWsDir = tempDir.getAbsolutePath()
2960     writeProps = true
2961   }
2962   eclipseWorkspace = file(eclipseWsDir)
2963
2964   doFirst {
2965     // do not run a headless transpile when we claim to be in Eclipse
2966     if (IN_ECLIPSE) {
2967       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2968       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2969     } else {
2970       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2971     }
2972
2973     if (writeProps) {
2974       props.setProperty(propKey, eclipseWsDir)
2975       propsFile.parentFile.mkdirs()
2976       def bytes = new ByteArrayOutputStream()
2977       props.store(bytes, null)
2978       def propertiesString = bytes.toString()
2979       propsFile.text = propertiesString
2980       print("NEW ")
2981     } else {
2982       print("EXISTING ")
2983     }
2984
2985     println("ECLIPSE WORKSPACE: "+eclipseWorkspace.getPath())
2986   }
2987
2988   //inputs.property(propKey, eclipseWsDir) // eclipseWsDir only gets set once this task runs, so will be out-of-date
2989   outputs.file(propsFileName)
2990   outputs.upToDateWhen { eclipseWorkspace.exists() && propsFile.exists() }
2991 }
2992
2993
2994 task jalviewjsEclipsePaths {
2995   def eclipseProduct
2996
2997   def eclipseRoot = jalviewjs_eclipse_root
2998   if (eclipseRoot.startsWith("~/")) {
2999     eclipseRoot = System.getProperty("user.home") + eclipseRoot.substring(1)
3000   }
3001   if (OperatingSystem.current().isMacOsX()) {
3002     eclipseRoot += "/Eclipse.app"
3003     eclipseBinary = "${eclipseRoot}/Contents/MacOS/eclipse"
3004     eclipseProduct = "${eclipseRoot}/Contents/Eclipse/.eclipseproduct"
3005   } else if (OperatingSystem.current().isWindows()) { // check these paths!!
3006     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
3007       eclipseRoot += "/eclipse"
3008     }
3009     eclipseBinary = "${eclipseRoot}/eclipse.exe"
3010     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
3011   } else { // linux or unix
3012     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
3013       eclipseRoot += "/eclipse"
3014 println("eclipseDir exists")
3015     }
3016     eclipseBinary = "${eclipseRoot}/eclipse"
3017     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
3018   }
3019
3020   eclipseVersion = "4.13" // default
3021   def assumedVersion = true
3022   if (file(eclipseProduct).exists()) {
3023     def fis = new FileInputStream(eclipseProduct)
3024     def props = new Properties()
3025     props.load(fis)
3026     eclipseVersion = props.getProperty("version")
3027     fis.close()
3028     assumedVersion = false
3029   }
3030   
3031   def propKey = "eclipse_debug"
3032   eclipseDebug = (project.hasProperty(propKey) && project.getProperty(propKey).equals("true"))
3033
3034   doFirst {
3035     // do not run a headless transpile when we claim to be in Eclipse
3036     if (IN_ECLIPSE) {
3037       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3038       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3039     } else {
3040       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3041     }
3042
3043     if (!assumedVersion) {
3044       println("ECLIPSE VERSION=${eclipseVersion}")
3045     }
3046   }
3047 }
3048
3049
3050 task printProperties {
3051   group "Debug"
3052   description "Output to console all System.properties"
3053   doFirst {
3054     System.properties.each { key, val -> System.out.println("Property: ${key}=${val}") }
3055   }
3056 }
3057
3058
3059 task eclipseSetup {
3060   dependsOn eclipseProject
3061   dependsOn eclipseClasspath
3062   dependsOn eclipseJdt
3063 }
3064
3065
3066 // this version (type: Copy) will delete anything in the eclipse dropins folder that isn't in fromDropinsDir
3067 task jalviewjsEclipseCopyDropins(type: Copy) {
3068   dependsOn jalviewjsEclipsePaths
3069
3070   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_eclipse_dropins_dir}", include: "*.jar")
3071   inputFiles += file("${jalviewDir}/${jalviewjsJ2sPlugin}")
3072   def outputDir = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
3073
3074   from inputFiles
3075   into outputDir
3076 }
3077
3078
3079 // this eclipse -clean doesn't actually work
3080 task jalviewjsCleanEclipse(type: Exec) {
3081   dependsOn eclipseSetup
3082   dependsOn jalviewjsEclipsePaths
3083   dependsOn jalviewjsEclipseCopyDropins
3084
3085   executable(eclipseBinary)
3086   args(["-nosplash", "--launcher.suppressErrors", "-data", eclipseWorkspace.getPath(), "-clean", "-console", "-consoleLog"])
3087   if (eclipseDebug) {
3088     args += "-debug"
3089   }
3090   args += "-l"
3091
3092   def inputString = """exit
3093 y
3094 """
3095   def inputByteStream = new ByteArrayInputStream(inputString.getBytes())
3096   standardInput = inputByteStream
3097 }
3098
3099 /* not really working yet
3100 jalviewjsEclipseCopyDropins.finalizedBy jalviewjsCleanEclipse
3101 */
3102
3103
3104 task jalviewjsTransferUnzipSwingJs {
3105   def file_zip = "${jalviewDir}/${jalviewjs_swingjs_zip}"
3106
3107   doLast {
3108     copy {
3109       from zipTree(file_zip)
3110       into "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3111     }
3112   }
3113
3114   inputs.file file_zip
3115   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3116 }
3117
3118
3119 task jalviewjsTransferUnzipLib {
3120   def zipFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_libjs_dir}", include: "*.zip")
3121
3122   doLast {
3123     zipFiles.each { file_zip -> 
3124       copy {
3125         from zipTree(file_zip)
3126         into "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3127       }
3128     }
3129   }
3130
3131   inputs.files zipFiles
3132   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3133 }
3134
3135
3136 task jalviewjsTransferUnzipAllLibs {
3137   dependsOn jalviewjsTransferUnzipSwingJs
3138   dependsOn jalviewjsTransferUnzipLib
3139 }
3140
3141
3142 task jalviewjsCreateJ2sSettings(type: WriteProperties) {
3143   group "JalviewJS"
3144   description "Create the alternative j2s file from the j2s.* properties"
3145
3146   jalviewjsJ2sProps = project.properties.findAll { it.key.startsWith("j2s.") }.sort { it.key }
3147   def siteDirProperty = "j2s.site.directory"
3148   def setSiteDir = false
3149   jalviewjsJ2sProps.each { prop, val ->
3150     if (val != null) {
3151       if (prop == siteDirProperty) {
3152         if (!(val.startsWith('/') || val.startsWith("file://") )) {
3153           val = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${val}"
3154         }
3155         setSiteDir = true
3156       }
3157       property(prop,val)
3158     }
3159     if (!setSiteDir) { // default site location, don't override specifically set property
3160       property(siteDirProperty,"${jalviewDirRelativePath}/${jalviewjsTransferSiteJsDir}")
3161     }
3162   }
3163   outputFile = jalviewjsJ2sAltSettingsFileName
3164
3165   if (! IN_ECLIPSE) {
3166     inputs.properties(jalviewjsJ2sProps)
3167     outputs.file(jalviewjsJ2sAltSettingsFileName)
3168   }
3169 }
3170
3171
3172 task jalviewjsEclipseSetup {
3173   dependsOn jalviewjsEclipseCopyDropins
3174   dependsOn jalviewjsSetEclipseWorkspace
3175   dependsOn jalviewjsCreateJ2sSettings
3176 }
3177
3178
3179 task jalviewjsSyncAllLibs (type: Sync) {
3180   dependsOn jalviewjsTransferUnzipAllLibs
3181   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteLibDir}")
3182   inputFiles += fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
3183   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3184
3185   from inputFiles
3186   into outputDir
3187   def outputFiles = []
3188   rename { filename ->
3189     outputFiles += "${outputDir}/${filename}"
3190     null
3191   }
3192   preserve {
3193     include "**"
3194   }
3195
3196   // should this be exclude really ?
3197   duplicatesStrategy "INCLUDE"
3198
3199   outputs.files outputFiles
3200   inputs.files inputFiles
3201 }
3202
3203
3204 task jalviewjsSyncResources (type: Sync) {
3205   dependsOn buildResources
3206
3207   def inputFiles = fileTree(dir: resourcesBuildDir)
3208   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
3209
3210   from inputFiles
3211   into outputDir
3212   def outputFiles = []
3213   rename { filename ->
3214     outputFiles += "${outputDir}/${filename}"
3215     null
3216   }
3217   preserve {
3218     include "**"
3219   }
3220   outputs.files outputFiles
3221   inputs.files inputFiles
3222 }
3223
3224
3225 task jalviewjsSyncSiteResources (type: Sync) {
3226   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_site_resource_dir}")
3227   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3228
3229   from inputFiles
3230   into outputDir
3231   def outputFiles = []
3232   rename { filename ->
3233     outputFiles += "${outputDir}/${filename}"
3234     null
3235   }
3236   preserve {
3237     include "**"
3238   }
3239   outputs.files outputFiles
3240   inputs.files inputFiles
3241 }
3242
3243
3244 task jalviewjsSyncBuildProperties (type: Sync) {
3245   dependsOn createBuildProperties
3246   def inputFiles = [file(buildProperties)]
3247   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
3248
3249   from inputFiles
3250   into outputDir
3251   def outputFiles = []
3252   rename { filename ->
3253     outputFiles += "${outputDir}/${filename}"
3254     null
3255   }
3256   preserve {
3257     include "**"
3258   }
3259   outputs.files outputFiles
3260   inputs.files inputFiles
3261 }
3262
3263
3264 task jalviewjsProjectImport(type: Exec) {
3265   dependsOn eclipseSetup
3266   dependsOn jalviewjsEclipsePaths
3267   dependsOn jalviewjsEclipseSetup
3268
3269   doFirst {
3270     // do not run a headless import when we claim to be in Eclipse
3271     if (IN_ECLIPSE) {
3272       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3273       throw new StopExecutionException("Not running headless import whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3274     } else {
3275       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3276     }
3277   }
3278
3279   //def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview/org.eclipse.jdt.core"
3280   def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview"
3281   executable(eclipseBinary)
3282   args(["-nosplash", "--launcher.suppressErrors", "-application", "com.seeq.eclipse.importprojects.headlessimport", "-data", eclipseWorkspace.getPath(), "-import", jalviewDirAbsolutePath])
3283   if (eclipseDebug) {
3284     args += "-debug"
3285   }
3286   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
3287   if (!IN_ECLIPSE) {
3288     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
3289     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
3290   }
3291
3292   inputs.file("${jalviewDir}/.project")
3293   outputs.upToDateWhen { 
3294     file(projdir).exists()
3295   }
3296 }
3297
3298
3299 task jalviewjsTranspile(type: Exec) {
3300   dependsOn jalviewjsEclipseSetup 
3301   dependsOn jalviewjsProjectImport
3302   dependsOn jalviewjsEclipsePaths
3303   if (!IN_ECLIPSE) {
3304     dependsOn jalviewjsEnableAltFileProperty
3305   }
3306
3307   doFirst {
3308     // do not run a headless transpile when we claim to be in Eclipse
3309     if (IN_ECLIPSE) {
3310       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3311       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3312     } else {
3313       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3314     }
3315   }
3316
3317   executable(eclipseBinary)
3318   args(["-nosplash", "--launcher.suppressErrors", "-application", "org.eclipse.jdt.apt.core.aptBuild", "-data", eclipseWorkspace, "-${jalviewjs_eclipse_build_arg}", eclipse_project_name ])
3319   if (eclipseDebug) {
3320     args += "-debug"
3321   }
3322   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
3323   if (!IN_ECLIPSE) {
3324     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
3325     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
3326   }
3327
3328   def stdout
3329   def stderr
3330   doFirst {
3331     stdout = new ByteArrayOutputStream()
3332     stderr = new ByteArrayOutputStream()
3333
3334     def logOutFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}"
3335     def logOutFile = file(logOutFileName)
3336     logOutFile.createNewFile()
3337     logOutFile.text = """ROOT: ${jalviewjs_eclipse_root}
3338 BINARY: ${eclipseBinary}
3339 VERSION: ${eclipseVersion}
3340 WORKSPACE: ${eclipseWorkspace}
3341 DEBUG: ${eclipseDebug}
3342 ----
3343 """
3344     def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
3345     // combine stdout and stderr
3346     def logErrFOS = logOutFOS
3347
3348     if (jalviewjs_j2s_to_console.equals("true")) {
3349       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3350         new org.apache.tools.ant.util.TeeOutputStream(
3351           logOutFOS,
3352           stdout),
3353         System.out)
3354       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3355         new org.apache.tools.ant.util.TeeOutputStream(
3356           logErrFOS,
3357           stderr),
3358         System.err)
3359     } else {
3360       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3361         logOutFOS,
3362         stdout)
3363       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3364         logErrFOS,
3365         stderr)
3366     }
3367   }
3368
3369   doLast {
3370     if (stdout.toString().contains("Error processing ")) {
3371       // j2s did not complete transpile
3372       //throw new TaskExecutionException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3373       if (jalviewjs_ignore_transpile_errors.equals("true")) {
3374         println("IGNORING TRANSPILE ERRORS")
3375         println("See eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3376       } else {
3377         throw new GradleException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3378       }
3379     }
3380   }
3381
3382   inputs.dir("${jalviewDir}/${sourceDir}")
3383   outputs.dir("${jalviewDir}/${jalviewjsTransferSiteJsDir}")
3384   outputs.upToDateWhen( { file("${jalviewDir}/${jalviewjsTransferSiteJsDir}${jalviewjs_server_resource}").exists() } )
3385 }
3386
3387
3388 def jalviewjsCallCore(String name, FileCollection list, String prefixFile, String suffixFile, String jsfile, String zjsfile, File logOutFile, Boolean logOutConsole) {
3389
3390   def stdout = new ByteArrayOutputStream()
3391   def stderr = new ByteArrayOutputStream()
3392
3393   def coreFile = file(jsfile)
3394   def msg = ""
3395   msg = "Creating core for ${name}...\nGenerating ${jsfile}"
3396   println(msg)
3397   logOutFile.createNewFile()
3398   logOutFile.append(msg+"\n")
3399
3400   def coreTop = file(prefixFile)
3401   def coreBottom = file(suffixFile)
3402   coreFile.getParentFile().mkdirs()
3403   coreFile.createNewFile()
3404   coreFile.write( coreTop.getText("UTF-8") )
3405   list.each {
3406     f ->
3407     if (f.exists()) {
3408       def t = f.getText("UTF-8")
3409       t.replaceAll("Clazz\\.([^_])","Clazz_${1}")
3410       coreFile.append( t )
3411     } else {
3412       msg = "...file '"+f.getPath()+"' does not exist, skipping"
3413       println(msg)
3414       logOutFile.append(msg+"\n")
3415     }
3416   }
3417   coreFile.append( coreBottom.getText("UTF-8") )
3418
3419   msg = "Generating ${zjsfile}"
3420   println(msg)
3421   logOutFile.append(msg+"\n")
3422   def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
3423   def logErrFOS = logOutFOS
3424
3425   javaexec {
3426     classpath = files(["${jalviewDir}/${jalviewjs_closure_compiler}"])
3427     main = "com.google.javascript.jscomp.CommandLineRunner"
3428     jvmArgs = [ "-Dfile.encoding=UTF-8" ]
3429     args = [ "--compilation_level", "SIMPLE_OPTIMIZATIONS", "--warning_level", "QUIET", "--charset", "UTF-8", "--js", jsfile, "--js_output_file", zjsfile ]
3430     maxHeapSize = "2g"
3431
3432     msg = "\nRunning '"+commandLine.join(' ')+"'\n"
3433     println(msg)
3434     logOutFile.append(msg+"\n")
3435
3436     if (logOutConsole) {
3437       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3438         new org.apache.tools.ant.util.TeeOutputStream(
3439           logOutFOS,
3440           stdout),
3441         standardOutput)
3442         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3443           new org.apache.tools.ant.util.TeeOutputStream(
3444             logErrFOS,
3445             stderr),
3446           System.err)
3447     } else {
3448       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3449         logOutFOS,
3450         stdout)
3451         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3452           logErrFOS,
3453           stderr)
3454     }
3455   }
3456   msg = "--"
3457   println(msg)
3458   logOutFile.append(msg+"\n")
3459 }
3460
3461
3462 task jalviewjsBuildAllCores {
3463   group "JalviewJS"
3464   description "Build the core js lib closures listed in the classlists dir"
3465   dependsOn jalviewjsTranspile
3466   dependsOn jalviewjsTransferUnzipSwingJs
3467
3468   def j2sDir = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${jalviewjs_j2s_subdir}"
3469   def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_j2s_subdir}"
3470   def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteLibDir}/${jalviewjs_j2s_subdir}"
3471   def jsDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_js_subdir}"
3472   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
3473   def prefixFile = "${jsDir}/core/coretop2.js"
3474   def suffixFile = "${jsDir}/core/corebottom2.js"
3475
3476   inputs.file prefixFile
3477   inputs.file suffixFile
3478
3479   def classlistFiles = []
3480   // add the classlists found int the jalviewjs_classlists_dir
3481   fileTree(dir: "${jalviewDir}/${jalviewjs_classlists_dir}", include: "*.txt").each {
3482     file ->
3483     def name = file.getName() - ".txt"
3484     classlistFiles += [
3485       'file': file,
3486       'name': name
3487     ]
3488   }
3489
3490   // _jmol and _jalview cores. Add any other peculiar classlist.txt files here
3491   //classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jmol}"), 'name': "_jvjmol" ]
3492   classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jalview}"), 'name': jalviewjsJalviewCoreName ]
3493
3494   jalviewjsCoreClasslists = []
3495
3496   classlistFiles.each {
3497     hash ->
3498
3499     def file = hash['file']
3500     if (! file.exists()) {
3501       //println("...classlist file '"+file.getPath()+"' does not exist, skipping")
3502       return false // this is a "continue" in groovy .each closure
3503     }
3504     def name = hash['name']
3505     if (name == null) {
3506       name = file.getName() - ".txt"
3507     }
3508
3509     def filelist = []
3510     file.eachLine {
3511       line ->
3512         filelist += line
3513     }
3514     def list = fileTree(dir: j2sDir, includes: filelist)
3515
3516     def jsfile = "${outputDir}/core${name}.js"
3517     def zjsfile = "${outputDir}/core${name}.z.js"
3518
3519     jalviewjsCoreClasslists += [
3520       'jsfile': jsfile,
3521       'zjsfile': zjsfile,
3522       'list': list,
3523       'name': name
3524     ]
3525
3526     inputs.file(file)
3527     inputs.files(list)
3528     outputs.file(jsfile)
3529     outputs.file(zjsfile)
3530   }
3531   
3532   // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
3533   def stevesoftClasslistName = "_stevesoft"
3534   def stevesoftClasslist = [
3535     'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
3536     'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
3537     'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
3538     'name': stevesoftClasslistName
3539   ]
3540   jalviewjsCoreClasslists += stevesoftClasslist
3541   inputs.files(stevesoftClasslist['list'])
3542   outputs.file(stevesoftClasslist['jsfile'])
3543   outputs.file(stevesoftClasslist['zjsfile'])
3544
3545   // _all core
3546   def allClasslistName = "_all"
3547   def allJsFiles = fileTree(dir: j2sDir, include: "**/*.js")
3548   allJsFiles += fileTree(
3549     dir: libJ2sDir,
3550     include: "**/*.js",
3551     excludes: [
3552       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3553       "**/org/jmol/jvxl/readers/IsoIntersectFileReader.js",
3554       "**/org/jmol/export/JSExporter.js"
3555     ]
3556   )
3557   allJsFiles += fileTree(
3558     dir: swingJ2sDir,
3559     include: "**/*.js",
3560     excludes: [
3561       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3562       "**/sun/misc/Unsafe.js",
3563       "**/swingjs/jquery/jquery-editable-select.js",
3564       "**/swingjs/jquery/j2sComboBox.js",
3565       "**/sun/misc/FloatingDecimal.js"
3566     ]
3567   )
3568   def allClasslist = [
3569     'jsfile': "${outputDir}/core${allClasslistName}.js",
3570     'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
3571     'list': allJsFiles,
3572     'name': allClasslistName
3573   ]
3574   // not including this version of "all" core at the moment
3575   //jalviewjsCoreClasslists += allClasslist
3576   inputs.files(allClasslist['list'])
3577   outputs.file(allClasslist['jsfile'])
3578   outputs.file(allClasslist['zjsfile'])
3579
3580   doFirst {
3581     def logOutFile = file("${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_closure_stdout}")
3582     logOutFile.getParentFile().mkdirs()
3583     logOutFile.createNewFile()
3584     logOutFile.write(getDate("yyyy-MM-dd HH:mm:ss")+" jalviewjsBuildAllCores\n----\n")
3585
3586     jalviewjsCoreClasslists.each {
3587       jalviewjsCallCore(it.name, it.list, prefixFile, suffixFile, it.jsfile, it.zjsfile, logOutFile, jalviewjs_j2s_to_console.equals("true"))
3588     }
3589   }
3590
3591 }
3592
3593
3594 def jalviewjsPublishCoreTemplate(String coreName, String templateName, File inputFile, String outputFile) {
3595   copy {
3596     from inputFile
3597     into file(outputFile).getParentFile()
3598     rename { filename ->
3599       if (filename.equals(inputFile.getName())) {
3600         return file(outputFile).getName()
3601       }
3602       return null
3603     }
3604     filter(ReplaceTokens,
3605       beginToken: '_',
3606       endToken: '_',
3607       tokens: [
3608         'MAIN': '"'+main_class+'"',
3609         'CODE': "null",
3610         'NAME': jalviewjsJalviewTemplateName+" [core ${coreName}]",
3611         'COREKEY': jalviewjs_core_key,
3612         'CORENAME': coreName
3613       ]
3614     )
3615   }
3616 }
3617
3618
3619 task jalviewjsPublishCoreTemplates {
3620   dependsOn jalviewjsBuildAllCores
3621   def inputFileName = "${jalviewDir}/${j2s_coretemplate_html}"
3622   def inputFile = file(inputFileName)
3623   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3624
3625   def outputFiles = []
3626   jalviewjsCoreClasslists.each { cl ->
3627     def outputFile = "${outputDir}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
3628     cl['outputfile'] = outputFile
3629     outputFiles += outputFile
3630   }
3631
3632   doFirst {
3633     jalviewjsCoreClasslists.each { cl ->
3634       jalviewjsPublishCoreTemplate(cl.name, jalviewjsJalviewTemplateName, inputFile, cl.outputfile)
3635     }
3636   }
3637   inputs.file(inputFile)
3638   outputs.files(outputFiles)
3639 }
3640
3641
3642 task jalviewjsSyncCore (type: Sync) {
3643   dependsOn jalviewjsBuildAllCores
3644   dependsOn jalviewjsPublishCoreTemplates
3645   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
3646   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3647
3648   from inputFiles
3649   into outputDir
3650   def outputFiles = []
3651   rename { filename ->
3652     outputFiles += "${outputDir}/${filename}"
3653     null
3654   }
3655   preserve {
3656     include "**"
3657   }
3658   outputs.files outputFiles
3659   inputs.files inputFiles
3660 }
3661
3662
3663 // this Copy version of TransferSiteJs will delete anything else in the target dir
3664 task jalviewjsCopyTransferSiteJs(type: Copy) {
3665   dependsOn jalviewjsTranspile
3666   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3667   into "${jalviewDir}/${jalviewjsSiteDir}"
3668 }
3669
3670
3671 // this Sync version of TransferSite is used by buildship to keep the website automatically up to date when a file changes
3672 task jalviewjsSyncTransferSiteJs(type: Sync) {
3673   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3674   include "**/*.*"
3675   into "${jalviewDir}/${jalviewjsSiteDir}"
3676   preserve {
3677     include "**"
3678   }
3679 }
3680
3681
3682 jalviewjsSyncAllLibs.mustRunAfter jalviewjsCopyTransferSiteJs
3683 jalviewjsSyncResources.mustRunAfter jalviewjsCopyTransferSiteJs
3684 jalviewjsSyncSiteResources.mustRunAfter jalviewjsCopyTransferSiteJs
3685 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsCopyTransferSiteJs
3686
3687 jalviewjsSyncAllLibs.mustRunAfter jalviewjsSyncTransferSiteJs
3688 jalviewjsSyncResources.mustRunAfter jalviewjsSyncTransferSiteJs
3689 jalviewjsSyncSiteResources.mustRunAfter jalviewjsSyncTransferSiteJs
3690 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsSyncTransferSiteJs
3691
3692
3693 task jalviewjsPrepareSite {
3694   group "JalviewJS"
3695   description "Prepares the website folder including unzipping files and copying resources"
3696   dependsOn jalviewjsSyncAllLibs
3697   dependsOn jalviewjsSyncResources
3698   dependsOn jalviewjsSyncSiteResources
3699   dependsOn jalviewjsSyncBuildProperties
3700   dependsOn jalviewjsSyncCore
3701 }
3702
3703
3704 task jalviewjsBuildSite {
3705   group "JalviewJS"
3706   description "Builds the whole website including transpiled code"
3707   dependsOn jalviewjsCopyTransferSiteJs
3708   dependsOn jalviewjsPrepareSite
3709 }
3710
3711
3712 task cleanJalviewjsTransferSite {
3713   doFirst {
3714     delete "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3715     delete "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3716     delete "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3717     delete "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3718   }
3719 }
3720
3721
3722 task cleanJalviewjsSite {
3723   dependsOn cleanJalviewjsTransferSite
3724   doFirst {
3725     delete "${jalviewDir}/${jalviewjsSiteDir}"
3726   }
3727 }
3728
3729
3730 task jalviewjsSiteTar(type: Tar) {
3731   group "JalviewJS"
3732   description "Creates a tar.gz file for the website"
3733   dependsOn jalviewjsBuildSite
3734   def outputFilename = "jalviewjs-site-${JALVIEW_VERSION}.tar.gz"
3735   archiveFileName = outputFilename
3736
3737   compression Compression.GZIP
3738
3739   from "${jalviewDir}/${jalviewjsSiteDir}"
3740   into jalviewjs_site_dir // this is inside the tar file
3741
3742   inputs.dir("${jalviewDir}/${jalviewjsSiteDir}")
3743 }
3744
3745
3746 task jalviewjsServer {
3747   group "JalviewJS"
3748   def filename = "jalviewjsTest.html"
3749   description "Starts a webserver on localhost to test the website. See ${filename} to access local site on most recently used port."
3750   def htmlFile = "${jalviewDirAbsolutePath}/${filename}"
3751   doLast {
3752
3753     def factory
3754     try {
3755       def f = Class.forName("org.gradle.plugins.javascript.envjs.http.simple.SimpleHttpFileServerFactory")
3756       factory = f.newInstance()
3757     } catch (ClassNotFoundException e) {
3758       throw new GradleException("Unable to create SimpleHttpFileServerFactory")
3759     }
3760     def port = Integer.valueOf(jalviewjs_server_port)
3761     def start = port
3762     def running = false
3763     def url
3764     def jalviewjsServer
3765     while(port < start+1000 && !running) {
3766       try {
3767         def doc_root = new File("${jalviewDirAbsolutePath}/${jalviewjsSiteDir}")
3768         jalviewjsServer = factory.start(doc_root, port)
3769         running = true
3770         url = jalviewjsServer.getResourceUrl(jalviewjs_server_resource)
3771         println("SERVER STARTED with document root ${doc_root}.")
3772         println("Go to "+url+" . Run  gradle --stop  to stop (kills all gradle daemons).")
3773         println("For debug: "+url+"?j2sdebug")
3774         println("For verbose: "+url+"?j2sverbose")
3775       } catch (Exception e) {
3776         port++;
3777       }
3778     }
3779     def htmlText = """
3780       <p><a href="${url}">JalviewJS Test. &lt;${url}&gt;</a></p>
3781       <p><a href="${url}?j2sdebug">JalviewJS Test with debug. &lt;${url}?j2sdebug&gt;</a></p>
3782       <p><a href="${url}?j2sverbose">JalviewJS Test with verbose. &lt;${url}?j2sdebug&gt;</a></p>
3783       """
3784     jalviewjsCoreClasslists.each { cl ->
3785       def urlcore = jalviewjsServer.getResourceUrl(file(cl.outputfile).getName())
3786       htmlText += """
3787       <p><a href="${urlcore}">${jalviewjsJalviewTemplateName} [core ${cl.name}]. &lt;${urlcore}&gt;</a></p>
3788       """
3789       println("For core ${cl.name}: "+urlcore)
3790     }
3791
3792     file(htmlFile).text = htmlText
3793   }
3794
3795   outputs.file(htmlFile)
3796   outputs.upToDateWhen({false})
3797 }
3798
3799
3800 task cleanJalviewjsAll {
3801   group "JalviewJS"
3802   description "Delete all configuration and build artifacts to do with JalviewJS build"
3803   dependsOn cleanJalviewjsSite
3804   dependsOn jalviewjsEclipsePaths
3805   
3806   doFirst {
3807     delete "${jalviewDir}/${jalviewjsBuildDir}"
3808     delete "${jalviewDir}/${eclipse_bin_dir}"
3809     if (eclipseWorkspace != null && file(eclipseWorkspace.getAbsolutePath()+"/.metadata").exists()) {
3810       delete file(eclipseWorkspace.getAbsolutePath()+"/.metadata")
3811     }
3812     delete jalviewjsJ2sAltSettingsFileName
3813   }
3814
3815   outputs.upToDateWhen( { false } )
3816 }
3817
3818
3819 task jalviewjsIDE_checkJ2sPlugin {
3820   group "00 JalviewJS in Eclipse"
3821   description "Compare the swingjs/net.sf.j2s.core(-j11)?.jar file with the Eclipse IDE's plugin version (found in the 'dropins' dir)"
3822
3823   doFirst {
3824     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
3825     def j2sPluginFile = file(j2sPlugin)
3826     def eclipseHome = System.properties["eclipse.home.location"]
3827     if (eclipseHome == null || ! IN_ECLIPSE) {
3828       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. Skipping J2S Plugin Check.")
3829     }
3830     def eclipseJ2sPluginDirs = [ "${eclipseHome}/dropins" ]
3831     def altPluginsDir = System.properties["org.eclipse.equinox.p2.reconciler.dropins.directory"]
3832     if (altPluginsDir != null && file(altPluginsDir).exists()) {
3833       eclipseJ2sPluginDirs += altPluginsDir
3834     }
3835     def foundPlugin = false
3836     def j2sPluginFileName = j2sPluginFile.getName()
3837     def eclipseJ2sPlugin
3838     def eclipseJ2sPluginFile
3839     eclipseJ2sPluginDirs.any { dir ->
3840       eclipseJ2sPlugin = "${dir}/${j2sPluginFileName}"
3841       eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
3842       if (eclipseJ2sPluginFile.exists()) {
3843         foundPlugin = true
3844         return true
3845       }
3846     }
3847     if (!foundPlugin) {
3848       def msg = "Eclipse J2S Plugin is not installed (could not find '${j2sPluginFileName}' in\n"+eclipseJ2sPluginDirs.join("\n")+"\n)\nTry running task jalviewjsIDE_copyJ2sPlugin"
3849       System.err.println(msg)
3850       throw new StopExecutionException(msg)
3851     }
3852
3853     def digest = MessageDigest.getInstance("MD5")
3854
3855     digest.update(j2sPluginFile.text.bytes)
3856     def j2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
3857
3858     digest.update(eclipseJ2sPluginFile.text.bytes)
3859     def eclipseJ2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
3860      
3861     if (j2sPluginMd5 != eclipseJ2sPluginMd5) {
3862       def msg = "WARNING! Eclipse J2S Plugin '${eclipseJ2sPlugin}' is different to this commit's version '${j2sPlugin}'"
3863       System.err.println(msg)
3864       throw new StopExecutionException(msg)
3865     } else {
3866       def msg = "Eclipse J2S Plugin '${eclipseJ2sPlugin}' is the same as '${j2sPlugin}' (this is good)"
3867       println(msg)
3868     }
3869   }
3870 }
3871
3872 task jalviewjsIDE_copyJ2sPlugin {
3873   group "00 JalviewJS in Eclipse"
3874   description "Copy the swingjs/net.sf.j2s.core(-j11)?.jar file into the Eclipse IDE's 'dropins' dir"
3875
3876   doFirst {
3877     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
3878     def j2sPluginFile = file(j2sPlugin)
3879     def eclipseHome = System.properties["eclipse.home.location"]
3880     if (eclipseHome == null || ! IN_ECLIPSE) {
3881       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. NOT copying J2S Plugin.")
3882     }
3883     def eclipseJ2sPlugin = "${eclipseHome}/dropins/${j2sPluginFile.getName()}"
3884     def eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
3885     def msg = "WARNING! Copying this commit's j2s plugin '${j2sPlugin}' to Eclipse J2S Plugin '${eclipseJ2sPlugin}'\n* May require an Eclipse restart"
3886     System.err.println(msg)
3887     copy {
3888       from j2sPlugin
3889       eclipseJ2sPluginFile.getParentFile().mkdirs()
3890       into eclipseJ2sPluginFile.getParent()
3891     }
3892   }
3893 }
3894
3895
3896 task jalviewjsIDE_j2sFile {
3897   group "00 JalviewJS in Eclipse"
3898   description "Creates the .j2s file"
3899   dependsOn jalviewjsCreateJ2sSettings
3900 }
3901
3902
3903 task jalviewjsIDE_SyncCore {
3904   group "00 JalviewJS in Eclipse"
3905   description "Build the core js lib closures listed in the classlists dir and publish core html from template"
3906   dependsOn jalviewjsSyncCore
3907 }
3908
3909
3910 task jalviewjsIDE_SyncSiteAll {
3911   dependsOn jalviewjsSyncAllLibs
3912   dependsOn jalviewjsSyncResources
3913   dependsOn jalviewjsSyncSiteResources
3914   dependsOn jalviewjsSyncBuildProperties
3915 }
3916
3917
3918 cleanJalviewjsTransferSite.mustRunAfter jalviewjsIDE_SyncSiteAll
3919
3920
3921 task jalviewjsIDE_PrepareSite {
3922   group "00 JalviewJS in Eclipse"
3923   description "Sync libs and resources to site dir, but not closure cores"
3924
3925   dependsOn jalviewjsIDE_SyncSiteAll
3926   //dependsOn cleanJalviewjsTransferSite // not sure why this clean is here -- will slow down a re-run of this task
3927 }
3928
3929
3930 task jalviewjsIDE_AssembleSite {
3931   group "00 JalviewJS in Eclipse"
3932   description "Assembles unzipped supporting zipfiles, resources, site resources and closure cores into the Eclipse transpiled site"
3933   dependsOn jalviewjsPrepareSite
3934 }
3935
3936
3937 task jalviewjsIDE_SiteClean {
3938   group "00 JalviewJS in Eclipse"
3939   description "Deletes the Eclipse transpiled site"
3940   dependsOn cleanJalviewjsSite
3941 }
3942
3943
3944 task jalviewjsIDE_Server {
3945   group "00 JalviewJS in Eclipse"
3946   description "Starts a webserver on localhost to test the website"
3947   dependsOn jalviewjsServer
3948 }
3949
3950
3951 // buildship runs this at import or gradle refresh
3952 task eclipseSynchronizationTask {
3953   //dependsOn eclipseSetup
3954   dependsOn createBuildProperties
3955   if (J2S_ENABLED) {
3956     dependsOn jalviewjsIDE_j2sFile
3957     dependsOn jalviewjsIDE_checkJ2sPlugin
3958     dependsOn jalviewjsIDE_PrepareSite
3959   }
3960 }
3961
3962
3963 // buildship runs this at build time or project refresh
3964 task eclipseAutoBuildTask {
3965   //dependsOn jalviewjsIDE_checkJ2sPlugin
3966   //dependsOn jalviewjsIDE_PrepareSite
3967 }
3968
3969
3970 task jalviewjs {
3971   group "JalviewJS"
3972   description "Build the site"
3973   dependsOn jalviewjsBuildSite
3974 }