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