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