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