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