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