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