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