JAL-4418 first development release for 2.11.4.0 - release notes for
[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 '6.0.0'
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   timeout = Duration.ofMinutes(15)
1773 }
1774
1775 /* separated tests */
1776 task testTask1(type: Test) {
1777   group = "Verification"
1778   description = "Tests that need to be isolated from the main test run"
1779   useTestNG() {
1780     includeGroups name
1781     excludeGroups testng_excluded_groups.split(",")
1782     preserveOrder true
1783     useDefaultListeners=true
1784   }
1785   timeout = Duration.ofMinutes(5)
1786 }
1787
1788 task testTask2(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   timeout = Duration.ofMinutes(5)
1798 }
1799 task testTask3(type: Test) {
1800   group = "Verification"
1801   description = "Tests that need to be isolated from the main test run"
1802   useTestNG() {
1803     includeGroups name
1804     excludeGroups testng_excluded_groups.split(",")
1805     preserveOrder true
1806     useDefaultListeners=true
1807   }
1808   timeout = Duration.ofMinutes(5)
1809 }
1810
1811 /* insert more testTaskNs here -- change N to next digit or other string */
1812 /*
1813 task testTaskN(type: Test) {
1814   group = "Verification"
1815   description = "Tests that need to be isolated from the main test run"
1816   useTestNG() {
1817     includeGroups name
1818     excludeGroups testng_excluded_groups.split(",")
1819     preserveOrder true
1820     useDefaultListeners=true
1821   }
1822 }
1823 */
1824
1825
1826 /*
1827  * adapted from https://medium.com/@wasyl/pretty-tests-summary-in-gradle-744804dd676c
1828  * to summarise test results from all Test tasks
1829  */
1830 /* START of test tasks results summary */
1831 import groovy.time.TimeCategory
1832 import org.gradle.api.tasks.testing.logging.TestExceptionFormat
1833 import org.gradle.api.tasks.testing.logging.TestLogEvent
1834 rootProject.ext.testsResults = [] // Container for tests summaries
1835
1836 tasks.withType(Test).matching {t -> t.getName().startsWith("testTask")}.all { testTask ->
1837
1838   // from original test task
1839   if (useClover) {
1840     dependsOn cloverClasses
1841   } else { //?
1842     dependsOn testClasses //?
1843   }
1844
1845   // run main tests first
1846   if (!testTask.name.equals("testTask0"))
1847     testTask.mustRunAfter "testTask0"
1848
1849   testTask.testLogging { logging ->
1850     events TestLogEvent.FAILED
1851 //      TestLogEvent.SKIPPED,
1852 //      TestLogEvent.STANDARD_OUT,
1853 //      TestLogEvent.STANDARD_ERROR
1854
1855     exceptionFormat TestExceptionFormat.FULL
1856     showExceptions true
1857     showCauses true
1858     showStackTraces true
1859     if (test_output) {
1860       showStandardStreams true
1861     }
1862     info.events = [ TestLogEvent.FAILED ]
1863   }
1864
1865   if (OperatingSystem.current().isMacOsX()) {
1866     testTask.systemProperty "apple.awt.UIElement", "true"
1867     testTask.environment "JAVA_TOOL_OPTIONS", "-Dapple.awt.UIElement=true"
1868   }
1869
1870
1871   ignoreFailures = true // Always try to run all tests for all modules
1872
1873   afterSuite { desc, result ->
1874     if (desc.parent)
1875       return // Only summarize results for whole modules
1876
1877     def resultsInfo = [testTask.project.name, testTask.name, result, TimeCategory.minus(new Date(result.endTime), new Date(result.startTime)), testTask.reports.html.entryPoint]
1878
1879     rootProject.ext.testsResults.add(resultsInfo)
1880   }
1881
1882   // from original test task
1883   maxHeapSize = "1024m"
1884
1885   workingDir = jalviewDir
1886   def testLaf = project.findProperty("test_laf")
1887   if (testLaf != null) {
1888     println("Setting Test LaF to '${testLaf}'")
1889     systemProperty "laf", testLaf
1890   }
1891   def testHiDPIScale = project.findProperty("test_HiDPIScale")
1892   if (testHiDPIScale != null) {
1893     println("Setting Test HiDPI Scale to '${testHiDPIScale}'")
1894     systemProperty "sun.java2d.uiScale", testHiDPIScale
1895   }
1896   sourceCompatibility = compile_source_compatibility
1897   targetCompatibility = compile_target_compatibility
1898   jvmArgs += additional_compiler_args
1899
1900   doFirst {
1901     // this is not perfect yet -- we should only add the commandLineIncludePatterns to the
1902     // testTasks that include the tests, and exclude all from the others.
1903     // get --test argument
1904     filter.commandLineIncludePatterns = test.filter.commandLineIncludePatterns
1905     // do something with testTask.getCandidateClassFiles() to see if the test should silently finish because of the
1906     // commandLineIncludePatterns not matching anything.  Instead we are doing setFailOnNoMatchingTests(false) below
1907
1908
1909     if (useClover) {
1910       println("Running tests " + (useClover?"WITH":"WITHOUT") + " clover")
1911     }
1912   }
1913
1914
1915   /* don't fail on no matching tests (so --tests will run across all testTasks) */
1916   testTask.filter.setFailOnNoMatchingTests(false)
1917
1918   /* ensure the "test" task dependsOn all the testTasks */
1919   test.dependsOn testTask
1920 }
1921
1922 gradle.buildFinished {
1923     def allResults = rootProject.ext.testsResults
1924
1925     if (!allResults.isEmpty()) {
1926         printResults allResults
1927         allResults.each {r ->
1928           if (r[2].resultType == TestResult.ResultType.FAILURE)
1929             throw new GradleException("Failed tests!")
1930         }
1931     }
1932 }
1933
1934 private static String colString(styler, col, colour, text) {
1935   return col?"${styler[colour](text)}":text
1936 }
1937
1938 private static String getSummaryLine(s, pn, tn, rt, rc, rs, rf, rsk, t, col) {
1939   def colour = 'black'
1940   def text = rt
1941   def nocol = false
1942   if (rc == 0) {
1943     text = "-----"
1944     nocol = true
1945   } else {
1946     switch(rt) {
1947       case TestResult.ResultType.SUCCESS:
1948         colour = 'green'
1949         break;
1950       case TestResult.ResultType.FAILURE:
1951         colour = 'red'
1952         break;
1953       default:
1954         nocol = true
1955         break;
1956     }
1957   }
1958   StringBuilder sb = new StringBuilder()
1959   sb.append("${pn}")
1960   if (tn != null)
1961     sb.append(":${tn}")
1962   sb.append(" results: ")
1963   sb.append(colString(s, col && !nocol, colour, text))
1964   sb.append(" (")
1965   sb.append("${rc} tests, ")
1966   sb.append(colString(s, col && rs > 0, 'green', rs))
1967   sb.append(" successes, ")
1968   sb.append(colString(s, col && rf > 0, 'red', rf))
1969   sb.append(" failures, ")
1970   sb.append("${rsk} skipped) in ${t}")
1971   return sb.toString()
1972 }
1973
1974 private static void printResults(allResults) {
1975
1976     // styler from https://stackoverflow.com/a/56139852
1977     def styler = 'black red green yellow blue magenta cyan white'.split().toList().withIndex(30).collectEntries { key, val -> [(key) : { "\033[${val}m${it}\033[0m" }] }
1978
1979     def maxLength = 0
1980     def failedTests = false
1981     def summaryLines = []
1982     def totalcount = 0
1983     def totalsuccess = 0
1984     def totalfail = 0
1985     def totalskip = 0
1986     def totaltime = TimeCategory.getSeconds(0)
1987     // sort on project name then task name
1988     allResults.sort {a, b -> a[0] == b[0]? a[1]<=>b[1]:a[0] <=> b[0]}.each {
1989       def projectName = it[0]
1990       def taskName = it[1]
1991       def result = it[2]
1992       def time = it[3]
1993       def report = it[4]
1994       def summaryCol = getSummaryLine(styler, projectName, taskName, result.resultType, result.testCount, result.successfulTestCount, result.failedTestCount, result.skippedTestCount, time, true)
1995       def summaryPlain = getSummaryLine(styler, projectName, taskName, result.resultType, result.testCount, result.successfulTestCount, result.failedTestCount, result.skippedTestCount, time, false)
1996       def reportLine = "Report file: ${report}"
1997       def ls = summaryPlain.length()
1998       def lr = reportLine.length()
1999       def m = [ls, lr].max()
2000       if (m > maxLength)
2001         maxLength = m
2002       def info = [ls, summaryCol, reportLine]
2003       summaryLines.add(info)
2004       failedTests |= result.resultType == TestResult.ResultType.FAILURE
2005       totalcount += result.testCount
2006       totalsuccess += result.successfulTestCount
2007       totalfail += result.failedTestCount
2008       totalskip += result.skippedTestCount
2009       totaltime += time
2010     }
2011     def totalSummaryCol = getSummaryLine(styler, "OVERALL", "", failedTests?TestResult.ResultType.FAILURE:TestResult.ResultType.SUCCESS, totalcount, totalsuccess, totalfail, totalskip, totaltime, true)
2012     def totalSummaryPlain = getSummaryLine(styler, "OVERALL", "", failedTests?TestResult.ResultType.FAILURE:TestResult.ResultType.SUCCESS, totalcount, totalsuccess, totalfail, totalskip, totaltime, false)
2013     def tls = totalSummaryPlain.length()
2014     if (tls > maxLength)
2015       maxLength = tls
2016     def info = [tls, totalSummaryCol, null]
2017     summaryLines.add(info)
2018
2019     def allSummaries = []
2020     for(sInfo : summaryLines) {
2021       def ls = sInfo[0]
2022       def summary = sInfo[1]
2023       def report = sInfo[2]
2024
2025       StringBuilder sb = new StringBuilder()
2026       sb.append("│" + summary + " " * (maxLength - ls) + "│")
2027       if (report != null) {
2028         sb.append("\n│" + report + " " * (maxLength - report.length()) + "│")
2029       }
2030       allSummaries += sb.toString()
2031     }
2032
2033     println "┌${"${"─" * maxLength}"}┐"
2034     println allSummaries.join("\n├${"${"─" * maxLength}"}┤\n")
2035     println "└${"${"─" * maxLength}"}┘"
2036 }
2037 /* END of test tasks results summary */
2038
2039
2040 task compileLinkCheck(type: JavaCompile) {
2041   options.fork = true
2042   classpath = files("${jalviewDir}/${utils_dir}")
2043   destinationDir = file("${jalviewDir}/${utils_dir}")
2044   source = fileTree(dir: "${jalviewDir}/${utils_dir}", include: ["HelpLinksChecker.java", "BufferedLineReader.java"])
2045
2046   inputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.java")
2047   inputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.java")
2048   outputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.class")
2049   outputs.file("${jalviewDir}/${utils_dir}/BufferedLineReader.class")
2050 }
2051
2052
2053 task linkCheck(type: JavaExec) {
2054   dependsOn prepare
2055   dependsOn compileLinkCheck
2056
2057   def helpLinksCheckerOutFile = file("${jalviewDir}/${utils_dir}/HelpLinksChecker.out")
2058   classpath = files("${jalviewDir}/${utils_dir}")
2059   main = "HelpLinksChecker"
2060   workingDir = "${helpBuildDir}"
2061   args = [ "${helpBuildDir}/${help_dir}", "-nointernet" ]
2062
2063   def outFOS = new FileOutputStream(helpLinksCheckerOutFile, false) // false == don't append
2064   standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2065     outFOS,
2066     System.out)
2067   errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2068     outFOS,
2069     System.err)
2070
2071   inputs.dir(helpBuildDir)
2072   outputs.file(helpLinksCheckerOutFile)
2073 }
2074
2075
2076 // import the pubhtmlhelp target
2077 ant.properties.basedir = "${jalviewDir}"
2078 ant.properties.helpBuildDir = "${helpBuildDir}/${help_dir}"
2079 ant.importBuild "${utils_dir}/publishHelp.xml"
2080
2081
2082 task cleanPackageDir(type: Delete) {
2083   doFirst {
2084     delete fileTree(dir: "${jalviewDir}/${package_dir}", include: "*.jar")
2085   }
2086 }
2087
2088
2089 jar {
2090   dependsOn prepare
2091   dependsOn linkCheck
2092
2093   manifest {
2094     attributes "Main-Class": main_class,
2095     "Permissions": "all-permissions",
2096     "Application-Name": applicationName,
2097     "Codebase": application_codebase,
2098     "Implementation-Version": JALVIEW_VERSION
2099   }
2100
2101   def outputDir = "${jalviewDir}/${package_dir}"
2102   destinationDirectory = file(outputDir)
2103   archiveFileName = rootProject.name+".jar"
2104   duplicatesStrategy "EXCLUDE"
2105
2106
2107   exclude "cache*/**"
2108   exclude "*.jar"
2109   exclude "*.jar.*"
2110   exclude "**/*.jar"
2111   exclude "**/*.jar.*"
2112
2113   inputs.dir(sourceSets.main.java.outputDir)
2114   sourceSets.main.resources.srcDirs.each{ dir ->
2115     inputs.dir(dir)
2116   }
2117   outputs.file("${outputDir}/${archiveFileName}")
2118 }
2119
2120
2121 task copyJars(type: Copy) {
2122   from fileTree(dir: classesDir, include: "**/*.jar").files
2123   into "${jalviewDir}/${package_dir}"
2124 }
2125
2126
2127 // doing a Sync instead of Copy as Copy doesn't deal with "outputs" very well
2128 task syncJars(type: Sync) {
2129   dependsOn jar
2130   from fileTree(dir: "${jalviewDir}/${libDistDir}", include: "**/*.jar").files
2131   into "${jalviewDir}/${package_dir}"
2132   preserve {
2133     include jar.archiveFileName.getOrNull()
2134   }
2135 }
2136
2137
2138 task makeDist {
2139   group = "build"
2140   description = "Put all required libraries in dist"
2141   // order of "cleanPackageDir", "copyJars", "jar" important!
2142   jar.mustRunAfter cleanPackageDir
2143   syncJars.mustRunAfter cleanPackageDir
2144   dependsOn cleanPackageDir
2145   dependsOn syncJars
2146   dependsOn jar
2147   outputs.dir("${jalviewDir}/${package_dir}")
2148 }
2149
2150
2151 task cleanDist {
2152   dependsOn cleanPackageDir
2153   dependsOn cleanTest
2154   dependsOn clean
2155 }
2156
2157
2158 task launcherJar(type: Jar) {
2159   manifest {
2160       attributes (
2161         "Main-Class": shadow_jar_main_class,
2162         "Implementation-Version": JALVIEW_VERSION,
2163         "Application-Name": applicationName
2164       )
2165   }
2166 }
2167
2168 shadowJar {
2169   group = "distribution"
2170   description = "Create a single jar file with all dependency libraries merged. Can be run with java -jar"
2171   if (buildDist) {
2172     dependsOn makeDist
2173   }
2174
2175   def jarFiles = fileTree(dir: "${jalviewDir}/${libDistDir}", include: "*.jar", exclude: "regex.jar").getFiles()
2176   def groovyJars = jarFiles.findAll {it1 -> file(it1).getName().startsWith("groovy-swing")}
2177   def otherJars = jarFiles.findAll {it2 -> !file(it2).getName().startsWith("groovy-swing")}
2178   from groovyJars
2179   from otherJars
2180
2181   manifest {
2182     // shadowJar manifest must inheritFrom another Jar task.  Can't set attributes here.
2183     inheritFrom(project.tasks.launcherJar.manifest)
2184   }
2185   // we need to include the groovy-swing Include-Package for it to run in the shadowJar
2186   doFirst {
2187     def jarFileManifests = []
2188     groovyJars.each { jarFile ->
2189       def mf = zipTree(jarFile).getFiles().find { it.getName().equals("MANIFEST.MF") }
2190       if (mf != null) {
2191         jarFileManifests += mf
2192       }
2193     }
2194     manifest {
2195       from (jarFileManifests) {
2196         eachEntry { details ->
2197           if (!details.key.equals("Import-Package")) {
2198             details.exclude()
2199           }
2200         }
2201       }
2202     }
2203   }
2204
2205   duplicatesStrategy "INCLUDE"
2206
2207   // this mainClassName is mandatory but gets ignored due to manifest created in doFirst{}. Set the Main-Class as an attribute in launcherJar instead
2208   mainClassName = shadow_jar_main_class
2209   mergeServiceFiles()
2210   classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
2211   minimize()
2212 }
2213
2214 task getdownImagesCopy() {
2215   inputs.dir getdownImagesDir
2216   outputs.dir getdownImagesBuildDir
2217
2218   doFirst {
2219     copy {
2220       from(getdownImagesDir) {
2221         include("*getdown*.png")
2222       }
2223       into getdownImagesBuildDir
2224     }
2225   }
2226 }
2227
2228 task getdownImagesProcess() {
2229   dependsOn getdownImagesCopy
2230
2231   doFirst {
2232     if (backgroundImageText) {
2233       if (convertBinary == null) {
2234         throw new StopExecutionException("No ImageMagick convert binary installed at '${convertBinaryExpectedLocation}'")
2235       }
2236       if (!project.hasProperty("getdown_background_image_text_suffix_cmd")) {
2237         throw new StopExecutionException("No property 'getdown_background_image_text_suffix_cmd' defined. See channel_gradle.properties for channel ${CHANNEL}")
2238       }
2239       fileTree(dir: getdownImagesBuildDir, include: "*background*.png").getFiles().each { file ->
2240         exec {
2241           executable convertBinary
2242           args = [
2243             file.getPath(),
2244             '-font', getdown_background_image_text_font,
2245             '-fill', getdown_background_image_text_colour,
2246             '-draw', sprintf(getdown_background_image_text_suffix_cmd, channelSuffix),
2247             '-draw', sprintf(getdown_background_image_text_commit_cmd, "git-commit: ${gitHash}"),
2248             '-draw', sprintf(getdown_background_image_text_date_cmd, getDate("yyyy-MM-dd HH:mm:ss")),
2249             file.getPath()
2250           ]
2251         }
2252       }
2253     }
2254   }
2255 }
2256
2257 task getdownImages() {
2258   dependsOn getdownImagesProcess
2259 }
2260
2261 task getdownWebsiteBuild() {
2262   group = "distribution"
2263   description = "Create the getdown minimal app folder, and website folder for this version of jalview. Website folder also used for offline app installer. No digest is created."
2264
2265   dependsOn getdownImages
2266   if (buildDist) {
2267     dependsOn makeDist
2268   }
2269
2270   def getdownWebsiteResourceFilenames = []
2271   def getdownResourceDir = getdownResourceDir
2272   def getdownResourceFilenames = []
2273
2274   doFirst {
2275     // clean the getdown website and files dir before creating getdown folders
2276     delete getdownAppBaseDir
2277     delete getdownFilesDir
2278
2279     copy {
2280       from buildProperties
2281       rename(file(buildProperties).getName(), getdown_build_properties)
2282       into getdownAppDir
2283     }
2284     getdownWebsiteResourceFilenames += "${getdownAppDistDir}/${getdown_build_properties}"
2285
2286     copy {
2287       from channelPropsFile
2288       filter(ReplaceTokens,
2289         beginToken: '__',
2290         endToken: '__',
2291         tokens: [
2292           'SUFFIX': channelSuffix
2293         ]
2294       )
2295       into getdownAppBaseDir
2296     }
2297     getdownWebsiteResourceFilenames += file(channelPropsFile).getName()
2298
2299     // set some getdownTxt_ properties then go through all properties looking for getdownTxt_...
2300     def props = project.properties.sort { it.key }
2301     if (getdownAltJavaMinVersion != null && getdownAltJavaMinVersion.length() > 0) {
2302       props.put("getdown_txt_java_min_version", getdownAltJavaMinVersion)
2303     }
2304     if (getdownAltJavaMaxVersion != null && getdownAltJavaMaxVersion.length() > 0) {
2305       props.put("getdown_txt_java_max_version", getdownAltJavaMaxVersion)
2306     }
2307     if (getdownAltMultiJavaLocation != null && getdownAltMultiJavaLocation.length() > 0) {
2308       props.put("getdown_txt_multi_java_location", getdownAltMultiJavaLocation)
2309     }
2310     if (getdownImagesBuildDir != null && file(getdownImagesBuildDir).exists()) {
2311       props.put("getdown_txt_ui.background_image", "${getdownImagesBuildDir}/${getdown_background_image}")
2312       props.put("getdown_txt_ui.instant_background_image", "${getdownImagesBuildDir}/${getdown_instant_background_image}")
2313       props.put("getdown_txt_ui.error_background", "${getdownImagesBuildDir}/${getdown_error_background}")
2314       props.put("getdown_txt_ui.progress_image", "${getdownImagesBuildDir}/${getdown_progress_image}")
2315       props.put("getdown_txt_ui.icon", "${getdownImagesDir}/${getdown_icon}")
2316       props.put("getdown_txt_ui.mac_dock_icon", "${getdownImagesDir}/${getdown_mac_dock_icon}")
2317     }
2318
2319     props.put("getdown_txt_title", jalview_name)
2320     props.put("getdown_txt_ui.name", applicationName)
2321
2322     // start with appbase
2323     getdownTextLines += "appbase = ${getdownAppBase}"
2324     props.each{ prop, val ->
2325       if (prop.startsWith("getdown_txt_") && val != null) {
2326         if (prop.startsWith("getdown_txt_multi_")) {
2327           def key = prop.substring(18)
2328           val.split(",").each{ v ->
2329             def line = "${key} = ${v}"
2330             getdownTextLines += line
2331           }
2332         } else {
2333           // file values rationalised
2334           if (val.indexOf('/') > -1 || prop.startsWith("getdown_txt_resource")) {
2335             def r = null
2336             if (val.indexOf('/') == 0) {
2337               // absolute path
2338               r = file(val)
2339             } else if (val.indexOf('/') > 0) {
2340               // relative path (relative to jalviewDir)
2341               r = file( "${jalviewDir}/${val}" )
2342             }
2343             if (r.exists()) {
2344               val = "${getdown_resource_dir}/" + r.getName()
2345               getdownWebsiteResourceFilenames += val
2346               getdownResourceFilenames += r.getPath()
2347             }
2348           }
2349           if (! prop.startsWith("getdown_txt_resource")) {
2350             def line = prop.substring(12) + " = ${val}"
2351             getdownTextLines += line
2352           }
2353         }
2354       }
2355     }
2356
2357     getdownWebsiteResourceFilenames.each{ filename ->
2358       getdownTextLines += "resource = ${filename}"
2359     }
2360     getdownResourceFilenames.each{ filename ->
2361       copy {
2362         from filename
2363         into getdownResourceDir
2364       }
2365     }
2366     
2367     def getdownWrapperScripts = [ getdown_bash_wrapper_script, getdown_powershell_wrapper_script, getdown_batch_wrapper_script ]
2368     getdownWrapperScripts.each{ script ->
2369       def s = file( "${jalviewDir}/utils/getdown/${getdown_wrapper_script_dir}/${script}" )
2370       if (s.exists()) {
2371         copy {
2372           from s
2373           into "${getdownAppBaseDir}/${getdown_wrapper_script_dir}"
2374         }
2375         getdownTextLines += "xresource = ${getdown_wrapper_script_dir}/${script}"
2376       }
2377     }
2378
2379     def codeFiles = []
2380     fileTree(file(package_dir)).each{ f ->
2381       if (f.isDirectory()) {
2382         def files = fileTree(dir: f, include: ["*"]).getFiles()
2383         codeFiles += files
2384       } else if (f.exists()) {
2385         codeFiles += f
2386       }
2387     }
2388     def jalviewJar = jar.archiveFileName.getOrNull()
2389     // put jalview.jar first for CLASSPATH and .properties files reasons
2390     codeFiles.sort{a, b -> ( a.getName() == jalviewJar ? -1 : ( b.getName() == jalviewJar ? 1 : a <=> b ) ) }.each{f ->
2391       def name = f.getName()
2392       def line = "code = ${getdownAppDistDir}/${name}"
2393       getdownTextLines += line
2394       copy {
2395         from f.getPath()
2396         into getdownAppDir
2397       }
2398     }
2399
2400     // NOT USING MODULES YET, EVERYTHING SHOULD BE IN dist
2401     /*
2402     if (JAVA_VERSION.equals("11")) {
2403     def j11libFiles = fileTree(dir: "${jalviewDir}/${j11libDir}", include: ["*.jar"]).getFiles()
2404     j11libFiles.sort().each{f ->
2405     def name = f.getName()
2406     def line = "code = ${getdown_j11lib_dir}/${name}"
2407     getdownTextLines += line
2408     copy {
2409     from f.getPath()
2410     into getdownJ11libDir
2411     }
2412     }
2413     }
2414      */
2415
2416     // 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.
2417     //getdownTextLines += "class = " + file(getdownLauncher).getName()
2418     getdownTextLines += "resource = ${getdown_launcher_new}"
2419     getdownTextLines += "class = ${main_class}"
2420     // Not setting these properties in general so that getdownappbase and getdowndistdir will default to release version in jalview.bin.Cache
2421     if (getdownSetAppBaseProperty) {
2422       getdownTextLines += "jvmarg = -Dgetdowndistdir=${getdownAppDistDir}"
2423       getdownTextLines += "jvmarg = -Dgetdownappbase=${getdownAppBase}"
2424     }
2425
2426     def getdownTxt = file("${getdownAppBaseDir}/getdown.txt")
2427     getdownTxt.write(getdownTextLines.join("\n"))
2428
2429     getdownLaunchJvl = getdown_launch_jvl_name + ( (jvlChannelName != null && jvlChannelName.length() > 0)?"-${jvlChannelName}":"" ) + ".jvl"
2430     def launchJvl = file("${getdownAppBaseDir}/${getdownLaunchJvl}")
2431     launchJvl.write("appbase=${getdownAppBase}")
2432
2433     // files going into the getdown website dir: getdown-launcher.jar
2434     copy {
2435       from getdownLauncher
2436       rename(file(getdownLauncher).getName(), getdown_launcher_new)
2437       into getdownAppBaseDir
2438     }
2439
2440     // files going into the getdown website dir: getdown-launcher(-local).jar
2441     copy {
2442       from getdownLauncher
2443       if (file(getdownLauncher).getName() != getdown_launcher) {
2444         rename(file(getdownLauncher).getName(), getdown_launcher)
2445       }
2446       into getdownAppBaseDir
2447     }
2448
2449     // files going into the getdown website dir: ./install dir and files
2450     if (! (CHANNEL.startsWith("ARCHIVE") || CHANNEL.startsWith("DEVELOP"))) {
2451       copy {
2452         from getdownTxt
2453         from getdownLauncher
2454         from "${getdownAppDir}/${getdown_build_properties}"
2455         if (file(getdownLauncher).getName() != getdown_launcher) {
2456           rename(file(getdownLauncher).getName(), getdown_launcher)
2457         }
2458         into getdownInstallDir
2459       }
2460
2461       // and make a copy in the getdown files dir (these are not downloaded by getdown)
2462       copy {
2463         from getdownInstallDir
2464         into getdownFilesInstallDir
2465       }
2466     }
2467
2468     // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
2469     copy {
2470       from getdownTxt
2471       from launchJvl
2472       from getdownLauncher
2473       from "${getdownAppBaseDir}/${getdown_build_properties}"
2474       from "${getdownAppBaseDir}/${channel_props}"
2475       if (file(getdownLauncher).getName() != getdown_launcher) {
2476         rename(file(getdownLauncher).getName(), getdown_launcher)
2477       }
2478       into getdownFilesDir
2479     }
2480
2481     // and ./resource (not all downloaded by getdown)
2482     copy {
2483       from getdownResourceDir
2484       into "${getdownFilesDir}/${getdown_resource_dir}"
2485     }
2486   }
2487
2488   if (buildDist) {
2489     inputs.dir("${jalviewDir}/${package_dir}")
2490   }
2491   outputs.dir(getdownAppBaseDir)
2492   outputs.dir(getdownFilesDir)
2493 }
2494
2495
2496 // a helper task to allow getdown digest of any dir: `gradle getdownDigestDir -PDIGESTDIR=/path/to/my/random/getdown/dir
2497 task getdownDigestDir(type: JavaExec) {
2498   group "Help"
2499   description "A task to run a getdown Digest on a dir with getdown.txt. Provide a DIGESTDIR property via -PDIGESTDIR=..."
2500
2501   def digestDirPropertyName = "DIGESTDIR"
2502   doFirst {
2503     classpath = files(getdownLauncher)
2504     def digestDir = findProperty(digestDirPropertyName)
2505     if (digestDir == null) {
2506       throw new GradleException("Must provide a DIGESTDIR value to produce an alternative getdown digest")
2507     }
2508     args digestDir
2509   }
2510   main = "com.threerings.getdown.tools.Digester"
2511 }
2512
2513
2514 task getdownDigest(type: JavaExec) {
2515   group = "distribution"
2516   description = "Digest the getdown website folder"
2517
2518   dependsOn getdownWebsiteBuild
2519
2520   doFirst {
2521     classpath = files(getdownLauncher)
2522   }
2523   main = "com.threerings.getdown.tools.Digester"
2524   args getdownAppBaseDir
2525   inputs.dir(getdownAppBaseDir)
2526   outputs.file("${getdownAppBaseDir}/digest2.txt")
2527 }
2528
2529
2530 task getdown() {
2531   group = "distribution"
2532   description = "Create the minimal and full getdown app folder for installers and website and create digest file"
2533   dependsOn getdownDigest
2534   doLast {
2535     if (reportRsyncCommand) {
2536       def fromDir = getdownAppBaseDir + (getdownAppBaseDir.endsWith('/')?'':'/')
2537       def toDir = "${getdown_rsync_dest}/${getdownDir}" + (getdownDir.endsWith('/')?'':'/')
2538       println "LIKELY RSYNC COMMAND:"
2539       println "mkdir -p '$toDir'\nrsync -avh --delete '$fromDir' '$toDir'"
2540       if (RUNRSYNC == "true") {
2541         exec {
2542           commandLine "mkdir", "-p", toDir
2543         }
2544         exec {
2545           commandLine "rsync", "-avh", "--delete", fromDir, toDir
2546         }
2547       }
2548     }
2549   }
2550 }
2551
2552 task getdownWebsite {
2553   group = "distribution"
2554   description = "A task to create the whole getdown channel website dir including digest file"
2555
2556   dependsOn getdownWebsiteBuild
2557   dependsOn getdownDigest
2558 }
2559
2560 task getdownArchiveBuild() {
2561   group = "distribution"
2562   description = "Put files in the archive dir to go on the website"
2563
2564   dependsOn getdownWebsiteBuild
2565
2566   def v = "v${JALVIEW_VERSION_UNDERSCORES}"
2567   def vDir = "${getdownArchiveDir}/${v}"
2568   getdownFullArchiveDir = "${vDir}/getdown"
2569   getdownVersionLaunchJvl = "${vDir}/jalview-${v}.jvl"
2570
2571   def vAltDir = "alt_${v}"
2572   def archiveImagesDir = "${jalviewDir}/${channel_properties_dir}/old/images"
2573
2574   doFirst {
2575     // cleanup old "old" dir
2576     delete getdownArchiveDir
2577
2578     def getdownArchiveTxt = file("${getdownFullArchiveDir}/getdown.txt")
2579     getdownArchiveTxt.getParentFile().mkdirs()
2580     def getdownArchiveTextLines = []
2581     def getdownFullArchiveAppBase = "${getdownArchiveAppBase}${getdownArchiveAppBase.endsWith("/")?"":"/"}${v}/getdown/"
2582
2583     // the libdir
2584     copy {
2585       from "${getdownAppBaseDir}/${getdownAppDistDir}"
2586       into "${getdownFullArchiveDir}/${vAltDir}"
2587     }
2588
2589     getdownTextLines.each { line ->
2590       line = line.replaceAll("^(?<s>appbase\\s*=\\s*).*", '${s}'+getdownFullArchiveAppBase)
2591       line = line.replaceAll("^(?<s>(resource|code)\\s*=\\s*)${getdownAppDistDir}/", '${s}'+vAltDir+"/")
2592       line = line.replaceAll("^(?<s>ui.background_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background.png")
2593       line = line.replaceAll("^(?<s>ui.instant_background_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background_initialising.png")
2594       line = line.replaceAll("^(?<s>ui.error_background\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background_error.png")
2595       line = line.replaceAll("^(?<s>ui.progress_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_progress_bar.png")
2596       // remove the existing resource = resource/ or bin/ lines
2597       if (! line.matches("resource\\s*=\\s*(resource|bin)/.*")) {
2598         getdownArchiveTextLines += line
2599       }
2600     }
2601
2602     // the resource dir -- add these files as resource lines in getdown.txt
2603     copy {
2604       from "${archiveImagesDir}"
2605       into "${getdownFullArchiveDir}/${getdown_resource_dir}"
2606       eachFile { file ->
2607         getdownArchiveTextLines += "resource = ${getdown_resource_dir}/${file.getName()}"
2608       }
2609     }
2610
2611     getdownArchiveTxt.write(getdownArchiveTextLines.join("\n"))
2612
2613     def vLaunchJvl = file(getdownVersionLaunchJvl)
2614     vLaunchJvl.getParentFile().mkdirs()
2615     vLaunchJvl.write("appbase=${getdownFullArchiveAppBase}\n")
2616     def vLaunchJvlPath = vLaunchJvl.toPath().toAbsolutePath()
2617     def jvlLinkPath = file("${vDir}/jalview.jvl").toPath().toAbsolutePath()
2618     // for some reason filepath.relativize(fileInSameDirPath) gives a path to "../" which is wrong
2619     //java.nio.file.Files.createSymbolicLink(jvlLinkPath, jvlLinkPath.relativize(vLaunchJvlPath));
2620     java.nio.file.Files.createSymbolicLink(jvlLinkPath, java.nio.file.Paths.get(".",vLaunchJvl.getName()));
2621
2622     // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
2623     copy {
2624       from getdownLauncher
2625       from "${getdownAppBaseDir}/${getdownLaunchJvl}"
2626       from "${getdownAppBaseDir}/${getdown_launcher_new}"
2627       from "${getdownAppBaseDir}/${channel_props}"
2628       if (file(getdownLauncher).getName() != getdown_launcher) {
2629         rename(file(getdownLauncher).getName(), getdown_launcher)
2630       }
2631       into getdownFullArchiveDir
2632     }
2633
2634   }
2635 }
2636
2637 task getdownArchiveDigest(type: JavaExec) {
2638   group = "distribution"
2639   description = "Digest the getdown archive folder"
2640
2641   dependsOn getdownArchiveBuild
2642
2643   doFirst {
2644     classpath = files(getdownLauncher)
2645     args getdownFullArchiveDir
2646   }
2647   main = "com.threerings.getdown.tools.Digester"
2648   inputs.dir(getdownFullArchiveDir)
2649   outputs.file("${getdownFullArchiveDir}/digest2.txt")
2650 }
2651
2652 task getdownArchive() {
2653   group = "distribution"
2654   description = "Build the website archive dir with getdown digest"
2655
2656   dependsOn getdownArchiveBuild
2657   dependsOn getdownArchiveDigest
2658 }
2659
2660 tasks.withType(JavaCompile) {
2661         options.encoding = 'UTF-8'
2662 }
2663
2664
2665 clean {
2666   doFirst {
2667     delete getdownAppBaseDir
2668     delete getdownFilesDir
2669     delete getdownArchiveDir
2670   }
2671 }
2672
2673
2674 install4j {
2675   if (file(install4jHomeDir).exists()) {
2676     // good to go!
2677   } else if (file(System.getProperty("user.home")+"/buildtools/install4j").exists()) {
2678     install4jHomeDir = System.getProperty("user.home")+"/buildtools/install4j"
2679   } else if (file("/Applications/install4j.app/Contents/Resources/app").exists()) {
2680     install4jHomeDir = "/Applications/install4j.app/Contents/Resources/app"
2681   }
2682   installDir(file(install4jHomeDir))
2683
2684   mediaTypes = Arrays.asList(install4j_media_types.split(","))
2685 }
2686
2687
2688 task copyInstall4jTemplate {
2689   def install4jTemplateFile = file("${install4jDir}/${install4j_template}")
2690   def install4jFileAssociationsFile = file("${install4jDir}/${install4j_installer_file_associations}")
2691   inputs.file(install4jTemplateFile)
2692   inputs.file(install4jFileAssociationsFile)
2693   inputs.property("CHANNEL", { CHANNEL })
2694   outputs.file(install4jConfFile)
2695
2696   doLast {
2697     def install4jConfigXml = new XmlParser().parse(install4jTemplateFile)
2698
2699     // turn off code signing if no OSX_KEYPASS
2700     if (OSX_KEYPASS == "") {
2701       install4jConfigXml.'**'.codeSigning.each { codeSigning ->
2702         codeSigning.'@macEnabled' = "false"
2703       }
2704       install4jConfigXml.'**'.windows.each { windows ->
2705         windows.'@runPostProcessor' = "false"
2706       }
2707     }
2708
2709     // disable install screen for OSX dmg (for 2.11.2.0)
2710     install4jConfigXml.'**'.macosArchive.each { macosArchive -> 
2711       macosArchive.attributes().remove('executeSetupApp')
2712       macosArchive.attributes().remove('setupAppId')
2713     }
2714
2715     // turn off checksum creation for LOCAL channel
2716     def e = install4jConfigXml.application[0]
2717     e.'@createChecksums' = string(install4jCheckSums)
2718
2719     // put file association actions where placeholder action is
2720     def install4jFileAssociationsText = install4jFileAssociationsFile.text
2721     def fileAssociationActions = new XmlParser().parseText("<actions>${install4jFileAssociationsText}</actions>")
2722     install4jConfigXml.'**'.action.any { a -> // .any{} stops after the first one that returns true
2723       if (a.'@name' == 'EXTENSIONS_REPLACED_BY_GRADLE') {
2724         def parent = a.parent()
2725         parent.remove(a)
2726         fileAssociationActions.each { faa ->
2727             parent.append(faa)
2728         }
2729         // don't need to continue in .any loop once replacements have been made
2730         return true
2731       }
2732     }
2733
2734     // use Windows Program Group with Examples folder for RELEASE, and Program Group without Examples for everything else
2735     // NB we're deleting the /other/ one!
2736     // Also remove the examples subdir from non-release versions
2737     def customizedIdToDelete = "PROGRAM_GROUP_RELEASE"
2738     // 2.11.1.0 NOT releasing with the Examples folder in the Program Group
2739     if (false && CHANNEL=="RELEASE") { // remove 'false && ' to include Examples folder in RELEASE channel
2740       customizedIdToDelete = "PROGRAM_GROUP_NON_RELEASE"
2741     } else {
2742       // remove the examples subdir from Full File Set
2743       def files = install4jConfigXml.files[0]
2744       def fileset = files.filesets.fileset.find { fs -> fs.'@customizedId' == "FULL_FILE_SET" }
2745       def root = files.roots.root.find { r -> r.'@fileset' == fileset.'@id' }
2746       def mountPoint = files.mountPoints.mountPoint.find { mp -> mp.'@root' == root.'@id' }
2747       def dirEntry = files.entries.dirEntry.find { de -> de.'@mountPoint' == mountPoint.'@id' && de.'@subDirectory' == "examples" }
2748       dirEntry.parent().remove(dirEntry)
2749     }
2750     install4jConfigXml.'**'.action.any { a ->
2751       if (a.'@customizedId' == customizedIdToDelete) {
2752         def parent = a.parent()
2753         parent.remove(a)
2754         return true
2755       }
2756     }
2757
2758     // write install4j file
2759     install4jConfFile.text = XmlUtil.serialize(install4jConfigXml)
2760   }
2761 }
2762
2763
2764 clean {
2765   doFirst {
2766     delete install4jConfFile
2767   }
2768 }
2769
2770 task cleanInstallersDataFiles {
2771   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
2772   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
2773   def hugoDataJsonFile = file("${jalviewDir}/${install4jBuildDir}/installers-${JALVIEW_VERSION_UNDERSCORES}.json")
2774   doFirst {
2775     delete installersOutputTxt
2776     delete installersSha256
2777     delete hugoDataJsonFile
2778   }
2779 }
2780
2781 task install4jDMGBackgroundImageCopy {
2782   inputs.file "${install4jDMGBackgroundImageDir}/${install4jDMGBackgroundImageFile}"
2783   outputs.dir "${install4jDMGBackgroundImageBuildDir}"
2784   doFirst {
2785     copy {
2786       from(install4jDMGBackgroundImageDir) {
2787         include(install4jDMGBackgroundImageFile)
2788       }
2789       into install4jDMGBackgroundImageBuildDir
2790     }
2791   }
2792 }
2793
2794 task install4jDMGBackgroundImageProcess {
2795   dependsOn install4jDMGBackgroundImageCopy
2796
2797   doFirst {
2798     if (backgroundImageText) {
2799       if (convertBinary == null) {
2800         throw new StopExecutionException("No ImageMagick convert binary installed at '${convertBinaryExpectedLocation}'")
2801       }
2802       if (!project.hasProperty("install4j_background_image_text_suffix_cmd")) {
2803         throw new StopExecutionException("No property 'install4j_background_image_text_suffix_cmd' defined. See channel_gradle.properties for channel ${CHANNEL}")
2804       }
2805       fileTree(dir: install4jDMGBackgroundImageBuildDir, include: "*.png").getFiles().each { file ->
2806         exec {
2807           executable convertBinary
2808           args = [
2809             file.getPath(),
2810             '-font', install4j_background_image_text_font,
2811             '-fill', install4j_background_image_text_colour,
2812             '-draw', sprintf(install4j_background_image_text_suffix_cmd, channelSuffix),
2813             '-draw', sprintf(install4j_background_image_text_commit_cmd, "git-commit: ${gitHash}"),
2814             '-draw', sprintf(install4j_background_image_text_date_cmd, getDate("yyyy-MM-dd HH:mm:ss")),
2815             file.getPath()
2816           ]
2817         }
2818       }
2819     }
2820   }
2821 }
2822
2823 task install4jDMGBackgroundImage {
2824   dependsOn install4jDMGBackgroundImageProcess
2825 }
2826
2827 task installerFiles(type: com.install4j.gradle.Install4jTask) {
2828   group = "distribution"
2829   description = "Create the install4j installers"
2830   dependsOn getdown
2831   dependsOn copyInstall4jTemplate
2832   dependsOn cleanInstallersDataFiles
2833   dependsOn install4jDMGBackgroundImage
2834
2835   projectFile = install4jConfFile
2836
2837   // create an md5 for the input files to use as version for install4j conf file
2838   def digest = MessageDigest.getInstance("MD5")
2839   digest.update(
2840     (file("${install4jDir}/${install4j_template}").text + 
2841     file("${install4jDir}/${install4j_info_plist_file_associations}").text +
2842     file("${install4jDir}/${install4j_installer_file_associations}").text).bytes)
2843   def filesMd5 = new BigInteger(1, digest.digest()).toString(16)
2844   if (filesMd5.length() >= 8) {
2845     filesMd5 = filesMd5.substring(0,8)
2846   }
2847   def install4jTemplateVersion = "${JALVIEW_VERSION}_F${filesMd5}_C${gitHash}"
2848
2849   variables = [
2850     'JALVIEW_NAME': jalview_name,
2851     'JALVIEW_APPLICATION_NAME': applicationName,
2852     'JALVIEW_DIR': "../..",
2853     'OSX_KEYSTORE': OSX_KEYSTORE,
2854     'OSX_APPLEID': OSX_APPLEID,
2855     'OSX_ALTOOLPASS': OSX_ALTOOLPASS,
2856     'JSIGN_SH': JSIGN_SH,
2857     'JRE_DIR': getdown_app_dir_java,
2858     'INSTALLER_TEMPLATE_VERSION': install4jTemplateVersion,
2859     'JALVIEW_VERSION': JALVIEW_VERSION,
2860     'JAVA_MIN_VERSION': JAVA_MIN_VERSION,
2861     'JAVA_MAX_VERSION': JAVA_MAX_VERSION,
2862     'JAVA_VERSION': JAVA_VERSION,
2863     'JAVA_INTEGER_VERSION': JAVA_INTEGER_VERSION,
2864     'VERSION': JALVIEW_VERSION,
2865     'COPYRIGHT_MESSAGE': install4j_copyright_message,
2866     'BUNDLE_ID': install4jBundleId,
2867     'INTERNAL_ID': install4jInternalId,
2868     'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
2869     'MACOS_DMG_DS_STORE': install4jDMGDSStore,
2870     'MACOS_DMG_BG_IMAGE': "${install4jDMGBackgroundImageBuildDir}/${install4jDMGBackgroundImageFile}",
2871     'WRAPPER_LINK': getdownWrapperLink,
2872     'BASH_WRAPPER_SCRIPT': getdown_bash_wrapper_script,
2873     'POWERSHELL_WRAPPER_SCRIPT': getdown_powershell_wrapper_script,
2874     'BATCH_WRAPPER_SCRIPT': getdown_batch_wrapper_script,
2875     'WRAPPER_SCRIPT_BIN_DIR': getdown_wrapper_script_dir,
2876     'INSTALLER_NAME': install4jInstallerName,
2877     'INSTALL4J_UTILS_DIR': install4j_utils_dir,
2878     'GETDOWN_CHANNEL_DIR': getdownChannelDir,
2879     'GETDOWN_FILES_DIR': getdown_files_dir,
2880     'GETDOWN_RESOURCE_DIR': getdown_resource_dir,
2881     'GETDOWN_DIST_DIR': getdownAppDistDir,
2882     'GETDOWN_ALT_DIR': getdown_app_dir_alt,
2883     'GETDOWN_INSTALL_DIR': getdown_install_dir,
2884     'INFO_PLIST_FILE_ASSOCIATIONS_FILE': install4j_info_plist_file_associations,
2885     'BUILD_DIR': install4jBuildDir,
2886     'APPLICATION_CATEGORIES': install4j_application_categories,
2887     'APPLICATION_FOLDER': install4jApplicationFolder,
2888     'UNIX_APPLICATION_FOLDER': install4jUnixApplicationFolder,
2889     'EXECUTABLE_NAME': install4jExecutableName,
2890     'EXTRA_SCHEME': install4jExtraScheme,
2891     'MAC_ICONS_FILE': install4jMacIconsFile,
2892     'WINDOWS_ICONS_FILE': install4jWindowsIconsFile,
2893     'PNG_ICON_FILE': install4jPngIconFile,
2894     'BACKGROUND': install4jBackground,
2895   ]
2896
2897   def varNameMap = [
2898     'mac': 'MACOS',
2899     'windows': 'WINDOWS',
2900     'linux': 'LINUX'
2901   ]
2902   
2903   // these are the bundled OS/architecture VMs needed by install4j
2904   def osArch = [
2905     [ "mac", "x64" ],
2906     [ "mac", "aarch64" ],
2907     [ "windows", "x64" ],
2908     [ "linux", "x64" ],
2909     [ "linux", "aarch64" ]
2910   ]
2911   osArch.forEach { os, arch ->
2912     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)
2913     // N.B. For some reason install4j requires the below filename to have underscores and not hyphens
2914     // otherwise running `gradle installers` generates a non-useful error:
2915     // `install4j: compilation failed. Reason: java.lang.NumberFormatException: For input string: "windows"`
2916     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)
2917   }
2918
2919   //println("INSTALL4J VARIABLES:")
2920   //variables.each{k,v->println("${k}=${v}")}
2921
2922   destination = "${jalviewDir}/${install4jBuildDir}"
2923   buildSelected = true
2924
2925   if (install4j_faster.equals("true") || CHANNEL.startsWith("LOCAL")) {
2926     faster = true
2927     disableSigning = true
2928     disableNotarization = true
2929   }
2930
2931   if (OSX_KEYPASS) {
2932     macKeystorePassword = OSX_KEYPASS
2933   } 
2934   
2935   if (OSX_ALTOOLPASS) {
2936     appleIdPassword = OSX_ALTOOLPASS
2937     disableNotarization = false
2938   } else {
2939     disableNotarization = true
2940   }
2941
2942   doFirst {
2943     println("Using projectFile "+projectFile)
2944     if (!disableNotarization) { println("Will notarize OSX App DMG") }
2945   }
2946   //verbose=true
2947
2948   inputs.dir(getdownAppBaseDir)
2949   inputs.file(install4jConfFile)
2950   inputs.file("${install4jDir}/${install4j_info_plist_file_associations}")
2951   outputs.dir("${jalviewDir}/${install4j_build_dir}/${JAVA_VERSION}")
2952 }
2953
2954 def getDataHash(File myFile) {
2955   HashCode hash = Files.asByteSource(myFile).hash(Hashing.sha256())
2956   return myFile.exists()
2957   ? [
2958       "file" : myFile.getName(),
2959       "filesize" : myFile.length(),
2960       "sha256" : hash.toString()
2961     ]
2962   : null
2963 }
2964
2965 def writeDataJsonFile(File installersOutputTxt, File installersSha256, File dataJsonFile) {
2966   def hash = [
2967     "channel" : getdownChannelName,
2968     "date" : getDate("yyyy-MM-dd HH:mm:ss"),
2969     "git-commit" : "${gitHash} [${gitBranch}]",
2970     "version" : JALVIEW_VERSION
2971   ]
2972   // install4j installer files
2973   if (installersOutputTxt.exists()) {
2974     def idHash = [:]
2975     installersOutputTxt.readLines().each { def line ->
2976       if (line.startsWith("#")) {
2977         return;
2978       }
2979       line.replaceAll("\n","")
2980       def vals = line.split("\t")
2981       def filename = vals[3]
2982       def filesize = file(filename).length()
2983       filename = filename.replaceAll(/^.*\//, "")
2984       hash[vals[0]] = [ "id" : vals[0], "os" : vals[1], "name" : vals[2], "file" : filename, "filesize" : filesize ]
2985       idHash."${filename}" = vals[0]
2986     }
2987     if (install4jCheckSums && installersSha256.exists()) {
2988       installersSha256.readLines().each { def line ->
2989         if (line.startsWith("#")) {
2990           return;
2991         }
2992         line.replaceAll("\n","")
2993         def vals = line.split(/\s+\*?/)
2994         def filename = vals[1]
2995         def innerHash = (hash.(idHash."${filename}"))."sha256" = vals[0]
2996       }
2997     }
2998   }
2999
3000   [
3001     "JAR": shadowJar.archiveFile, // executable JAR
3002     "JVL": getdownVersionLaunchJvl, // version JVL
3003     "SOURCE": sourceDist.archiveFile // source TGZ
3004   ].each { key, value ->
3005     def file = file(value)
3006     if (file.exists()) {
3007       def fileHash = getDataHash(file)
3008       if (fileHash != null) {
3009         hash."${key}" = fileHash;
3010       }
3011     }
3012   }
3013   return dataJsonFile.write(new JsonBuilder(hash).toPrettyString())
3014 }
3015
3016 task staticMakeInstallersJsonFile {
3017   doFirst {
3018     def output = findProperty("i4j_output")
3019     def sha256 = findProperty("i4j_sha256")
3020     def json = findProperty("i4j_json")
3021     if (output == null || sha256 == null || json == null) {
3022       throw new GradleException("Must provide paths to all of output.txt, sha256sums, and output.json with '-Pi4j_output=... -Pi4j_sha256=... -Pi4j_json=...")
3023     }
3024     writeDataJsonFile(file(output), file(sha256), file(json))
3025   }
3026 }
3027
3028 task installers {
3029   dependsOn installerFiles
3030 }
3031
3032
3033 spotless {
3034   java {
3035     eclipse().configFile(eclipse_codestyle_file)
3036   }
3037 }
3038
3039 task createSourceReleaseProperties(type: WriteProperties) {
3040   group = "distribution"
3041   description = "Create the source RELEASE properties file"
3042   
3043   def sourceTarBuildDir = "${buildDir}/sourceTar"
3044   def sourceReleasePropertiesFile = "${sourceTarBuildDir}/RELEASE"
3045   outputFile (sourceReleasePropertiesFile)
3046
3047   doFirst {
3048     releaseProps.each{ key, val -> property key, val }
3049     property "git.branch", gitBranch
3050     property "git.hash", gitHash
3051   }
3052
3053   outputs.file(outputFile)
3054 }
3055
3056 task sourceDist(type: Tar) {
3057   group "distribution"
3058   description "Create a source .tar.gz file for distribution"
3059
3060   dependsOn createBuildProperties
3061   dependsOn convertMdFiles
3062   dependsOn eclipseAllPreferences
3063   dependsOn createSourceReleaseProperties
3064
3065
3066   def outputFileName = "${project.name}_${JALVIEW_VERSION_UNDERSCORES}.tar.gz"
3067   archiveFileName = outputFileName
3068   
3069   compression Compression.GZIP
3070   
3071   into project.name
3072
3073   def EXCLUDE_FILES=[
3074     "dist/*",
3075     "build/*",
3076     "bin/*",
3077     "test-output/",
3078     "test-reports",
3079     "tests",
3080     "clover*/*",
3081     ".*",
3082     "benchmarking/*",
3083     "**/.*",
3084     "*.class",
3085     "**/*.class","$j11modDir/**/*.jar","appletlib","**/*locales",
3086     "*locales/**",
3087     "utils/InstallAnywhere",
3088     "**/*.log",
3089     "RELEASE",
3090   ] 
3091   def PROCESS_FILES=[
3092     "AUTHORS",
3093     "CITATION",
3094     "FEATURETODO",
3095     "JAVA-11-README",
3096     "FEATURETODO",
3097     "LICENSE",
3098     "**/README",
3099     "THIRDPARTYLIBS",
3100     "TESTNG",
3101     "build.gradle",
3102     "gradle.properties",
3103     "**/*.java",
3104     "**/*.html",
3105     "**/*.xml",
3106     "**/*.gradle",
3107     "**/*.groovy",
3108     "**/*.properties",
3109     "**/*.perl",
3110     "**/*.sh",
3111   ]
3112   def INCLUDE_FILES=[
3113     ".classpath",
3114     ".settings/org.eclipse.buildship.core.prefs",
3115     ".settings/org.eclipse.jdt.core.prefs"
3116   ]
3117
3118   from(jalviewDir) {
3119     exclude (EXCLUDE_FILES)
3120     include (PROCESS_FILES)
3121     filter(ReplaceTokens,
3122       beginToken: '$$',
3123       endToken: '$$',
3124       tokens: [
3125         'Version-Rel': JALVIEW_VERSION,
3126         'Year-Rel': getDate("yyyy")
3127       ]
3128     )
3129   }
3130   from(jalviewDir) {
3131     exclude (EXCLUDE_FILES)
3132     exclude (PROCESS_FILES)
3133     exclude ("appletlib")
3134     exclude ("**/*locales")
3135     exclude ("*locales/**")
3136     exclude ("utils/InstallAnywhere")
3137
3138     exclude (getdown_files_dir)
3139     // getdown_website_dir and getdown_archive_dir moved to build/website/docroot/getdown
3140     //exclude (getdown_website_dir)
3141     //exclude (getdown_archive_dir)
3142
3143     // exluding these as not using jars as modules yet
3144     exclude ("${j11modDir}/**/*.jar")
3145   }
3146   from(jalviewDir) {
3147     include(INCLUDE_FILES)
3148   }
3149 //  from (jalviewDir) {
3150 //    // explicit includes for stuff that seemed to not get included
3151 //    include(fileTree("test/**/*."))
3152 //    exclude(EXCLUDE_FILES)
3153 //    exclude(PROCESS_FILES)
3154 //  }
3155
3156   from(file(buildProperties).getParent()) {
3157     include(file(buildProperties).getName())
3158     rename(file(buildProperties).getName(), "build_properties")
3159     filter({ line ->
3160       line.replaceAll("^INSTALLATION=.*\$","INSTALLATION=Source Release"+" git-commit\\\\:"+gitHash+" ["+gitBranch+"]")
3161     })
3162   }
3163
3164   def sourceTarBuildDir = "${buildDir}/sourceTar"
3165   from(sourceTarBuildDir) {
3166     // this includes the appended RELEASE properties file
3167   }
3168 }
3169
3170 task dataInstallersJson {
3171   group "website"
3172   description "Create the installers-VERSION.json data file for installer files created"
3173
3174   mustRunAfter installers
3175   mustRunAfter shadowJar
3176   mustRunAfter sourceDist
3177   mustRunAfter getdownArchive
3178
3179   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
3180   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
3181
3182   if (installersOutputTxt.exists()) {
3183     inputs.file(installersOutputTxt)
3184   }
3185   if (install4jCheckSums && installersSha256.exists()) {
3186     inputs.file(installersSha256)
3187   }
3188   [
3189     shadowJar.archiveFile, // executable JAR
3190     getdownVersionLaunchJvl, // version JVL
3191     sourceDist.archiveFile // source TGZ
3192   ].each { fileName ->
3193     if (file(fileName).exists()) {
3194       inputs.file(fileName)
3195     }
3196   }
3197
3198   outputs.file(hugoDataJsonFile)
3199
3200   doFirst {
3201     writeDataJsonFile(installersOutputTxt, installersSha256, hugoDataJsonFile)
3202   }
3203 }
3204
3205 task helppages {
3206   group "help"
3207   description "Copies all help pages to build dir. Runs ant task 'pubhtmlhelp'."
3208
3209   dependsOn copyHelp
3210   dependsOn pubhtmlhelp
3211   
3212   inputs.dir("${helpBuildDir}/${help_dir}")
3213   outputs.dir("${buildDir}/distributions/${help_dir}")
3214 }
3215
3216
3217 task j2sSetHeadlessBuild {
3218   doFirst {
3219     IN_ECLIPSE = false
3220   }
3221 }
3222
3223
3224 task jalviewjsEnableAltFileProperty(type: WriteProperties) {
3225   group "jalviewjs"
3226   description "Enable the alternative J2S Config file for headless build"
3227
3228   outputFile = jalviewjsJ2sSettingsFileName
3229   def j2sPropsFile = file(jalviewjsJ2sSettingsFileName)
3230   def j2sProps = new Properties()
3231   if (j2sPropsFile.exists()) {
3232     try {
3233       def j2sPropsFileFIS = new FileInputStream(j2sPropsFile)
3234       j2sProps.load(j2sPropsFileFIS)
3235       j2sPropsFileFIS.close()
3236
3237       j2sProps.each { prop, val ->
3238         property(prop, val)
3239       }
3240     } catch (Exception e) {
3241       println("Exception reading ${jalviewjsJ2sSettingsFileName}")
3242       e.printStackTrace()
3243     }
3244   }
3245   if (! j2sProps.stringPropertyNames().contains(jalviewjs_j2s_alt_file_property_config)) {
3246     property(jalviewjs_j2s_alt_file_property_config, jalviewjs_j2s_alt_file_property)
3247   }
3248 }
3249
3250
3251 task jalviewjsSetEclipseWorkspace {
3252   def propKey = "jalviewjs_eclipse_workspace"
3253   def propVal = null
3254   if (project.hasProperty(propKey)) {
3255     propVal = project.getProperty(propKey)
3256     if (propVal.startsWith("~/")) {
3257       propVal = System.getProperty("user.home") + propVal.substring(1)
3258     }
3259   }
3260   def propsFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_workspace_location_file}"
3261   def propsFile = file(propsFileName)
3262   def eclipseWsDir = propVal
3263   def props = new Properties()
3264
3265   def writeProps = true
3266   if (( eclipseWsDir == null || !file(eclipseWsDir).exists() ) && propsFile.exists()) {
3267     def ins = new FileInputStream(propsFileName)
3268     props.load(ins)
3269     ins.close()
3270     if (props.getProperty(propKey, null) != null) {
3271       eclipseWsDir = props.getProperty(propKey)
3272       writeProps = false
3273     }
3274   }
3275
3276   if (eclipseWsDir == null || !file(eclipseWsDir).exists()) {
3277     def tempDir = File.createTempDir()
3278     eclipseWsDir = tempDir.getAbsolutePath()
3279     writeProps = true
3280   }
3281   eclipseWorkspace = file(eclipseWsDir)
3282
3283   doFirst {
3284     // do not run a headless transpile when we claim to be in Eclipse
3285     if (IN_ECLIPSE) {
3286       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3287       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3288     } else {
3289       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3290     }
3291
3292     if (writeProps) {
3293       props.setProperty(propKey, eclipseWsDir)
3294       propsFile.parentFile.mkdirs()
3295       def bytes = new ByteArrayOutputStream()
3296       props.store(bytes, null)
3297       def propertiesString = bytes.toString()
3298       propsFile.text = propertiesString
3299       print("NEW ")
3300     } else {
3301       print("EXISTING ")
3302     }
3303
3304     println("ECLIPSE WORKSPACE: "+eclipseWorkspace.getPath())
3305   }
3306
3307   //inputs.property(propKey, eclipseWsDir) // eclipseWsDir only gets set once this task runs, so will be out-of-date
3308   outputs.file(propsFileName)
3309   outputs.upToDateWhen { eclipseWorkspace.exists() && propsFile.exists() }
3310 }
3311
3312
3313 task jalviewjsEclipsePaths {
3314   def eclipseProduct
3315
3316   def eclipseRoot = jalviewjs_eclipse_root
3317   if (eclipseRoot.startsWith("~/")) {
3318     eclipseRoot = System.getProperty("user.home") + eclipseRoot.substring(1)
3319   }
3320   if (OperatingSystem.current().isMacOsX()) {
3321     eclipseRoot += "/Eclipse.app"
3322     eclipseBinary = "${eclipseRoot}/Contents/MacOS/eclipse"
3323     eclipseProduct = "${eclipseRoot}/Contents/Eclipse/.eclipseproduct"
3324   } else if (OperatingSystem.current().isWindows()) { // check these paths!!
3325     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
3326       eclipseRoot += "/eclipse"
3327     }
3328     eclipseBinary = "${eclipseRoot}/eclipse.exe"
3329     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
3330   } else { // linux or unix
3331     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
3332       eclipseRoot += "/eclipse"
3333 println("eclipseDir exists")
3334     }
3335     eclipseBinary = "${eclipseRoot}/eclipse"
3336     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
3337   }
3338
3339   eclipseVersion = "4.13" // default
3340   def assumedVersion = true
3341   if (file(eclipseProduct).exists()) {
3342     def fis = new FileInputStream(eclipseProduct)
3343     def props = new Properties()
3344     props.load(fis)
3345     eclipseVersion = props.getProperty("version")
3346     fis.close()
3347     assumedVersion = false
3348   }
3349   
3350   def propKey = "eclipse_debug"
3351   eclipseDebug = (project.hasProperty(propKey) && project.getProperty(propKey).equals("true"))
3352
3353   doFirst {
3354     // do not run a headless transpile when we claim to be in Eclipse
3355     if (IN_ECLIPSE) {
3356       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3357       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3358     } else {
3359       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3360     }
3361
3362     if (!assumedVersion) {
3363       println("ECLIPSE VERSION=${eclipseVersion}")
3364     }
3365   }
3366 }
3367
3368
3369 task printProperties {
3370   group "Debug"
3371   description "Output to console all System.properties"
3372   doFirst {
3373     System.properties.each { key, val -> System.out.println("Property: ${key}=${val}") }
3374   }
3375 }
3376
3377
3378 task eclipseSetup {
3379   dependsOn eclipseProject
3380   dependsOn eclipseClasspath
3381   dependsOn eclipseJdt
3382 }
3383
3384
3385 // this version (type: Copy) will delete anything in the eclipse dropins folder that isn't in fromDropinsDir
3386 task jalviewjsEclipseCopyDropins(type: Copy) {
3387   dependsOn jalviewjsEclipsePaths
3388
3389   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_eclipse_dropins_dir}", include: "*.jar")
3390   inputFiles += file("${jalviewDir}/${jalviewjsJ2sPlugin}")
3391   def outputDir = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
3392
3393   from inputFiles
3394   into outputDir
3395 }
3396
3397
3398 // this eclipse -clean doesn't actually work
3399 task jalviewjsCleanEclipse(type: Exec) {
3400   dependsOn eclipseSetup
3401   dependsOn jalviewjsEclipsePaths
3402   dependsOn jalviewjsEclipseCopyDropins
3403
3404   executable(eclipseBinary)
3405   args(["-nosplash", "--launcher.suppressErrors", "-data", eclipseWorkspace.getPath(), "-clean", "-console", "-consoleLog"])
3406   if (eclipseDebug) {
3407     args += "-debug"
3408   }
3409   args += "-l"
3410
3411   def inputString = """exit
3412 y
3413 """
3414   def inputByteStream = new ByteArrayInputStream(inputString.getBytes())
3415   standardInput = inputByteStream
3416 }
3417
3418 /* not really working yet
3419 jalviewjsEclipseCopyDropins.finalizedBy jalviewjsCleanEclipse
3420 */
3421
3422
3423 task jalviewjsTransferUnzipSwingJs {
3424   def file_zip = "${jalviewDir}/${jalviewjs_swingjs_zip}"
3425
3426   doLast {
3427     copy {
3428       from zipTree(file_zip)
3429       into "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3430     }
3431   }
3432
3433   inputs.file file_zip
3434   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3435 }
3436
3437
3438 task jalviewjsTransferUnzipLib {
3439   def zipFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_libjs_dir}", include: "*.zip")
3440
3441   doLast {
3442     zipFiles.each { file_zip -> 
3443       copy {
3444         from zipTree(file_zip)
3445         into "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3446       }
3447     }
3448   }
3449
3450   inputs.files zipFiles
3451   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3452 }
3453
3454
3455 task jalviewjsTransferUnzipAllLibs {
3456   dependsOn jalviewjsTransferUnzipSwingJs
3457   dependsOn jalviewjsTransferUnzipLib
3458 }
3459
3460
3461 task jalviewjsCreateJ2sSettings(type: WriteProperties) {
3462   group "JalviewJS"
3463   description "Create the alternative j2s file from the j2s.* properties"
3464
3465   jalviewjsJ2sProps = project.properties.findAll { it.key.startsWith("j2s.") }.sort { it.key }
3466   def siteDirProperty = "j2s.site.directory"
3467   def setSiteDir = false
3468   jalviewjsJ2sProps.each { prop, val ->
3469     if (val != null) {
3470       if (prop == siteDirProperty) {
3471         if (!(val.startsWith('/') || val.startsWith("file://") )) {
3472           val = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${val}"
3473         }
3474         setSiteDir = true
3475       }
3476       property(prop,val)
3477     }
3478     if (!setSiteDir) { // default site location, don't override specifically set property
3479       property(siteDirProperty,"${jalviewDirRelativePath}/${jalviewjsTransferSiteJsDir}")
3480     }
3481   }
3482   outputFile = jalviewjsJ2sAltSettingsFileName
3483
3484   if (! IN_ECLIPSE) {
3485     inputs.properties(jalviewjsJ2sProps)
3486     outputs.file(jalviewjsJ2sAltSettingsFileName)
3487   }
3488 }
3489
3490
3491 task jalviewjsEclipseSetup {
3492   dependsOn jalviewjsEclipseCopyDropins
3493   dependsOn jalviewjsSetEclipseWorkspace
3494   dependsOn jalviewjsCreateJ2sSettings
3495 }
3496
3497
3498 task jalviewjsSyncAllLibs (type: Sync) {
3499   dependsOn jalviewjsTransferUnzipAllLibs
3500   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteLibDir}")
3501   inputFiles += fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
3502   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3503
3504   from inputFiles
3505   into outputDir
3506   def outputFiles = []
3507   rename { filename ->
3508     outputFiles += "${outputDir}/${filename}"
3509     null
3510   }
3511   preserve {
3512     include "**"
3513   }
3514
3515   // should this be exclude really ?
3516   duplicatesStrategy "INCLUDE"
3517
3518   outputs.files outputFiles
3519   inputs.files inputFiles
3520 }
3521
3522
3523 task jalviewjsSyncResources (type: Sync) {
3524   dependsOn buildResources
3525
3526   def inputFiles = fileTree(dir: resourcesBuildDir)
3527   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
3528
3529   from inputFiles
3530   into outputDir
3531   def outputFiles = []
3532   rename { filename ->
3533     outputFiles += "${outputDir}/${filename}"
3534     null
3535   }
3536   preserve {
3537     include "**"
3538   }
3539   outputs.files outputFiles
3540   inputs.files inputFiles
3541 }
3542
3543
3544 task jalviewjsSyncSiteResources (type: Sync) {
3545   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_site_resource_dir}")
3546   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3547
3548   from inputFiles
3549   into outputDir
3550   def outputFiles = []
3551   rename { filename ->
3552     outputFiles += "${outputDir}/${filename}"
3553     null
3554   }
3555   preserve {
3556     include "**"
3557   }
3558   outputs.files outputFiles
3559   inputs.files inputFiles
3560 }
3561
3562
3563 task jalviewjsSyncBuildProperties (type: Sync) {
3564   dependsOn createBuildProperties
3565   def inputFiles = [file(buildProperties)]
3566   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
3567
3568   from inputFiles
3569   into outputDir
3570   def outputFiles = []
3571   rename { filename ->
3572     outputFiles += "${outputDir}/${filename}"
3573     null
3574   }
3575   preserve {
3576     include "**"
3577   }
3578   outputs.files outputFiles
3579   inputs.files inputFiles
3580 }
3581
3582
3583 task jalviewjsProjectImport(type: Exec) {
3584   dependsOn eclipseSetup
3585   dependsOn jalviewjsEclipsePaths
3586   dependsOn jalviewjsEclipseSetup
3587
3588   doFirst {
3589     // do not run a headless import when we claim to be in Eclipse
3590     if (IN_ECLIPSE) {
3591       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3592       throw new StopExecutionException("Not running headless import whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3593     } else {
3594       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3595     }
3596   }
3597
3598   //def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview/org.eclipse.jdt.core"
3599   def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview"
3600   executable(eclipseBinary)
3601   args(["-nosplash", "--launcher.suppressErrors", "-application", "com.seeq.eclipse.importprojects.headlessimport", "-data", eclipseWorkspace.getPath(), "-import", jalviewDirAbsolutePath])
3602   if (eclipseDebug) {
3603     args += "-debug"
3604   }
3605   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
3606   if (!IN_ECLIPSE) {
3607     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
3608     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
3609   }
3610
3611   inputs.file("${jalviewDir}/.project")
3612   outputs.upToDateWhen { 
3613     file(projdir).exists()
3614   }
3615 }
3616
3617
3618 task jalviewjsTranspile(type: Exec) {
3619   dependsOn jalviewjsEclipseSetup 
3620   dependsOn jalviewjsProjectImport
3621   dependsOn jalviewjsEclipsePaths
3622   if (!IN_ECLIPSE) {
3623     dependsOn jalviewjsEnableAltFileProperty
3624   }
3625
3626   doFirst {
3627     // do not run a headless transpile when we claim to be in Eclipse
3628     if (IN_ECLIPSE) {
3629       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3630       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3631     } else {
3632       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3633     }
3634   }
3635
3636   executable(eclipseBinary)
3637   args(["-nosplash", "--launcher.suppressErrors", "-application", "org.eclipse.jdt.apt.core.aptBuild", "-data", eclipseWorkspace, "-${jalviewjs_eclipse_build_arg}", eclipse_project_name ])
3638   if (eclipseDebug) {
3639     args += "-debug"
3640   }
3641   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
3642   if (!IN_ECLIPSE) {
3643     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
3644     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
3645   }
3646
3647   def stdout
3648   def stderr
3649   doFirst {
3650     stdout = new ByteArrayOutputStream()
3651     stderr = new ByteArrayOutputStream()
3652
3653     def logOutFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}"
3654     def logOutFile = file(logOutFileName)
3655     logOutFile.createNewFile()
3656     logOutFile.text = """ROOT: ${jalviewjs_eclipse_root}
3657 BINARY: ${eclipseBinary}
3658 VERSION: ${eclipseVersion}
3659 WORKSPACE: ${eclipseWorkspace}
3660 DEBUG: ${eclipseDebug}
3661 ----
3662 """
3663     def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
3664     // combine stdout and stderr
3665     def logErrFOS = logOutFOS
3666
3667     if (jalviewjs_j2s_to_console.equals("true")) {
3668       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3669         new org.apache.tools.ant.util.TeeOutputStream(
3670           logOutFOS,
3671           stdout),
3672         System.out)
3673       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3674         new org.apache.tools.ant.util.TeeOutputStream(
3675           logErrFOS,
3676           stderr),
3677         System.err)
3678     } else {
3679       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3680         logOutFOS,
3681         stdout)
3682       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3683         logErrFOS,
3684         stderr)
3685     }
3686   }
3687
3688   doLast {
3689     if (stdout.toString().contains("Error processing ")) {
3690       // j2s did not complete transpile
3691       //throw new TaskExecutionException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3692       if (jalviewjs_ignore_transpile_errors.equals("true")) {
3693         println("IGNORING TRANSPILE ERRORS")
3694         println("See eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3695       } else {
3696         throw new GradleException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3697       }
3698     }
3699   }
3700
3701   inputs.dir("${jalviewDir}/${sourceDir}")
3702   outputs.dir("${jalviewDir}/${jalviewjsTransferSiteJsDir}")
3703   outputs.upToDateWhen( { file("${jalviewDir}/${jalviewjsTransferSiteJsDir}${jalviewjs_server_resource}").exists() } )
3704 }
3705
3706
3707 def jalviewjsCallCore(String name, FileCollection list, String prefixFile, String suffixFile, String jsfile, String zjsfile, File logOutFile, Boolean logOutConsole) {
3708
3709   def stdout = new ByteArrayOutputStream()
3710   def stderr = new ByteArrayOutputStream()
3711
3712   def coreFile = file(jsfile)
3713   def msg = ""
3714   msg = "Creating core for ${name}...\nGenerating ${jsfile}"
3715   println(msg)
3716   logOutFile.createNewFile()
3717   logOutFile.append(msg+"\n")
3718
3719   def coreTop = file(prefixFile)
3720   def coreBottom = file(suffixFile)
3721   coreFile.getParentFile().mkdirs()
3722   coreFile.createNewFile()
3723   coreFile.write( coreTop.getText("UTF-8") )
3724   list.each {
3725     f ->
3726     if (f.exists()) {
3727       def t = f.getText("UTF-8")
3728       t.replaceAll("Clazz\\.([^_])","Clazz_${1}")
3729       coreFile.append( t )
3730     } else {
3731       msg = "...file '"+f.getPath()+"' does not exist, skipping"
3732       println(msg)
3733       logOutFile.append(msg+"\n")
3734     }
3735   }
3736   coreFile.append( coreBottom.getText("UTF-8") )
3737
3738   msg = "Generating ${zjsfile}"
3739   println(msg)
3740   logOutFile.append(msg+"\n")
3741   def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
3742   def logErrFOS = logOutFOS
3743
3744   javaexec {
3745     classpath = files(["${jalviewDir}/${jalviewjs_closure_compiler}"])
3746     main = "com.google.javascript.jscomp.CommandLineRunner"
3747     jvmArgs = [ "-Dfile.encoding=UTF-8" ]
3748     args = [ "--compilation_level", "SIMPLE_OPTIMIZATIONS", "--warning_level", "QUIET", "--charset", "UTF-8", "--js", jsfile, "--js_output_file", zjsfile ]
3749     maxHeapSize = "2g"
3750
3751     msg = "\nRunning '"+commandLine.join(' ')+"'\n"
3752     println(msg)
3753     logOutFile.append(msg+"\n")
3754
3755     if (logOutConsole) {
3756       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3757         new org.apache.tools.ant.util.TeeOutputStream(
3758           logOutFOS,
3759           stdout),
3760         standardOutput)
3761         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3762           new org.apache.tools.ant.util.TeeOutputStream(
3763             logErrFOS,
3764             stderr),
3765           System.err)
3766     } else {
3767       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3768         logOutFOS,
3769         stdout)
3770         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3771           logErrFOS,
3772           stderr)
3773     }
3774   }
3775   msg = "--"
3776   println(msg)
3777   logOutFile.append(msg+"\n")
3778 }
3779
3780
3781 task jalviewjsBuildAllCores {
3782   group "JalviewJS"
3783   description "Build the core js lib closures listed in the classlists dir"
3784   dependsOn jalviewjsTranspile
3785   dependsOn jalviewjsTransferUnzipSwingJs
3786
3787   def j2sDir = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${jalviewjs_j2s_subdir}"
3788   def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_j2s_subdir}"
3789   def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteLibDir}/${jalviewjs_j2s_subdir}"
3790   def jsDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_js_subdir}"
3791   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
3792   def prefixFile = "${jsDir}/core/coretop2.js"
3793   def suffixFile = "${jsDir}/core/corebottom2.js"
3794
3795   inputs.file prefixFile
3796   inputs.file suffixFile
3797
3798   def classlistFiles = []
3799   // add the classlists found int the jalviewjs_classlists_dir
3800   fileTree(dir: "${jalviewDir}/${jalviewjs_classlists_dir}", include: "*.txt").each {
3801     file ->
3802     def name = file.getName() - ".txt"
3803     classlistFiles += [
3804       'file': file,
3805       'name': name
3806     ]
3807   }
3808
3809   // _jmol and _jalview cores. Add any other peculiar classlist.txt files here
3810   //classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jmol}"), 'name': "_jvjmol" ]
3811   classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jalview}"), 'name': jalviewjsJalviewCoreName ]
3812
3813   jalviewjsCoreClasslists = []
3814
3815   classlistFiles.each {
3816     hash ->
3817
3818     def file = hash['file']
3819     if (! file.exists()) {
3820       //println("...classlist file '"+file.getPath()+"' does not exist, skipping")
3821       return false // this is a "continue" in groovy .each closure
3822     }
3823     def name = hash['name']
3824     if (name == null) {
3825       name = file.getName() - ".txt"
3826     }
3827
3828     def filelist = []
3829     file.eachLine {
3830       line ->
3831         filelist += line
3832     }
3833     def list = fileTree(dir: j2sDir, includes: filelist)
3834
3835     def jsfile = "${outputDir}/core${name}.js"
3836     def zjsfile = "${outputDir}/core${name}.z.js"
3837
3838     jalviewjsCoreClasslists += [
3839       'jsfile': jsfile,
3840       'zjsfile': zjsfile,
3841       'list': list,
3842       'name': name
3843     ]
3844
3845     inputs.file(file)
3846     inputs.files(list)
3847     outputs.file(jsfile)
3848     outputs.file(zjsfile)
3849   }
3850   
3851   // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
3852   def stevesoftClasslistName = "_stevesoft"
3853   def stevesoftClasslist = [
3854     'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
3855     'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
3856     'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
3857     'name': stevesoftClasslistName
3858   ]
3859   jalviewjsCoreClasslists += stevesoftClasslist
3860   inputs.files(stevesoftClasslist['list'])
3861   outputs.file(stevesoftClasslist['jsfile'])
3862   outputs.file(stevesoftClasslist['zjsfile'])
3863
3864   // _all core
3865   def allClasslistName = "_all"
3866   def allJsFiles = fileTree(dir: j2sDir, include: "**/*.js")
3867   allJsFiles += fileTree(
3868     dir: libJ2sDir,
3869     include: "**/*.js",
3870     excludes: [
3871       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3872       "**/org/jmol/jvxl/readers/IsoIntersectFileReader.js",
3873       "**/org/jmol/export/JSExporter.js"
3874     ]
3875   )
3876   allJsFiles += fileTree(
3877     dir: swingJ2sDir,
3878     include: "**/*.js",
3879     excludes: [
3880       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3881       "**/sun/misc/Unsafe.js",
3882       "**/swingjs/jquery/jquery-editable-select.js",
3883       "**/swingjs/jquery/j2sComboBox.js",
3884       "**/sun/misc/FloatingDecimal.js"
3885     ]
3886   )
3887   def allClasslist = [
3888     'jsfile': "${outputDir}/core${allClasslistName}.js",
3889     'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
3890     'list': allJsFiles,
3891     'name': allClasslistName
3892   ]
3893   // not including this version of "all" core at the moment
3894   //jalviewjsCoreClasslists += allClasslist
3895   inputs.files(allClasslist['list'])
3896   outputs.file(allClasslist['jsfile'])
3897   outputs.file(allClasslist['zjsfile'])
3898
3899   doFirst {
3900     def logOutFile = file("${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_closure_stdout}")
3901     logOutFile.getParentFile().mkdirs()
3902     logOutFile.createNewFile()
3903     logOutFile.write(getDate("yyyy-MM-dd HH:mm:ss")+" jalviewjsBuildAllCores\n----\n")
3904
3905     jalviewjsCoreClasslists.each {
3906       jalviewjsCallCore(it.name, it.list, prefixFile, suffixFile, it.jsfile, it.zjsfile, logOutFile, jalviewjs_j2s_to_console.equals("true"))
3907     }
3908   }
3909
3910 }
3911
3912
3913 def jalviewjsPublishCoreTemplate(String coreName, String templateName, File inputFile, String outputFile) {
3914   copy {
3915     from inputFile
3916     into file(outputFile).getParentFile()
3917     rename { filename ->
3918       if (filename.equals(inputFile.getName())) {
3919         return file(outputFile).getName()
3920       }
3921       return null
3922     }
3923     filter(ReplaceTokens,
3924       beginToken: '_',
3925       endToken: '_',
3926       tokens: [
3927         'MAIN': '"'+main_class+'"',
3928         'CODE': "null",
3929         'NAME': jalviewjsJalviewTemplateName+" [core ${coreName}]",
3930         'COREKEY': jalviewjs_core_key,
3931         'CORENAME': coreName
3932       ]
3933     )
3934   }
3935 }
3936
3937
3938 task jalviewjsPublishCoreTemplates {
3939   dependsOn jalviewjsBuildAllCores
3940   def inputFileName = "${jalviewDir}/${j2s_coretemplate_html}"
3941   def inputFile = file(inputFileName)
3942   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3943
3944   def outputFiles = []
3945   jalviewjsCoreClasslists.each { cl ->
3946     def outputFile = "${outputDir}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
3947     cl['outputfile'] = outputFile
3948     outputFiles += outputFile
3949   }
3950
3951   doFirst {
3952     jalviewjsCoreClasslists.each { cl ->
3953       jalviewjsPublishCoreTemplate(cl.name, jalviewjsJalviewTemplateName, inputFile, cl.outputfile)
3954     }
3955   }
3956   inputs.file(inputFile)
3957   outputs.files(outputFiles)
3958 }
3959
3960
3961 task jalviewjsSyncCore (type: Sync) {
3962   dependsOn jalviewjsBuildAllCores
3963   dependsOn jalviewjsPublishCoreTemplates
3964   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
3965   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3966
3967   from inputFiles
3968   into outputDir
3969   def outputFiles = []
3970   rename { filename ->
3971     outputFiles += "${outputDir}/${filename}"
3972     null
3973   }
3974   preserve {
3975     include "**"
3976   }
3977   outputs.files outputFiles
3978   inputs.files inputFiles
3979 }
3980
3981
3982 // this Copy version of TransferSiteJs will delete anything else in the target dir
3983 task jalviewjsCopyTransferSiteJs(type: Copy) {
3984   dependsOn jalviewjsTranspile
3985   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3986   into "${jalviewDir}/${jalviewjsSiteDir}"
3987 }
3988
3989
3990 // this Sync version of TransferSite is used by buildship to keep the website automatically up to date when a file changes
3991 task jalviewjsSyncTransferSiteJs(type: Sync) {
3992   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3993   include "**/*.*"
3994   into "${jalviewDir}/${jalviewjsSiteDir}"
3995   preserve {
3996     include "**"
3997   }
3998 }
3999
4000
4001 jalviewjsSyncAllLibs.mustRunAfter jalviewjsCopyTransferSiteJs
4002 jalviewjsSyncResources.mustRunAfter jalviewjsCopyTransferSiteJs
4003 jalviewjsSyncSiteResources.mustRunAfter jalviewjsCopyTransferSiteJs
4004 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsCopyTransferSiteJs
4005
4006 jalviewjsSyncAllLibs.mustRunAfter jalviewjsSyncTransferSiteJs
4007 jalviewjsSyncResources.mustRunAfter jalviewjsSyncTransferSiteJs
4008 jalviewjsSyncSiteResources.mustRunAfter jalviewjsSyncTransferSiteJs
4009 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsSyncTransferSiteJs
4010
4011
4012 task jalviewjsPrepareSite {
4013   group "JalviewJS"
4014   description "Prepares the website folder including unzipping files and copying resources"
4015   dependsOn jalviewjsSyncAllLibs
4016   dependsOn jalviewjsSyncResources
4017   dependsOn jalviewjsSyncSiteResources
4018   dependsOn jalviewjsSyncBuildProperties
4019   dependsOn jalviewjsSyncCore
4020 }
4021
4022
4023 task jalviewjsBuildSite {
4024   group "JalviewJS"
4025   description "Builds the whole website including transpiled code"
4026   dependsOn jalviewjsCopyTransferSiteJs
4027   dependsOn jalviewjsPrepareSite
4028 }
4029
4030
4031 task cleanJalviewjsTransferSite {
4032   doFirst {
4033     delete "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
4034     delete "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
4035     delete "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
4036     delete "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
4037   }
4038 }
4039
4040
4041 task cleanJalviewjsSite {
4042   dependsOn cleanJalviewjsTransferSite
4043   doFirst {
4044     delete "${jalviewDir}/${jalviewjsSiteDir}"
4045   }
4046 }
4047
4048
4049 task jalviewjsSiteTar(type: Tar) {
4050   group "JalviewJS"
4051   description "Creates a tar.gz file for the website"
4052   dependsOn jalviewjsBuildSite
4053   def outputFilename = "jalviewjs-site-${JALVIEW_VERSION}.tar.gz"
4054   archiveFileName = outputFilename
4055
4056   compression Compression.GZIP
4057
4058   from "${jalviewDir}/${jalviewjsSiteDir}"
4059   into jalviewjs_site_dir // this is inside the tar file
4060
4061   inputs.dir("${jalviewDir}/${jalviewjsSiteDir}")
4062 }
4063
4064
4065 task jalviewjsServer {
4066   group "JalviewJS"
4067   def filename = "jalviewjsTest.html"
4068   description "Starts a webserver on localhost to test the website. See ${filename} to access local site on most recently used port."
4069   def htmlFile = "${jalviewDirAbsolutePath}/${filename}"
4070   doLast {
4071
4072     def factory
4073     try {
4074       def f = Class.forName("org.gradle.plugins.javascript.envjs.http.simple.SimpleHttpFileServerFactory")
4075       factory = f.newInstance()
4076     } catch (ClassNotFoundException e) {
4077       throw new GradleException("Unable to create SimpleHttpFileServerFactory")
4078     }
4079     def port = Integer.valueOf(jalviewjs_server_port)
4080     def start = port
4081     def running = false
4082     def url
4083     def jalviewjsServer
4084     while(port < start+1000 && !running) {
4085       try {
4086         def doc_root = new File("${jalviewDirAbsolutePath}/${jalviewjsSiteDir}")
4087         jalviewjsServer = factory.start(doc_root, port)
4088         running = true
4089         url = jalviewjsServer.getResourceUrl(jalviewjs_server_resource)
4090         println("SERVER STARTED with document root ${doc_root}.")
4091         println("Go to "+url+" . Run  gradle --stop  to stop (kills all gradle daemons).")
4092         println("For debug: "+url+"?j2sdebug")
4093         println("For verbose: "+url+"?j2sverbose")
4094       } catch (Exception e) {
4095         port++;
4096       }
4097     }
4098     def htmlText = """
4099       <p><a href="${url}">JalviewJS Test. &lt;${url}&gt;</a></p>
4100       <p><a href="${url}?j2sdebug">JalviewJS Test with debug. &lt;${url}?j2sdebug&gt;</a></p>
4101       <p><a href="${url}?j2sverbose">JalviewJS Test with verbose. &lt;${url}?j2sdebug&gt;</a></p>
4102       """
4103     jalviewjsCoreClasslists.each { cl ->
4104       def urlcore = jalviewjsServer.getResourceUrl(file(cl.outputfile).getName())
4105       htmlText += """
4106       <p><a href="${urlcore}">${jalviewjsJalviewTemplateName} [core ${cl.name}]. &lt;${urlcore}&gt;</a></p>
4107       """
4108       println("For core ${cl.name}: "+urlcore)
4109     }
4110
4111     file(htmlFile).text = htmlText
4112   }
4113
4114   outputs.file(htmlFile)
4115   outputs.upToDateWhen({false})
4116 }
4117
4118
4119 task cleanJalviewjsAll {
4120   group "JalviewJS"
4121   description "Delete all configuration and build artifacts to do with JalviewJS build"
4122   dependsOn cleanJalviewjsSite
4123   dependsOn jalviewjsEclipsePaths
4124   
4125   doFirst {
4126     delete "${jalviewDir}/${jalviewjsBuildDir}"
4127     delete "${jalviewDir}/${eclipse_bin_dir}"
4128     if (eclipseWorkspace != null && file(eclipseWorkspace.getAbsolutePath()+"/.metadata").exists()) {
4129       delete file(eclipseWorkspace.getAbsolutePath()+"/.metadata")
4130     }
4131     delete jalviewjsJ2sAltSettingsFileName
4132   }
4133
4134   outputs.upToDateWhen( { false } )
4135 }
4136
4137
4138 task jalviewjsIDE_checkJ2sPlugin {
4139   group "00 JalviewJS in Eclipse"
4140   description "Compare the swingjs/net.sf.j2s.core(-j11)?.jar file with the Eclipse IDE's plugin version (found in the 'dropins' dir)"
4141
4142   doFirst {
4143     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
4144     def j2sPluginFile = file(j2sPlugin)
4145     def eclipseHome = System.properties["eclipse.home.location"]
4146     if (eclipseHome == null || ! IN_ECLIPSE) {
4147       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. Skipping J2S Plugin Check.")
4148     }
4149     def eclipseJ2sPluginDirs = [ "${eclipseHome}/dropins" ]
4150     def altPluginsDir = System.properties["org.eclipse.equinox.p2.reconciler.dropins.directory"]
4151     if (altPluginsDir != null && file(altPluginsDir).exists()) {
4152       eclipseJ2sPluginDirs += altPluginsDir
4153     }
4154     def foundPlugin = false
4155     def j2sPluginFileName = j2sPluginFile.getName()
4156     def eclipseJ2sPlugin
4157     def eclipseJ2sPluginFile
4158     eclipseJ2sPluginDirs.any { dir ->
4159       eclipseJ2sPlugin = "${dir}/${j2sPluginFileName}"
4160       eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
4161       if (eclipseJ2sPluginFile.exists()) {
4162         foundPlugin = true
4163         return true
4164       }
4165     }
4166     if (!foundPlugin) {
4167       def msg = "Eclipse J2S Plugin is not installed (could not find '${j2sPluginFileName}' in\n"+eclipseJ2sPluginDirs.join("\n")+"\n)\nTry running task jalviewjsIDE_copyJ2sPlugin"
4168       System.err.println(msg)
4169       throw new StopExecutionException(msg)
4170     }
4171
4172     def digest = MessageDigest.getInstance("MD5")
4173
4174     digest.update(j2sPluginFile.text.bytes)
4175     def j2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
4176
4177     digest.update(eclipseJ2sPluginFile.text.bytes)
4178     def eclipseJ2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
4179      
4180     if (j2sPluginMd5 != eclipseJ2sPluginMd5) {
4181       def msg = "WARNING! Eclipse J2S Plugin '${eclipseJ2sPlugin}' is different to this commit's version '${j2sPlugin}'"
4182       System.err.println(msg)
4183       throw new StopExecutionException(msg)
4184     } else {
4185       def msg = "Eclipse J2S Plugin '${eclipseJ2sPlugin}' is the same as '${j2sPlugin}' (this is good)"
4186       println(msg)
4187     }
4188   }
4189 }
4190
4191 task jalviewjsIDE_copyJ2sPlugin {
4192   group "00 JalviewJS in Eclipse"
4193   description "Copy the swingjs/net.sf.j2s.core(-j11)?.jar file into the Eclipse IDE's 'dropins' dir"
4194
4195   doFirst {
4196     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
4197     def j2sPluginFile = file(j2sPlugin)
4198     def eclipseHome = System.properties["eclipse.home.location"]
4199     if (eclipseHome == null || ! IN_ECLIPSE) {
4200       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. NOT copying J2S Plugin.")
4201     }
4202     def eclipseJ2sPlugin = "${eclipseHome}/dropins/${j2sPluginFile.getName()}"
4203     def eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
4204     def msg = "WARNING! Copying this commit's j2s plugin '${j2sPlugin}' to Eclipse J2S Plugin '${eclipseJ2sPlugin}'\n* May require an Eclipse restart"
4205     System.err.println(msg)
4206     copy {
4207       from j2sPlugin
4208       eclipseJ2sPluginFile.getParentFile().mkdirs()
4209       into eclipseJ2sPluginFile.getParent()
4210     }
4211   }
4212 }
4213
4214
4215 task jalviewjsIDE_j2sFile {
4216   group "00 JalviewJS in Eclipse"
4217   description "Creates the .j2s file"
4218   dependsOn jalviewjsCreateJ2sSettings
4219 }
4220
4221
4222 task jalviewjsIDE_SyncCore {
4223   group "00 JalviewJS in Eclipse"
4224   description "Build the core js lib closures listed in the classlists dir and publish core html from template"
4225   dependsOn jalviewjsSyncCore
4226 }
4227
4228
4229 task jalviewjsIDE_SyncSiteAll {
4230   dependsOn jalviewjsSyncAllLibs
4231   dependsOn jalviewjsSyncResources
4232   dependsOn jalviewjsSyncSiteResources
4233   dependsOn jalviewjsSyncBuildProperties
4234 }
4235
4236
4237 cleanJalviewjsTransferSite.mustRunAfter jalviewjsIDE_SyncSiteAll
4238
4239
4240 task jalviewjsIDE_PrepareSite {
4241   group "00 JalviewJS in Eclipse"
4242   description "Sync libs and resources to site dir, but not closure cores"
4243
4244   dependsOn jalviewjsIDE_SyncSiteAll
4245   //dependsOn cleanJalviewjsTransferSite // not sure why this clean is here -- will slow down a re-run of this task
4246 }
4247
4248
4249 task jalviewjsIDE_AssembleSite {
4250   group "00 JalviewJS in Eclipse"
4251   description "Assembles unzipped supporting zipfiles, resources, site resources and closure cores into the Eclipse transpiled site"
4252   dependsOn jalviewjsPrepareSite
4253 }
4254
4255
4256 task jalviewjsIDE_SiteClean {
4257   group "00 JalviewJS in Eclipse"
4258   description "Deletes the Eclipse transpiled site"
4259   dependsOn cleanJalviewjsSite
4260 }
4261
4262
4263 task jalviewjsIDE_Server {
4264   group "00 JalviewJS in Eclipse"
4265   description "Starts a webserver on localhost to test the website"
4266   dependsOn jalviewjsServer
4267 }
4268
4269
4270 // buildship runs this at import or gradle refresh
4271 task eclipseSynchronizationTask {
4272   //dependsOn eclipseSetup
4273   dependsOn createBuildProperties
4274   if (J2S_ENABLED) {
4275     dependsOn jalviewjsIDE_j2sFile
4276     dependsOn jalviewjsIDE_checkJ2sPlugin
4277     dependsOn jalviewjsIDE_PrepareSite
4278   }
4279 }
4280
4281
4282 // buildship runs this at build time or project refresh
4283 task eclipseAutoBuildTask {
4284   //dependsOn jalviewjsIDE_checkJ2sPlugin
4285   //dependsOn jalviewjsIDE_PrepareSite
4286 }
4287
4288
4289 task jalviewjsCopyStderrLaunchFile(type: Copy) {
4290   from file(jalviewjs_stderr_launch)
4291   into jalviewjsSiteDir
4292
4293   inputs.file jalviewjs_stderr_launch
4294   outputs.file jalviewjsStderrLaunchFilename
4295 }
4296
4297 task cleanJalviewjsChromiumUserDir {
4298   doFirst {
4299     delete jalviewjsChromiumUserDir
4300   }
4301   outputs.dir jalviewjsChromiumUserDir
4302   // always run when depended on
4303   outputs.upToDateWhen { !file(jalviewjsChromiumUserDir).exists() }
4304 }
4305
4306 task jalviewjsChromiumProfile {
4307   dependsOn cleanJalviewjsChromiumUserDir
4308   mustRunAfter cleanJalviewjsChromiumUserDir
4309
4310   def firstRun = file("${jalviewjsChromiumUserDir}/First Run")
4311
4312   doFirst {
4313     mkdir jalviewjsChromiumProfileDir
4314     firstRun.text = ""
4315   }
4316   outputs.file firstRun
4317 }
4318
4319 task jalviewjsLaunchTest {
4320   group "Test"
4321   description "Check JalviewJS opens in a browser"
4322   dependsOn jalviewjsBuildSite
4323   dependsOn jalviewjsCopyStderrLaunchFile
4324   dependsOn jalviewjsChromiumProfile
4325
4326   def macOS = OperatingSystem.current().isMacOsX()
4327   def chromiumBinary = macOS ? jalviewjs_macos_chromium_binary : jalviewjs_chromium_binary
4328   if (chromiumBinary.startsWith("~/")) {
4329     chromiumBinary = System.getProperty("user.home") + chromiumBinary.substring(1)
4330   }
4331   
4332   def stdout
4333   def stderr
4334   doFirst {
4335     def timeoutms = Integer.valueOf(jalviewjs_chromium_overall_timeout) * 1000
4336     
4337     def binary = file(chromiumBinary)
4338     if (!binary.exists()) {
4339       throw new StopExecutionException("Could not find chromium binary '${chromiumBinary}'. Cannot run task ${name}.")
4340     }
4341     stdout = new ByteArrayOutputStream()
4342     stderr = new ByteArrayOutputStream()
4343     def execStdout
4344     def execStderr
4345     if (jalviewjs_j2s_to_console.equals("true")) {
4346       execStdout = new org.apache.tools.ant.util.TeeOutputStream(
4347         stdout,
4348         System.out)
4349       execStderr = new org.apache.tools.ant.util.TeeOutputStream(
4350         stderr,
4351         System.err)
4352     } else {
4353       execStdout = stdout
4354       execStderr = stderr
4355     }
4356     def execArgs = [
4357       "--no-sandbox", // --no-sandbox IS USED BY THE THORIUM APPIMAGE ON THE BUILDSERVER
4358       "--headless=new",
4359       "--disable-gpu",
4360       "--timeout=${timeoutms}",
4361       "--virtual-time-budget=${timeoutms}",
4362       "--user-data-dir=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_chromium_user_dir}",
4363       "--profile-directory=${jalviewjs_chromium_profile_name}",
4364       "--allow-file-access-from-files",
4365       "--enable-logging=stderr",
4366       "file://${jalviewDirAbsolutePath}/${jalviewjsStderrLaunchFilename}"
4367     ]
4368     
4369     if (true || macOS) {
4370       ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
4371       Future f1 = executor.submit(
4372         () -> {
4373           exec {
4374             standardOutput = execStdout
4375             errorOutput = execStderr
4376             executable(chromiumBinary)
4377             args(execArgs)
4378             println "COMMAND: '"+commandLine.join(" ")+"'"
4379           }
4380           executor.shutdownNow()
4381         }
4382       )
4383
4384       def noChangeBytes = 0
4385       def noChangeIterations = 0
4386       executor.scheduleAtFixedRate(
4387         () -> {
4388           String stderrString = stderr.toString()
4389           // shutdown the task if we have a success string
4390           if (stderrString.contains(jalviewjs_desktop_init_string)) {
4391             f1.cancel()
4392             Thread.sleep(1000)
4393             executor.shutdownNow()
4394           }
4395           // if no change in stderr for 10s then also end
4396           if (noChangeIterations >= jalviewjs_chromium_idle_timeout) {
4397             executor.shutdownNow()
4398           }
4399           if (stderrString.length() == noChangeBytes) {
4400             noChangeIterations++
4401           } else {
4402             noChangeBytes = stderrString.length()
4403             noChangeIterations = 0
4404           }
4405         },
4406         1, 1, TimeUnit.SECONDS)
4407
4408       executor.schedule(new Runnable(){
4409         public void run(){
4410           f1.cancel()
4411           executor.shutdownNow()
4412         }
4413       }, timeoutms, TimeUnit.MILLISECONDS)
4414
4415       executor.awaitTermination(timeoutms+10000, TimeUnit.MILLISECONDS)
4416       executor.shutdownNow()
4417     }
4418
4419   }
4420   
4421   doLast {
4422     def found = false
4423     stderr.toString().eachLine { line ->
4424       if (line.contains(jalviewjs_desktop_init_string)) {
4425         println("Found line '"+line+"'")
4426         found = true
4427         return
4428       }
4429     }
4430     if (!found) {
4431       throw new GradleException("Could not find evidence of Desktop launch in JalviewJS.")
4432     }
4433   }
4434 }
4435   
4436
4437 task jalviewjs {
4438   group "JalviewJS"
4439   description "Build the JalviewJS site and run the launch test"
4440   dependsOn jalviewjsBuildSite
4441   dependsOn jalviewjsLaunchTest
4442 }