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