JAL-629 Part-way to honouring --tests argument
[jalview.git] / build.gradle
1 /* Convention for properties.  Read from gradle.properties, use lower_case_underlines for property names.
2  * For properties set within build.gradle, use camelCaseNoSpace.
3  */
4 import org.apache.tools.ant.filters.ReplaceTokens
5 import org.gradle.internal.os.OperatingSystem
6 import org.gradle.plugins.ide.internal.generator.PropertiesPersistableConfigurationObject
7 import org.gradle.api.internal.PropertiesTransformer
8 import org.gradle.util.ConfigureUtil
9 import org.gradle.plugins.ide.eclipse.model.Output
10 import org.gradle.plugins.ide.eclipse.model.Library
11 import java.security.MessageDigest
12 import java.util.regex.Matcher
13 import groovy.transform.ExternalizeMethods
14 import groovy.util.XmlParser
15 import groovy.xml.XmlUtil
16 import groovy.json.JsonBuilder
17 import com.vladsch.flexmark.util.ast.Node
18 import com.vladsch.flexmark.html.HtmlRenderer
19 import com.vladsch.flexmark.parser.Parser
20 import com.vladsch.flexmark.util.data.MutableDataSet
21 import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension
22 import com.vladsch.flexmark.ext.tables.TablesExtension
23 import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension
24 import com.vladsch.flexmark.ext.autolink.AutolinkExtension
25 import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension
26 import com.vladsch.flexmark.ext.toc.TocExtension
27 import com.google.common.hash.HashCode
28 import com.google.common.hash.Hashing
29 import com.google.common.io.Files
30 import org.jsoup.Jsoup
31 import org.jsoup.nodes.Element
32
33 buildscript {
34   repositories {
35     mavenCentral()
36     mavenLocal()
37   }
38   dependencies {
39     classpath "com.vladsch.flexmark:flexmark-all:0.62.0"
40     classpath "org.jsoup:jsoup:1.14.3"
41     classpath "com.eowise:gradle-imagemagick:0.5.1"
42   }
43 }
44
45
46 plugins {
47   id 'java'
48   id 'application'
49   id 'eclipse'
50   id "com.diffplug.gradle.spotless" version "3.28.0"
51   id 'com.github.johnrengelman.shadow' version '4.0.3'
52   id 'com.install4j.gradle' version '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
1739 test {
1740   group = "Verification"
1741   description = "Runs all testTaskN tasks)"
1742
1743   if (useClover) {
1744     dependsOn cloverClasses
1745   } else { //?
1746     dependsOn testClasses
1747   }
1748
1749   // not running tests in this task
1750   exclude "**/*"
1751
1752 }
1753 /* testTask0 is the main test task */
1754 task testTask0(type: Test) {
1755   group = "Verification"
1756   description = "The main test task. Runs all non-testTaskN-labelled tests (unless excluded)"
1757   useTestNG() {
1758     includeGroups testng_groups.split(",")
1759     excludeGroups testng_excluded_groups.split(",")
1760     tasks.withType(Test).matching {it.name.startsWith("testTask") && it.name != name}.all {t -> excludeGroups t.name}
1761     preserveOrder true
1762     useDefaultListeners=true
1763   }
1764 }
1765
1766 /* separated tests */
1767 task testTask1(type: Test) {
1768   group = "Verification"
1769   description = "Tests that need to be isolated from the main test run"
1770   useTestNG() {
1771     includeGroups name
1772     excludeGroups testng_excluded_groups.split(",")
1773     preserveOrder true
1774     useDefaultListeners=true
1775   }
1776 }
1777
1778 /* insert more testTaskNs here -- change N to next digit or other string */
1779 /*
1780 task testTaskN(type: Test) {
1781   group = "Verification"
1782   description = "Tests that need to be isolated from the main test run"
1783   useTestNG() {
1784     includeGroups name
1785     excludeGroups testng_excluded_groups.split(",")
1786     preserveOrder true
1787     useDefaultListeners=true
1788   }
1789 }
1790 */
1791
1792 /*
1793  * adapted from https://medium.com/@wasyl/pretty-tests-summary-in-gradle-744804dd676c
1794  * to summarise test results from all Test tasks
1795  */
1796 /* START of test tasks results summary */
1797 import groovy.time.TimeCategory
1798 import org.gradle.api.tasks.testing.logging.TestExceptionFormat
1799 import org.gradle.api.tasks.testing.logging.TestLogEvent
1800 rootProject.ext.testsResults = [] // Container for tests summaries
1801
1802 tasks.withType(Test).matching {t -> t.getName().startsWith("testTask")}.all { testTask ->
1803
1804   // from original test task
1805   if (useClover) {
1806     dependsOn cloverClasses
1807   } else { //?
1808     dependsOn testClasses //?
1809   }
1810
1811   // run main tests first
1812   if (!testTask.name.equals("testTask0"))
1813     testTask.mustRunAfter "testTask0"
1814
1815   testTask.testLogging { logging ->
1816     events TestLogEvent.FAILED,
1817       TestLogEvent.SKIPPED,
1818       TestLogEvent.STANDARD_OUT,
1819       TestLogEvent.STANDARD_ERROR
1820
1821     exceptionFormat TestExceptionFormat.FULL
1822     showExceptions true
1823     showCauses true
1824     showStackTraces true
1825   }
1826
1827   ignoreFailures = true // Always try to run all tests for all modules
1828
1829   afterSuite { desc, result ->
1830     if (desc.parent)
1831       return // Only summarize results for whole modules
1832
1833     def resultsInfo = [testTask.project.name, testTask.name, result, TimeCategory.minus(new Date(result.endTime), new Date(result.startTime)), testTask.reports.html.entryPoint]
1834
1835     rootProject.ext.testsResults.add(resultsInfo)
1836   }
1837
1838   // from original test task
1839   maxHeapSize = "1024m"
1840
1841   workingDir = jalviewDir
1842   def testLaf = project.findProperty("test_laf")
1843   if (testLaf != null) {
1844     println("Setting Test LaF to '${testLaf}'")
1845     systemProperty "laf", testLaf
1846   }
1847   def testHiDPIScale = project.findProperty("test_HiDPIScale")
1848   if (testHiDPIScale != null) {
1849     println("Setting Test HiDPI Scale to '${testHiDPIScale}'")
1850     systemProperty "sun.java2d.uiScale", testHiDPIScale
1851   }
1852   sourceCompatibility = compile_source_compatibility
1853   targetCompatibility = compile_target_compatibility
1854   jvmArgs += additional_compiler_args
1855
1856   doFirst {
1857     // this is not perfect yet -- we should only add the commandLineIncludePatterns to the
1858     // testTasks that include the tests, and exclude all from the others.
1859     // get --test argument
1860     filter.commandLineIncludePatterns = test.filter.commandLineIncludePatterns
1861
1862     if (useClover) {
1863       println("Running tests " + (useClover?"WITH":"WITHOUT") + " clover")
1864     }
1865   }
1866
1867   /* ensure the "test" task dependsOn all the testTasks */
1868   test.dependsOn testTask
1869 }
1870
1871 gradle.buildFinished {
1872     def allResults = rootProject.ext.testsResults
1873
1874     if (!allResults.isEmpty()) {
1875         printResults allResults
1876         allResults.each {r ->
1877           if (r[2].resultType == TestResult.ResultType.FAILURE)
1878             throw new GradleException("Failed tests!")
1879         }
1880     }
1881 }
1882
1883 private static String colString(styler, col, colour, text) {
1884   return col?"${styler[colour](text)}":text
1885 }
1886
1887 private static String getSummaryLine(s, pn, tn, rt, rc, rs, rf, rsk, t, col) {
1888   def colour = 'black'
1889   def nocol = false
1890   switch(rt) {
1891     case TestResult.ResultType.SUCCESS:
1892       colour = 'green'
1893       break;
1894     case TestResult.ResultType.FAILURE:
1895       colour = 'red'
1896       break;
1897     default:
1898       nocol = true
1899       break;
1900   }
1901   StringBuilder sb = new StringBuilder()
1902   sb.append("${pn}")
1903   if (tn != null)
1904     sb.append(":${tn}")
1905   sb.append(" results: ")
1906   sb.append(colString(s, col && !nocol, colour, rt))
1907   sb.append(" (")
1908   sb.append("${rc} tests, ")
1909   sb.append(colString(s, col && rs > 0, 'green', rs))
1910   sb.append(" successes, ")
1911   sb.append(colString(s, col && rf > 0, 'red', rf))
1912   sb.append(" failures, ")
1913   sb.append("${rsk} skipped) in ${t}")
1914   return sb.toString()
1915 }
1916
1917 private static void printResults(allResults) {
1918
1919     // styler from https://stackoverflow.com/a/56139852
1920     def styler = 'black red green yellow blue magenta cyan white'.split().toList().withIndex(30).collectEntries { key, val -> [(key) : { "\033[${val}m${it}\033[0m" }] }
1921
1922     def maxLength = 0
1923     def failedTests = false
1924     def summaryLines = []
1925     def totalcount = 0
1926     def totalsuccess = 0
1927     def totalfail = 0
1928     def totalskip = 0
1929     def totaltime = TimeCategory.getSeconds(0)
1930     // sort on project name then task name
1931     allResults.sort {a, b -> a[0] == b[0]? a[1]<=>b[1]:a[0] <=> b[0]}.each {
1932       def projectName = it[0]
1933       def taskName = it[1]
1934       def result = it[2]
1935       def time = it[3]
1936       def report = it[4]
1937       def summaryCol = getSummaryLine(styler, projectName, taskName, result.resultType, result.testCount, result.successfulTestCount, result.failedTestCount, result.skippedTestCount, time, true)
1938       def summaryPlain = getSummaryLine(styler, projectName, taskName, result.resultType, result.testCount, result.successfulTestCount, result.failedTestCount, result.skippedTestCount, time, false)
1939       def reportLine = "Report file: ${report}"
1940       def ls = summaryPlain.length()
1941       def lr = reportLine.length()
1942       def m = [ls, lr].max()
1943       if (m > maxLength)
1944         maxLength = m
1945       def info = [ls, summaryCol, reportLine]
1946       summaryLines.add(info)
1947       failedTests |= result.resultType == TestResult.ResultType.FAILURE
1948       totalcount += result.testCount
1949       totalsuccess += result.successfulTestCount
1950       totalfail += result.failedTestCount
1951       totalskip += result.skippedTestCount
1952       totaltime += time
1953     }
1954     def totalSummaryCol = getSummaryLine(styler, "OVERALL", "", failedTests?TestResult.ResultType.FAILURE:TestResult.ResultType.SUCCESS, totalcount, totalsuccess, totalfail, totalskip, totaltime, true)
1955     def totalSummaryPlain = getSummaryLine(styler, "OVERALL", "", failedTests?TestResult.ResultType.FAILURE:TestResult.ResultType.SUCCESS, totalcount, totalsuccess, totalfail, totalskip, totaltime, false)
1956     def tls = totalSummaryPlain.length()
1957     if (tls > maxLength)
1958       maxLength = tls
1959     def info = [tls, totalSummaryCol, null]
1960     summaryLines.add(info)
1961
1962     def allSummaries = []
1963     for(sInfo : summaryLines) {
1964       def ls = sInfo[0]
1965       def summary = sInfo[1]
1966       def report = sInfo[2]
1967
1968       StringBuilder sb = new StringBuilder()
1969       sb.append("│" + summary + " " * (maxLength - ls) + "│")
1970       if (report != null) {
1971         sb.append("\n│" + report + " " * (maxLength - report.length()) + "│")
1972       }
1973       allSummaries += sb.toString()
1974     }
1975
1976     println "┌${"${"─" * maxLength}"}┐"
1977     println allSummaries.join("\n├${"${"─" * maxLength}"}┤\n")
1978     println "└${"${"─" * maxLength}"}┘"
1979 }
1980 /* END of test tasks results summary */
1981
1982
1983 task compileLinkCheck(type: JavaCompile) {
1984   options.fork = true
1985   classpath = files("${jalviewDir}/${utils_dir}")
1986   destinationDir = file("${jalviewDir}/${utils_dir}")
1987   source = fileTree(dir: "${jalviewDir}/${utils_dir}", include: ["HelpLinksChecker.java", "BufferedLineReader.java"])
1988
1989   inputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.java")
1990   inputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.java")
1991   outputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.class")
1992   outputs.file("${jalviewDir}/${utils_dir}/BufferedLineReader.class")
1993 }
1994
1995
1996 task linkCheck(type: JavaExec) {
1997   dependsOn prepare
1998   dependsOn compileLinkCheck
1999
2000   def helpLinksCheckerOutFile = file("${jalviewDir}/${utils_dir}/HelpLinksChecker.out")
2001   classpath = files("${jalviewDir}/${utils_dir}")
2002   main = "HelpLinksChecker"
2003   workingDir = "${helpBuildDir}"
2004   args = [ "${helpBuildDir}/${help_dir}", "-nointernet" ]
2005
2006   def outFOS = new FileOutputStream(helpLinksCheckerOutFile, false) // false == don't append
2007   standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2008     outFOS,
2009     System.out)
2010   errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2011     outFOS,
2012     System.err)
2013
2014   inputs.dir(helpBuildDir)
2015   outputs.file(helpLinksCheckerOutFile)
2016 }
2017
2018
2019 // import the pubhtmlhelp target
2020 ant.properties.basedir = "${jalviewDir}"
2021 ant.properties.helpBuildDir = "${helpBuildDir}/${help_dir}"
2022 ant.importBuild "${utils_dir}/publishHelp.xml"
2023
2024
2025 task cleanPackageDir(type: Delete) {
2026   doFirst {
2027     delete fileTree(dir: "${jalviewDir}/${package_dir}", include: "*.jar")
2028   }
2029 }
2030
2031
2032 jar {
2033   dependsOn prepare
2034   dependsOn linkCheck
2035
2036   manifest {
2037     attributes "Main-Class": main_class,
2038     "Permissions": "all-permissions",
2039     "Application-Name": applicationName,
2040     "Codebase": application_codebase,
2041     "Implementation-Version": JALVIEW_VERSION
2042   }
2043
2044   def outputDir = "${jalviewDir}/${package_dir}"
2045   destinationDirectory = file(outputDir)
2046   archiveFileName = rootProject.name+".jar"
2047   duplicatesStrategy "EXCLUDE"
2048
2049
2050   exclude "cache*/**"
2051   exclude "*.jar"
2052   exclude "*.jar.*"
2053   exclude "**/*.jar"
2054   exclude "**/*.jar.*"
2055
2056   inputs.dir(sourceSets.main.java.outputDir)
2057   sourceSets.main.resources.srcDirs.each{ dir ->
2058     inputs.dir(dir)
2059   }
2060   outputs.file("${outputDir}/${archiveFileName}")
2061 }
2062
2063
2064 task copyJars(type: Copy) {
2065   from fileTree(dir: classesDir, include: "**/*.jar").files
2066   into "${jalviewDir}/${package_dir}"
2067 }
2068
2069
2070 // doing a Sync instead of Copy as Copy doesn't deal with "outputs" very well
2071 task syncJars(type: Sync) {
2072   dependsOn jar
2073   from fileTree(dir: "${jalviewDir}/${libDistDir}", include: "**/*.jar").files
2074   into "${jalviewDir}/${package_dir}"
2075   preserve {
2076     include jar.archiveFileName.getOrNull()
2077   }
2078 }
2079
2080
2081 task makeDist {
2082   group = "build"
2083   description = "Put all required libraries in dist"
2084   // order of "cleanPackageDir", "copyJars", "jar" important!
2085   jar.mustRunAfter cleanPackageDir
2086   syncJars.mustRunAfter cleanPackageDir
2087   dependsOn cleanPackageDir
2088   dependsOn syncJars
2089   dependsOn jar
2090   outputs.dir("${jalviewDir}/${package_dir}")
2091 }
2092
2093
2094 task cleanDist {
2095   dependsOn cleanPackageDir
2096   dependsOn cleanTest
2097   dependsOn clean
2098 }
2099
2100
2101 shadowJar {
2102   group = "distribution"
2103   description = "Create a single jar file with all dependency libraries merged. Can be run with java -jar"
2104   if (buildDist) {
2105     dependsOn makeDist
2106   }
2107   from ("${jalviewDir}/${libDistDir}") {
2108     include("*.jar")
2109   }
2110   manifest {
2111     attributes "Implementation-Version": JALVIEW_VERSION,
2112     "Application-Name": applicationName
2113   }
2114
2115   duplicatesStrategy "INCLUDE"
2116
2117   mainClassName = shadow_jar_main_class
2118   mergeServiceFiles()
2119   classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
2120   minimize()
2121 }
2122
2123 task getdownImagesCopy() {
2124   inputs.dir getdownImagesDir
2125   outputs.dir getdownImagesBuildDir
2126
2127   doFirst {
2128     copy {
2129       from(getdownImagesDir) {
2130         include("*getdown*.png")
2131       }
2132       into getdownImagesBuildDir
2133     }
2134   }
2135 }
2136
2137 task getdownImagesProcess() {
2138   dependsOn getdownImagesCopy
2139
2140   doFirst {
2141     if (backgroundImageText) {
2142       if (convertBinary == null) {
2143         throw new StopExecutionException("No ImageMagick convert binary installed at '${convertBinaryExpectedLocation}'")
2144       }
2145       if (!project.hasProperty("getdown_background_image_text_suffix_cmd")) {
2146         throw new StopExecutionException("No property 'getdown_background_image_text_suffix_cmd' defined. See channel_gradle.properties for channel ${CHANNEL}")
2147       }
2148       fileTree(dir: getdownImagesBuildDir, include: "*background*.png").getFiles().each { file ->
2149         exec {
2150           executable convertBinary
2151           args = [
2152             file.getPath(),
2153             '-font', getdown_background_image_text_font,
2154             '-fill', getdown_background_image_text_colour,
2155             '-draw', sprintf(getdown_background_image_text_suffix_cmd, channelSuffix),
2156             '-draw', sprintf(getdown_background_image_text_commit_cmd, "git-commit: ${gitHash}"),
2157             '-draw', sprintf(getdown_background_image_text_date_cmd, getDate("yyyy-MM-dd HH:mm:ss")),
2158             file.getPath()
2159           ]
2160         }
2161       }
2162     }
2163   }
2164 }
2165
2166 task getdownImages() {
2167   dependsOn getdownImagesProcess
2168 }
2169
2170 task getdownWebsite() {
2171   group = "distribution"
2172   description = "Create the getdown minimal app folder, and website folder for this version of jalview. Website folder also used for offline app installer"
2173
2174   dependsOn getdownImages
2175   if (buildDist) {
2176     dependsOn makeDist
2177   }
2178
2179   def getdownWebsiteResourceFilenames = []
2180   def getdownResourceDir = getdownResourceDir
2181   def getdownResourceFilenames = []
2182
2183   doFirst {
2184     // clean the getdown website and files dir before creating getdown folders
2185     delete getdownAppBaseDir
2186     delete getdownFilesDir
2187
2188     copy {
2189       from buildProperties
2190       rename(file(buildProperties).getName(), getdown_build_properties)
2191       into getdownAppDir
2192     }
2193     getdownWebsiteResourceFilenames += "${getdownAppDistDir}/${getdown_build_properties}"
2194
2195     copy {
2196       from channelPropsFile
2197       filter(ReplaceTokens,
2198         beginToken: '__',
2199         endToken: '__',
2200         tokens: [
2201           'SUFFIX': channelSuffix
2202         ]
2203       )
2204       into getdownAppBaseDir
2205     }
2206     getdownWebsiteResourceFilenames += file(channelPropsFile).getName()
2207
2208     // set some getdownTxt_ properties then go through all properties looking for getdownTxt_...
2209     def props = project.properties.sort { it.key }
2210     if (getdownAltJavaMinVersion != null && getdownAltJavaMinVersion.length() > 0) {
2211       props.put("getdown_txt_java_min_version", getdownAltJavaMinVersion)
2212     }
2213     if (getdownAltJavaMaxVersion != null && getdownAltJavaMaxVersion.length() > 0) {
2214       props.put("getdown_txt_java_max_version", getdownAltJavaMaxVersion)
2215     }
2216     if (getdownAltMultiJavaLocation != null && getdownAltMultiJavaLocation.length() > 0) {
2217       props.put("getdown_txt_multi_java_location", getdownAltMultiJavaLocation)
2218     }
2219     if (getdownImagesBuildDir != null && file(getdownImagesBuildDir).exists()) {
2220       props.put("getdown_txt_ui.background_image", "${getdownImagesBuildDir}/${getdown_background_image}")
2221       props.put("getdown_txt_ui.instant_background_image", "${getdownImagesBuildDir}/${getdown_instant_background_image}")
2222       props.put("getdown_txt_ui.error_background", "${getdownImagesBuildDir}/${getdown_error_background}")
2223       props.put("getdown_txt_ui.progress_image", "${getdownImagesBuildDir}/${getdown_progress_image}")
2224       props.put("getdown_txt_ui.icon", "${getdownImagesBuildDir}/${getdown_icon}")
2225       props.put("getdown_txt_ui.mac_dock_icon", "${getdownImagesBuildDir}/${getdown_mac_dock_icon}")
2226     }
2227
2228     props.put("getdown_txt_title", jalview_name)
2229     props.put("getdown_txt_ui.name", applicationName)
2230
2231     // start with appbase
2232     getdownTextLines += "appbase = ${getdownAppBase}"
2233     props.each{ prop, val ->
2234       if (prop.startsWith("getdown_txt_") && val != null) {
2235         if (prop.startsWith("getdown_txt_multi_")) {
2236           def key = prop.substring(18)
2237           val.split(",").each{ v ->
2238             def line = "${key} = ${v}"
2239             getdownTextLines += line
2240           }
2241         } else {
2242           // file values rationalised
2243           if (val.indexOf('/') > -1 || prop.startsWith("getdown_txt_resource")) {
2244             def r = null
2245             if (val.indexOf('/') == 0) {
2246               // absolute path
2247               r = file(val)
2248             } else if (val.indexOf('/') > 0) {
2249               // relative path (relative to jalviewDir)
2250               r = file( "${jalviewDir}/${val}" )
2251             }
2252             if (r.exists()) {
2253               val = "${getdown_resource_dir}/" + r.getName()
2254               getdownWebsiteResourceFilenames += val
2255               getdownResourceFilenames += r.getPath()
2256             }
2257           }
2258           if (! prop.startsWith("getdown_txt_resource")) {
2259             def line = prop.substring(12) + " = ${val}"
2260             getdownTextLines += line
2261           }
2262         }
2263       }
2264     }
2265
2266     getdownWebsiteResourceFilenames.each{ filename ->
2267       getdownTextLines += "resource = ${filename}"
2268     }
2269     getdownResourceFilenames.each{ filename ->
2270       copy {
2271         from filename
2272         into getdownResourceDir
2273       }
2274     }
2275     
2276     def getdownWrapperScripts = [ getdown_bash_wrapper_script, getdown_powershell_wrapper_script, getdown_batch_wrapper_script ]
2277     getdownWrapperScripts.each{ script ->
2278       def s = file( "${jalviewDir}/utils/getdown/${getdown_wrapper_script_dir}/${script}" )
2279       if (s.exists()) {
2280         copy {
2281           from s
2282           into "${getdownAppBaseDir}/${getdown_wrapper_script_dir}"
2283         }
2284         getdownTextLines += "resource = ${getdown_wrapper_script_dir}/${script}"
2285       }
2286     }
2287
2288     def codeFiles = []
2289     fileTree(file(package_dir)).each{ f ->
2290       if (f.isDirectory()) {
2291         def files = fileTree(dir: f, include: ["*"]).getFiles()
2292         codeFiles += files
2293       } else if (f.exists()) {
2294         codeFiles += f
2295       }
2296     }
2297     def jalviewJar = jar.archiveFileName.getOrNull()
2298     // put jalview.jar first for CLASSPATH and .properties files reasons
2299     codeFiles.sort{a, b -> ( a.getName() == jalviewJar ? -1 : ( b.getName() == jalviewJar ? 1 : a <=> b ) ) }.each{f ->
2300       def name = f.getName()
2301       def line = "code = ${getdownAppDistDir}/${name}"
2302       getdownTextLines += line
2303       copy {
2304         from f.getPath()
2305         into getdownAppDir
2306       }
2307     }
2308
2309     // NOT USING MODULES YET, EVERYTHING SHOULD BE IN dist
2310     /*
2311     if (JAVA_VERSION.equals("11")) {
2312     def j11libFiles = fileTree(dir: "${jalviewDir}/${j11libDir}", include: ["*.jar"]).getFiles()
2313     j11libFiles.sort().each{f ->
2314     def name = f.getName()
2315     def line = "code = ${getdown_j11lib_dir}/${name}"
2316     getdownTextLines += line
2317     copy {
2318     from f.getPath()
2319     into getdownJ11libDir
2320     }
2321     }
2322     }
2323      */
2324
2325     // 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.
2326     //getdownTextLines += "class = " + file(getdownLauncher).getName()
2327     getdownTextLines += "resource = ${getdown_launcher_new}"
2328     getdownTextLines += "class = ${main_class}"
2329     // Not setting these properties in general so that getdownappbase and getdowndistdir will default to release version in jalview.bin.Cache
2330     if (getdownSetAppBaseProperty) {
2331       getdownTextLines += "jvmarg = -Dgetdowndistdir=${getdownAppDistDir}"
2332       getdownTextLines += "jvmarg = -Dgetdownappbase=${getdownAppBase}"
2333     }
2334
2335     def getdownTxt = file("${getdownAppBaseDir}/getdown.txt")
2336     getdownTxt.write(getdownTextLines.join("\n"))
2337
2338     getdownLaunchJvl = getdown_launch_jvl_name + ( (jvlChannelName != null && jvlChannelName.length() > 0)?"-${jvlChannelName}":"" ) + ".jvl"
2339     def launchJvl = file("${getdownAppBaseDir}/${getdownLaunchJvl}")
2340     launchJvl.write("appbase=${getdownAppBase}")
2341
2342     // files going into the getdown website dir: getdown-launcher.jar
2343     copy {
2344       from getdownLauncher
2345       rename(file(getdownLauncher).getName(), getdown_launcher_new)
2346       into getdownAppBaseDir
2347     }
2348
2349     // files going into the getdown website dir: getdown-launcher(-local).jar
2350     copy {
2351       from getdownLauncher
2352       if (file(getdownLauncher).getName() != getdown_launcher) {
2353         rename(file(getdownLauncher).getName(), getdown_launcher)
2354       }
2355       into getdownAppBaseDir
2356     }
2357
2358     // files going into the getdown website dir: ./install dir and files
2359     if (! (CHANNEL.startsWith("ARCHIVE") || CHANNEL.startsWith("DEVELOP"))) {
2360       copy {
2361         from getdownTxt
2362         from getdownLauncher
2363         from "${getdownAppDir}/${getdown_build_properties}"
2364         if (file(getdownLauncher).getName() != getdown_launcher) {
2365           rename(file(getdownLauncher).getName(), getdown_launcher)
2366         }
2367         into getdownInstallDir
2368       }
2369
2370       // and make a copy in the getdown files dir (these are not downloaded by getdown)
2371       copy {
2372         from getdownInstallDir
2373         into getdownFilesInstallDir
2374       }
2375     }
2376
2377     // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
2378     copy {
2379       from getdownTxt
2380       from launchJvl
2381       from getdownLauncher
2382       from "${getdownAppBaseDir}/${getdown_build_properties}"
2383       from "${getdownAppBaseDir}/${channel_props}"
2384       if (file(getdownLauncher).getName() != getdown_launcher) {
2385         rename(file(getdownLauncher).getName(), getdown_launcher)
2386       }
2387       into getdownFilesDir
2388     }
2389
2390     // and ./resource (not all downloaded by getdown)
2391     copy {
2392       from getdownResourceDir
2393       into "${getdownFilesDir}/${getdown_resource_dir}"
2394     }
2395   }
2396
2397   if (buildDist) {
2398     inputs.dir("${jalviewDir}/${package_dir}")
2399   }
2400   outputs.dir(getdownAppBaseDir)
2401   outputs.dir(getdownFilesDir)
2402 }
2403
2404
2405 // a helper task to allow getdown digest of any dir: `gradle getdownDigestDir -PDIGESTDIR=/path/to/my/random/getdown/dir
2406 task getdownDigestDir(type: JavaExec) {
2407   group "Help"
2408   description "A task to run a getdown Digest on a dir with getdown.txt. Provide a DIGESTDIR property via -PDIGESTDIR=..."
2409
2410   def digestDirPropertyName = "DIGESTDIR"
2411   doFirst {
2412     classpath = files(getdownLauncher)
2413     def digestDir = findProperty(digestDirPropertyName)
2414     if (digestDir == null) {
2415       throw new GradleException("Must provide a DIGESTDIR value to produce an alternative getdown digest")
2416     }
2417     args digestDir
2418   }
2419   main = "com.threerings.getdown.tools.Digester"
2420 }
2421
2422
2423 task getdownDigest(type: JavaExec) {
2424   group = "distribution"
2425   description = "Digest the getdown website folder"
2426   dependsOn getdownWebsite
2427   doFirst {
2428     classpath = files(getdownLauncher)
2429   }
2430   main = "com.threerings.getdown.tools.Digester"
2431   args getdownAppBaseDir
2432   inputs.dir(getdownAppBaseDir)
2433   outputs.file("${getdownAppBaseDir}/digest2.txt")
2434 }
2435
2436
2437 task getdown() {
2438   group = "distribution"
2439   description = "Create the minimal and full getdown app folder for installers and website and create digest file"
2440   dependsOn getdownDigest
2441   doLast {
2442     if (reportRsyncCommand) {
2443       def fromDir = getdownAppBaseDir + (getdownAppBaseDir.endsWith('/')?'':'/')
2444       def toDir = "${getdown_rsync_dest}/${getdownDir}" + (getdownDir.endsWith('/')?'':'/')
2445       println "LIKELY RSYNC COMMAND:"
2446       println "mkdir -p '$toDir'\nrsync -avh --delete '$fromDir' '$toDir'"
2447       if (RUNRSYNC == "true") {
2448         exec {
2449           commandLine "mkdir", "-p", toDir
2450         }
2451         exec {
2452           commandLine "rsync", "-avh", "--delete", fromDir, toDir
2453         }
2454       }
2455     }
2456   }
2457 }
2458
2459
2460 task getdownArchiveBuild() {
2461   group = "distribution"
2462   description = "Put files in the archive dir to go on the website"
2463
2464   dependsOn getdownWebsite
2465
2466   def v = "v${JALVIEW_VERSION_UNDERSCORES}"
2467   def vDir = "${getdownArchiveDir}/${v}"
2468   getdownFullArchiveDir = "${vDir}/getdown"
2469   getdownVersionLaunchJvl = "${vDir}/jalview-${v}.jvl"
2470
2471   def vAltDir = "alt_${v}"
2472   def archiveImagesDir = "${jalviewDir}/${channel_properties_dir}/old/images"
2473
2474   doFirst {
2475     // cleanup old "old" dir
2476     delete getdownArchiveDir
2477
2478     def getdownArchiveTxt = file("${getdownFullArchiveDir}/getdown.txt")
2479     getdownArchiveTxt.getParentFile().mkdirs()
2480     def getdownArchiveTextLines = []
2481     def getdownFullArchiveAppBase = "${getdownArchiveAppBase}${getdownArchiveAppBase.endsWith("/")?"":"/"}${v}/getdown/"
2482
2483     // the libdir
2484     copy {
2485       from "${getdownAppBaseDir}/${getdownAppDistDir}"
2486       into "${getdownFullArchiveDir}/${vAltDir}"
2487     }
2488
2489     getdownTextLines.each { line ->
2490       line = line.replaceAll("^(?<s>appbase\\s*=\\s*).*", '${s}'+getdownFullArchiveAppBase)
2491       line = line.replaceAll("^(?<s>(resource|code)\\s*=\\s*)${getdownAppDistDir}/", '${s}'+vAltDir+"/")
2492       line = line.replaceAll("^(?<s>ui.background_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background.png")
2493       line = line.replaceAll("^(?<s>ui.instant_background_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background_initialising.png")
2494       line = line.replaceAll("^(?<s>ui.error_background\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background_error.png")
2495       line = line.replaceAll("^(?<s>ui.progress_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_progress_bar.png")
2496       // remove the existing resource = resource/ or bin/ lines
2497       if (! line.matches("resource\\s*=\\s*(resource|bin)/.*")) {
2498         getdownArchiveTextLines += line
2499       }
2500     }
2501
2502     // the resource dir -- add these files as resource lines in getdown.txt
2503     copy {
2504       from "${archiveImagesDir}"
2505       into "${getdownFullArchiveDir}/${getdown_resource_dir}"
2506       eachFile { file ->
2507         getdownArchiveTextLines += "resource = ${getdown_resource_dir}/${file.getName()}"
2508       }
2509     }
2510
2511     getdownArchiveTxt.write(getdownArchiveTextLines.join("\n"))
2512
2513     def vLaunchJvl = file(getdownVersionLaunchJvl)
2514     vLaunchJvl.getParentFile().mkdirs()
2515     vLaunchJvl.write("appbase=${getdownFullArchiveAppBase}\n")
2516     def vLaunchJvlPath = vLaunchJvl.toPath().toAbsolutePath()
2517     def jvlLinkPath = file("${vDir}/jalview.jvl").toPath().toAbsolutePath()
2518     // for some reason filepath.relativize(fileInSameDirPath) gives a path to "../" which is wrong
2519     //java.nio.file.Files.createSymbolicLink(jvlLinkPath, jvlLinkPath.relativize(vLaunchJvlPath));
2520     java.nio.file.Files.createSymbolicLink(jvlLinkPath, java.nio.file.Paths.get(".",vLaunchJvl.getName()));
2521
2522     // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
2523     copy {
2524       from getdownLauncher
2525       from "${getdownAppBaseDir}/${getdownLaunchJvl}"
2526       from "${getdownAppBaseDir}/${getdown_launcher_new}"
2527       from "${getdownAppBaseDir}/${channel_props}"
2528       if (file(getdownLauncher).getName() != getdown_launcher) {
2529         rename(file(getdownLauncher).getName(), getdown_launcher)
2530       }
2531       into getdownFullArchiveDir
2532     }
2533
2534   }
2535 }
2536
2537 task getdownArchiveDigest(type: JavaExec) {
2538   group = "distribution"
2539   description = "Digest the getdown archive folder"
2540
2541   dependsOn getdownArchiveBuild
2542
2543   doFirst {
2544     classpath = files(getdownLauncher)
2545     args getdownFullArchiveDir
2546   }
2547   main = "com.threerings.getdown.tools.Digester"
2548   inputs.dir(getdownFullArchiveDir)
2549   outputs.file("${getdownFullArchiveDir}/digest2.txt")
2550 }
2551
2552 task getdownArchive() {
2553   group = "distribution"
2554   description = "Build the website archive dir with getdown digest"
2555
2556   dependsOn getdownArchiveBuild
2557   dependsOn getdownArchiveDigest
2558 }
2559
2560 tasks.withType(JavaCompile) {
2561         options.encoding = 'UTF-8'
2562 }
2563
2564
2565 clean {
2566   doFirst {
2567     delete getdownAppBaseDir
2568     delete getdownFilesDir
2569     delete getdownArchiveDir
2570   }
2571 }
2572
2573
2574 install4j {
2575   if (file(install4jHomeDir).exists()) {
2576     // good to go!
2577   } else if (file(System.getProperty("user.home")+"/buildtools/install4j").exists()) {
2578     install4jHomeDir = System.getProperty("user.home")+"/buildtools/install4j"
2579   } else if (file("/Applications/install4j.app/Contents/Resources/app").exists()) {
2580     install4jHomeDir = "/Applications/install4j.app/Contents/Resources/app"
2581   }
2582   installDir(file(install4jHomeDir))
2583
2584   mediaTypes = Arrays.asList(install4j_media_types.split(","))
2585 }
2586
2587
2588 task copyInstall4jTemplate {
2589   def install4jTemplateFile = file("${install4jDir}/${install4j_template}")
2590   def install4jFileAssociationsFile = file("${install4jDir}/${install4j_installer_file_associations}")
2591   inputs.file(install4jTemplateFile)
2592   inputs.file(install4jFileAssociationsFile)
2593   inputs.property("CHANNEL", { CHANNEL })
2594   outputs.file(install4jConfFile)
2595
2596   doLast {
2597     def install4jConfigXml = new XmlParser().parse(install4jTemplateFile)
2598
2599     // turn off code signing if no OSX_KEYPASS
2600     if (OSX_KEYPASS == "") {
2601       install4jConfigXml.'**'.codeSigning.each { codeSigning ->
2602         codeSigning.'@macEnabled' = "false"
2603       }
2604       install4jConfigXml.'**'.windows.each { windows ->
2605         windows.'@runPostProcessor' = "false"
2606       }
2607     }
2608
2609     // disable install screen for OSX dmg (for 2.11.2.0)
2610     install4jConfigXml.'**'.macosArchive.each { macosArchive -> 
2611       macosArchive.attributes().remove('executeSetupApp')
2612       macosArchive.attributes().remove('setupAppId')
2613     }
2614
2615     // turn off checksum creation for LOCAL channel
2616     def e = install4jConfigXml.application[0]
2617     e.'@createChecksums' = string(install4jCheckSums)
2618
2619     // put file association actions where placeholder action is
2620     def install4jFileAssociationsText = install4jFileAssociationsFile.text
2621     def fileAssociationActions = new XmlParser().parseText("<actions>${install4jFileAssociationsText}</actions>")
2622     install4jConfigXml.'**'.action.any { a -> // .any{} stops after the first one that returns true
2623       if (a.'@name' == 'EXTENSIONS_REPLACED_BY_GRADLE') {
2624         def parent = a.parent()
2625         parent.remove(a)
2626         fileAssociationActions.each { faa ->
2627             parent.append(faa)
2628         }
2629         // don't need to continue in .any loop once replacements have been made
2630         return true
2631       }
2632     }
2633
2634     // use Windows Program Group with Examples folder for RELEASE, and Program Group without Examples for everything else
2635     // NB we're deleting the /other/ one!
2636     // Also remove the examples subdir from non-release versions
2637     def customizedIdToDelete = "PROGRAM_GROUP_RELEASE"
2638     // 2.11.1.0 NOT releasing with the Examples folder in the Program Group
2639     if (false && CHANNEL=="RELEASE") { // remove 'false && ' to include Examples folder in RELEASE channel
2640       customizedIdToDelete = "PROGRAM_GROUP_NON_RELEASE"
2641     } else {
2642       // remove the examples subdir from Full File Set
2643       def files = install4jConfigXml.files[0]
2644       def fileset = files.filesets.fileset.find { fs -> fs.'@customizedId' == "FULL_FILE_SET" }
2645       def root = files.roots.root.find { r -> r.'@fileset' == fileset.'@id' }
2646       def mountPoint = files.mountPoints.mountPoint.find { mp -> mp.'@root' == root.'@id' }
2647       def dirEntry = files.entries.dirEntry.find { de -> de.'@mountPoint' == mountPoint.'@id' && de.'@subDirectory' == "examples" }
2648       dirEntry.parent().remove(dirEntry)
2649     }
2650     install4jConfigXml.'**'.action.any { a ->
2651       if (a.'@customizedId' == customizedIdToDelete) {
2652         def parent = a.parent()
2653         parent.remove(a)
2654         return true
2655       }
2656     }
2657
2658     // write install4j file
2659     install4jConfFile.text = XmlUtil.serialize(install4jConfigXml)
2660   }
2661 }
2662
2663
2664 clean {
2665   doFirst {
2666     delete install4jConfFile
2667   }
2668 }
2669
2670 task cleanInstallersDataFiles {
2671   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
2672   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
2673   def hugoDataJsonFile = file("${jalviewDir}/${install4jBuildDir}/installers-${JALVIEW_VERSION_UNDERSCORES}.json")
2674   doFirst {
2675     delete installersOutputTxt
2676     delete installersSha256
2677     delete hugoDataJsonFile
2678   }
2679 }
2680
2681 task install4jDMGBackgroundImageCopy {
2682   inputs.file "${install4jDMGBackgroundImageDir}/${install4jDMGBackgroundImageFile}"
2683   outputs.dir "${install4jDMGBackgroundImageBuildDir}"
2684   doFirst {
2685     copy {
2686       from(install4jDMGBackgroundImageDir) {
2687         include(install4jDMGBackgroundImageFile)
2688       }
2689       into install4jDMGBackgroundImageBuildDir
2690     }
2691   }
2692 }
2693
2694 task install4jDMGBackgroundImageProcess {
2695   dependsOn install4jDMGBackgroundImageCopy
2696
2697   doFirst {
2698     if (backgroundImageText) {
2699       if (convertBinary == null) {
2700         throw new StopExecutionException("No ImageMagick convert binary installed at '${convertBinaryExpectedLocation}'")
2701       }
2702       if (!project.hasProperty("install4j_background_image_text_suffix_cmd")) {
2703         throw new StopExecutionException("No property 'install4j_background_image_text_suffix_cmd' defined. See channel_gradle.properties for channel ${CHANNEL}")
2704       }
2705       fileTree(dir: install4jDMGBackgroundImageBuildDir, include: "*.png").getFiles().each { file ->
2706         exec {
2707           executable convertBinary
2708           args = [
2709             file.getPath(),
2710             '-font', install4j_background_image_text_font,
2711             '-fill', install4j_background_image_text_colour,
2712             '-draw', sprintf(install4j_background_image_text_suffix_cmd, channelSuffix),
2713             '-draw', sprintf(install4j_background_image_text_commit_cmd, "git-commit: ${gitHash}"),
2714             '-draw', sprintf(install4j_background_image_text_date_cmd, getDate("yyyy-MM-dd HH:mm:ss")),
2715             file.getPath()
2716           ]
2717         }
2718       }
2719     }
2720   }
2721 }
2722
2723 task install4jDMGBackgroundImage {
2724   dependsOn install4jDMGBackgroundImageProcess
2725 }
2726
2727 task installerFiles(type: com.install4j.gradle.Install4jTask) {
2728   group = "distribution"
2729   description = "Create the install4j installers"
2730   dependsOn getdown
2731   dependsOn copyInstall4jTemplate
2732   dependsOn cleanInstallersDataFiles
2733   dependsOn install4jDMGBackgroundImage
2734
2735   projectFile = install4jConfFile
2736
2737   // create an md5 for the input files to use as version for install4j conf file
2738   def digest = MessageDigest.getInstance("MD5")
2739   digest.update(
2740     (file("${install4jDir}/${install4j_template}").text + 
2741     file("${install4jDir}/${install4j_info_plist_file_associations}").text +
2742     file("${install4jDir}/${install4j_installer_file_associations}").text).bytes)
2743   def filesMd5 = new BigInteger(1, digest.digest()).toString(16)
2744   if (filesMd5.length() >= 8) {
2745     filesMd5 = filesMd5.substring(0,8)
2746   }
2747   def install4jTemplateVersion = "${JALVIEW_VERSION}_F${filesMd5}_C${gitHash}"
2748
2749   variables = [
2750     'JALVIEW_NAME': jalview_name,
2751     'JALVIEW_APPLICATION_NAME': applicationName,
2752     'JALVIEW_DIR': "../..",
2753     'OSX_KEYSTORE': OSX_KEYSTORE,
2754     'OSX_APPLEID': OSX_APPLEID,
2755     'OSX_ALTOOLPASS': OSX_ALTOOLPASS,
2756     'JSIGN_SH': JSIGN_SH,
2757     'JRE_DIR': getdown_app_dir_java,
2758     'INSTALLER_TEMPLATE_VERSION': install4jTemplateVersion,
2759     'JALVIEW_VERSION': JALVIEW_VERSION,
2760     'JAVA_MIN_VERSION': JAVA_MIN_VERSION,
2761     'JAVA_MAX_VERSION': JAVA_MAX_VERSION,
2762     'JAVA_VERSION': JAVA_VERSION,
2763     'JAVA_INTEGER_VERSION': JAVA_INTEGER_VERSION,
2764     'VERSION': JALVIEW_VERSION,
2765     'COPYRIGHT_MESSAGE': install4j_copyright_message,
2766     'BUNDLE_ID': install4jBundleId,
2767     'INTERNAL_ID': install4jInternalId,
2768     'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
2769     'MACOS_DMG_DS_STORE': install4jDMGDSStore,
2770     'MACOS_DMG_BG_IMAGE': "${install4jDMGBackgroundImageBuildDir}/${install4jDMGBackgroundImageFile}",
2771     'WRAPPER_LINK': getdownWrapperLink,
2772     'BASH_WRAPPER_SCRIPT': getdown_bash_wrapper_script,
2773     'POWERSHELL_WRAPPER_SCRIPT': getdown_powershell_wrapper_script,
2774     'WRAPPER_SCRIPT_BIN_DIR': getdown_wrapper_script_dir,
2775     'INSTALLER_NAME': install4jInstallerName,
2776     'INSTALL4J_UTILS_DIR': install4j_utils_dir,
2777     'GETDOWN_CHANNEL_DIR': getdownChannelDir,
2778     'GETDOWN_FILES_DIR': getdown_files_dir,
2779     'GETDOWN_RESOURCE_DIR': getdown_resource_dir,
2780     'GETDOWN_DIST_DIR': getdownAppDistDir,
2781     'GETDOWN_ALT_DIR': getdown_app_dir_alt,
2782     'GETDOWN_INSTALL_DIR': getdown_install_dir,
2783     'INFO_PLIST_FILE_ASSOCIATIONS_FILE': install4j_info_plist_file_associations,
2784     'BUILD_DIR': install4jBuildDir,
2785     'APPLICATION_CATEGORIES': install4j_application_categories,
2786     'APPLICATION_FOLDER': install4jApplicationFolder,
2787     'UNIX_APPLICATION_FOLDER': install4jUnixApplicationFolder,
2788     'EXECUTABLE_NAME': install4jExecutableName,
2789     'EXTRA_SCHEME': install4jExtraScheme,
2790     'MAC_ICONS_FILE': install4jMacIconsFile,
2791     'WINDOWS_ICONS_FILE': install4jWindowsIconsFile,
2792     'PNG_ICON_FILE': install4jPngIconFile,
2793     'BACKGROUND': install4jBackground,
2794   ]
2795
2796   def varNameMap = [
2797     'mac': 'MACOS',
2798     'windows': 'WINDOWS',
2799     'linux': 'LINUX'
2800   ]
2801   
2802   // these are the bundled OS/architecture VMs needed by install4j
2803   def osArch = [
2804     [ "mac", "x64" ],
2805     [ "mac", "aarch64" ],
2806     [ "windows", "x64" ],
2807     [ "linux", "x64" ],
2808     [ "linux", "aarch64" ]
2809   ]
2810   osArch.forEach { os, arch ->
2811     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)
2812     // N.B. For some reason install4j requires the below filename to have underscores and not hyphens
2813     // otherwise running `gradle installers` generates a non-useful error:
2814     // `install4j: compilation failed. Reason: java.lang.NumberFormatException: For input string: "windows"`
2815     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)
2816   }
2817
2818   //println("INSTALL4J VARIABLES:")
2819   //variables.each{k,v->println("${k}=${v}")}
2820
2821   destination = "${jalviewDir}/${install4jBuildDir}"
2822   buildSelected = true
2823
2824   if (install4j_faster.equals("true") || CHANNEL.startsWith("LOCAL")) {
2825     faster = true
2826     disableSigning = true
2827     disableNotarization = true
2828   }
2829
2830   if (OSX_KEYPASS) {
2831     macKeystorePassword = OSX_KEYPASS
2832   } 
2833   
2834   if (OSX_ALTOOLPASS) {
2835     appleIdPassword = OSX_ALTOOLPASS
2836     disableNotarization = false
2837   } else {
2838     disableNotarization = true
2839   }
2840
2841   doFirst {
2842     println("Using projectFile "+projectFile)
2843     if (!disableNotarization) { println("Will notarize OSX App DMG") }
2844   }
2845   //verbose=true
2846
2847   inputs.dir(getdownAppBaseDir)
2848   inputs.file(install4jConfFile)
2849   inputs.file("${install4jDir}/${install4j_info_plist_file_associations}")
2850   outputs.dir("${jalviewDir}/${install4j_build_dir}/${JAVA_VERSION}")
2851 }
2852
2853 def getDataHash(File myFile) {
2854   HashCode hash = Files.asByteSource(myFile).hash(Hashing.sha256())
2855   return myFile.exists()
2856   ? [
2857       "file" : myFile.getName(),
2858       "filesize" : myFile.length(),
2859       "sha256" : hash.toString()
2860     ]
2861   : null
2862 }
2863
2864 def writeDataJsonFile(File installersOutputTxt, File installersSha256, File dataJsonFile) {
2865   def hash = [
2866     "channel" : getdownChannelName,
2867     "date" : getDate("yyyy-MM-dd HH:mm:ss"),
2868     "git-commit" : "${gitHash} [${gitBranch}]",
2869     "version" : JALVIEW_VERSION
2870   ]
2871   // install4j installer files
2872   if (installersOutputTxt.exists()) {
2873     def idHash = [:]
2874     installersOutputTxt.readLines().each { def line ->
2875       if (line.startsWith("#")) {
2876         return;
2877       }
2878       line.replaceAll("\n","")
2879       def vals = line.split("\t")
2880       def filename = vals[3]
2881       def filesize = file(filename).length()
2882       filename = filename.replaceAll(/^.*\//, "")
2883       hash[vals[0]] = [ "id" : vals[0], "os" : vals[1], "name" : vals[2], "file" : filename, "filesize" : filesize ]
2884       idHash."${filename}" = vals[0]
2885     }
2886     if (install4jCheckSums && installersSha256.exists()) {
2887       installersSha256.readLines().each { def line ->
2888         if (line.startsWith("#")) {
2889           return;
2890         }
2891         line.replaceAll("\n","")
2892         def vals = line.split(/\s+\*?/)
2893         def filename = vals[1]
2894         def innerHash = (hash.(idHash."${filename}"))."sha256" = vals[0]
2895       }
2896     }
2897   }
2898
2899   [
2900     "JAR": shadowJar.archiveFile, // executable JAR
2901     "JVL": getdownVersionLaunchJvl, // version JVL
2902     "SOURCE": sourceDist.archiveFile // source TGZ
2903   ].each { key, value ->
2904     def file = file(value)
2905     if (file.exists()) {
2906       def fileHash = getDataHash(file)
2907       if (fileHash != null) {
2908         hash."${key}" = fileHash;
2909       }
2910     }
2911   }
2912   return dataJsonFile.write(new JsonBuilder(hash).toPrettyString())
2913 }
2914
2915 task staticMakeInstallersJsonFile {
2916   doFirst {
2917     def output = findProperty("i4j_output")
2918     def sha256 = findProperty("i4j_sha256")
2919     def json = findProperty("i4j_json")
2920     if (output == null || sha256 == null || json == null) {
2921       throw new GradleException("Must provide paths to all of output.txt, sha256sums, and output.json with '-Pi4j_output=... -Pi4j_sha256=... -Pi4j_json=...")
2922     }
2923     writeDataJsonFile(file(output), file(sha256), file(json))
2924   }
2925 }
2926
2927 task installers {
2928   dependsOn installerFiles
2929 }
2930
2931
2932 spotless {
2933   java {
2934     eclipse().configFile(eclipse_codestyle_file)
2935   }
2936 }
2937
2938 task createSourceReleaseProperties(type: WriteProperties) {
2939   group = "distribution"
2940   description = "Create the source RELEASE properties file"
2941   
2942   def sourceTarBuildDir = "${buildDir}/sourceTar"
2943   def sourceReleasePropertiesFile = "${sourceTarBuildDir}/RELEASE"
2944   outputFile (sourceReleasePropertiesFile)
2945
2946   doFirst {
2947     releaseProps.each{ key, val -> property key, val }
2948     property "git.branch", gitBranch
2949     property "git.hash", gitHash
2950   }
2951
2952   outputs.file(outputFile)
2953 }
2954
2955 task sourceDist(type: Tar) {
2956   group "distribution"
2957   description "Create a source .tar.gz file for distribution"
2958
2959   dependsOn createBuildProperties
2960   dependsOn convertMdFiles
2961   dependsOn eclipseAllPreferences
2962   dependsOn createSourceReleaseProperties
2963
2964
2965   def outputFileName = "${project.name}_${JALVIEW_VERSION_UNDERSCORES}.tar.gz"
2966   archiveFileName = outputFileName
2967   
2968   compression Compression.GZIP
2969   
2970   into project.name
2971
2972   def EXCLUDE_FILES=[
2973     "build/*",
2974     "bin/*",
2975     "test-output/",
2976     "test-reports",
2977     "tests",
2978     "clover*/*",
2979     ".*",
2980     "benchmarking/*",
2981     "**/.*",
2982     "*.class",
2983     "**/*.class","$j11modDir/**/*.jar","appletlib","**/*locales",
2984     "*locales/**",
2985     "utils/InstallAnywhere",
2986     "**/*.log",
2987     "RELEASE",
2988   ] 
2989   def PROCESS_FILES=[
2990     "AUTHORS",
2991     "CITATION",
2992     "FEATURETODO",
2993     "JAVA-11-README",
2994     "FEATURETODO",
2995     "LICENSE",
2996     "**/README",
2997     "THIRDPARTYLIBS",
2998     "TESTNG",
2999     "build.gradle",
3000     "gradle.properties",
3001     "**/*.java",
3002     "**/*.html",
3003     "**/*.xml",
3004     "**/*.gradle",
3005     "**/*.groovy",
3006     "**/*.properties",
3007     "**/*.perl",
3008     "**/*.sh",
3009   ]
3010   def INCLUDE_FILES=[
3011     ".classpath",
3012     ".settings/org.eclipse.buildship.core.prefs",
3013     ".settings/org.eclipse.jdt.core.prefs"
3014   ]
3015
3016   from(jalviewDir) {
3017     exclude (EXCLUDE_FILES)
3018     include (PROCESS_FILES)
3019     filter(ReplaceTokens,
3020       beginToken: '$$',
3021       endToken: '$$',
3022       tokens: [
3023         'Version-Rel': JALVIEW_VERSION,
3024         'Year-Rel': getDate("yyyy")
3025       ]
3026     )
3027   }
3028   from(jalviewDir) {
3029     exclude (EXCLUDE_FILES)
3030     exclude (PROCESS_FILES)
3031     exclude ("appletlib")
3032     exclude ("**/*locales")
3033     exclude ("*locales/**")
3034     exclude ("utils/InstallAnywhere")
3035
3036     exclude (getdown_files_dir)
3037     // getdown_website_dir and getdown_archive_dir moved to build/website/docroot/getdown
3038     //exclude (getdown_website_dir)
3039     //exclude (getdown_archive_dir)
3040
3041     // exluding these as not using jars as modules yet
3042     exclude ("${j11modDir}/**/*.jar")
3043   }
3044   from(jalviewDir) {
3045     include(INCLUDE_FILES)
3046   }
3047 //  from (jalviewDir) {
3048 //    // explicit includes for stuff that seemed to not get included
3049 //    include(fileTree("test/**/*."))
3050 //    exclude(EXCLUDE_FILES)
3051 //    exclude(PROCESS_FILES)
3052 //  }
3053
3054   from(file(buildProperties).getParent()) {
3055     include(file(buildProperties).getName())
3056     rename(file(buildProperties).getName(), "build_properties")
3057     filter({ line ->
3058       line.replaceAll("^INSTALLATION=.*\$","INSTALLATION=Source Release"+" git-commit\\\\:"+gitHash+" ["+gitBranch+"]")
3059     })
3060   }
3061
3062   def sourceTarBuildDir = "${buildDir}/sourceTar"
3063   from(sourceTarBuildDir) {
3064     // this includes the appended RELEASE properties file
3065   }
3066 }
3067
3068 task dataInstallersJson {
3069   group "website"
3070   description "Create the installers-VERSION.json data file for installer files created"
3071
3072   mustRunAfter installers
3073   mustRunAfter shadowJar
3074   mustRunAfter sourceDist
3075   mustRunAfter getdownArchive
3076
3077   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
3078   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
3079
3080   if (installersOutputTxt.exists()) {
3081     inputs.file(installersOutputTxt)
3082   }
3083   if (install4jCheckSums && installersSha256.exists()) {
3084     inputs.file(installersSha256)
3085   }
3086   [
3087     shadowJar.archiveFile, // executable JAR
3088     getdownVersionLaunchJvl, // version JVL
3089     sourceDist.archiveFile // source TGZ
3090   ].each { fileName ->
3091     if (file(fileName).exists()) {
3092       inputs.file(fileName)
3093     }
3094   }
3095
3096   outputs.file(hugoDataJsonFile)
3097
3098   doFirst {
3099     writeDataJsonFile(installersOutputTxt, installersSha256, hugoDataJsonFile)
3100   }
3101 }
3102
3103 task helppages {
3104   group "help"
3105   description "Copies all help pages to build dir. Runs ant task 'pubhtmlhelp'."
3106
3107   dependsOn copyHelp
3108   dependsOn pubhtmlhelp
3109   
3110   inputs.dir("${helpBuildDir}/${help_dir}")
3111   outputs.dir("${buildDir}/distributions/${help_dir}")
3112 }
3113
3114
3115 task j2sSetHeadlessBuild {
3116   doFirst {
3117     IN_ECLIPSE = false
3118   }
3119 }
3120
3121
3122 task jalviewjsEnableAltFileProperty(type: WriteProperties) {
3123   group "jalviewjs"
3124   description "Enable the alternative J2S Config file for headless build"
3125
3126   outputFile = jalviewjsJ2sSettingsFileName
3127   def j2sPropsFile = file(jalviewjsJ2sSettingsFileName)
3128   def j2sProps = new Properties()
3129   if (j2sPropsFile.exists()) {
3130     try {
3131       def j2sPropsFileFIS = new FileInputStream(j2sPropsFile)
3132       j2sProps.load(j2sPropsFileFIS)
3133       j2sPropsFileFIS.close()
3134
3135       j2sProps.each { prop, val ->
3136         property(prop, val)
3137       }
3138     } catch (Exception e) {
3139       println("Exception reading ${jalviewjsJ2sSettingsFileName}")
3140       e.printStackTrace()
3141     }
3142   }
3143   if (! j2sProps.stringPropertyNames().contains(jalviewjs_j2s_alt_file_property_config)) {
3144     property(jalviewjs_j2s_alt_file_property_config, jalviewjs_j2s_alt_file_property)
3145   }
3146 }
3147
3148
3149 task jalviewjsSetEclipseWorkspace {
3150   def propKey = "jalviewjs_eclipse_workspace"
3151   def propVal = null
3152   if (project.hasProperty(propKey)) {
3153     propVal = project.getProperty(propKey)
3154     if (propVal.startsWith("~/")) {
3155       propVal = System.getProperty("user.home") + propVal.substring(1)
3156     }
3157   }
3158   def propsFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_workspace_location_file}"
3159   def propsFile = file(propsFileName)
3160   def eclipseWsDir = propVal
3161   def props = new Properties()
3162
3163   def writeProps = true
3164   if (( eclipseWsDir == null || !file(eclipseWsDir).exists() ) && propsFile.exists()) {
3165     def ins = new FileInputStream(propsFileName)
3166     props.load(ins)
3167     ins.close()
3168     if (props.getProperty(propKey, null) != null) {
3169       eclipseWsDir = props.getProperty(propKey)
3170       writeProps = false
3171     }
3172   }
3173
3174   if (eclipseWsDir == null || !file(eclipseWsDir).exists()) {
3175     def tempDir = File.createTempDir()
3176     eclipseWsDir = tempDir.getAbsolutePath()
3177     writeProps = true
3178   }
3179   eclipseWorkspace = file(eclipseWsDir)
3180
3181   doFirst {
3182     // do not run a headless transpile when we claim to be in Eclipse
3183     if (IN_ECLIPSE) {
3184       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3185       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3186     } else {
3187       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3188     }
3189
3190     if (writeProps) {
3191       props.setProperty(propKey, eclipseWsDir)
3192       propsFile.parentFile.mkdirs()
3193       def bytes = new ByteArrayOutputStream()
3194       props.store(bytes, null)
3195       def propertiesString = bytes.toString()
3196       propsFile.text = propertiesString
3197       print("NEW ")
3198     } else {
3199       print("EXISTING ")
3200     }
3201
3202     println("ECLIPSE WORKSPACE: "+eclipseWorkspace.getPath())
3203   }
3204
3205   //inputs.property(propKey, eclipseWsDir) // eclipseWsDir only gets set once this task runs, so will be out-of-date
3206   outputs.file(propsFileName)
3207   outputs.upToDateWhen { eclipseWorkspace.exists() && propsFile.exists() }
3208 }
3209
3210
3211 task jalviewjsEclipsePaths {
3212   def eclipseProduct
3213
3214   def eclipseRoot = jalviewjs_eclipse_root
3215   if (eclipseRoot.startsWith("~/")) {
3216     eclipseRoot = System.getProperty("user.home") + eclipseRoot.substring(1)
3217   }
3218   if (OperatingSystem.current().isMacOsX()) {
3219     eclipseRoot += "/Eclipse.app"
3220     eclipseBinary = "${eclipseRoot}/Contents/MacOS/eclipse"
3221     eclipseProduct = "${eclipseRoot}/Contents/Eclipse/.eclipseproduct"
3222   } else if (OperatingSystem.current().isWindows()) { // check these paths!!
3223     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
3224       eclipseRoot += "/eclipse"
3225     }
3226     eclipseBinary = "${eclipseRoot}/eclipse.exe"
3227     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
3228   } else { // linux or unix
3229     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
3230       eclipseRoot += "/eclipse"
3231 println("eclipseDir exists")
3232     }
3233     eclipseBinary = "${eclipseRoot}/eclipse"
3234     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
3235   }
3236
3237   eclipseVersion = "4.13" // default
3238   def assumedVersion = true
3239   if (file(eclipseProduct).exists()) {
3240     def fis = new FileInputStream(eclipseProduct)
3241     def props = new Properties()
3242     props.load(fis)
3243     eclipseVersion = props.getProperty("version")
3244     fis.close()
3245     assumedVersion = false
3246   }
3247   
3248   def propKey = "eclipse_debug"
3249   eclipseDebug = (project.hasProperty(propKey) && project.getProperty(propKey).equals("true"))
3250
3251   doFirst {
3252     // do not run a headless transpile when we claim to be in Eclipse
3253     if (IN_ECLIPSE) {
3254       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3255       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3256     } else {
3257       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3258     }
3259
3260     if (!assumedVersion) {
3261       println("ECLIPSE VERSION=${eclipseVersion}")
3262     }
3263   }
3264 }
3265
3266
3267 task printProperties {
3268   group "Debug"
3269   description "Output to console all System.properties"
3270   doFirst {
3271     System.properties.each { key, val -> System.out.println("Property: ${key}=${val}") }
3272   }
3273 }
3274
3275
3276 task eclipseSetup {
3277   dependsOn eclipseProject
3278   dependsOn eclipseClasspath
3279   dependsOn eclipseJdt
3280 }
3281
3282
3283 // this version (type: Copy) will delete anything in the eclipse dropins folder that isn't in fromDropinsDir
3284 task jalviewjsEclipseCopyDropins(type: Copy) {
3285   dependsOn jalviewjsEclipsePaths
3286
3287   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_eclipse_dropins_dir}", include: "*.jar")
3288   inputFiles += file("${jalviewDir}/${jalviewjsJ2sPlugin}")
3289   def outputDir = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
3290
3291   from inputFiles
3292   into outputDir
3293 }
3294
3295
3296 // this eclipse -clean doesn't actually work
3297 task jalviewjsCleanEclipse(type: Exec) {
3298   dependsOn eclipseSetup
3299   dependsOn jalviewjsEclipsePaths
3300   dependsOn jalviewjsEclipseCopyDropins
3301
3302   executable(eclipseBinary)
3303   args(["-nosplash", "--launcher.suppressErrors", "-data", eclipseWorkspace.getPath(), "-clean", "-console", "-consoleLog"])
3304   if (eclipseDebug) {
3305     args += "-debug"
3306   }
3307   args += "-l"
3308
3309   def inputString = """exit
3310 y
3311 """
3312   def inputByteStream = new ByteArrayInputStream(inputString.getBytes())
3313   standardInput = inputByteStream
3314 }
3315
3316 /* not really working yet
3317 jalviewjsEclipseCopyDropins.finalizedBy jalviewjsCleanEclipse
3318 */
3319
3320
3321 task jalviewjsTransferUnzipSwingJs {
3322   def file_zip = "${jalviewDir}/${jalviewjs_swingjs_zip}"
3323
3324   doLast {
3325     copy {
3326       from zipTree(file_zip)
3327       into "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3328     }
3329   }
3330
3331   inputs.file file_zip
3332   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3333 }
3334
3335
3336 task jalviewjsTransferUnzipLib {
3337   def zipFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_libjs_dir}", include: "*.zip")
3338
3339   doLast {
3340     zipFiles.each { file_zip -> 
3341       copy {
3342         from zipTree(file_zip)
3343         into "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3344       }
3345     }
3346   }
3347
3348   inputs.files zipFiles
3349   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3350 }
3351
3352
3353 task jalviewjsTransferUnzipAllLibs {
3354   dependsOn jalviewjsTransferUnzipSwingJs
3355   dependsOn jalviewjsTransferUnzipLib
3356 }
3357
3358
3359 task jalviewjsCreateJ2sSettings(type: WriteProperties) {
3360   group "JalviewJS"
3361   description "Create the alternative j2s file from the j2s.* properties"
3362
3363   jalviewjsJ2sProps = project.properties.findAll { it.key.startsWith("j2s.") }.sort { it.key }
3364   def siteDirProperty = "j2s.site.directory"
3365   def setSiteDir = false
3366   jalviewjsJ2sProps.each { prop, val ->
3367     if (val != null) {
3368       if (prop == siteDirProperty) {
3369         if (!(val.startsWith('/') || val.startsWith("file://") )) {
3370           val = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${val}"
3371         }
3372         setSiteDir = true
3373       }
3374       property(prop,val)
3375     }
3376     if (!setSiteDir) { // default site location, don't override specifically set property
3377       property(siteDirProperty,"${jalviewDirRelativePath}/${jalviewjsTransferSiteJsDir}")
3378     }
3379   }
3380   outputFile = jalviewjsJ2sAltSettingsFileName
3381
3382   if (! IN_ECLIPSE) {
3383     inputs.properties(jalviewjsJ2sProps)
3384     outputs.file(jalviewjsJ2sAltSettingsFileName)
3385   }
3386 }
3387
3388
3389 task jalviewjsEclipseSetup {
3390   dependsOn jalviewjsEclipseCopyDropins
3391   dependsOn jalviewjsSetEclipseWorkspace
3392   dependsOn jalviewjsCreateJ2sSettings
3393 }
3394
3395
3396 task jalviewjsSyncAllLibs (type: Sync) {
3397   dependsOn jalviewjsTransferUnzipAllLibs
3398   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteLibDir}")
3399   inputFiles += fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
3400   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3401
3402   from inputFiles
3403   into outputDir
3404   def outputFiles = []
3405   rename { filename ->
3406     outputFiles += "${outputDir}/${filename}"
3407     null
3408   }
3409   preserve {
3410     include "**"
3411   }
3412
3413   // should this be exclude really ?
3414   duplicatesStrategy "INCLUDE"
3415
3416   outputs.files outputFiles
3417   inputs.files inputFiles
3418 }
3419
3420
3421 task jalviewjsSyncResources (type: Sync) {
3422   dependsOn buildResources
3423
3424   def inputFiles = fileTree(dir: resourcesBuildDir)
3425   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
3426
3427   from inputFiles
3428   into outputDir
3429   def outputFiles = []
3430   rename { filename ->
3431     outputFiles += "${outputDir}/${filename}"
3432     null
3433   }
3434   preserve {
3435     include "**"
3436   }
3437   outputs.files outputFiles
3438   inputs.files inputFiles
3439 }
3440
3441
3442 task jalviewjsSyncSiteResources (type: Sync) {
3443   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_site_resource_dir}")
3444   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3445
3446   from inputFiles
3447   into outputDir
3448   def outputFiles = []
3449   rename { filename ->
3450     outputFiles += "${outputDir}/${filename}"
3451     null
3452   }
3453   preserve {
3454     include "**"
3455   }
3456   outputs.files outputFiles
3457   inputs.files inputFiles
3458 }
3459
3460
3461 task jalviewjsSyncBuildProperties (type: Sync) {
3462   dependsOn createBuildProperties
3463   def inputFiles = [file(buildProperties)]
3464   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
3465
3466   from inputFiles
3467   into outputDir
3468   def outputFiles = []
3469   rename { filename ->
3470     outputFiles += "${outputDir}/${filename}"
3471     null
3472   }
3473   preserve {
3474     include "**"
3475   }
3476   outputs.files outputFiles
3477   inputs.files inputFiles
3478 }
3479
3480
3481 task jalviewjsProjectImport(type: Exec) {
3482   dependsOn eclipseSetup
3483   dependsOn jalviewjsEclipsePaths
3484   dependsOn jalviewjsEclipseSetup
3485
3486   doFirst {
3487     // do not run a headless import when we claim to be in Eclipse
3488     if (IN_ECLIPSE) {
3489       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3490       throw new StopExecutionException("Not running headless import whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3491     } else {
3492       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3493     }
3494   }
3495
3496   //def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview/org.eclipse.jdt.core"
3497   def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview"
3498   executable(eclipseBinary)
3499   args(["-nosplash", "--launcher.suppressErrors", "-application", "com.seeq.eclipse.importprojects.headlessimport", "-data", eclipseWorkspace.getPath(), "-import", jalviewDirAbsolutePath])
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   inputs.file("${jalviewDir}/.project")
3510   outputs.upToDateWhen { 
3511     file(projdir).exists()
3512   }
3513 }
3514
3515
3516 task jalviewjsTranspile(type: Exec) {
3517   dependsOn jalviewjsEclipseSetup 
3518   dependsOn jalviewjsProjectImport
3519   dependsOn jalviewjsEclipsePaths
3520   if (!IN_ECLIPSE) {
3521     dependsOn jalviewjsEnableAltFileProperty
3522   }
3523
3524   doFirst {
3525     // do not run a headless transpile when we claim to be in Eclipse
3526     if (IN_ECLIPSE) {
3527       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3528       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3529     } else {
3530       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3531     }
3532   }
3533
3534   executable(eclipseBinary)
3535   args(["-nosplash", "--launcher.suppressErrors", "-application", "org.eclipse.jdt.apt.core.aptBuild", "-data", eclipseWorkspace, "-${jalviewjs_eclipse_build_arg}", eclipse_project_name ])
3536   if (eclipseDebug) {
3537     args += "-debug"
3538   }
3539   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
3540   if (!IN_ECLIPSE) {
3541     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
3542     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
3543   }
3544
3545   def stdout
3546   def stderr
3547   doFirst {
3548     stdout = new ByteArrayOutputStream()
3549     stderr = new ByteArrayOutputStream()
3550
3551     def logOutFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}"
3552     def logOutFile = file(logOutFileName)
3553     logOutFile.createNewFile()
3554     logOutFile.text = """ROOT: ${jalviewjs_eclipse_root}
3555 BINARY: ${eclipseBinary}
3556 VERSION: ${eclipseVersion}
3557 WORKSPACE: ${eclipseWorkspace}
3558 DEBUG: ${eclipseDebug}
3559 ----
3560 """
3561     def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
3562     // combine stdout and stderr
3563     def logErrFOS = logOutFOS
3564
3565     if (jalviewjs_j2s_to_console.equals("true")) {
3566       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3567         new org.apache.tools.ant.util.TeeOutputStream(
3568           logOutFOS,
3569           stdout),
3570         System.out)
3571       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3572         new org.apache.tools.ant.util.TeeOutputStream(
3573           logErrFOS,
3574           stderr),
3575         System.err)
3576     } else {
3577       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3578         logOutFOS,
3579         stdout)
3580       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3581         logErrFOS,
3582         stderr)
3583     }
3584   }
3585
3586   doLast {
3587     if (stdout.toString().contains("Error processing ")) {
3588       // j2s did not complete transpile
3589       //throw new TaskExecutionException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3590       if (jalviewjs_ignore_transpile_errors.equals("true")) {
3591         println("IGNORING TRANSPILE ERRORS")
3592         println("See eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3593       } else {
3594         throw new GradleException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3595       }
3596     }
3597   }
3598
3599   inputs.dir("${jalviewDir}/${sourceDir}")
3600   outputs.dir("${jalviewDir}/${jalviewjsTransferSiteJsDir}")
3601   outputs.upToDateWhen( { file("${jalviewDir}/${jalviewjsTransferSiteJsDir}${jalviewjs_server_resource}").exists() } )
3602 }
3603
3604
3605 def jalviewjsCallCore(String name, FileCollection list, String prefixFile, String suffixFile, String jsfile, String zjsfile, File logOutFile, Boolean logOutConsole) {
3606
3607   def stdout = new ByteArrayOutputStream()
3608   def stderr = new ByteArrayOutputStream()
3609
3610   def coreFile = file(jsfile)
3611   def msg = ""
3612   msg = "Creating core for ${name}...\nGenerating ${jsfile}"
3613   println(msg)
3614   logOutFile.createNewFile()
3615   logOutFile.append(msg+"\n")
3616
3617   def coreTop = file(prefixFile)
3618   def coreBottom = file(suffixFile)
3619   coreFile.getParentFile().mkdirs()
3620   coreFile.createNewFile()
3621   coreFile.write( coreTop.getText("UTF-8") )
3622   list.each {
3623     f ->
3624     if (f.exists()) {
3625       def t = f.getText("UTF-8")
3626       t.replaceAll("Clazz\\.([^_])","Clazz_${1}")
3627       coreFile.append( t )
3628     } else {
3629       msg = "...file '"+f.getPath()+"' does not exist, skipping"
3630       println(msg)
3631       logOutFile.append(msg+"\n")
3632     }
3633   }
3634   coreFile.append( coreBottom.getText("UTF-8") )
3635
3636   msg = "Generating ${zjsfile}"
3637   println(msg)
3638   logOutFile.append(msg+"\n")
3639   def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
3640   def logErrFOS = logOutFOS
3641
3642   javaexec {
3643     classpath = files(["${jalviewDir}/${jalviewjs_closure_compiler}"])
3644     main = "com.google.javascript.jscomp.CommandLineRunner"
3645     jvmArgs = [ "-Dfile.encoding=UTF-8" ]
3646     args = [ "--compilation_level", "SIMPLE_OPTIMIZATIONS", "--warning_level", "QUIET", "--charset", "UTF-8", "--js", jsfile, "--js_output_file", zjsfile ]
3647     maxHeapSize = "2g"
3648
3649     msg = "\nRunning '"+commandLine.join(' ')+"'\n"
3650     println(msg)
3651     logOutFile.append(msg+"\n")
3652
3653     if (logOutConsole) {
3654       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3655         new org.apache.tools.ant.util.TeeOutputStream(
3656           logOutFOS,
3657           stdout),
3658         standardOutput)
3659         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3660           new org.apache.tools.ant.util.TeeOutputStream(
3661             logErrFOS,
3662             stderr),
3663           System.err)
3664     } else {
3665       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3666         logOutFOS,
3667         stdout)
3668         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3669           logErrFOS,
3670           stderr)
3671     }
3672   }
3673   msg = "--"
3674   println(msg)
3675   logOutFile.append(msg+"\n")
3676 }
3677
3678
3679 task jalviewjsBuildAllCores {
3680   group "JalviewJS"
3681   description "Build the core js lib closures listed in the classlists dir"
3682   dependsOn jalviewjsTranspile
3683   dependsOn jalviewjsTransferUnzipSwingJs
3684
3685   def j2sDir = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${jalviewjs_j2s_subdir}"
3686   def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_j2s_subdir}"
3687   def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteLibDir}/${jalviewjs_j2s_subdir}"
3688   def jsDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_js_subdir}"
3689   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
3690   def prefixFile = "${jsDir}/core/coretop2.js"
3691   def suffixFile = "${jsDir}/core/corebottom2.js"
3692
3693   inputs.file prefixFile
3694   inputs.file suffixFile
3695
3696   def classlistFiles = []
3697   // add the classlists found int the jalviewjs_classlists_dir
3698   fileTree(dir: "${jalviewDir}/${jalviewjs_classlists_dir}", include: "*.txt").each {
3699     file ->
3700     def name = file.getName() - ".txt"
3701     classlistFiles += [
3702       'file': file,
3703       'name': name
3704     ]
3705   }
3706
3707   // _jmol and _jalview cores. Add any other peculiar classlist.txt files here
3708   //classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jmol}"), 'name': "_jvjmol" ]
3709   classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jalview}"), 'name': jalviewjsJalviewCoreName ]
3710
3711   jalviewjsCoreClasslists = []
3712
3713   classlistFiles.each {
3714     hash ->
3715
3716     def file = hash['file']
3717     if (! file.exists()) {
3718       //println("...classlist file '"+file.getPath()+"' does not exist, skipping")
3719       return false // this is a "continue" in groovy .each closure
3720     }
3721     def name = hash['name']
3722     if (name == null) {
3723       name = file.getName() - ".txt"
3724     }
3725
3726     def filelist = []
3727     file.eachLine {
3728       line ->
3729         filelist += line
3730     }
3731     def list = fileTree(dir: j2sDir, includes: filelist)
3732
3733     def jsfile = "${outputDir}/core${name}.js"
3734     def zjsfile = "${outputDir}/core${name}.z.js"
3735
3736     jalviewjsCoreClasslists += [
3737       'jsfile': jsfile,
3738       'zjsfile': zjsfile,
3739       'list': list,
3740       'name': name
3741     ]
3742
3743     inputs.file(file)
3744     inputs.files(list)
3745     outputs.file(jsfile)
3746     outputs.file(zjsfile)
3747   }
3748   
3749   // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
3750   def stevesoftClasslistName = "_stevesoft"
3751   def stevesoftClasslist = [
3752     'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
3753     'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
3754     'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
3755     'name': stevesoftClasslistName
3756   ]
3757   jalviewjsCoreClasslists += stevesoftClasslist
3758   inputs.files(stevesoftClasslist['list'])
3759   outputs.file(stevesoftClasslist['jsfile'])
3760   outputs.file(stevesoftClasslist['zjsfile'])
3761
3762   // _all core
3763   def allClasslistName = "_all"
3764   def allJsFiles = fileTree(dir: j2sDir, include: "**/*.js")
3765   allJsFiles += fileTree(
3766     dir: libJ2sDir,
3767     include: "**/*.js",
3768     excludes: [
3769       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3770       "**/org/jmol/jvxl/readers/IsoIntersectFileReader.js",
3771       "**/org/jmol/export/JSExporter.js"
3772     ]
3773   )
3774   allJsFiles += fileTree(
3775     dir: swingJ2sDir,
3776     include: "**/*.js",
3777     excludes: [
3778       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3779       "**/sun/misc/Unsafe.js",
3780       "**/swingjs/jquery/jquery-editable-select.js",
3781       "**/swingjs/jquery/j2sComboBox.js",
3782       "**/sun/misc/FloatingDecimal.js"
3783     ]
3784   )
3785   def allClasslist = [
3786     'jsfile': "${outputDir}/core${allClasslistName}.js",
3787     'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
3788     'list': allJsFiles,
3789     'name': allClasslistName
3790   ]
3791   // not including this version of "all" core at the moment
3792   //jalviewjsCoreClasslists += allClasslist
3793   inputs.files(allClasslist['list'])
3794   outputs.file(allClasslist['jsfile'])
3795   outputs.file(allClasslist['zjsfile'])
3796
3797   doFirst {
3798     def logOutFile = file("${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_closure_stdout}")
3799     logOutFile.getParentFile().mkdirs()
3800     logOutFile.createNewFile()
3801     logOutFile.write(getDate("yyyy-MM-dd HH:mm:ss")+" jalviewjsBuildAllCores\n----\n")
3802
3803     jalviewjsCoreClasslists.each {
3804       jalviewjsCallCore(it.name, it.list, prefixFile, suffixFile, it.jsfile, it.zjsfile, logOutFile, jalviewjs_j2s_to_console.equals("true"))
3805     }
3806   }
3807
3808 }
3809
3810
3811 def jalviewjsPublishCoreTemplate(String coreName, String templateName, File inputFile, String outputFile) {
3812   copy {
3813     from inputFile
3814     into file(outputFile).getParentFile()
3815     rename { filename ->
3816       if (filename.equals(inputFile.getName())) {
3817         return file(outputFile).getName()
3818       }
3819       return null
3820     }
3821     filter(ReplaceTokens,
3822       beginToken: '_',
3823       endToken: '_',
3824       tokens: [
3825         'MAIN': '"'+main_class+'"',
3826         'CODE': "null",
3827         'NAME': jalviewjsJalviewTemplateName+" [core ${coreName}]",
3828         'COREKEY': jalviewjs_core_key,
3829         'CORENAME': coreName
3830       ]
3831     )
3832   }
3833 }
3834
3835
3836 task jalviewjsPublishCoreTemplates {
3837   dependsOn jalviewjsBuildAllCores
3838   def inputFileName = "${jalviewDir}/${j2s_coretemplate_html}"
3839   def inputFile = file(inputFileName)
3840   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3841
3842   def outputFiles = []
3843   jalviewjsCoreClasslists.each { cl ->
3844     def outputFile = "${outputDir}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
3845     cl['outputfile'] = outputFile
3846     outputFiles += outputFile
3847   }
3848
3849   doFirst {
3850     jalviewjsCoreClasslists.each { cl ->
3851       jalviewjsPublishCoreTemplate(cl.name, jalviewjsJalviewTemplateName, inputFile, cl.outputfile)
3852     }
3853   }
3854   inputs.file(inputFile)
3855   outputs.files(outputFiles)
3856 }
3857
3858
3859 task jalviewjsSyncCore (type: Sync) {
3860   dependsOn jalviewjsBuildAllCores
3861   dependsOn jalviewjsPublishCoreTemplates
3862   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
3863   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3864
3865   from inputFiles
3866   into outputDir
3867   def outputFiles = []
3868   rename { filename ->
3869     outputFiles += "${outputDir}/${filename}"
3870     null
3871   }
3872   preserve {
3873     include "**"
3874   }
3875   outputs.files outputFiles
3876   inputs.files inputFiles
3877 }
3878
3879
3880 // this Copy version of TransferSiteJs will delete anything else in the target dir
3881 task jalviewjsCopyTransferSiteJs(type: Copy) {
3882   dependsOn jalviewjsTranspile
3883   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3884   into "${jalviewDir}/${jalviewjsSiteDir}"
3885 }
3886
3887
3888 // this Sync version of TransferSite is used by buildship to keep the website automatically up to date when a file changes
3889 task jalviewjsSyncTransferSiteJs(type: Sync) {
3890   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3891   include "**/*.*"
3892   into "${jalviewDir}/${jalviewjsSiteDir}"
3893   preserve {
3894     include "**"
3895   }
3896 }
3897
3898
3899 jalviewjsSyncAllLibs.mustRunAfter jalviewjsCopyTransferSiteJs
3900 jalviewjsSyncResources.mustRunAfter jalviewjsCopyTransferSiteJs
3901 jalviewjsSyncSiteResources.mustRunAfter jalviewjsCopyTransferSiteJs
3902 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsCopyTransferSiteJs
3903
3904 jalviewjsSyncAllLibs.mustRunAfter jalviewjsSyncTransferSiteJs
3905 jalviewjsSyncResources.mustRunAfter jalviewjsSyncTransferSiteJs
3906 jalviewjsSyncSiteResources.mustRunAfter jalviewjsSyncTransferSiteJs
3907 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsSyncTransferSiteJs
3908
3909
3910 task jalviewjsPrepareSite {
3911   group "JalviewJS"
3912   description "Prepares the website folder including unzipping files and copying resources"
3913   dependsOn jalviewjsSyncAllLibs
3914   dependsOn jalviewjsSyncResources
3915   dependsOn jalviewjsSyncSiteResources
3916   dependsOn jalviewjsSyncBuildProperties
3917   dependsOn jalviewjsSyncCore
3918 }
3919
3920
3921 task jalviewjsBuildSite {
3922   group "JalviewJS"
3923   description "Builds the whole website including transpiled code"
3924   dependsOn jalviewjsCopyTransferSiteJs
3925   dependsOn jalviewjsPrepareSite
3926 }
3927
3928
3929 task cleanJalviewjsTransferSite {
3930   doFirst {
3931     delete "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3932     delete "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3933     delete "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3934     delete "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3935   }
3936 }
3937
3938
3939 task cleanJalviewjsSite {
3940   dependsOn cleanJalviewjsTransferSite
3941   doFirst {
3942     delete "${jalviewDir}/${jalviewjsSiteDir}"
3943   }
3944 }
3945
3946
3947 task jalviewjsSiteTar(type: Tar) {
3948   group "JalviewJS"
3949   description "Creates a tar.gz file for the website"
3950   dependsOn jalviewjsBuildSite
3951   def outputFilename = "jalviewjs-site-${JALVIEW_VERSION}.tar.gz"
3952   archiveFileName = outputFilename
3953
3954   compression Compression.GZIP
3955
3956   from "${jalviewDir}/${jalviewjsSiteDir}"
3957   into jalviewjs_site_dir // this is inside the tar file
3958
3959   inputs.dir("${jalviewDir}/${jalviewjsSiteDir}")
3960 }
3961
3962
3963 task jalviewjsServer {
3964   group "JalviewJS"
3965   def filename = "jalviewjsTest.html"
3966   description "Starts a webserver on localhost to test the website. See ${filename} to access local site on most recently used port."
3967   def htmlFile = "${jalviewDirAbsolutePath}/${filename}"
3968   doLast {
3969
3970     def factory
3971     try {
3972       def f = Class.forName("org.gradle.plugins.javascript.envjs.http.simple.SimpleHttpFileServerFactory")
3973       factory = f.newInstance()
3974     } catch (ClassNotFoundException e) {
3975       throw new GradleException("Unable to create SimpleHttpFileServerFactory")
3976     }
3977     def port = Integer.valueOf(jalviewjs_server_port)
3978     def start = port
3979     def running = false
3980     def url
3981     def jalviewjsServer
3982     while(port < start+1000 && !running) {
3983       try {
3984         def doc_root = new File("${jalviewDirAbsolutePath}/${jalviewjsSiteDir}")
3985         jalviewjsServer = factory.start(doc_root, port)
3986         running = true
3987         url = jalviewjsServer.getResourceUrl(jalviewjs_server_resource)
3988         println("SERVER STARTED with document root ${doc_root}.")
3989         println("Go to "+url+" . Run  gradle --stop  to stop (kills all gradle daemons).")
3990         println("For debug: "+url+"?j2sdebug")
3991         println("For verbose: "+url+"?j2sverbose")
3992       } catch (Exception e) {
3993         port++;
3994       }
3995     }
3996     def htmlText = """
3997       <p><a href="${url}">JalviewJS Test. &lt;${url}&gt;</a></p>
3998       <p><a href="${url}?j2sdebug">JalviewJS Test with debug. &lt;${url}?j2sdebug&gt;</a></p>
3999       <p><a href="${url}?j2sverbose">JalviewJS Test with verbose. &lt;${url}?j2sdebug&gt;</a></p>
4000       """
4001     jalviewjsCoreClasslists.each { cl ->
4002       def urlcore = jalviewjsServer.getResourceUrl(file(cl.outputfile).getName())
4003       htmlText += """
4004       <p><a href="${urlcore}">${jalviewjsJalviewTemplateName} [core ${cl.name}]. &lt;${urlcore}&gt;</a></p>
4005       """
4006       println("For core ${cl.name}: "+urlcore)
4007     }
4008
4009     file(htmlFile).text = htmlText
4010   }
4011
4012   outputs.file(htmlFile)
4013   outputs.upToDateWhen({false})
4014 }
4015
4016
4017 task cleanJalviewjsAll {
4018   group "JalviewJS"
4019   description "Delete all configuration and build artifacts to do with JalviewJS build"
4020   dependsOn cleanJalviewjsSite
4021   dependsOn jalviewjsEclipsePaths
4022   
4023   doFirst {
4024     delete "${jalviewDir}/${jalviewjsBuildDir}"
4025     delete "${jalviewDir}/${eclipse_bin_dir}"
4026     if (eclipseWorkspace != null && file(eclipseWorkspace.getAbsolutePath()+"/.metadata").exists()) {
4027       delete file(eclipseWorkspace.getAbsolutePath()+"/.metadata")
4028     }
4029     delete jalviewjsJ2sAltSettingsFileName
4030   }
4031
4032   outputs.upToDateWhen( { false } )
4033 }
4034
4035
4036 task jalviewjsIDE_checkJ2sPlugin {
4037   group "00 JalviewJS in Eclipse"
4038   description "Compare the swingjs/net.sf.j2s.core(-j11)?.jar file with the Eclipse IDE's plugin version (found in the 'dropins' dir)"
4039
4040   doFirst {
4041     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
4042     def j2sPluginFile = file(j2sPlugin)
4043     def eclipseHome = System.properties["eclipse.home.location"]
4044     if (eclipseHome == null || ! IN_ECLIPSE) {
4045       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. Skipping J2S Plugin Check.")
4046     }
4047     def eclipseJ2sPluginDirs = [ "${eclipseHome}/dropins" ]
4048     def altPluginsDir = System.properties["org.eclipse.equinox.p2.reconciler.dropins.directory"]
4049     if (altPluginsDir != null && file(altPluginsDir).exists()) {
4050       eclipseJ2sPluginDirs += altPluginsDir
4051     }
4052     def foundPlugin = false
4053     def j2sPluginFileName = j2sPluginFile.getName()
4054     def eclipseJ2sPlugin
4055     def eclipseJ2sPluginFile
4056     eclipseJ2sPluginDirs.any { dir ->
4057       eclipseJ2sPlugin = "${dir}/${j2sPluginFileName}"
4058       eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
4059       if (eclipseJ2sPluginFile.exists()) {
4060         foundPlugin = true
4061         return true
4062       }
4063     }
4064     if (!foundPlugin) {
4065       def msg = "Eclipse J2S Plugin is not installed (could not find '${j2sPluginFileName}' in\n"+eclipseJ2sPluginDirs.join("\n")+"\n)\nTry running task jalviewjsIDE_copyJ2sPlugin"
4066       System.err.println(msg)
4067       throw new StopExecutionException(msg)
4068     }
4069
4070     def digest = MessageDigest.getInstance("MD5")
4071
4072     digest.update(j2sPluginFile.text.bytes)
4073     def j2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
4074
4075     digest.update(eclipseJ2sPluginFile.text.bytes)
4076     def eclipseJ2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
4077      
4078     if (j2sPluginMd5 != eclipseJ2sPluginMd5) {
4079       def msg = "WARNING! Eclipse J2S Plugin '${eclipseJ2sPlugin}' is different to this commit's version '${j2sPlugin}'"
4080       System.err.println(msg)
4081       throw new StopExecutionException(msg)
4082     } else {
4083       def msg = "Eclipse J2S Plugin '${eclipseJ2sPlugin}' is the same as '${j2sPlugin}' (this is good)"
4084       println(msg)
4085     }
4086   }
4087 }
4088
4089 task jalviewjsIDE_copyJ2sPlugin {
4090   group "00 JalviewJS in Eclipse"
4091   description "Copy the swingjs/net.sf.j2s.core(-j11)?.jar file into the Eclipse IDE's 'dropins' dir"
4092
4093   doFirst {
4094     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
4095     def j2sPluginFile = file(j2sPlugin)
4096     def eclipseHome = System.properties["eclipse.home.location"]
4097     if (eclipseHome == null || ! IN_ECLIPSE) {
4098       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. NOT copying J2S Plugin.")
4099     }
4100     def eclipseJ2sPlugin = "${eclipseHome}/dropins/${j2sPluginFile.getName()}"
4101     def eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
4102     def msg = "WARNING! Copying this commit's j2s plugin '${j2sPlugin}' to Eclipse J2S Plugin '${eclipseJ2sPlugin}'\n* May require an Eclipse restart"
4103     System.err.println(msg)
4104     copy {
4105       from j2sPlugin
4106       eclipseJ2sPluginFile.getParentFile().mkdirs()
4107       into eclipseJ2sPluginFile.getParent()
4108     }
4109   }
4110 }
4111
4112
4113 task jalviewjsIDE_j2sFile {
4114   group "00 JalviewJS in Eclipse"
4115   description "Creates the .j2s file"
4116   dependsOn jalviewjsCreateJ2sSettings
4117 }
4118
4119
4120 task jalviewjsIDE_SyncCore {
4121   group "00 JalviewJS in Eclipse"
4122   description "Build the core js lib closures listed in the classlists dir and publish core html from template"
4123   dependsOn jalviewjsSyncCore
4124 }
4125
4126
4127 task jalviewjsIDE_SyncSiteAll {
4128   dependsOn jalviewjsSyncAllLibs
4129   dependsOn jalviewjsSyncResources
4130   dependsOn jalviewjsSyncSiteResources
4131   dependsOn jalviewjsSyncBuildProperties
4132 }
4133
4134
4135 cleanJalviewjsTransferSite.mustRunAfter jalviewjsIDE_SyncSiteAll
4136
4137
4138 task jalviewjsIDE_PrepareSite {
4139   group "00 JalviewJS in Eclipse"
4140   description "Sync libs and resources to site dir, but not closure cores"
4141
4142   dependsOn jalviewjsIDE_SyncSiteAll
4143   //dependsOn cleanJalviewjsTransferSite // not sure why this clean is here -- will slow down a re-run of this task
4144 }
4145
4146
4147 task jalviewjsIDE_AssembleSite {
4148   group "00 JalviewJS in Eclipse"
4149   description "Assembles unzipped supporting zipfiles, resources, site resources and closure cores into the Eclipse transpiled site"
4150   dependsOn jalviewjsPrepareSite
4151 }
4152
4153
4154 task jalviewjsIDE_SiteClean {
4155   group "00 JalviewJS in Eclipse"
4156   description "Deletes the Eclipse transpiled site"
4157   dependsOn cleanJalviewjsSite
4158 }
4159
4160
4161 task jalviewjsIDE_Server {
4162   group "00 JalviewJS in Eclipse"
4163   description "Starts a webserver on localhost to test the website"
4164   dependsOn jalviewjsServer
4165 }
4166
4167
4168 // buildship runs this at import or gradle refresh
4169 task eclipseSynchronizationTask {
4170   //dependsOn eclipseSetup
4171   dependsOn createBuildProperties
4172   if (J2S_ENABLED) {
4173     dependsOn jalviewjsIDE_j2sFile
4174     dependsOn jalviewjsIDE_checkJ2sPlugin
4175     dependsOn jalviewjsIDE_PrepareSite
4176   }
4177 }
4178
4179
4180 // buildship runs this at build time or project refresh
4181 task eclipseAutoBuildTask {
4182   //dependsOn jalviewjsIDE_checkJ2sPlugin
4183   //dependsOn jalviewjsIDE_PrepareSite
4184 }
4185
4186
4187 task jalviewjs {
4188   group "JalviewJS"
4189   description "Build the site"
4190   dependsOn jalviewjsBuildSite
4191 }
4192