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