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