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