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