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