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