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