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