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