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