JAL-4421 Remove the disk_image_alias from the backgroundImageAlias altogether as...
[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     // delete .VolumeIcon.icns in macos DMG if there isn't one
2730     if (!file(install4jDMGVolumeIcon).exists()) {
2731       println("No '.VolumeIcon.icns' file found. Removing from install4j file.")
2732       install4jConfigXml.'**'.macosArchive.topLevelFiles.each { topLevelFiles ->
2733         topLevelFiles.file.each() { file ->
2734           if (file.attribute("name") && file.attribute("name").equals(".VolumeIcon.icns")) {
2735             println("Removing "+file.toString())
2736             topLevelFiles.remove(file)
2737           }
2738         }
2739       }
2740     } else {
2741       println("Using '.VolumeIcon.icns' file '${install4jDMGVolumeIcon}'")
2742     }
2743
2744     // disable install screen for OSX dmg (for 2.11.2.0)
2745     install4jConfigXml.'**'.macosArchive.each { macosArchive -> 
2746       macosArchive.attributes().remove('executeSetupApp')
2747       macosArchive.attributes().remove('setupAppId')
2748     }
2749
2750     // turn off checksum creation for LOCAL channel
2751     def e = install4jConfigXml.application[0]
2752     e.'@createChecksums' = string(install4jCheckSums)
2753
2754     // put file association actions where placeholder action is
2755     def install4jFileAssociationsText = install4jFileAssociationsFile.text
2756     def fileAssociationActions = new XmlParser().parseText("<actions>${install4jFileAssociationsText}</actions>")
2757     install4jConfigXml.'**'.action.any { a -> // .any{} stops after the first one that returns true
2758       if (a.'@name' == 'EXTENSIONS_REPLACED_BY_GRADLE') {
2759         def parent = a.parent()
2760         parent.remove(a)
2761         fileAssociationActions.each { faa ->
2762             parent.append(faa)
2763         }
2764         // don't need to continue in .any loop once replacements have been made
2765         return true
2766       }
2767     }
2768
2769     // use Windows Program Group with Examples folder for RELEASE, and Program Group without Examples for everything else
2770     // NB we're deleting the /other/ one!
2771     // Also remove the examples subdir from non-release versions
2772     def customizedIdToDelete = "PROGRAM_GROUP_RELEASE"
2773     // 2.11.1.0 NOT releasing with the Examples folder in the Program Group
2774     if (false && CHANNEL=="RELEASE") { // remove 'false && ' to include Examples folder in RELEASE channel
2775       customizedIdToDelete = "PROGRAM_GROUP_NON_RELEASE"
2776     } else {
2777       // remove the examples subdir from Full File Set
2778       def files = install4jConfigXml.files[0]
2779       def fileset = files.filesets.fileset.find { fs -> fs.'@customizedId' == "FULL_FILE_SET" }
2780       def root = files.roots.root.find { r -> r.'@fileset' == fileset.'@id' }
2781       def mountPoint = files.mountPoints.mountPoint.find { mp -> mp.'@root' == root.'@id' }
2782       def dirEntry = files.entries.dirEntry.find { de -> de.'@mountPoint' == mountPoint.'@id' && de.'@subDirectory' == "examples" }
2783       dirEntry.parent().remove(dirEntry)
2784     }
2785     install4jConfigXml.'**'.action.any { a ->
2786       if (a.'@customizedId' == customizedIdToDelete) {
2787         def parent = a.parent()
2788         parent.remove(a)
2789         return true
2790       }
2791     }
2792
2793     // write install4j file
2794     install4jConfFile.text = XmlUtil.serialize(install4jConfigXml)
2795   }
2796 }
2797
2798
2799 clean {
2800   doFirst {
2801     delete install4jConfFile
2802   }
2803 }
2804
2805 task cleanInstallersDataFiles {
2806   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
2807   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
2808   def hugoDataJsonFile = file("${jalviewDir}/${install4jBuildDir}/installers-${JALVIEW_VERSION_UNDERSCORES}.json")
2809   doFirst {
2810     delete installersOutputTxt
2811     delete installersSha256
2812     delete hugoDataJsonFile
2813   }
2814 }
2815
2816 task install4jDMGBackgroundImageCopy {
2817   inputs.file "${install4jDMGBackgroundImageDir}/${install4jDMGBackgroundImageFile}"
2818   outputs.dir "${install4jDMGBackgroundImageBuildDir}"
2819   doFirst {
2820     copy {
2821       from(install4jDMGBackgroundImageDir) {
2822         include(install4jDMGBackgroundImageFile)
2823       }
2824       into install4jDMGBackgroundImageBuildDir
2825     }
2826   }
2827 }
2828
2829 task install4jDMGBackgroundImageProcess {
2830   dependsOn install4jDMGBackgroundImageCopy
2831
2832   doFirst {
2833     if (backgroundImageText) {
2834       if (convertBinary == null) {
2835         throw new StopExecutionException("No ImageMagick convert binary installed at '${convertBinaryExpectedLocation}'")
2836       }
2837       if (!project.hasProperty("install4j_background_image_text_suffix_cmd")) {
2838         throw new StopExecutionException("No property 'install4j_background_image_text_suffix_cmd' defined. See channel_gradle.properties for channel ${CHANNEL}")
2839       }
2840       fileTree(dir: install4jDMGBackgroundImageBuildDir, include: "*.png").getFiles().each { file ->
2841         exec {
2842           executable convertBinary
2843           args = [
2844             file.getPath(),
2845             '-font', install4j_background_image_text_font,
2846             '-fill', install4j_background_image_text_colour,
2847             '-draw', sprintf(install4j_background_image_text_suffix_cmd, channelSuffix),
2848             '-draw', sprintf(install4j_background_image_text_commit_cmd, "git-commit: ${gitHash}"),
2849             '-draw', sprintf(install4j_background_image_text_date_cmd, getDate("yyyy-MM-dd HH:mm:ss")),
2850             file.getPath()
2851           ]
2852         }
2853       }
2854     }
2855   }
2856 }
2857
2858
2859 python {
2860   pip 'ds_store:1.3.1'
2861 }
2862
2863 task install4jCustomiseDS_StoreX64(type: PythonTask) {
2864   inputs.file(install4jDMGDSStore)
2865   outputs.file(install4jDMGFixedDSStoreX64)
2866   def command_args = [ jalview_customise_ds_store, '--input', install4jDMGDSStore, '--output', install4jDMGFixedDSStoreX64, '--volumename', install4jmacOSArchiveX64Name, '--backgroundfile', install4j_dmg_background_filename, '--dmg', install4jmacOSArchiveX64DMGFilename + ".dmg" ]
2867   if (file(install4jDMGDSStoreJSON).exists()) {
2868     command_args += [ '--config', install4jDMGDSStoreJSON ]
2869     inputs.file(install4jDMGDSStoreJSON)
2870   }
2871   command = command_args
2872   doFirst {
2873     println("Running command '${command_args.join(' ')}'")
2874   }
2875 }
2876
2877 task install4jCustomiseDS_StoreAarch64(type: PythonTask) {
2878   inputs.file(install4jDMGDSStore)
2879   outputs.file(install4jDMGFixedDSStoreAarch64)
2880   def command_args = [ jalview_customise_ds_store, '--input', install4jDMGDSStore, '--output', install4jDMGFixedDSStoreAarch64, '--volumename', install4jmacOSArchiveAarch64Name, '--backgroundfile', install4j_dmg_background_filename, '--dmg', install4jmacOSArchiveAarch64DMGFilename + ".dmg" ]
2881   if (file(install4jDMGDSStoreJSON).exists()) {
2882     command_args += [ '--config', install4jDMGDSStoreJSON ]
2883     inputs.file(install4jDMGDSStoreJSON)
2884   }
2885   command = command_args
2886   doFirst {
2887     def print_args = []
2888     for (int i = 0; i < command_args.size(); i++) {
2889       def arg = command_args[i]
2890       print_args += (i > 0 && !arg.startsWith("-")) ? "\"${arg}\"" : arg
2891     }
2892     println("Running command '${print_args.join(' ')}'")
2893   }
2894 }
2895
2896 task install4jCustomiseDS_Store {
2897   dependsOn install4jCustomiseDS_StoreX64
2898   dependsOn install4jCustomiseDS_StoreAarch64
2899 }
2900
2901 task install4jDMGProcesses {
2902   dependsOn install4jDMGBackgroundImageProcess
2903   dependsOn install4jCustomiseDS_Store
2904 }
2905
2906 task installerFiles(type: com.install4j.gradle.Install4jTask) {
2907   group = "distribution"
2908   description = "Create the install4j installers"
2909   dependsOn getdown
2910   dependsOn copyInstall4jTemplate
2911   dependsOn cleanInstallersDataFiles
2912   dependsOn install4jDMGProcesses
2913
2914   projectFile = install4jConfFile
2915
2916   // run install4j with 4g
2917   vmParameters = ["-Xmx4294967296"]
2918
2919   // create an md5 for the input files to use as version for install4j conf file
2920   def digest = MessageDigest.getInstance("MD5")
2921   digest.update(
2922     (file("${install4jDir}/${install4j_template}").text + 
2923     file("${install4jDir}/${install4j_info_plist_file_associations}").text +
2924     file("${install4jDir}/${install4j_installer_file_associations}").text).bytes)
2925   def filesMd5 = new BigInteger(1, digest.digest()).toString(16)
2926   if (filesMd5.length() >= 8) {
2927     filesMd5 = filesMd5.substring(0,8)
2928   }
2929   def install4jTemplateVersion = "${JALVIEW_VERSION}_F${filesMd5}_C${gitHash}"
2930
2931   variables = [
2932     'JALVIEW_NAME': jalview_name,
2933     'JALVIEW_APPLICATION_NAME': applicationName,
2934     'JALVIEW_DIR': "../..",
2935     'OSX_KEYSTORE': OSX_KEYSTORE,
2936     'OSX_APPLEID': OSX_APPLEID,
2937     'OSX_ALTOOLPASS': OSX_ALTOOLPASS,
2938     'JSIGN_SH': JSIGN_SH,
2939     'JRE_DIR': getdown_app_dir_java,
2940     'INSTALLER_TEMPLATE_VERSION': install4jTemplateVersion,
2941     'JALVIEW_VERSION': JALVIEW_VERSION,
2942     'JAVA_MIN_VERSION': JAVA_MIN_VERSION,
2943     'JAVA_MAX_VERSION': JAVA_MAX_VERSION,
2944     'JAVA_VERSION': JAVA_VERSION,
2945     'JAVA_INTEGER_VERSION': JAVA_INTEGER_VERSION,
2946     'VERSION': JALVIEW_VERSION,
2947     'COPYRIGHT_MESSAGE': install4j_copyright_message,
2948     'BUNDLE_ID': install4jBundleId,
2949     'INTERNAL_ID': install4jInternalId,
2950     'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
2951     'MACOS_X64_DMG_DS_STORE': install4jDMGFixedDSStoreX64,
2952     'MACOS_AARCH64_DMG_DS_STORE': install4jDMGFixedDSStoreAarch64,
2953     'MACOS_DMG_BG_IMAGE': "${install4jDMGBackgroundImageBuildDir}/${install4jDMGBackgroundImageFile}",
2954     'MACOS_DMG_BG_FILENAME': install4j_dmg_background_filename,
2955     'WRAPPER_LINK': getdownWrapperLink,
2956     'BASH_WRAPPER_SCRIPT': getdown_bash_wrapper_script,
2957     'POWERSHELL_WRAPPER_SCRIPT': getdown_powershell_wrapper_script,
2958     'BATCH_WRAPPER_SCRIPT': getdown_batch_wrapper_script,
2959     'WRAPPER_SCRIPT_BIN_DIR': getdown_wrapper_script_dir,
2960     'MACOSARCHIVE_X64_NAME': install4jmacOSArchiveX64Name,
2961     'MACOSARCHIVE_AARCH64_NAME': install4jmacOSArchiveAarch64Name,
2962     'INSTALL4J_UTILS_DIR': install4j_utils_dir,
2963     'GETDOWN_CHANNEL_DIR': getdownChannelDir,
2964     'GETDOWN_FILES_DIR': getdown_files_dir,
2965     'GETDOWN_RESOURCE_DIR': getdown_resource_dir,
2966     'GETDOWN_DIST_DIR': getdownAppDistDir,
2967     'GETDOWN_ALT_DIR': getdown_app_dir_alt,
2968     'GETDOWN_INSTALL_DIR': getdown_install_dir,
2969     'INFO_PLIST_FILE_ASSOCIATIONS_FILE': install4j_info_plist_file_associations,
2970     'BUILD_DIR': install4jBuildDir,
2971     'APPLICATION_CATEGORIES': install4j_application_categories,
2972     'APPLICATION_FOLDER': install4jApplicationFolder,
2973     'UNIX_APPLICATION_FOLDER': install4jUnixApplicationFolder,
2974     'EXECUTABLE_NAME': install4jExecutableName,
2975     'EXTRA_SCHEME': install4jExtraScheme,
2976     'MAC_ICONS_FILE': install4jMacIconsFile,
2977     'WINDOWS_ICONS_FILE': install4jWindowsIconsFile,
2978     'PNG_ICON_FILE': install4jPngIconFile,
2979     'BACKGROUND': install4jBackground,
2980     'MACOSARCHIVE_X64_DMG_FILENAME': install4jmacOSArchiveX64DMGFilename,
2981     'MACOSARCHIVE_AARCH64_DMG_FILENAME': install4jmacOSArchiveAarch64DMGFilename,
2982     'MACOSARCHIVE_VOLUMEICON': install4jDMGVolumeIcon,
2983   ]
2984
2985   def varNameMap = [
2986     'mac': 'MACOS',
2987     'windows': 'WINDOWS',
2988     'linux': 'LINUX'
2989   ]
2990   
2991   // these are the bundled OS/architecture VMs needed by install4j
2992   def osArch = [
2993     [ "mac", "x64" ],
2994     [ "mac", "aarch64" ],
2995     [ "windows", "x64" ],
2996     [ "linux", "x64" ],
2997     [ "linux", "aarch64" ]
2998   ]
2999   osArch.forEach { os, arch ->
3000     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)
3001     // N.B. For some reason install4j requires the below filename to have underscores and not hyphens
3002     // otherwise running `gradle installers` generates a non-useful error:
3003     // `install4j: compilation failed. Reason: java.lang.NumberFormatException: For input string: "windows"`
3004     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)
3005   }
3006
3007   //println("INSTALL4J VARIABLES:")
3008   //variables.each{k,v->println("${k}=${v}")}
3009
3010   destination = "${jalviewDir}/${install4jBuildDir}"
3011   buildSelected = true
3012
3013   if (install4j_faster.equals("true") || CHANNEL.startsWith("LOCAL")) {
3014     faster = true
3015     disableSigning = true
3016     disableNotarization = true
3017   }
3018
3019   if (OSX_KEYPASS) {
3020     macKeystorePassword = OSX_KEYPASS
3021   } 
3022   
3023   if (OSX_ALTOOLPASS) {
3024     appleIdPassword = OSX_ALTOOLPASS
3025     disableNotarization = false
3026   } else {
3027     disableNotarization = true
3028   }
3029
3030   doFirst {
3031     println("Using projectFile "+projectFile)
3032     if (!disableNotarization) { println("Will notarize OSX App DMG") }
3033   }
3034   //verbose=true
3035
3036   inputs.dir(getdownAppBaseDir)
3037   inputs.file(install4jConfFile)
3038   inputs.file("${install4jDir}/${install4j_info_plist_file_associations}")
3039   outputs.dir("${jalviewDir}/${install4j_build_dir}/${JAVA_VERSION}")
3040 }
3041
3042 def getDataHash(File myFile) {
3043   HashCode hash = Files.asByteSource(myFile).hash(Hashing.sha256())
3044   return myFile.exists()
3045   ? [
3046       "file" : myFile.getName(),
3047       "filesize" : myFile.length(),
3048       "sha256" : hash.toString()
3049     ]
3050   : null
3051 }
3052
3053 def writeDataJsonFile(File installersOutputTxt, File installersSha256, File dataJsonFile) {
3054   def hash = [
3055     "channel" : getdownChannelName,
3056     "date" : getDate("yyyy-MM-dd HH:mm:ss"),
3057     "git-commit" : "${gitHash} [${gitBranch}]",
3058     "version" : JALVIEW_VERSION
3059   ]
3060   // install4j installer files
3061   if (installersOutputTxt.exists()) {
3062     def idHash = [:]
3063     installersOutputTxt.readLines().each { def line ->
3064       if (line.startsWith("#")) {
3065         return;
3066       }
3067       line.replaceAll("\n","")
3068       def vals = line.split("\t")
3069       def filename = vals[3]
3070       def filesize = file(filename).length()
3071       filename = filename.replaceAll(/^.*\//, "")
3072       hash[vals[0]] = [ "id" : vals[0], "os" : vals[1], "name" : vals[2], "file" : filename, "filesize" : filesize ]
3073       idHash."${filename}" = vals[0]
3074     }
3075     if (install4jCheckSums && installersSha256.exists()) {
3076       installersSha256.readLines().each { def line ->
3077         if (line.startsWith("#")) {
3078           return;
3079         }
3080         line.replaceAll("\n","")
3081         def vals = line.split(/\s+\*?/)
3082         def filename = vals[1]
3083         def innerHash = (hash.(idHash."${filename}"))."sha256" = vals[0]
3084       }
3085     }
3086   }
3087
3088   [
3089     "JAR": shadowJar.archiveFile, // executable JAR
3090     "JVL": getdownVersionLaunchJvl, // version JVL
3091     "SOURCE": sourceDist.archiveFile // source TGZ
3092   ].each { key, value ->
3093     def file = file(value)
3094     if (file.exists()) {
3095       def fileHash = getDataHash(file)
3096       if (fileHash != null) {
3097         hash."${key}" = fileHash;
3098       }
3099     }
3100   }
3101   return dataJsonFile.write(new JsonBuilder(hash).toPrettyString())
3102 }
3103
3104 task staticMakeInstallersJsonFile {
3105   doFirst {
3106     def output = findProperty("i4j_output")
3107     def sha256 = findProperty("i4j_sha256")
3108     def json = findProperty("i4j_json")
3109     if (output == null || sha256 == null || json == null) {
3110       throw new GradleException("Must provide paths to all of output.txt, sha256sums, and output.json with '-Pi4j_output=... -Pi4j_sha256=... -Pi4j_json=...")
3111     }
3112     writeDataJsonFile(file(output), file(sha256), file(json))
3113   }
3114 }
3115
3116 task installers {
3117   dependsOn installerFiles
3118 }
3119
3120
3121 spotless {
3122   java {
3123     eclipse().configFile(eclipse_codestyle_file)
3124   }
3125 }
3126
3127 task createSourceReleaseProperties(type: WriteProperties) {
3128   group = "distribution"
3129   description = "Create the source RELEASE properties file"
3130   
3131   def sourceTarBuildDir = "${buildDir}/sourceTar"
3132   def sourceReleasePropertiesFile = "${sourceTarBuildDir}/RELEASE"
3133   outputFile (sourceReleasePropertiesFile)
3134
3135   doFirst {
3136     releaseProps.each{ key, val -> property key, val }
3137     property "git.branch", gitBranch
3138     property "git.hash", gitHash
3139   }
3140
3141   outputs.file(outputFile)
3142 }
3143
3144 task sourceDist(type: Tar) {
3145   group "distribution"
3146   description "Create a source .tar.gz file for distribution"
3147
3148   dependsOn createBuildProperties
3149   dependsOn convertMdFiles
3150   dependsOn eclipseAllPreferences
3151   dependsOn createSourceReleaseProperties
3152
3153
3154   def outputFileName = "${project.name}_${JALVIEW_VERSION_UNDERSCORES}.tar.gz"
3155   archiveFileName = outputFileName
3156   
3157   compression Compression.GZIP
3158   
3159   into project.name
3160
3161   def EXCLUDE_FILES=[
3162     "dist/*",
3163     "build/*",
3164     "bin/*",
3165     "test-output/",
3166     "test-reports",
3167     "tests",
3168     "clover*/*",
3169     ".*",
3170     "benchmarking/*",
3171     "**/.*",
3172     "*.class",
3173     "**/*.class","$j11modDir/**/*.jar","appletlib","**/*locales",
3174     "*locales/**",
3175     "utils/InstallAnywhere",
3176     "**/*.log",
3177     "RELEASE",
3178   ] 
3179   def PROCESS_FILES=[
3180     "AUTHORS",
3181     "CITATION",
3182     "FEATURETODO",
3183     "JAVA-11-README",
3184     "FEATURETODO",
3185     "LICENSE",
3186     "**/README",
3187     "THIRDPARTYLIBS",
3188     "TESTNG",
3189     "build.gradle",
3190     "gradle.properties",
3191     "**/*.java",
3192     "**/*.html",
3193     "**/*.xml",
3194     "**/*.gradle",
3195     "**/*.groovy",
3196     "**/*.properties",
3197     "**/*.perl",
3198     "**/*.sh",
3199   ]
3200   def INCLUDE_FILES=[
3201     ".classpath",
3202     ".settings/org.eclipse.buildship.core.prefs",
3203     ".settings/org.eclipse.jdt.core.prefs"
3204   ]
3205
3206   from(jalviewDir) {
3207     exclude (EXCLUDE_FILES)
3208     include (PROCESS_FILES)
3209     filter(ReplaceTokens,
3210       beginToken: '$$',
3211       endToken: '$$',
3212       tokens: [
3213         'Version-Rel': JALVIEW_VERSION,
3214         'Year-Rel': getDate("yyyy")
3215       ]
3216     )
3217   }
3218   from(jalviewDir) {
3219     exclude (EXCLUDE_FILES)
3220     exclude (PROCESS_FILES)
3221     exclude ("appletlib")
3222     exclude ("**/*locales")
3223     exclude ("*locales/**")
3224     exclude ("utils/InstallAnywhere")
3225
3226     exclude (getdown_files_dir)
3227     // getdown_website_dir and getdown_archive_dir moved to build/website/docroot/getdown
3228     //exclude (getdown_website_dir)
3229     //exclude (getdown_archive_dir)
3230
3231     // exluding these as not using jars as modules yet
3232     exclude ("${j11modDir}/**/*.jar")
3233   }
3234   from(jalviewDir) {
3235     include(INCLUDE_FILES)
3236   }
3237 //  from (jalviewDir) {
3238 //    // explicit includes for stuff that seemed to not get included
3239 //    include(fileTree("test/**/*."))
3240 //    exclude(EXCLUDE_FILES)
3241 //    exclude(PROCESS_FILES)
3242 //  }
3243
3244   from(file(buildProperties).getParent()) {
3245     include(file(buildProperties).getName())
3246     rename(file(buildProperties).getName(), "build_properties")
3247     filter({ line ->
3248       line.replaceAll("^INSTALLATION=.*\$","INSTALLATION=Source Release"+" git-commit\\\\:"+gitHash+" ["+gitBranch+"]")
3249     })
3250   }
3251
3252   def sourceTarBuildDir = "${buildDir}/sourceTar"
3253   from(sourceTarBuildDir) {
3254     // this includes the appended RELEASE properties file
3255   }
3256 }
3257
3258 task dataInstallersJson {
3259   group "website"
3260   description "Create the installers-VERSION.json data file for installer files created"
3261
3262   mustRunAfter installers
3263   mustRunAfter shadowJar
3264   mustRunAfter sourceDist
3265   mustRunAfter getdownArchive
3266
3267   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
3268   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
3269
3270   if (installersOutputTxt.exists()) {
3271     inputs.file(installersOutputTxt)
3272   }
3273   if (install4jCheckSums && installersSha256.exists()) {
3274     inputs.file(installersSha256)
3275   }
3276   [
3277     shadowJar.archiveFile, // executable JAR
3278     getdownVersionLaunchJvl, // version JVL
3279     sourceDist.archiveFile // source TGZ
3280   ].each { fileName ->
3281     if (file(fileName).exists()) {
3282       inputs.file(fileName)
3283     }
3284   }
3285
3286   outputs.file(hugoDataJsonFile)
3287
3288   doFirst {
3289     writeDataJsonFile(installersOutputTxt, installersSha256, hugoDataJsonFile)
3290   }
3291 }
3292
3293 task helppages {
3294   group "help"
3295   description "Copies all help pages to build dir. Runs ant task 'pubhtmlhelp'."
3296
3297   dependsOn copyHelp
3298   dependsOn pubhtmlhelp
3299   
3300   inputs.dir("${helpBuildDir}/${help_dir}")
3301   outputs.dir("${buildDir}/distributions/${help_dir}")
3302 }
3303
3304
3305 task j2sSetHeadlessBuild {
3306   doFirst {
3307     IN_ECLIPSE = false
3308   }
3309 }
3310
3311
3312 task jalviewjsEnableAltFileProperty(type: WriteProperties) {
3313   group "jalviewjs"
3314   description "Enable the alternative J2S Config file for headless build"
3315
3316   outputFile = jalviewjsJ2sSettingsFileName
3317   def j2sPropsFile = file(jalviewjsJ2sSettingsFileName)
3318   def j2sProps = new Properties()
3319   if (j2sPropsFile.exists()) {
3320     try {
3321       def j2sPropsFileFIS = new FileInputStream(j2sPropsFile)
3322       j2sProps.load(j2sPropsFileFIS)
3323       j2sPropsFileFIS.close()
3324
3325       j2sProps.each { prop, val ->
3326         property(prop, val)
3327       }
3328     } catch (Exception e) {
3329       println("Exception reading ${jalviewjsJ2sSettingsFileName}")
3330       e.printStackTrace()
3331     }
3332   }
3333   if (! j2sProps.stringPropertyNames().contains(jalviewjs_j2s_alt_file_property_config)) {
3334     property(jalviewjs_j2s_alt_file_property_config, jalviewjs_j2s_alt_file_property)
3335   }
3336 }
3337
3338
3339 task jalviewjsSetEclipseWorkspace {
3340   def propKey = "jalviewjs_eclipse_workspace"
3341   def propVal = null
3342   if (project.hasProperty(propKey)) {
3343     propVal = project.getProperty(propKey)
3344     if (propVal.startsWith("~/")) {
3345       propVal = System.getProperty("user.home") + propVal.substring(1)
3346     }
3347   }
3348   def propsFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_workspace_location_file}"
3349   def propsFile = file(propsFileName)
3350   def eclipseWsDir = propVal
3351   def props = new Properties()
3352
3353   def writeProps = true
3354   if (( eclipseWsDir == null || !file(eclipseWsDir).exists() ) && propsFile.exists()) {
3355     def ins = new FileInputStream(propsFileName)
3356     props.load(ins)
3357     ins.close()
3358     if (props.getProperty(propKey, null) != null) {
3359       eclipseWsDir = props.getProperty(propKey)
3360       writeProps = false
3361     }
3362   }
3363
3364   if (eclipseWsDir == null || !file(eclipseWsDir).exists()) {
3365     def tempDir = File.createTempDir()
3366     eclipseWsDir = tempDir.getAbsolutePath()
3367     writeProps = true
3368   }
3369   eclipseWorkspace = file(eclipseWsDir)
3370
3371   doFirst {
3372     // do not run a headless transpile when we claim to be in Eclipse
3373     if (IN_ECLIPSE) {
3374       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3375       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3376     } else {
3377       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3378     }
3379
3380     if (writeProps) {
3381       props.setProperty(propKey, eclipseWsDir)
3382       propsFile.parentFile.mkdirs()
3383       def bytes = new ByteArrayOutputStream()
3384       props.store(bytes, null)
3385       def propertiesString = bytes.toString()
3386       propsFile.text = propertiesString
3387       print("NEW ")
3388     } else {
3389       print("EXISTING ")
3390     }
3391
3392     println("ECLIPSE WORKSPACE: "+eclipseWorkspace.getPath())
3393   }
3394
3395   //inputs.property(propKey, eclipseWsDir) // eclipseWsDir only gets set once this task runs, so will be out-of-date
3396   outputs.file(propsFileName)
3397   outputs.upToDateWhen { eclipseWorkspace.exists() && propsFile.exists() }
3398 }
3399
3400
3401 task jalviewjsEclipsePaths {
3402   def eclipseProduct
3403
3404   def eclipseRoot = jalviewjs_eclipse_root
3405   if (eclipseRoot.startsWith("~/")) {
3406     eclipseRoot = System.getProperty("user.home") + eclipseRoot.substring(1)
3407   }
3408   if (OperatingSystem.current().isMacOsX()) {
3409     eclipseRoot += "/Eclipse.app"
3410     eclipseBinary = "${eclipseRoot}/Contents/MacOS/eclipse"
3411     eclipseProduct = "${eclipseRoot}/Contents/Eclipse/.eclipseproduct"
3412   } else if (OperatingSystem.current().isWindows()) { // check these paths!!
3413     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
3414       eclipseRoot += "/eclipse"
3415     }
3416     eclipseBinary = "${eclipseRoot}/eclipse.exe"
3417     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
3418   } else { // linux or unix
3419     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
3420       eclipseRoot += "/eclipse"
3421 println("eclipseDir exists")
3422     }
3423     eclipseBinary = "${eclipseRoot}/eclipse"
3424     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
3425   }
3426
3427   eclipseVersion = "4.13" // default
3428   def assumedVersion = true
3429   if (file(eclipseProduct).exists()) {
3430     def fis = new FileInputStream(eclipseProduct)
3431     def props = new Properties()
3432     props.load(fis)
3433     eclipseVersion = props.getProperty("version")
3434     fis.close()
3435     assumedVersion = false
3436   }
3437   
3438   def propKey = "eclipse_debug"
3439   eclipseDebug = (project.hasProperty(propKey) && project.getProperty(propKey).equals("true"))
3440
3441   doFirst {
3442     // do not run a headless transpile when we claim to be in Eclipse
3443     if (IN_ECLIPSE) {
3444       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3445       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3446     } else {
3447       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3448     }
3449
3450     if (!assumedVersion) {
3451       println("ECLIPSE VERSION=${eclipseVersion}")
3452     }
3453   }
3454 }
3455
3456
3457 task printProperties {
3458   group "Debug"
3459   description "Output to console all System.properties"
3460   doFirst {
3461     System.properties.each { key, val -> System.out.println("Property: ${key}=${val}") }
3462   }
3463 }
3464
3465
3466 task eclipseSetup {
3467   dependsOn eclipseProject
3468   dependsOn eclipseClasspath
3469   dependsOn eclipseJdt
3470 }
3471
3472
3473 // this version (type: Copy) will delete anything in the eclipse dropins folder that isn't in fromDropinsDir
3474 task jalviewjsEclipseCopyDropins(type: Copy) {
3475   dependsOn jalviewjsEclipsePaths
3476
3477   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_eclipse_dropins_dir}", include: "*.jar")
3478   inputFiles += file("${jalviewDir}/${jalviewjsJ2sPlugin}")
3479   def outputDir = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
3480
3481   from inputFiles
3482   into outputDir
3483 }
3484
3485
3486 // this eclipse -clean doesn't actually work
3487 task jalviewjsCleanEclipse(type: Exec) {
3488   dependsOn eclipseSetup
3489   dependsOn jalviewjsEclipsePaths
3490   dependsOn jalviewjsEclipseCopyDropins
3491
3492   executable(eclipseBinary)
3493   args(["-nosplash", "--launcher.suppressErrors", "-data", eclipseWorkspace.getPath(), "-clean", "-console", "-consoleLog"])
3494   if (eclipseDebug) {
3495     args += "-debug"
3496   }
3497   args += "-l"
3498
3499   def inputString = """exit
3500 y
3501 """
3502   def inputByteStream = new ByteArrayInputStream(inputString.getBytes())
3503   standardInput = inputByteStream
3504 }
3505
3506 /* not really working yet
3507 jalviewjsEclipseCopyDropins.finalizedBy jalviewjsCleanEclipse
3508 */
3509
3510
3511 task jalviewjsTransferUnzipSwingJs {
3512   def file_zip = "${jalviewDir}/${jalviewjs_swingjs_zip}"
3513
3514   doLast {
3515     copy {
3516       from zipTree(file_zip)
3517       into "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3518     }
3519   }
3520
3521   inputs.file file_zip
3522   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3523 }
3524
3525
3526 task jalviewjsTransferUnzipLib {
3527   def zipFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_libjs_dir}", include: "*.zip")
3528
3529   doLast {
3530     zipFiles.each { file_zip -> 
3531       copy {
3532         from zipTree(file_zip)
3533         into "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3534       }
3535     }
3536   }
3537
3538   inputs.files zipFiles
3539   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3540 }
3541
3542
3543 task jalviewjsTransferUnzipAllLibs {
3544   dependsOn jalviewjsTransferUnzipSwingJs
3545   dependsOn jalviewjsTransferUnzipLib
3546 }
3547
3548
3549 task jalviewjsCreateJ2sSettings(type: WriteProperties) {
3550   group "JalviewJS"
3551   description "Create the alternative j2s file from the j2s.* properties"
3552
3553   jalviewjsJ2sProps = project.properties.findAll { it.key.startsWith("j2s.") }.sort { it.key }
3554   def siteDirProperty = "j2s.site.directory"
3555   def setSiteDir = false
3556   jalviewjsJ2sProps.each { prop, val ->
3557     if (val != null) {
3558       if (prop == siteDirProperty) {
3559         if (!(val.startsWith('/') || val.startsWith("file://") )) {
3560           val = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${val}"
3561         }
3562         setSiteDir = true
3563       }
3564       property(prop,val)
3565     }
3566     if (!setSiteDir) { // default site location, don't override specifically set property
3567       property(siteDirProperty,"${jalviewDirRelativePath}/${jalviewjsTransferSiteJsDir}")
3568     }
3569   }
3570   outputFile = jalviewjsJ2sAltSettingsFileName
3571
3572   if (! IN_ECLIPSE) {
3573     inputs.properties(jalviewjsJ2sProps)
3574     outputs.file(jalviewjsJ2sAltSettingsFileName)
3575   }
3576 }
3577
3578
3579 task jalviewjsEclipseSetup {
3580   dependsOn jalviewjsEclipseCopyDropins
3581   dependsOn jalviewjsSetEclipseWorkspace
3582   dependsOn jalviewjsCreateJ2sSettings
3583 }
3584
3585
3586 task jalviewjsSyncAllLibs (type: Sync) {
3587   dependsOn jalviewjsTransferUnzipAllLibs
3588   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteLibDir}")
3589   inputFiles += fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
3590   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3591
3592   from inputFiles
3593   into outputDir
3594   def outputFiles = []
3595   rename { filename ->
3596     outputFiles += "${outputDir}/${filename}"
3597     null
3598   }
3599   preserve {
3600     include "**"
3601   }
3602
3603   // should this be exclude really ?
3604   duplicatesStrategy "INCLUDE"
3605
3606   outputs.files outputFiles
3607   inputs.files inputFiles
3608 }
3609
3610
3611 task jalviewjsSyncResources (type: Sync) {
3612   dependsOn buildResources
3613
3614   def inputFiles = fileTree(dir: resourcesBuildDir)
3615   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
3616
3617   from inputFiles
3618   into outputDir
3619   def outputFiles = []
3620   rename { filename ->
3621     outputFiles += "${outputDir}/${filename}"
3622     null
3623   }
3624   preserve {
3625     include "**"
3626   }
3627   outputs.files outputFiles
3628   inputs.files inputFiles
3629 }
3630
3631
3632 task jalviewjsSyncSiteResources (type: Sync) {
3633   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_site_resource_dir}")
3634   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3635
3636   from inputFiles
3637   into outputDir
3638   def outputFiles = []
3639   rename { filename ->
3640     outputFiles += "${outputDir}/${filename}"
3641     null
3642   }
3643   preserve {
3644     include "**"
3645   }
3646   outputs.files outputFiles
3647   inputs.files inputFiles
3648 }
3649
3650
3651 task jalviewjsSyncBuildProperties (type: Sync) {
3652   dependsOn createBuildProperties
3653   def inputFiles = [file(buildProperties)]
3654   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
3655
3656   from inputFiles
3657   into outputDir
3658   def outputFiles = []
3659   rename { filename ->
3660     outputFiles += "${outputDir}/${filename}"
3661     null
3662   }
3663   preserve {
3664     include "**"
3665   }
3666   outputs.files outputFiles
3667   inputs.files inputFiles
3668 }
3669
3670
3671 task jalviewjsProjectImport(type: Exec) {
3672   dependsOn eclipseSetup
3673   dependsOn jalviewjsEclipsePaths
3674   dependsOn jalviewjsEclipseSetup
3675
3676   doFirst {
3677     // do not run a headless import when we claim to be in Eclipse
3678     if (IN_ECLIPSE) {
3679       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3680       throw new StopExecutionException("Not running headless import whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3681     } else {
3682       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3683     }
3684   }
3685
3686   //def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview/org.eclipse.jdt.core"
3687   def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview"
3688   executable(eclipseBinary)
3689   args(["-nosplash", "--launcher.suppressErrors", "-application", "com.seeq.eclipse.importprojects.headlessimport", "-data", eclipseWorkspace.getPath(), "-import", jalviewDirAbsolutePath])
3690   if (eclipseDebug) {
3691     args += "-debug"
3692   }
3693   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
3694   if (!IN_ECLIPSE) {
3695     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
3696     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
3697   }
3698
3699   inputs.file("${jalviewDir}/.project")
3700   outputs.upToDateWhen { 
3701     file(projdir).exists()
3702   }
3703 }
3704
3705
3706 task jalviewjsTranspile(type: Exec) {
3707   dependsOn jalviewjsEclipseSetup 
3708   dependsOn jalviewjsProjectImport
3709   dependsOn jalviewjsEclipsePaths
3710   if (!IN_ECLIPSE) {
3711     dependsOn jalviewjsEnableAltFileProperty
3712   }
3713
3714   doFirst {
3715     // do not run a headless transpile when we claim to be in Eclipse
3716     if (IN_ECLIPSE) {
3717       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3718       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3719     } else {
3720       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3721     }
3722   }
3723
3724   executable(eclipseBinary)
3725   args(["-nosplash", "--launcher.suppressErrors", "-application", "org.eclipse.jdt.apt.core.aptBuild", "-data", eclipseWorkspace, "-${jalviewjs_eclipse_build_arg}", eclipse_project_name ])
3726   if (eclipseDebug) {
3727     args += "-debug"
3728   }
3729   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
3730   if (!IN_ECLIPSE) {
3731     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
3732     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
3733   }
3734
3735   def stdout
3736   def stderr
3737   doFirst {
3738     stdout = new ByteArrayOutputStream()
3739     stderr = new ByteArrayOutputStream()
3740
3741     def logOutFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}"
3742     def logOutFile = file(logOutFileName)
3743     logOutFile.createNewFile()
3744     logOutFile.text = """ROOT: ${jalviewjs_eclipse_root}
3745 BINARY: ${eclipseBinary}
3746 VERSION: ${eclipseVersion}
3747 WORKSPACE: ${eclipseWorkspace}
3748 DEBUG: ${eclipseDebug}
3749 ----
3750 """
3751     def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
3752     // combine stdout and stderr
3753     def logErrFOS = logOutFOS
3754
3755     if (jalviewjs_j2s_to_console.equals("true")) {
3756       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3757         new org.apache.tools.ant.util.TeeOutputStream(
3758           logOutFOS,
3759           stdout),
3760         System.out)
3761       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3762         new org.apache.tools.ant.util.TeeOutputStream(
3763           logErrFOS,
3764           stderr),
3765         System.err)
3766     } else {
3767       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3768         logOutFOS,
3769         stdout)
3770       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3771         logErrFOS,
3772         stderr)
3773     }
3774   }
3775
3776   doLast {
3777     if (stdout.toString().contains("Error processing ")) {
3778       // j2s did not complete transpile
3779       //throw new TaskExecutionException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3780       if (jalviewjs_ignore_transpile_errors.equals("true")) {
3781         println("IGNORING TRANSPILE ERRORS")
3782         println("See eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3783       } else {
3784         throw new GradleException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3785       }
3786     }
3787   }
3788
3789   inputs.dir("${jalviewDir}/${sourceDir}")
3790   outputs.dir("${jalviewDir}/${jalviewjsTransferSiteJsDir}")
3791   outputs.upToDateWhen( { file("${jalviewDir}/${jalviewjsTransferSiteJsDir}${jalviewjs_server_resource}").exists() } )
3792 }
3793
3794
3795 def jalviewjsCallCore(String name, FileCollection list, String prefixFile, String suffixFile, String jsfile, String zjsfile, File logOutFile, Boolean logOutConsole) {
3796
3797   def stdout = new ByteArrayOutputStream()
3798   def stderr = new ByteArrayOutputStream()
3799
3800   def coreFile = file(jsfile)
3801   def msg = ""
3802   msg = "Creating core for ${name}...\nGenerating ${jsfile}"
3803   println(msg)
3804   logOutFile.createNewFile()
3805   logOutFile.append(msg+"\n")
3806
3807   def coreTop = file(prefixFile)
3808   def coreBottom = file(suffixFile)
3809   coreFile.getParentFile().mkdirs()
3810   coreFile.createNewFile()
3811   coreFile.write( coreTop.getText("UTF-8") )
3812   list.each {
3813     f ->
3814     if (f.exists()) {
3815       def t = f.getText("UTF-8")
3816       t.replaceAll("Clazz\\.([^_])","Clazz_${1}")
3817       coreFile.append( t )
3818     } else {
3819       msg = "...file '"+f.getPath()+"' does not exist, skipping"
3820       println(msg)
3821       logOutFile.append(msg+"\n")
3822     }
3823   }
3824   coreFile.append( coreBottom.getText("UTF-8") )
3825
3826   msg = "Generating ${zjsfile}"
3827   println(msg)
3828   logOutFile.append(msg+"\n")
3829   def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
3830   def logErrFOS = logOutFOS
3831
3832   javaexec {
3833     classpath = files(["${jalviewDir}/${jalviewjs_closure_compiler}"])
3834     main = "com.google.javascript.jscomp.CommandLineRunner"
3835     jvmArgs = [ "-Dfile.encoding=UTF-8" ]
3836     args = [ "--compilation_level", "SIMPLE_OPTIMIZATIONS", "--warning_level", "QUIET", "--charset", "UTF-8", "--js", jsfile, "--js_output_file", zjsfile ]
3837     maxHeapSize = "2g"
3838
3839     msg = "\nRunning '"+commandLine.join(' ')+"'\n"
3840     println(msg)
3841     logOutFile.append(msg+"\n")
3842
3843     if (logOutConsole) {
3844       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3845         new org.apache.tools.ant.util.TeeOutputStream(
3846           logOutFOS,
3847           stdout),
3848         standardOutput)
3849         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3850           new org.apache.tools.ant.util.TeeOutputStream(
3851             logErrFOS,
3852             stderr),
3853           System.err)
3854     } else {
3855       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3856         logOutFOS,
3857         stdout)
3858         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3859           logErrFOS,
3860           stderr)
3861     }
3862   }
3863   msg = "--"
3864   println(msg)
3865   logOutFile.append(msg+"\n")
3866 }
3867
3868
3869 task jalviewjsBuildAllCores {
3870   group "JalviewJS"
3871   description "Build the core js lib closures listed in the classlists dir"
3872   dependsOn jalviewjsTranspile
3873   dependsOn jalviewjsTransferUnzipSwingJs
3874
3875   def j2sDir = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${jalviewjs_j2s_subdir}"
3876   def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_j2s_subdir}"
3877   def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteLibDir}/${jalviewjs_j2s_subdir}"
3878   def jsDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_js_subdir}"
3879   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
3880   def prefixFile = "${jsDir}/core/coretop2.js"
3881   def suffixFile = "${jsDir}/core/corebottom2.js"
3882
3883   inputs.file prefixFile
3884   inputs.file suffixFile
3885
3886   def classlistFiles = []
3887   // add the classlists found int the jalviewjs_classlists_dir
3888   fileTree(dir: "${jalviewDir}/${jalviewjs_classlists_dir}", include: "*.txt").each {
3889     file ->
3890     def name = file.getName() - ".txt"
3891     classlistFiles += [
3892       'file': file,
3893       'name': name
3894     ]
3895   }
3896
3897   // _jmol and _jalview cores. Add any other peculiar classlist.txt files here
3898   //classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jmol}"), 'name': "_jvjmol" ]
3899   classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jalview}"), 'name': jalviewjsJalviewCoreName ]
3900
3901   jalviewjsCoreClasslists = []
3902
3903   classlistFiles.each {
3904     hash ->
3905
3906     def file = hash['file']
3907     if (! file.exists()) {
3908       //println("...classlist file '"+file.getPath()+"' does not exist, skipping")
3909       return false // this is a "continue" in groovy .each closure
3910     }
3911     def name = hash['name']
3912     if (name == null) {
3913       name = file.getName() - ".txt"
3914     }
3915
3916     def filelist = []
3917     file.eachLine {
3918       line ->
3919         filelist += line
3920     }
3921     def list = fileTree(dir: j2sDir, includes: filelist)
3922
3923     def jsfile = "${outputDir}/core${name}.js"
3924     def zjsfile = "${outputDir}/core${name}.z.js"
3925
3926     jalviewjsCoreClasslists += [
3927       'jsfile': jsfile,
3928       'zjsfile': zjsfile,
3929       'list': list,
3930       'name': name
3931     ]
3932
3933     inputs.file(file)
3934     inputs.files(list)
3935     outputs.file(jsfile)
3936     outputs.file(zjsfile)
3937   }
3938   
3939   // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
3940   def stevesoftClasslistName = "_stevesoft"
3941   def stevesoftClasslist = [
3942     'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
3943     'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
3944     'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
3945     'name': stevesoftClasslistName
3946   ]
3947   jalviewjsCoreClasslists += stevesoftClasslist
3948   inputs.files(stevesoftClasslist['list'])
3949   outputs.file(stevesoftClasslist['jsfile'])
3950   outputs.file(stevesoftClasslist['zjsfile'])
3951
3952   // _all core
3953   def allClasslistName = "_all"
3954   def allJsFiles = fileTree(dir: j2sDir, include: "**/*.js")
3955   allJsFiles += fileTree(
3956     dir: libJ2sDir,
3957     include: "**/*.js",
3958     excludes: [
3959       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3960       "**/org/jmol/jvxl/readers/IsoIntersectFileReader.js",
3961       "**/org/jmol/export/JSExporter.js"
3962     ]
3963   )
3964   allJsFiles += fileTree(
3965     dir: swingJ2sDir,
3966     include: "**/*.js",
3967     excludes: [
3968       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3969       "**/sun/misc/Unsafe.js",
3970       "**/swingjs/jquery/jquery-editable-select.js",
3971       "**/swingjs/jquery/j2sComboBox.js",
3972       "**/sun/misc/FloatingDecimal.js"
3973     ]
3974   )
3975   def allClasslist = [
3976     'jsfile': "${outputDir}/core${allClasslistName}.js",
3977     'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
3978     'list': allJsFiles,
3979     'name': allClasslistName
3980   ]
3981   // not including this version of "all" core at the moment
3982   //jalviewjsCoreClasslists += allClasslist
3983   inputs.files(allClasslist['list'])
3984   outputs.file(allClasslist['jsfile'])
3985   outputs.file(allClasslist['zjsfile'])
3986
3987   doFirst {
3988     def logOutFile = file("${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_closure_stdout}")
3989     logOutFile.getParentFile().mkdirs()
3990     logOutFile.createNewFile()
3991     logOutFile.write(getDate("yyyy-MM-dd HH:mm:ss")+" jalviewjsBuildAllCores\n----\n")
3992
3993     jalviewjsCoreClasslists.each {
3994       jalviewjsCallCore(it.name, it.list, prefixFile, suffixFile, it.jsfile, it.zjsfile, logOutFile, jalviewjs_j2s_to_console.equals("true"))
3995     }
3996   }
3997
3998 }
3999
4000
4001 def jalviewjsPublishCoreTemplate(String coreName, String templateName, File inputFile, String outputFile) {
4002   copy {
4003     from inputFile
4004     into file(outputFile).getParentFile()
4005     rename { filename ->
4006       if (filename.equals(inputFile.getName())) {
4007         return file(outputFile).getName()
4008       }
4009       return null
4010     }
4011     filter(ReplaceTokens,
4012       beginToken: '_',
4013       endToken: '_',
4014       tokens: [
4015         'MAIN': '"'+main_class+'"',
4016         'CODE': "null",
4017         'NAME': jalviewjsJalviewTemplateName+" [core ${coreName}]",
4018         'COREKEY': jalviewjs_core_key,
4019         'CORENAME': coreName
4020       ]
4021     )
4022   }
4023 }
4024
4025
4026 task jalviewjsPublishCoreTemplates {
4027   dependsOn jalviewjsBuildAllCores
4028   def inputFileName = "${jalviewDir}/${j2s_coretemplate_html}"
4029   def inputFile = file(inputFileName)
4030   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
4031
4032   def outputFiles = []
4033   jalviewjsCoreClasslists.each { cl ->
4034     def outputFile = "${outputDir}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
4035     cl['outputfile'] = outputFile
4036     outputFiles += outputFile
4037   }
4038
4039   doFirst {
4040     jalviewjsCoreClasslists.each { cl ->
4041       jalviewjsPublishCoreTemplate(cl.name, jalviewjsJalviewTemplateName, inputFile, cl.outputfile)
4042     }
4043   }
4044   inputs.file(inputFile)
4045   outputs.files(outputFiles)
4046 }
4047
4048
4049 task jalviewjsSyncCore (type: Sync) {
4050   dependsOn jalviewjsBuildAllCores
4051   dependsOn jalviewjsPublishCoreTemplates
4052   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
4053   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
4054
4055   from inputFiles
4056   into outputDir
4057   def outputFiles = []
4058   rename { filename ->
4059     outputFiles += "${outputDir}/${filename}"
4060     null
4061   }
4062   preserve {
4063     include "**"
4064   }
4065   outputs.files outputFiles
4066   inputs.files inputFiles
4067 }
4068
4069
4070 // this Copy version of TransferSiteJs will delete anything else in the target dir
4071 task jalviewjsCopyTransferSiteJs(type: Copy) {
4072   dependsOn jalviewjsTranspile
4073   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
4074   into "${jalviewDir}/${jalviewjsSiteDir}"
4075 }
4076
4077
4078 // this Sync version of TransferSite is used by buildship to keep the website automatically up to date when a file changes
4079 task jalviewjsSyncTransferSiteJs(type: Sync) {
4080   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
4081   include "**/*.*"
4082   into "${jalviewDir}/${jalviewjsSiteDir}"
4083   preserve {
4084     include "**"
4085   }
4086 }
4087
4088
4089 jalviewjsSyncAllLibs.mustRunAfter jalviewjsCopyTransferSiteJs
4090 jalviewjsSyncResources.mustRunAfter jalviewjsCopyTransferSiteJs
4091 jalviewjsSyncSiteResources.mustRunAfter jalviewjsCopyTransferSiteJs
4092 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsCopyTransferSiteJs
4093
4094 jalviewjsSyncAllLibs.mustRunAfter jalviewjsSyncTransferSiteJs
4095 jalviewjsSyncResources.mustRunAfter jalviewjsSyncTransferSiteJs
4096 jalviewjsSyncSiteResources.mustRunAfter jalviewjsSyncTransferSiteJs
4097 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsSyncTransferSiteJs
4098
4099
4100 task jalviewjsPrepareSite {
4101   group "JalviewJS"
4102   description "Prepares the website folder including unzipping files and copying resources"
4103   dependsOn jalviewjsSyncAllLibs
4104   dependsOn jalviewjsSyncResources
4105   dependsOn jalviewjsSyncSiteResources
4106   dependsOn jalviewjsSyncBuildProperties
4107   dependsOn jalviewjsSyncCore
4108 }
4109
4110
4111 task jalviewjsBuildSite {
4112   group "JalviewJS"
4113   description "Builds the whole website including transpiled code"
4114   dependsOn jalviewjsCopyTransferSiteJs
4115   dependsOn jalviewjsPrepareSite
4116 }
4117
4118
4119 task cleanJalviewjsTransferSite {
4120   doFirst {
4121     delete "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
4122     delete "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
4123     delete "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
4124     delete "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
4125   }
4126 }
4127
4128
4129 task cleanJalviewjsSite {
4130   dependsOn cleanJalviewjsTransferSite
4131   doFirst {
4132     delete "${jalviewDir}/${jalviewjsSiteDir}"
4133   }
4134 }
4135
4136
4137 task jalviewjsSiteTar(type: Tar) {
4138   group "JalviewJS"
4139   description "Creates a tar.gz file for the website"
4140   dependsOn jalviewjsBuildSite
4141   def outputFilename = "jalviewjs-site-${JALVIEW_VERSION}.tar.gz"
4142   archiveFileName = outputFilename
4143
4144   compression Compression.GZIP
4145
4146   from "${jalviewDir}/${jalviewjsSiteDir}"
4147   into jalviewjs_site_dir // this is inside the tar file
4148
4149   inputs.dir("${jalviewDir}/${jalviewjsSiteDir}")
4150 }
4151
4152
4153 task jalviewjsServer {
4154   group "JalviewJS"
4155   def filename = "jalviewjsTest.html"
4156   description "Starts a webserver on localhost to test the website. See ${filename} to access local site on most recently used port."
4157   def htmlFile = "${jalviewDirAbsolutePath}/${filename}"
4158   doLast {
4159
4160     def factory
4161     try {
4162       def f = Class.forName("org.gradle.plugins.javascript.envjs.http.simple.SimpleHttpFileServerFactory")
4163       factory = f.newInstance()
4164     } catch (ClassNotFoundException e) {
4165       throw new GradleException("Unable to create SimpleHttpFileServerFactory")
4166     }
4167     def port = Integer.valueOf(jalviewjs_server_port)
4168     def start = port
4169     def running = false
4170     def url
4171     def jalviewjsServer
4172     while(port < start+1000 && !running) {
4173       try {
4174         def doc_root = new File("${jalviewDirAbsolutePath}/${jalviewjsSiteDir}")
4175         jalviewjsServer = factory.start(doc_root, port)
4176         running = true
4177         url = jalviewjsServer.getResourceUrl(jalviewjs_server_resource)
4178         println("SERVER STARTED with document root ${doc_root}.")
4179         println("Go to "+url+" . Run  gradle --stop  to stop (kills all gradle daemons).")
4180         println("For debug: "+url+"?j2sdebug")
4181         println("For verbose: "+url+"?j2sverbose")
4182       } catch (Exception e) {
4183         port++;
4184       }
4185     }
4186     def htmlText = """
4187       <p><a href="${url}">JalviewJS Test. &lt;${url}&gt;</a></p>
4188       <p><a href="${url}?j2sdebug">JalviewJS Test with debug. &lt;${url}?j2sdebug&gt;</a></p>
4189       <p><a href="${url}?j2sverbose">JalviewJS Test with verbose. &lt;${url}?j2sdebug&gt;</a></p>
4190       """
4191     jalviewjsCoreClasslists.each { cl ->
4192       def urlcore = jalviewjsServer.getResourceUrl(file(cl.outputfile).getName())
4193       htmlText += """
4194       <p><a href="${urlcore}">${jalviewjsJalviewTemplateName} [core ${cl.name}]. &lt;${urlcore}&gt;</a></p>
4195       """
4196       println("For core ${cl.name}: "+urlcore)
4197     }
4198
4199     file(htmlFile).text = htmlText
4200   }
4201
4202   outputs.file(htmlFile)
4203   outputs.upToDateWhen({false})
4204 }
4205
4206
4207 task cleanJalviewjsAll {
4208   group "JalviewJS"
4209   description "Delete all configuration and build artifacts to do with JalviewJS build"
4210   dependsOn cleanJalviewjsSite
4211   dependsOn jalviewjsEclipsePaths
4212   
4213   doFirst {
4214     delete "${jalviewDir}/${jalviewjsBuildDir}"
4215     delete "${jalviewDir}/${eclipse_bin_dir}"
4216     if (eclipseWorkspace != null && file(eclipseWorkspace.getAbsolutePath()+"/.metadata").exists()) {
4217       delete file(eclipseWorkspace.getAbsolutePath()+"/.metadata")
4218     }
4219     delete jalviewjsJ2sAltSettingsFileName
4220   }
4221
4222   outputs.upToDateWhen( { false } )
4223 }
4224
4225
4226 task jalviewjsIDE_checkJ2sPlugin {
4227   group "00 JalviewJS in Eclipse"
4228   description "Compare the swingjs/net.sf.j2s.core(-j11)?.jar file with the Eclipse IDE's plugin version (found in the 'dropins' dir)"
4229
4230   doFirst {
4231     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
4232     def j2sPluginFile = file(j2sPlugin)
4233     def eclipseHome = System.properties["eclipse.home.location"]
4234     if (eclipseHome == null || ! IN_ECLIPSE) {
4235       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. Skipping J2S Plugin Check.")
4236     }
4237     def eclipseJ2sPluginDirs = [ "${eclipseHome}/dropins" ]
4238     def altPluginsDir = System.properties["org.eclipse.equinox.p2.reconciler.dropins.directory"]
4239     if (altPluginsDir != null && file(altPluginsDir).exists()) {
4240       eclipseJ2sPluginDirs += altPluginsDir
4241     }
4242     def foundPlugin = false
4243     def j2sPluginFileName = j2sPluginFile.getName()
4244     def eclipseJ2sPlugin
4245     def eclipseJ2sPluginFile
4246     eclipseJ2sPluginDirs.any { dir ->
4247       eclipseJ2sPlugin = "${dir}/${j2sPluginFileName}"
4248       eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
4249       if (eclipseJ2sPluginFile.exists()) {
4250         foundPlugin = true
4251         return true
4252       }
4253     }
4254     if (!foundPlugin) {
4255       def msg = "Eclipse J2S Plugin is not installed (could not find '${j2sPluginFileName}' in\n"+eclipseJ2sPluginDirs.join("\n")+"\n)\nTry running task jalviewjsIDE_copyJ2sPlugin"
4256       System.err.println(msg)
4257       throw new StopExecutionException(msg)
4258     }
4259
4260     def digest = MessageDigest.getInstance("MD5")
4261
4262     digest.update(j2sPluginFile.text.bytes)
4263     def j2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
4264
4265     digest.update(eclipseJ2sPluginFile.text.bytes)
4266     def eclipseJ2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
4267      
4268     if (j2sPluginMd5 != eclipseJ2sPluginMd5) {
4269       def msg = "WARNING! Eclipse J2S Plugin '${eclipseJ2sPlugin}' is different to this commit's version '${j2sPlugin}'"
4270       System.err.println(msg)
4271       throw new StopExecutionException(msg)
4272     } else {
4273       def msg = "Eclipse J2S Plugin '${eclipseJ2sPlugin}' is the same as '${j2sPlugin}' (this is good)"
4274       println(msg)
4275     }
4276   }
4277 }
4278
4279 task jalviewjsIDE_copyJ2sPlugin {
4280   group "00 JalviewJS in Eclipse"
4281   description "Copy the swingjs/net.sf.j2s.core(-j11)?.jar file into the Eclipse IDE's 'dropins' dir"
4282
4283   doFirst {
4284     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
4285     def j2sPluginFile = file(j2sPlugin)
4286     def eclipseHome = System.properties["eclipse.home.location"]
4287     if (eclipseHome == null || ! IN_ECLIPSE) {
4288       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. NOT copying J2S Plugin.")
4289     }
4290     def eclipseJ2sPlugin = "${eclipseHome}/dropins/${j2sPluginFile.getName()}"
4291     def eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
4292     def msg = "WARNING! Copying this commit's j2s plugin '${j2sPlugin}' to Eclipse J2S Plugin '${eclipseJ2sPlugin}'\n* May require an Eclipse restart"
4293     System.err.println(msg)
4294     copy {
4295       from j2sPlugin
4296       eclipseJ2sPluginFile.getParentFile().mkdirs()
4297       into eclipseJ2sPluginFile.getParent()
4298     }
4299   }
4300 }
4301
4302
4303 task jalviewjsIDE_j2sFile {
4304   group "00 JalviewJS in Eclipse"
4305   description "Creates the .j2s file"
4306   dependsOn jalviewjsCreateJ2sSettings
4307 }
4308
4309
4310 task jalviewjsIDE_SyncCore {
4311   group "00 JalviewJS in Eclipse"
4312   description "Build the core js lib closures listed in the classlists dir and publish core html from template"
4313   dependsOn jalviewjsSyncCore
4314 }
4315
4316
4317 task jalviewjsIDE_SyncSiteAll {
4318   dependsOn jalviewjsSyncAllLibs
4319   dependsOn jalviewjsSyncResources
4320   dependsOn jalviewjsSyncSiteResources
4321   dependsOn jalviewjsSyncBuildProperties
4322 }
4323
4324
4325 cleanJalviewjsTransferSite.mustRunAfter jalviewjsIDE_SyncSiteAll
4326
4327
4328 task jalviewjsIDE_PrepareSite {
4329   group "00 JalviewJS in Eclipse"
4330   description "Sync libs and resources to site dir, but not closure cores"
4331
4332   dependsOn jalviewjsIDE_SyncSiteAll
4333   //dependsOn cleanJalviewjsTransferSite // not sure why this clean is here -- will slow down a re-run of this task
4334 }
4335
4336
4337 task jalviewjsIDE_AssembleSite {
4338   group "00 JalviewJS in Eclipse"
4339   description "Assembles unzipped supporting zipfiles, resources, site resources and closure cores into the Eclipse transpiled site"
4340   dependsOn jalviewjsPrepareSite
4341 }
4342
4343
4344 task jalviewjsIDE_SiteClean {
4345   group "00 JalviewJS in Eclipse"
4346   description "Deletes the Eclipse transpiled site"
4347   dependsOn cleanJalviewjsSite
4348 }
4349
4350
4351 task jalviewjsIDE_Server {
4352   group "00 JalviewJS in Eclipse"
4353   description "Starts a webserver on localhost to test the website"
4354   dependsOn jalviewjsServer
4355 }
4356
4357
4358 // buildship runs this at import or gradle refresh
4359 task eclipseSynchronizationTask {
4360   //dependsOn eclipseSetup
4361   dependsOn createBuildProperties
4362   if (J2S_ENABLED) {
4363     dependsOn jalviewjsIDE_j2sFile
4364     dependsOn jalviewjsIDE_checkJ2sPlugin
4365     dependsOn jalviewjsIDE_PrepareSite
4366   }
4367 }
4368
4369
4370 // buildship runs this at build time or project refresh
4371 task eclipseAutoBuildTask {
4372   //dependsOn jalviewjsIDE_checkJ2sPlugin
4373   //dependsOn jalviewjsIDE_PrepareSite
4374 }
4375
4376
4377 task jalviewjsCopyStderrLaunchFile(type: Copy) {
4378   from file(jalviewjs_stderr_launch)
4379   into jalviewjsSiteDir
4380
4381   inputs.file jalviewjs_stderr_launch
4382   outputs.file jalviewjsStderrLaunchFilename
4383 }
4384
4385 task cleanJalviewjsChromiumUserDir {
4386   doFirst {
4387     delete jalviewjsChromiumUserDir
4388   }
4389   outputs.dir jalviewjsChromiumUserDir
4390   // always run when depended on
4391   outputs.upToDateWhen { !file(jalviewjsChromiumUserDir).exists() }
4392 }
4393
4394 task jalviewjsChromiumProfile {
4395   dependsOn cleanJalviewjsChromiumUserDir
4396   mustRunAfter cleanJalviewjsChromiumUserDir
4397
4398   def firstRun = file("${jalviewjsChromiumUserDir}/First Run")
4399
4400   doFirst {
4401     mkdir jalviewjsChromiumProfileDir
4402     firstRun.text = ""
4403   }
4404   outputs.file firstRun
4405 }
4406
4407 task jalviewjsLaunchTest {
4408   group "Test"
4409   description "Check JalviewJS opens in a browser"
4410   dependsOn jalviewjsBuildSite
4411   dependsOn jalviewjsCopyStderrLaunchFile
4412   dependsOn jalviewjsChromiumProfile
4413
4414   def macOS = OperatingSystem.current().isMacOsX()
4415   def chromiumBinary = macOS ? jalviewjs_macos_chromium_binary : jalviewjs_chromium_binary
4416   if (chromiumBinary.startsWith("~/")) {
4417     chromiumBinary = System.getProperty("user.home") + chromiumBinary.substring(1)
4418   }
4419   
4420   def stdout
4421   def stderr
4422   doFirst {
4423     def timeoutms = Integer.valueOf(jalviewjs_chromium_overall_timeout) * 1000
4424     
4425     def binary = file(chromiumBinary)
4426     if (!binary.exists()) {
4427       throw new StopExecutionException("Could not find chromium binary '${chromiumBinary}'. Cannot run task ${name}.")
4428     }
4429     stdout = new ByteArrayOutputStream()
4430     stderr = new ByteArrayOutputStream()
4431     def execStdout
4432     def execStderr
4433     if (jalviewjs_j2s_to_console.equals("true")) {
4434       execStdout = new org.apache.tools.ant.util.TeeOutputStream(
4435         stdout,
4436         System.out)
4437       execStderr = new org.apache.tools.ant.util.TeeOutputStream(
4438         stderr,
4439         System.err)
4440     } else {
4441       execStdout = stdout
4442       execStderr = stderr
4443     }
4444     def execArgs = [
4445       "--no-sandbox", // --no-sandbox IS USED BY THE THORIUM APPIMAGE ON THE BUILDSERVER
4446       "--headless=new",
4447       "--disable-gpu",
4448       "--timeout=${timeoutms}",
4449       "--virtual-time-budget=${timeoutms}",
4450       "--user-data-dir=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_chromium_user_dir}",
4451       "--profile-directory=${jalviewjs_chromium_profile_name}",
4452       "--allow-file-access-from-files",
4453       "--enable-logging=stderr",
4454       "file://${jalviewDirAbsolutePath}/${jalviewjsStderrLaunchFilename}"
4455     ]
4456     
4457     if (true || macOS) {
4458       ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
4459       Future f1 = executor.submit(
4460         () -> {
4461           exec {
4462             standardOutput = execStdout
4463             errorOutput = execStderr
4464             executable(chromiumBinary)
4465             args(execArgs)
4466             println "COMMAND: '"+commandLine.join(" ")+"'"
4467           }
4468           executor.shutdownNow()
4469         }
4470       )
4471
4472       def noChangeBytes = 0
4473       def noChangeIterations = 0
4474       executor.scheduleAtFixedRate(
4475         () -> {
4476           String stderrString = stderr.toString()
4477           // shutdown the task if we have a success string
4478           if (stderrString.contains(jalviewjs_desktop_init_string)) {
4479             f1.cancel()
4480             Thread.sleep(1000)
4481             executor.shutdownNow()
4482           }
4483           // if no change in stderr for 10s then also end
4484           if (noChangeIterations >= jalviewjs_chromium_idle_timeout) {
4485             executor.shutdownNow()
4486           }
4487           if (stderrString.length() == noChangeBytes) {
4488             noChangeIterations++
4489           } else {
4490             noChangeBytes = stderrString.length()
4491             noChangeIterations = 0
4492           }
4493         },
4494         1, 1, TimeUnit.SECONDS)
4495
4496       executor.schedule(new Runnable(){
4497         public void run(){
4498           f1.cancel()
4499           executor.shutdownNow()
4500         }
4501       }, timeoutms, TimeUnit.MILLISECONDS)
4502
4503       executor.awaitTermination(timeoutms+10000, TimeUnit.MILLISECONDS)
4504       executor.shutdownNow()
4505     }
4506
4507   }
4508   
4509   doLast {
4510     def found = false
4511     stderr.toString().eachLine { line ->
4512       if (line.contains(jalviewjs_desktop_init_string)) {
4513         println("Found line '"+line+"'")
4514         found = true
4515         return
4516       }
4517     }
4518     if (!found) {
4519       throw new GradleException("Could not find evidence of Desktop launch in JalviewJS.")
4520     }
4521   }
4522 }
4523   
4524
4525 task jalviewjs {
4526   group "JalviewJS"
4527   description "Build the JalviewJS site and run the launch test"
4528   dependsOn jalviewjsBuildSite
4529   dependsOn jalviewjsLaunchTest
4530 }