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