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