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