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