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