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