JAL-629 failFast and new threads for each test
[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.0' // 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 //run.dependsOn prepare
1736
1737
1738 //testReportDirName = "test-reports" // note that test workingDir will be $jalviewDir
1739 test {
1740   dependsOn prepare
1741
1742   if (useClover) {
1743     dependsOn cloverClasses
1744    } else { //?
1745     dependsOn compileJava //?
1746   }
1747
1748   useTestNG() {
1749     includeGroups testng_groups
1750     excludeGroups testng_excluded_groups
1751     preserveOrder true
1752     useDefaultListeners=true
1753   }
1754
1755   maxHeapSize = "1024m"
1756
1757 /* delete these! */
1758   maxParallelForks = 1
1759   forkEvery = 1
1760   failFast = true
1761
1762   workingDir = jalviewDir
1763   def testLaf = project.findProperty("test_laf")
1764   if (testLaf != null) {
1765     println("Setting Test LaF to '${testLaf}'")
1766     systemProperty "laf", testLaf
1767   }
1768   def testHiDPIScale = project.findProperty("test_HiDPIScale")
1769   if (testHiDPIScale != null) {
1770     println("Setting Test HiDPI Scale to '${testHiDPIScale}'")
1771     systemProperty "sun.java2d.uiScale", testHiDPIScale
1772   }
1773   sourceCompatibility = compile_source_compatibility
1774   targetCompatibility = compile_target_compatibility
1775   jvmArgs += additional_compiler_args
1776
1777   doFirst {
1778     if (useClover) {
1779       println("Running tests " + (useClover?"WITH":"WITHOUT") + " clover")
1780     }
1781   }
1782 }
1783
1784
1785 task compileLinkCheck(type: JavaCompile) {
1786   options.fork = true
1787   classpath = files("${jalviewDir}/${utils_dir}")
1788   destinationDir = file("${jalviewDir}/${utils_dir}")
1789   source = fileTree(dir: "${jalviewDir}/${utils_dir}", include: ["HelpLinksChecker.java", "BufferedLineReader.java"])
1790
1791   inputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.java")
1792   inputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.java")
1793   outputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.class")
1794   outputs.file("${jalviewDir}/${utils_dir}/BufferedLineReader.class")
1795 }
1796
1797
1798 task linkCheck(type: JavaExec) {
1799   dependsOn prepare
1800   dependsOn compileLinkCheck
1801
1802   def helpLinksCheckerOutFile = file("${jalviewDir}/${utils_dir}/HelpLinksChecker.out")
1803   classpath = files("${jalviewDir}/${utils_dir}")
1804   main = "HelpLinksChecker"
1805   workingDir = "${helpBuildDir}"
1806   args = [ "${helpBuildDir}/${help_dir}", "-nointernet" ]
1807
1808   def outFOS = new FileOutputStream(helpLinksCheckerOutFile, false) // false == don't append
1809   standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
1810     outFOS,
1811     System.out)
1812   errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
1813     outFOS,
1814     System.err)
1815
1816   inputs.dir(helpBuildDir)
1817   outputs.file(helpLinksCheckerOutFile)
1818 }
1819
1820
1821 // import the pubhtmlhelp target
1822 ant.properties.basedir = "${jalviewDir}"
1823 ant.properties.helpBuildDir = "${helpBuildDir}/${help_dir}"
1824 ant.importBuild "${utils_dir}/publishHelp.xml"
1825
1826
1827 task cleanPackageDir(type: Delete) {
1828   doFirst {
1829     delete fileTree(dir: "${jalviewDir}/${package_dir}", include: "*.jar")
1830   }
1831 }
1832
1833
1834 jar {
1835   dependsOn prepare
1836   dependsOn linkCheck
1837
1838   manifest {
1839     attributes "Main-Class": main_class,
1840     "Permissions": "all-permissions",
1841     "Application-Name": applicationName,
1842     "Codebase": application_codebase,
1843     "Implementation-Version": JALVIEW_VERSION
1844   }
1845
1846   def outputDir = "${jalviewDir}/${package_dir}"
1847   destinationDirectory = file(outputDir)
1848   archiveFileName = rootProject.name+".jar"
1849   duplicatesStrategy "EXCLUDE"
1850
1851
1852   exclude "cache*/**"
1853   exclude "*.jar"
1854   exclude "*.jar.*"
1855   exclude "**/*.jar"
1856   exclude "**/*.jar.*"
1857
1858   inputs.dir(sourceSets.main.java.outputDir)
1859   sourceSets.main.resources.srcDirs.each{ dir ->
1860     inputs.dir(dir)
1861   }
1862   outputs.file("${outputDir}/${archiveFileName}")
1863 }
1864
1865
1866 task copyJars(type: Copy) {
1867   from fileTree(dir: classesDir, include: "**/*.jar").files
1868   into "${jalviewDir}/${package_dir}"
1869 }
1870
1871
1872 // doing a Sync instead of Copy as Copy doesn't deal with "outputs" very well
1873 task syncJars(type: Sync) {
1874   dependsOn jar
1875   from fileTree(dir: "${jalviewDir}/${libDistDir}", include: "**/*.jar").files
1876   into "${jalviewDir}/${package_dir}"
1877   preserve {
1878     include jar.archiveFileName.getOrNull()
1879   }
1880 }
1881
1882
1883 task makeDist {
1884   group = "build"
1885   description = "Put all required libraries in dist"
1886   // order of "cleanPackageDir", "copyJars", "jar" important!
1887   jar.mustRunAfter cleanPackageDir
1888   syncJars.mustRunAfter cleanPackageDir
1889   dependsOn cleanPackageDir
1890   dependsOn syncJars
1891   dependsOn jar
1892   outputs.dir("${jalviewDir}/${package_dir}")
1893 }
1894
1895
1896 task cleanDist {
1897   dependsOn cleanPackageDir
1898   dependsOn cleanTest
1899   dependsOn clean
1900 }
1901
1902
1903 shadowJar {
1904   group = "distribution"
1905   description = "Create a single jar file with all dependency libraries merged. Can be run with java -jar"
1906   if (buildDist) {
1907     dependsOn makeDist
1908   }
1909   from ("${jalviewDir}/${libDistDir}") {
1910     include("*.jar")
1911   }
1912   manifest {
1913     attributes "Implementation-Version": JALVIEW_VERSION,
1914     "Application-Name": applicationName
1915   }
1916
1917   duplicatesStrategy "INCLUDE"
1918
1919   mainClassName = shadow_jar_main_class
1920   mergeServiceFiles()
1921   classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
1922   minimize()
1923 }
1924
1925 task getdownImagesCopy() {
1926   inputs.dir getdownImagesDir
1927   outputs.dir getdownImagesBuildDir
1928
1929   doFirst {
1930     copy {
1931       from(getdownImagesDir) {
1932         include("*getdown*.png")
1933       }
1934       into getdownImagesBuildDir
1935     }
1936   }
1937 }
1938
1939 task getdownImagesProcess() {
1940   dependsOn getdownImagesCopy
1941
1942   doFirst {
1943     if (backgroundImageText) {
1944       if (convertBinary == null) {
1945         throw new StopExecutionException("No ImageMagick convert binary installed at '${convertBinaryExpectedLocation}'")
1946       }
1947       if (!project.hasProperty("getdown_background_image_text_suffix_cmd")) {
1948         throw new StopExecutionException("No property 'getdown_background_image_text_suffix_cmd' defined. See channel_gradle.properties for channel ${CHANNEL}")
1949       }
1950       fileTree(dir: getdownImagesBuildDir, include: "*background*.png").getFiles().each { file ->
1951         exec {
1952           executable convertBinary
1953           args = [
1954             file.getPath(),
1955             '-font', getdown_background_image_text_font,
1956             '-fill', getdown_background_image_text_colour,
1957             '-draw', sprintf(getdown_background_image_text_suffix_cmd, channelSuffix),
1958             '-draw', sprintf(getdown_background_image_text_commit_cmd, "git-commit: ${gitHash}"),
1959             '-draw', sprintf(getdown_background_image_text_date_cmd, getDate("yyyy-MM-dd HH:mm:ss")),
1960             file.getPath()
1961           ]
1962         }
1963       }
1964     }
1965   }
1966 }
1967
1968 task getdownImages() {
1969   dependsOn getdownImagesProcess
1970 }
1971
1972 task getdownWebsite() {
1973   group = "distribution"
1974   description = "Create the getdown minimal app folder, and website folder for this version of jalview. Website folder also used for offline app installer"
1975
1976   dependsOn getdownImages
1977   if (buildDist) {
1978     dependsOn makeDist
1979   }
1980
1981   def getdownWebsiteResourceFilenames = []
1982   def getdownResourceDir = getdownResourceDir
1983   def getdownResourceFilenames = []
1984
1985   doFirst {
1986     // clean the getdown website and files dir before creating getdown folders
1987     delete getdownAppBaseDir
1988     delete getdownFilesDir
1989
1990     copy {
1991       from buildProperties
1992       rename(file(buildProperties).getName(), getdown_build_properties)
1993       into getdownAppDir
1994     }
1995     getdownWebsiteResourceFilenames += "${getdownAppDistDir}/${getdown_build_properties}"
1996
1997     copy {
1998       from channelPropsFile
1999       filter(ReplaceTokens,
2000         beginToken: '__',
2001         endToken: '__',
2002         tokens: [
2003           'SUFFIX': channelSuffix
2004         ]
2005       )
2006       into getdownAppBaseDir
2007     }
2008     getdownWebsiteResourceFilenames += file(channelPropsFile).getName()
2009
2010     // set some getdownTxt_ properties then go through all properties looking for getdownTxt_...
2011     def props = project.properties.sort { it.key }
2012     if (getdownAltJavaMinVersion != null && getdownAltJavaMinVersion.length() > 0) {
2013       props.put("getdown_txt_java_min_version", getdownAltJavaMinVersion)
2014     }
2015     if (getdownAltJavaMaxVersion != null && getdownAltJavaMaxVersion.length() > 0) {
2016       props.put("getdown_txt_java_max_version", getdownAltJavaMaxVersion)
2017     }
2018     if (getdownAltMultiJavaLocation != null && getdownAltMultiJavaLocation.length() > 0) {
2019       props.put("getdown_txt_multi_java_location", getdownAltMultiJavaLocation)
2020     }
2021     if (getdownImagesBuildDir != null && file(getdownImagesBuildDir).exists()) {
2022       props.put("getdown_txt_ui.background_image", "${getdownImagesBuildDir}/${getdown_background_image}")
2023       props.put("getdown_txt_ui.instant_background_image", "${getdownImagesBuildDir}/${getdown_instant_background_image}")
2024       props.put("getdown_txt_ui.error_background", "${getdownImagesBuildDir}/${getdown_error_background}")
2025       props.put("getdown_txt_ui.progress_image", "${getdownImagesBuildDir}/${getdown_progress_image}")
2026       props.put("getdown_txt_ui.icon", "${getdownImagesBuildDir}/${getdown_icon}")
2027       props.put("getdown_txt_ui.mac_dock_icon", "${getdownImagesBuildDir}/${getdown_mac_dock_icon}")
2028     }
2029
2030     props.put("getdown_txt_title", jalview_name)
2031     props.put("getdown_txt_ui.name", applicationName)
2032
2033     // start with appbase
2034     getdownTextLines += "appbase = ${getdownAppBase}"
2035     props.each{ prop, val ->
2036       if (prop.startsWith("getdown_txt_") && val != null) {
2037         if (prop.startsWith("getdown_txt_multi_")) {
2038           def key = prop.substring(18)
2039           val.split(",").each{ v ->
2040             def line = "${key} = ${v}"
2041             getdownTextLines += line
2042           }
2043         } else {
2044           // file values rationalised
2045           if (val.indexOf('/') > -1 || prop.startsWith("getdown_txt_resource")) {
2046             def r = null
2047             if (val.indexOf('/') == 0) {
2048               // absolute path
2049               r = file(val)
2050             } else if (val.indexOf('/') > 0) {
2051               // relative path (relative to jalviewDir)
2052               r = file( "${jalviewDir}/${val}" )
2053             }
2054             if (r.exists()) {
2055               val = "${getdown_resource_dir}/" + r.getName()
2056               getdownWebsiteResourceFilenames += val
2057               getdownResourceFilenames += r.getPath()
2058             }
2059           }
2060           if (! prop.startsWith("getdown_txt_resource")) {
2061             def line = prop.substring(12) + " = ${val}"
2062             getdownTextLines += line
2063           }
2064         }
2065       }
2066     }
2067
2068     getdownWebsiteResourceFilenames.each{ filename ->
2069       getdownTextLines += "resource = ${filename}"
2070     }
2071     getdownResourceFilenames.each{ filename ->
2072       copy {
2073         from filename
2074         into getdownResourceDir
2075       }
2076     }
2077     
2078     def getdownWrapperScripts = [ getdown_bash_wrapper_script, getdown_powershell_wrapper_script, getdown_batch_wrapper_script ]
2079     getdownWrapperScripts.each{ script ->
2080       def s = file( "${jalviewDir}/utils/getdown/${getdown_wrapper_script_dir}/${script}" )
2081       if (s.exists()) {
2082         copy {
2083           from s
2084           into "${getdownAppBaseDir}/${getdown_wrapper_script_dir}"
2085         }
2086         getdownTextLines += "resource = ${getdown_wrapper_script_dir}/${script}"
2087       }
2088     }
2089
2090     def codeFiles = []
2091     fileTree(file(package_dir)).each{ f ->
2092       if (f.isDirectory()) {
2093         def files = fileTree(dir: f, include: ["*"]).getFiles()
2094         codeFiles += files
2095       } else if (f.exists()) {
2096         codeFiles += f
2097       }
2098     }
2099     def jalviewJar = jar.archiveFileName.getOrNull()
2100     // put jalview.jar first for CLASSPATH and .properties files reasons
2101     codeFiles.sort{a, b -> ( a.getName() == jalviewJar ? -1 : ( b.getName() == jalviewJar ? 1 : a <=> b ) ) }.each{f ->
2102       def name = f.getName()
2103       def line = "code = ${getdownAppDistDir}/${name}"
2104       getdownTextLines += line
2105       copy {
2106         from f.getPath()
2107         into getdownAppDir
2108       }
2109     }
2110
2111     // NOT USING MODULES YET, EVERYTHING SHOULD BE IN dist
2112     /*
2113     if (JAVA_VERSION.equals("11")) {
2114     def j11libFiles = fileTree(dir: "${jalviewDir}/${j11libDir}", include: ["*.jar"]).getFiles()
2115     j11libFiles.sort().each{f ->
2116     def name = f.getName()
2117     def line = "code = ${getdown_j11lib_dir}/${name}"
2118     getdownTextLines += line
2119     copy {
2120     from f.getPath()
2121     into getdownJ11libDir
2122     }
2123     }
2124     }
2125      */
2126
2127     // 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.
2128     //getdownTextLines += "class = " + file(getdownLauncher).getName()
2129     getdownTextLines += "resource = ${getdown_launcher_new}"
2130     getdownTextLines += "class = ${main_class}"
2131     // Not setting these properties in general so that getdownappbase and getdowndistdir will default to release version in jalview.bin.Cache
2132     if (getdownSetAppBaseProperty) {
2133       getdownTextLines += "jvmarg = -Dgetdowndistdir=${getdownAppDistDir}"
2134       getdownTextLines += "jvmarg = -Dgetdownappbase=${getdownAppBase}"
2135     }
2136
2137     def getdownTxt = file("${getdownAppBaseDir}/getdown.txt")
2138     getdownTxt.write(getdownTextLines.join("\n"))
2139
2140     getdownLaunchJvl = getdown_launch_jvl_name + ( (jvlChannelName != null && jvlChannelName.length() > 0)?"-${jvlChannelName}":"" ) + ".jvl"
2141     def launchJvl = file("${getdownAppBaseDir}/${getdownLaunchJvl}")
2142     launchJvl.write("appbase=${getdownAppBase}")
2143
2144     // files going into the getdown website dir: getdown-launcher.jar
2145     copy {
2146       from getdownLauncher
2147       rename(file(getdownLauncher).getName(), getdown_launcher_new)
2148       into getdownAppBaseDir
2149     }
2150
2151     // files going into the getdown website dir: getdown-launcher(-local).jar
2152     copy {
2153       from getdownLauncher
2154       if (file(getdownLauncher).getName() != getdown_launcher) {
2155         rename(file(getdownLauncher).getName(), getdown_launcher)
2156       }
2157       into getdownAppBaseDir
2158     }
2159
2160     // files going into the getdown website dir: ./install dir and files
2161     if (! (CHANNEL.startsWith("ARCHIVE") || CHANNEL.startsWith("DEVELOP"))) {
2162       copy {
2163         from getdownTxt
2164         from getdownLauncher
2165         from "${getdownAppDir}/${getdown_build_properties}"
2166         if (file(getdownLauncher).getName() != getdown_launcher) {
2167           rename(file(getdownLauncher).getName(), getdown_launcher)
2168         }
2169         into getdownInstallDir
2170       }
2171
2172       // and make a copy in the getdown files dir (these are not downloaded by getdown)
2173       copy {
2174         from getdownInstallDir
2175         into getdownFilesInstallDir
2176       }
2177     }
2178
2179     // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
2180     copy {
2181       from getdownTxt
2182       from launchJvl
2183       from getdownLauncher
2184       from "${getdownAppBaseDir}/${getdown_build_properties}"
2185       from "${getdownAppBaseDir}/${channel_props}"
2186       if (file(getdownLauncher).getName() != getdown_launcher) {
2187         rename(file(getdownLauncher).getName(), getdown_launcher)
2188       }
2189       into getdownFilesDir
2190     }
2191
2192     // and ./resource (not all downloaded by getdown)
2193     copy {
2194       from getdownResourceDir
2195       into "${getdownFilesDir}/${getdown_resource_dir}"
2196     }
2197   }
2198
2199   if (buildDist) {
2200     inputs.dir("${jalviewDir}/${package_dir}")
2201   }
2202   outputs.dir(getdownAppBaseDir)
2203   outputs.dir(getdownFilesDir)
2204 }
2205
2206
2207 // a helper task to allow getdown digest of any dir: `gradle getdownDigestDir -PDIGESTDIR=/path/to/my/random/getdown/dir
2208 task getdownDigestDir(type: JavaExec) {
2209   group "Help"
2210   description "A task to run a getdown Digest on a dir with getdown.txt. Provide a DIGESTDIR property via -PDIGESTDIR=..."
2211
2212   def digestDirPropertyName = "DIGESTDIR"
2213   doFirst {
2214     classpath = files(getdownLauncher)
2215     def digestDir = findProperty(digestDirPropertyName)
2216     if (digestDir == null) {
2217       throw new GradleException("Must provide a DIGESTDIR value to produce an alternative getdown digest")
2218     }
2219     args digestDir
2220   }
2221   main = "com.threerings.getdown.tools.Digester"
2222 }
2223
2224
2225 task getdownDigest(type: JavaExec) {
2226   group = "distribution"
2227   description = "Digest the getdown website folder"
2228   dependsOn getdownWebsite
2229   doFirst {
2230     classpath = files(getdownLauncher)
2231   }
2232   main = "com.threerings.getdown.tools.Digester"
2233   args getdownAppBaseDir
2234   inputs.dir(getdownAppBaseDir)
2235   outputs.file("${getdownAppBaseDir}/digest2.txt")
2236 }
2237
2238
2239 task getdown() {
2240   group = "distribution"
2241   description = "Create the minimal and full getdown app folder for installers and website and create digest file"
2242   dependsOn getdownDigest
2243   doLast {
2244     if (reportRsyncCommand) {
2245       def fromDir = getdownAppBaseDir + (getdownAppBaseDir.endsWith('/')?'':'/')
2246       def toDir = "${getdown_rsync_dest}/${getdownDir}" + (getdownDir.endsWith('/')?'':'/')
2247       println "LIKELY RSYNC COMMAND:"
2248       println "mkdir -p '$toDir'\nrsync -avh --delete '$fromDir' '$toDir'"
2249       if (RUNRSYNC == "true") {
2250         exec {
2251           commandLine "mkdir", "-p", toDir
2252         }
2253         exec {
2254           commandLine "rsync", "-avh", "--delete", fromDir, toDir
2255         }
2256       }
2257     }
2258   }
2259 }
2260
2261
2262 task getdownArchiveBuild() {
2263   group = "distribution"
2264   description = "Put files in the archive dir to go on the website"
2265
2266   dependsOn getdownWebsite
2267
2268   def v = "v${JALVIEW_VERSION_UNDERSCORES}"
2269   def vDir = "${getdownArchiveDir}/${v}"
2270   getdownFullArchiveDir = "${vDir}/getdown"
2271   getdownVersionLaunchJvl = "${vDir}/jalview-${v}.jvl"
2272
2273   def vAltDir = "alt_${v}"
2274   def archiveImagesDir = "${jalviewDir}/${channel_properties_dir}/old/images"
2275
2276   doFirst {
2277     // cleanup old "old" dir
2278     delete getdownArchiveDir
2279
2280     def getdownArchiveTxt = file("${getdownFullArchiveDir}/getdown.txt")
2281     getdownArchiveTxt.getParentFile().mkdirs()
2282     def getdownArchiveTextLines = []
2283     def getdownFullArchiveAppBase = "${getdownArchiveAppBase}${getdownArchiveAppBase.endsWith("/")?"":"/"}${v}/getdown/"
2284
2285     // the libdir
2286     copy {
2287       from "${getdownAppBaseDir}/${getdownAppDistDir}"
2288       into "${getdownFullArchiveDir}/${vAltDir}"
2289     }
2290
2291     getdownTextLines.each { line ->
2292       line = line.replaceAll("^(?<s>appbase\\s*=\\s*).*", '${s}'+getdownFullArchiveAppBase)
2293       line = line.replaceAll("^(?<s>(resource|code)\\s*=\\s*)${getdownAppDistDir}/", '${s}'+vAltDir+"/")
2294       line = line.replaceAll("^(?<s>ui.background_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background.png")
2295       line = line.replaceAll("^(?<s>ui.instant_background_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background_initialising.png")
2296       line = line.replaceAll("^(?<s>ui.error_background\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background_error.png")
2297       line = line.replaceAll("^(?<s>ui.progress_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_progress_bar.png")
2298       // remove the existing resource = resource/ or bin/ lines
2299       if (! line.matches("resource\\s*=\\s*(resource|bin)/.*")) {
2300         getdownArchiveTextLines += line
2301       }
2302     }
2303
2304     // the resource dir -- add these files as resource lines in getdown.txt
2305     copy {
2306       from "${archiveImagesDir}"
2307       into "${getdownFullArchiveDir}/${getdown_resource_dir}"
2308       eachFile { file ->
2309         getdownArchiveTextLines += "resource = ${getdown_resource_dir}/${file.getName()}"
2310       }
2311     }
2312
2313     getdownArchiveTxt.write(getdownArchiveTextLines.join("\n"))
2314
2315     def vLaunchJvl = file(getdownVersionLaunchJvl)
2316     vLaunchJvl.getParentFile().mkdirs()
2317     vLaunchJvl.write("appbase=${getdownFullArchiveAppBase}\n")
2318     def vLaunchJvlPath = vLaunchJvl.toPath().toAbsolutePath()
2319     def jvlLinkPath = file("${vDir}/jalview.jvl").toPath().toAbsolutePath()
2320     // for some reason filepath.relativize(fileInSameDirPath) gives a path to "../" which is wrong
2321     //java.nio.file.Files.createSymbolicLink(jvlLinkPath, jvlLinkPath.relativize(vLaunchJvlPath));
2322     java.nio.file.Files.createSymbolicLink(jvlLinkPath, java.nio.file.Paths.get(".",vLaunchJvl.getName()));
2323
2324     // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
2325     copy {
2326       from getdownLauncher
2327       from "${getdownAppBaseDir}/${getdownLaunchJvl}"
2328       from "${getdownAppBaseDir}/${getdown_launcher_new}"
2329       from "${getdownAppBaseDir}/${channel_props}"
2330       if (file(getdownLauncher).getName() != getdown_launcher) {
2331         rename(file(getdownLauncher).getName(), getdown_launcher)
2332       }
2333       into getdownFullArchiveDir
2334     }
2335
2336   }
2337 }
2338
2339 task getdownArchiveDigest(type: JavaExec) {
2340   group = "distribution"
2341   description = "Digest the getdown archive folder"
2342
2343   dependsOn getdownArchiveBuild
2344
2345   doFirst {
2346     classpath = files(getdownLauncher)
2347     args getdownFullArchiveDir
2348   }
2349   main = "com.threerings.getdown.tools.Digester"
2350   inputs.dir(getdownFullArchiveDir)
2351   outputs.file("${getdownFullArchiveDir}/digest2.txt")
2352 }
2353
2354 task getdownArchive() {
2355   group = "distribution"
2356   description = "Build the website archive dir with getdown digest"
2357
2358   dependsOn getdownArchiveBuild
2359   dependsOn getdownArchiveDigest
2360 }
2361
2362 tasks.withType(JavaCompile) {
2363         options.encoding = 'UTF-8'
2364 }
2365
2366
2367 clean {
2368   doFirst {
2369     delete getdownAppBaseDir
2370     delete getdownFilesDir
2371     delete getdownArchiveDir
2372   }
2373 }
2374
2375
2376 install4j {
2377   if (file(install4jHomeDir).exists()) {
2378     // good to go!
2379   } else if (file(System.getProperty("user.home")+"/buildtools/install4j").exists()) {
2380     install4jHomeDir = System.getProperty("user.home")+"/buildtools/install4j"
2381   } else if (file("/Applications/install4j.app/Contents/Resources/app").exists()) {
2382     install4jHomeDir = "/Applications/install4j.app/Contents/Resources/app"
2383   }
2384   installDir(file(install4jHomeDir))
2385
2386   mediaTypes = Arrays.asList(install4j_media_types.split(","))
2387 }
2388
2389
2390 task copyInstall4jTemplate {
2391   def install4jTemplateFile = file("${install4jDir}/${install4j_template}")
2392   def install4jFileAssociationsFile = file("${install4jDir}/${install4j_installer_file_associations}")
2393   inputs.file(install4jTemplateFile)
2394   inputs.file(install4jFileAssociationsFile)
2395   inputs.property("CHANNEL", { CHANNEL })
2396   outputs.file(install4jConfFile)
2397
2398   doLast {
2399     def install4jConfigXml = new XmlParser().parse(install4jTemplateFile)
2400
2401     // turn off code signing if no OSX_KEYPASS
2402     if (OSX_KEYPASS == "") {
2403       install4jConfigXml.'**'.codeSigning.each { codeSigning ->
2404         codeSigning.'@macEnabled' = "false"
2405       }
2406       install4jConfigXml.'**'.windows.each { windows ->
2407         windows.'@runPostProcessor' = "false"
2408       }
2409     }
2410
2411     // disable install screen for OSX dmg (for 2.11.2.0)
2412     install4jConfigXml.'**'.macosArchive.each { macosArchive -> 
2413       macosArchive.attributes().remove('executeSetupApp')
2414       macosArchive.attributes().remove('setupAppId')
2415     }
2416
2417     // turn off checksum creation for LOCAL channel
2418     def e = install4jConfigXml.application[0]
2419     e.'@createChecksums' = string(install4jCheckSums)
2420
2421     // put file association actions where placeholder action is
2422     def install4jFileAssociationsText = install4jFileAssociationsFile.text
2423     def fileAssociationActions = new XmlParser().parseText("<actions>${install4jFileAssociationsText}</actions>")
2424     install4jConfigXml.'**'.action.any { a -> // .any{} stops after the first one that returns true
2425       if (a.'@name' == 'EXTENSIONS_REPLACED_BY_GRADLE') {
2426         def parent = a.parent()
2427         parent.remove(a)
2428         fileAssociationActions.each { faa ->
2429             parent.append(faa)
2430         }
2431         // don't need to continue in .any loop once replacements have been made
2432         return true
2433       }
2434     }
2435
2436     // use Windows Program Group with Examples folder for RELEASE, and Program Group without Examples for everything else
2437     // NB we're deleting the /other/ one!
2438     // Also remove the examples subdir from non-release versions
2439     def customizedIdToDelete = "PROGRAM_GROUP_RELEASE"
2440     // 2.11.1.0 NOT releasing with the Examples folder in the Program Group
2441     if (false && CHANNEL=="RELEASE") { // remove 'false && ' to include Examples folder in RELEASE channel
2442       customizedIdToDelete = "PROGRAM_GROUP_NON_RELEASE"
2443     } else {
2444       // remove the examples subdir from Full File Set
2445       def files = install4jConfigXml.files[0]
2446       def fileset = files.filesets.fileset.find { fs -> fs.'@customizedId' == "FULL_FILE_SET" }
2447       def root = files.roots.root.find { r -> r.'@fileset' == fileset.'@id' }
2448       def mountPoint = files.mountPoints.mountPoint.find { mp -> mp.'@root' == root.'@id' }
2449       def dirEntry = files.entries.dirEntry.find { de -> de.'@mountPoint' == mountPoint.'@id' && de.'@subDirectory' == "examples" }
2450       dirEntry.parent().remove(dirEntry)
2451     }
2452     install4jConfigXml.'**'.action.any { a ->
2453       if (a.'@customizedId' == customizedIdToDelete) {
2454         def parent = a.parent()
2455         parent.remove(a)
2456         return true
2457       }
2458     }
2459
2460     // write install4j file
2461     install4jConfFile.text = XmlUtil.serialize(install4jConfigXml)
2462   }
2463 }
2464
2465
2466 clean {
2467   doFirst {
2468     delete install4jConfFile
2469   }
2470 }
2471
2472 task cleanInstallersDataFiles {
2473   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
2474   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
2475   def hugoDataJsonFile = file("${jalviewDir}/${install4jBuildDir}/installers-${JALVIEW_VERSION_UNDERSCORES}.json")
2476   doFirst {
2477     delete installersOutputTxt
2478     delete installersSha256
2479     delete hugoDataJsonFile
2480   }
2481 }
2482
2483 task install4jDMGBackgroundImageCopy {
2484   inputs.file "${install4jDMGBackgroundImageDir}/${install4jDMGBackgroundImageFile}"
2485   outputs.dir "${install4jDMGBackgroundImageBuildDir}"
2486   doFirst {
2487     copy {
2488       from(install4jDMGBackgroundImageDir) {
2489         include(install4jDMGBackgroundImageFile)
2490       }
2491       into install4jDMGBackgroundImageBuildDir
2492     }
2493   }
2494 }
2495
2496 task install4jDMGBackgroundImageProcess {
2497   dependsOn install4jDMGBackgroundImageCopy
2498
2499   doFirst {
2500     if (backgroundImageText) {
2501       if (convertBinary == null) {
2502         throw new StopExecutionException("No ImageMagick convert binary installed at '${convertBinaryExpectedLocation}'")
2503       }
2504       if (!project.hasProperty("install4j_background_image_text_suffix_cmd")) {
2505         throw new StopExecutionException("No property 'install4j_background_image_text_suffix_cmd' defined. See channel_gradle.properties for channel ${CHANNEL}")
2506       }
2507       fileTree(dir: install4jDMGBackgroundImageBuildDir, include: "*.png").getFiles().each { file ->
2508         exec {
2509           executable convertBinary
2510           args = [
2511             file.getPath(),
2512             '-font', install4j_background_image_text_font,
2513             '-fill', install4j_background_image_text_colour,
2514             '-draw', sprintf(install4j_background_image_text_suffix_cmd, channelSuffix),
2515             '-draw', sprintf(install4j_background_image_text_commit_cmd, "git-commit: ${gitHash}"),
2516             '-draw', sprintf(install4j_background_image_text_date_cmd, getDate("yyyy-MM-dd HH:mm:ss")),
2517             file.getPath()
2518           ]
2519         }
2520       }
2521     }
2522   }
2523 }
2524
2525 task install4jDMGBackgroundImage {
2526   dependsOn install4jDMGBackgroundImageProcess
2527 }
2528
2529 task installerFiles(type: com.install4j.gradle.Install4jTask) {
2530   group = "distribution"
2531   description = "Create the install4j installers"
2532   dependsOn getdown
2533   dependsOn copyInstall4jTemplate
2534   dependsOn cleanInstallersDataFiles
2535   dependsOn install4jDMGBackgroundImage
2536
2537   projectFile = install4jConfFile
2538
2539   // create an md5 for the input files to use as version for install4j conf file
2540   def digest = MessageDigest.getInstance("MD5")
2541   digest.update(
2542     (file("${install4jDir}/${install4j_template}").text + 
2543     file("${install4jDir}/${install4j_info_plist_file_associations}").text +
2544     file("${install4jDir}/${install4j_installer_file_associations}").text).bytes)
2545   def filesMd5 = new BigInteger(1, digest.digest()).toString(16)
2546   if (filesMd5.length() >= 8) {
2547     filesMd5 = filesMd5.substring(0,8)
2548   }
2549   def install4jTemplateVersion = "${JALVIEW_VERSION}_F${filesMd5}_C${gitHash}"
2550
2551   variables = [
2552     'JALVIEW_NAME': jalview_name,
2553     'JALVIEW_APPLICATION_NAME': applicationName,
2554     'JALVIEW_DIR': "../..",
2555     'OSX_KEYSTORE': OSX_KEYSTORE,
2556     'OSX_APPLEID': OSX_APPLEID,
2557     'OSX_ALTOOLPASS': OSX_ALTOOLPASS,
2558     'JSIGN_SH': JSIGN_SH,
2559     'JRE_DIR': getdown_app_dir_java,
2560     'INSTALLER_TEMPLATE_VERSION': install4jTemplateVersion,
2561     'JALVIEW_VERSION': JALVIEW_VERSION,
2562     'JAVA_MIN_VERSION': JAVA_MIN_VERSION,
2563     'JAVA_MAX_VERSION': JAVA_MAX_VERSION,
2564     'JAVA_VERSION': JAVA_VERSION,
2565     'JAVA_INTEGER_VERSION': JAVA_INTEGER_VERSION,
2566     'VERSION': JALVIEW_VERSION,
2567     'COPYRIGHT_MESSAGE': install4j_copyright_message,
2568     'BUNDLE_ID': install4jBundleId,
2569     'INTERNAL_ID': install4jInternalId,
2570     'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
2571     'MACOS_DMG_DS_STORE': install4jDMGDSStore,
2572     'MACOS_DMG_BG_IMAGE': "${install4jDMGBackgroundImageBuildDir}/${install4jDMGBackgroundImageFile}",
2573     'WRAPPER_LINK': getdownWrapperLink,
2574     'BASH_WRAPPER_SCRIPT': getdown_bash_wrapper_script,
2575     'POWERSHELL_WRAPPER_SCRIPT': getdown_powershell_wrapper_script,
2576     'WRAPPER_SCRIPT_BIN_DIR': getdown_wrapper_script_dir,
2577     'INSTALLER_NAME': install4jInstallerName,
2578     'INSTALL4J_UTILS_DIR': install4j_utils_dir,
2579     'GETDOWN_CHANNEL_DIR': getdownChannelDir,
2580     'GETDOWN_FILES_DIR': getdown_files_dir,
2581     'GETDOWN_RESOURCE_DIR': getdown_resource_dir,
2582     'GETDOWN_DIST_DIR': getdownAppDistDir,
2583     'GETDOWN_ALT_DIR': getdown_app_dir_alt,
2584     'GETDOWN_INSTALL_DIR': getdown_install_dir,
2585     'INFO_PLIST_FILE_ASSOCIATIONS_FILE': install4j_info_plist_file_associations,
2586     'BUILD_DIR': install4jBuildDir,
2587     'APPLICATION_CATEGORIES': install4j_application_categories,
2588     'APPLICATION_FOLDER': install4jApplicationFolder,
2589     'UNIX_APPLICATION_FOLDER': install4jUnixApplicationFolder,
2590     'EXECUTABLE_NAME': install4jExecutableName,
2591     'EXTRA_SCHEME': install4jExtraScheme,
2592     'MAC_ICONS_FILE': install4jMacIconsFile,
2593     'WINDOWS_ICONS_FILE': install4jWindowsIconsFile,
2594     'PNG_ICON_FILE': install4jPngIconFile,
2595     'BACKGROUND': install4jBackground,
2596   ]
2597
2598   def varNameMap = [
2599     'mac': 'MACOS',
2600     'windows': 'WINDOWS',
2601     'linux': 'LINUX'
2602   ]
2603   
2604   // these are the bundled OS/architecture VMs needed by install4j
2605   def osArch = [
2606     [ "mac", "x64" ],
2607     [ "mac", "aarch64" ],
2608     [ "windows", "x64" ],
2609     [ "linux", "x64" ],
2610     [ "linux", "aarch64" ]
2611   ]
2612   osArch.forEach { os, arch ->
2613     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)
2614     // N.B. For some reason install4j requires the below filename to have underscores and not hyphens
2615     // otherwise running `gradle installers` generates a non-useful error:
2616     // `install4j: compilation failed. Reason: java.lang.NumberFormatException: For input string: "windows"`
2617     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)
2618   }
2619
2620   //println("INSTALL4J VARIABLES:")
2621   //variables.each{k,v->println("${k}=${v}")}
2622
2623   destination = "${jalviewDir}/${install4jBuildDir}"
2624   buildSelected = true
2625
2626   if (install4j_faster.equals("true") || CHANNEL.startsWith("LOCAL")) {
2627     faster = true
2628     disableSigning = true
2629     disableNotarization = true
2630   }
2631
2632   if (OSX_KEYPASS) {
2633     macKeystorePassword = OSX_KEYPASS
2634   } 
2635   
2636   if (OSX_ALTOOLPASS) {
2637     appleIdPassword = OSX_ALTOOLPASS
2638     disableNotarization = false
2639   } else {
2640     disableNotarization = true
2641   }
2642
2643   doFirst {
2644     println("Using projectFile "+projectFile)
2645     if (!disableNotarization) { println("Will notarize OSX App DMG") }
2646   }
2647   //verbose=true
2648
2649   inputs.dir(getdownAppBaseDir)
2650   inputs.file(install4jConfFile)
2651   inputs.file("${install4jDir}/${install4j_info_plist_file_associations}")
2652   outputs.dir("${jalviewDir}/${install4j_build_dir}/${JAVA_VERSION}")
2653 }
2654
2655 def getDataHash(File myFile) {
2656   HashCode hash = Files.asByteSource(myFile).hash(Hashing.sha256())
2657   return myFile.exists()
2658   ? [
2659       "file" : myFile.getName(),
2660       "filesize" : myFile.length(),
2661       "sha256" : hash.toString()
2662     ]
2663   : null
2664 }
2665
2666 def writeDataJsonFile(File installersOutputTxt, File installersSha256, File dataJsonFile) {
2667   def hash = [
2668     "channel" : getdownChannelName,
2669     "date" : getDate("yyyy-MM-dd HH:mm:ss"),
2670     "git-commit" : "${gitHash} [${gitBranch}]",
2671     "version" : JALVIEW_VERSION
2672   ]
2673   // install4j installer files
2674   if (installersOutputTxt.exists()) {
2675     def idHash = [:]
2676     installersOutputTxt.readLines().each { def line ->
2677       if (line.startsWith("#")) {
2678         return;
2679       }
2680       line.replaceAll("\n","")
2681       def vals = line.split("\t")
2682       def filename = vals[3]
2683       def filesize = file(filename).length()
2684       filename = filename.replaceAll(/^.*\//, "")
2685       hash[vals[0]] = [ "id" : vals[0], "os" : vals[1], "name" : vals[2], "file" : filename, "filesize" : filesize ]
2686       idHash."${filename}" = vals[0]
2687     }
2688     if (install4jCheckSums && installersSha256.exists()) {
2689       installersSha256.readLines().each { def line ->
2690         if (line.startsWith("#")) {
2691           return;
2692         }
2693         line.replaceAll("\n","")
2694         def vals = line.split(/\s+\*?/)
2695         def filename = vals[1]
2696         def innerHash = (hash.(idHash."${filename}"))."sha256" = vals[0]
2697       }
2698     }
2699   }
2700
2701   [
2702     "JAR": shadowJar.archiveFile, // executable JAR
2703     "JVL": getdownVersionLaunchJvl, // version JVL
2704     "SOURCE": sourceDist.archiveFile // source TGZ
2705   ].each { key, value ->
2706     def file = file(value)
2707     if (file.exists()) {
2708       def fileHash = getDataHash(file)
2709       if (fileHash != null) {
2710         hash."${key}" = fileHash;
2711       }
2712     }
2713   }
2714   return dataJsonFile.write(new JsonBuilder(hash).toPrettyString())
2715 }
2716
2717 task staticMakeInstallersJsonFile {
2718   doFirst {
2719     def output = findProperty("i4j_output")
2720     def sha256 = findProperty("i4j_sha256")
2721     def json = findProperty("i4j_json")
2722     if (output == null || sha256 == null || json == null) {
2723       throw new GradleException("Must provide paths to all of output.txt, sha256sums, and output.json with '-Pi4j_output=... -Pi4j_sha256=... -Pi4j_json=...")
2724     }
2725     writeDataJsonFile(file(output), file(sha256), file(json))
2726   }
2727 }
2728
2729 task installers {
2730   dependsOn installerFiles
2731 }
2732
2733
2734 spotless {
2735   java {
2736     eclipse().configFile(eclipse_codestyle_file)
2737   }
2738 }
2739
2740 task createSourceReleaseProperties(type: WriteProperties) {
2741   group = "distribution"
2742   description = "Create the source RELEASE properties file"
2743   
2744   def sourceTarBuildDir = "${buildDir}/sourceTar"
2745   def sourceReleasePropertiesFile = "${sourceTarBuildDir}/RELEASE"
2746   outputFile (sourceReleasePropertiesFile)
2747
2748   doFirst {
2749     releaseProps.each{ key, val -> property key, val }
2750     property "git.branch", gitBranch
2751     property "git.hash", gitHash
2752   }
2753
2754   outputs.file(outputFile)
2755 }
2756
2757 task sourceDist(type: Tar) {
2758   group "distribution"
2759   description "Create a source .tar.gz file for distribution"
2760
2761   dependsOn createBuildProperties
2762   dependsOn convertMdFiles
2763   dependsOn eclipseAllPreferences
2764   dependsOn createSourceReleaseProperties
2765
2766
2767   def outputFileName = "${project.name}_${JALVIEW_VERSION_UNDERSCORES}.tar.gz"
2768   archiveFileName = outputFileName
2769   
2770   compression Compression.GZIP
2771   
2772   into project.name
2773
2774   def EXCLUDE_FILES=[
2775     "build/*",
2776     "bin/*",
2777     "test-output/",
2778     "test-reports",
2779     "tests",
2780     "clover*/*",
2781     ".*",
2782     "benchmarking/*",
2783     "**/.*",
2784     "*.class",
2785     "**/*.class","$j11modDir/**/*.jar","appletlib","**/*locales",
2786     "*locales/**",
2787     "utils/InstallAnywhere",
2788     "**/*.log",
2789     "RELEASE",
2790   ] 
2791   def PROCESS_FILES=[
2792     "AUTHORS",
2793     "CITATION",
2794     "FEATURETODO",
2795     "JAVA-11-README",
2796     "FEATURETODO",
2797     "LICENSE",
2798     "**/README",
2799     "THIRDPARTYLIBS",
2800     "TESTNG",
2801     "build.gradle",
2802     "gradle.properties",
2803     "**/*.java",
2804     "**/*.html",
2805     "**/*.xml",
2806     "**/*.gradle",
2807     "**/*.groovy",
2808     "**/*.properties",
2809     "**/*.perl",
2810     "**/*.sh",
2811   ]
2812   def INCLUDE_FILES=[
2813     ".classpath",
2814     ".settings/org.eclipse.buildship.core.prefs",
2815     ".settings/org.eclipse.jdt.core.prefs"
2816   ]
2817
2818   from(jalviewDir) {
2819     exclude (EXCLUDE_FILES)
2820     include (PROCESS_FILES)
2821     filter(ReplaceTokens,
2822       beginToken: '$$',
2823       endToken: '$$',
2824       tokens: [
2825         'Version-Rel': JALVIEW_VERSION,
2826         'Year-Rel': getDate("yyyy")
2827       ]
2828     )
2829   }
2830   from(jalviewDir) {
2831     exclude (EXCLUDE_FILES)
2832     exclude (PROCESS_FILES)
2833     exclude ("appletlib")
2834     exclude ("**/*locales")
2835     exclude ("*locales/**")
2836     exclude ("utils/InstallAnywhere")
2837
2838     exclude (getdown_files_dir)
2839     // getdown_website_dir and getdown_archive_dir moved to build/website/docroot/getdown
2840     //exclude (getdown_website_dir)
2841     //exclude (getdown_archive_dir)
2842
2843     // exluding these as not using jars as modules yet
2844     exclude ("${j11modDir}/**/*.jar")
2845   }
2846   from(jalviewDir) {
2847     include(INCLUDE_FILES)
2848   }
2849 //  from (jalviewDir) {
2850 //    // explicit includes for stuff that seemed to not get included
2851 //    include(fileTree("test/**/*."))
2852 //    exclude(EXCLUDE_FILES)
2853 //    exclude(PROCESS_FILES)
2854 //  }
2855
2856   from(file(buildProperties).getParent()) {
2857     include(file(buildProperties).getName())
2858     rename(file(buildProperties).getName(), "build_properties")
2859     filter({ line ->
2860       line.replaceAll("^INSTALLATION=.*\$","INSTALLATION=Source Release"+" git-commit\\\\:"+gitHash+" ["+gitBranch+"]")
2861     })
2862   }
2863
2864   def sourceTarBuildDir = "${buildDir}/sourceTar"
2865   from(sourceTarBuildDir) {
2866     // this includes the appended RELEASE properties file
2867   }
2868 }
2869
2870 task dataInstallersJson {
2871   group "website"
2872   description "Create the installers-VERSION.json data file for installer files created"
2873
2874   mustRunAfter installers
2875   mustRunAfter shadowJar
2876   mustRunAfter sourceDist
2877   mustRunAfter getdownArchive
2878
2879   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
2880   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
2881
2882   if (installersOutputTxt.exists()) {
2883     inputs.file(installersOutputTxt)
2884   }
2885   if (install4jCheckSums && installersSha256.exists()) {
2886     inputs.file(installersSha256)
2887   }
2888   [
2889     shadowJar.archiveFile, // executable JAR
2890     getdownVersionLaunchJvl, // version JVL
2891     sourceDist.archiveFile // source TGZ
2892   ].each { fileName ->
2893     if (file(fileName).exists()) {
2894       inputs.file(fileName)
2895     }
2896   }
2897
2898   outputs.file(hugoDataJsonFile)
2899
2900   doFirst {
2901     writeDataJsonFile(installersOutputTxt, installersSha256, hugoDataJsonFile)
2902   }
2903 }
2904
2905 task helppages {
2906   group "help"
2907   description "Copies all help pages to build dir. Runs ant task 'pubhtmlhelp'."
2908
2909   dependsOn copyHelp
2910   dependsOn pubhtmlhelp
2911   
2912   inputs.dir("${helpBuildDir}/${help_dir}")
2913   outputs.dir("${buildDir}/distributions/${help_dir}")
2914 }
2915
2916
2917 task j2sSetHeadlessBuild {
2918   doFirst {
2919     IN_ECLIPSE = false
2920   }
2921 }
2922
2923
2924 task jalviewjsEnableAltFileProperty(type: WriteProperties) {
2925   group "jalviewjs"
2926   description "Enable the alternative J2S Config file for headless build"
2927
2928   outputFile = jalviewjsJ2sSettingsFileName
2929   def j2sPropsFile = file(jalviewjsJ2sSettingsFileName)
2930   def j2sProps = new Properties()
2931   if (j2sPropsFile.exists()) {
2932     try {
2933       def j2sPropsFileFIS = new FileInputStream(j2sPropsFile)
2934       j2sProps.load(j2sPropsFileFIS)
2935       j2sPropsFileFIS.close()
2936
2937       j2sProps.each { prop, val ->
2938         property(prop, val)
2939       }
2940     } catch (Exception e) {
2941       println("Exception reading ${jalviewjsJ2sSettingsFileName}")
2942       e.printStackTrace()
2943     }
2944   }
2945   if (! j2sProps.stringPropertyNames().contains(jalviewjs_j2s_alt_file_property_config)) {
2946     property(jalviewjs_j2s_alt_file_property_config, jalviewjs_j2s_alt_file_property)
2947   }
2948 }
2949
2950
2951 task jalviewjsSetEclipseWorkspace {
2952   def propKey = "jalviewjs_eclipse_workspace"
2953   def propVal = null
2954   if (project.hasProperty(propKey)) {
2955     propVal = project.getProperty(propKey)
2956     if (propVal.startsWith("~/")) {
2957       propVal = System.getProperty("user.home") + propVal.substring(1)
2958     }
2959   }
2960   def propsFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_workspace_location_file}"
2961   def propsFile = file(propsFileName)
2962   def eclipseWsDir = propVal
2963   def props = new Properties()
2964
2965   def writeProps = true
2966   if (( eclipseWsDir == null || !file(eclipseWsDir).exists() ) && propsFile.exists()) {
2967     def ins = new FileInputStream(propsFileName)
2968     props.load(ins)
2969     ins.close()
2970     if (props.getProperty(propKey, null) != null) {
2971       eclipseWsDir = props.getProperty(propKey)
2972       writeProps = false
2973     }
2974   }
2975
2976   if (eclipseWsDir == null || !file(eclipseWsDir).exists()) {
2977     def tempDir = File.createTempDir()
2978     eclipseWsDir = tempDir.getAbsolutePath()
2979     writeProps = true
2980   }
2981   eclipseWorkspace = file(eclipseWsDir)
2982
2983   doFirst {
2984     // do not run a headless transpile when we claim to be in Eclipse
2985     if (IN_ECLIPSE) {
2986       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2987       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2988     } else {
2989       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2990     }
2991
2992     if (writeProps) {
2993       props.setProperty(propKey, eclipseWsDir)
2994       propsFile.parentFile.mkdirs()
2995       def bytes = new ByteArrayOutputStream()
2996       props.store(bytes, null)
2997       def propertiesString = bytes.toString()
2998       propsFile.text = propertiesString
2999       print("NEW ")
3000     } else {
3001       print("EXISTING ")
3002     }
3003
3004     println("ECLIPSE WORKSPACE: "+eclipseWorkspace.getPath())
3005   }
3006
3007   //inputs.property(propKey, eclipseWsDir) // eclipseWsDir only gets set once this task runs, so will be out-of-date
3008   outputs.file(propsFileName)
3009   outputs.upToDateWhen { eclipseWorkspace.exists() && propsFile.exists() }
3010 }
3011
3012
3013 task jalviewjsEclipsePaths {
3014   def eclipseProduct
3015
3016   def eclipseRoot = jalviewjs_eclipse_root
3017   if (eclipseRoot.startsWith("~/")) {
3018     eclipseRoot = System.getProperty("user.home") + eclipseRoot.substring(1)
3019   }
3020   if (OperatingSystem.current().isMacOsX()) {
3021     eclipseRoot += "/Eclipse.app"
3022     eclipseBinary = "${eclipseRoot}/Contents/MacOS/eclipse"
3023     eclipseProduct = "${eclipseRoot}/Contents/Eclipse/.eclipseproduct"
3024   } else if (OperatingSystem.current().isWindows()) { // check these paths!!
3025     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
3026       eclipseRoot += "/eclipse"
3027     }
3028     eclipseBinary = "${eclipseRoot}/eclipse.exe"
3029     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
3030   } else { // linux or unix
3031     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
3032       eclipseRoot += "/eclipse"
3033 println("eclipseDir exists")
3034     }
3035     eclipseBinary = "${eclipseRoot}/eclipse"
3036     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
3037   }
3038
3039   eclipseVersion = "4.13" // default
3040   def assumedVersion = true
3041   if (file(eclipseProduct).exists()) {
3042     def fis = new FileInputStream(eclipseProduct)
3043     def props = new Properties()
3044     props.load(fis)
3045     eclipseVersion = props.getProperty("version")
3046     fis.close()
3047     assumedVersion = false
3048   }
3049   
3050   def propKey = "eclipse_debug"
3051   eclipseDebug = (project.hasProperty(propKey) && project.getProperty(propKey).equals("true"))
3052
3053   doFirst {
3054     // do not run a headless transpile when we claim to be in Eclipse
3055     if (IN_ECLIPSE) {
3056       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3057       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3058     } else {
3059       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3060     }
3061
3062     if (!assumedVersion) {
3063       println("ECLIPSE VERSION=${eclipseVersion}")
3064     }
3065   }
3066 }
3067
3068
3069 task printProperties {
3070   group "Debug"
3071   description "Output to console all System.properties"
3072   doFirst {
3073     System.properties.each { key, val -> System.out.println("Property: ${key}=${val}") }
3074   }
3075 }
3076
3077
3078 task eclipseSetup {
3079   dependsOn eclipseProject
3080   dependsOn eclipseClasspath
3081   dependsOn eclipseJdt
3082 }
3083
3084
3085 // this version (type: Copy) will delete anything in the eclipse dropins folder that isn't in fromDropinsDir
3086 task jalviewjsEclipseCopyDropins(type: Copy) {
3087   dependsOn jalviewjsEclipsePaths
3088
3089   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_eclipse_dropins_dir}", include: "*.jar")
3090   inputFiles += file("${jalviewDir}/${jalviewjsJ2sPlugin}")
3091   def outputDir = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
3092
3093   from inputFiles
3094   into outputDir
3095 }
3096
3097
3098 // this eclipse -clean doesn't actually work
3099 task jalviewjsCleanEclipse(type: Exec) {
3100   dependsOn eclipseSetup
3101   dependsOn jalviewjsEclipsePaths
3102   dependsOn jalviewjsEclipseCopyDropins
3103
3104   executable(eclipseBinary)
3105   args(["-nosplash", "--launcher.suppressErrors", "-data", eclipseWorkspace.getPath(), "-clean", "-console", "-consoleLog"])
3106   if (eclipseDebug) {
3107     args += "-debug"
3108   }
3109   args += "-l"
3110
3111   def inputString = """exit
3112 y
3113 """
3114   def inputByteStream = new ByteArrayInputStream(inputString.getBytes())
3115   standardInput = inputByteStream
3116 }
3117
3118 /* not really working yet
3119 jalviewjsEclipseCopyDropins.finalizedBy jalviewjsCleanEclipse
3120 */
3121
3122
3123 task jalviewjsTransferUnzipSwingJs {
3124   def file_zip = "${jalviewDir}/${jalviewjs_swingjs_zip}"
3125
3126   doLast {
3127     copy {
3128       from zipTree(file_zip)
3129       into "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3130     }
3131   }
3132
3133   inputs.file file_zip
3134   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3135 }
3136
3137
3138 task jalviewjsTransferUnzipLib {
3139   def zipFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_libjs_dir}", include: "*.zip")
3140
3141   doLast {
3142     zipFiles.each { file_zip -> 
3143       copy {
3144         from zipTree(file_zip)
3145         into "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3146       }
3147     }
3148   }
3149
3150   inputs.files zipFiles
3151   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3152 }
3153
3154
3155 task jalviewjsTransferUnzipAllLibs {
3156   dependsOn jalviewjsTransferUnzipSwingJs
3157   dependsOn jalviewjsTransferUnzipLib
3158 }
3159
3160
3161 task jalviewjsCreateJ2sSettings(type: WriteProperties) {
3162   group "JalviewJS"
3163   description "Create the alternative j2s file from the j2s.* properties"
3164
3165   jalviewjsJ2sProps = project.properties.findAll { it.key.startsWith("j2s.") }.sort { it.key }
3166   def siteDirProperty = "j2s.site.directory"
3167   def setSiteDir = false
3168   jalviewjsJ2sProps.each { prop, val ->
3169     if (val != null) {
3170       if (prop == siteDirProperty) {
3171         if (!(val.startsWith('/') || val.startsWith("file://") )) {
3172           val = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${val}"
3173         }
3174         setSiteDir = true
3175       }
3176       property(prop,val)
3177     }
3178     if (!setSiteDir) { // default site location, don't override specifically set property
3179       property(siteDirProperty,"${jalviewDirRelativePath}/${jalviewjsTransferSiteJsDir}")
3180     }
3181   }
3182   outputFile = jalviewjsJ2sAltSettingsFileName
3183
3184   if (! IN_ECLIPSE) {
3185     inputs.properties(jalviewjsJ2sProps)
3186     outputs.file(jalviewjsJ2sAltSettingsFileName)
3187   }
3188 }
3189
3190
3191 task jalviewjsEclipseSetup {
3192   dependsOn jalviewjsEclipseCopyDropins
3193   dependsOn jalviewjsSetEclipseWorkspace
3194   dependsOn jalviewjsCreateJ2sSettings
3195 }
3196
3197
3198 task jalviewjsSyncAllLibs (type: Sync) {
3199   dependsOn jalviewjsTransferUnzipAllLibs
3200   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteLibDir}")
3201   inputFiles += fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
3202   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3203
3204   from inputFiles
3205   into outputDir
3206   def outputFiles = []
3207   rename { filename ->
3208     outputFiles += "${outputDir}/${filename}"
3209     null
3210   }
3211   preserve {
3212     include "**"
3213   }
3214
3215   // should this be exclude really ?
3216   duplicatesStrategy "INCLUDE"
3217
3218   outputs.files outputFiles
3219   inputs.files inputFiles
3220 }
3221
3222
3223 task jalviewjsSyncResources (type: Sync) {
3224   dependsOn buildResources
3225
3226   def inputFiles = fileTree(dir: resourcesBuildDir)
3227   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
3228
3229   from inputFiles
3230   into outputDir
3231   def outputFiles = []
3232   rename { filename ->
3233     outputFiles += "${outputDir}/${filename}"
3234     null
3235   }
3236   preserve {
3237     include "**"
3238   }
3239   outputs.files outputFiles
3240   inputs.files inputFiles
3241 }
3242
3243
3244 task jalviewjsSyncSiteResources (type: Sync) {
3245   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_site_resource_dir}")
3246   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3247
3248   from inputFiles
3249   into outputDir
3250   def outputFiles = []
3251   rename { filename ->
3252     outputFiles += "${outputDir}/${filename}"
3253     null
3254   }
3255   preserve {
3256     include "**"
3257   }
3258   outputs.files outputFiles
3259   inputs.files inputFiles
3260 }
3261
3262
3263 task jalviewjsSyncBuildProperties (type: Sync) {
3264   dependsOn createBuildProperties
3265   def inputFiles = [file(buildProperties)]
3266   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
3267
3268   from inputFiles
3269   into outputDir
3270   def outputFiles = []
3271   rename { filename ->
3272     outputFiles += "${outputDir}/${filename}"
3273     null
3274   }
3275   preserve {
3276     include "**"
3277   }
3278   outputs.files outputFiles
3279   inputs.files inputFiles
3280 }
3281
3282
3283 task jalviewjsProjectImport(type: Exec) {
3284   dependsOn eclipseSetup
3285   dependsOn jalviewjsEclipsePaths
3286   dependsOn jalviewjsEclipseSetup
3287
3288   doFirst {
3289     // do not run a headless import when we claim to be in Eclipse
3290     if (IN_ECLIPSE) {
3291       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3292       throw new StopExecutionException("Not running headless import whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3293     } else {
3294       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3295     }
3296   }
3297
3298   //def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview/org.eclipse.jdt.core"
3299   def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview"
3300   executable(eclipseBinary)
3301   args(["-nosplash", "--launcher.suppressErrors", "-application", "com.seeq.eclipse.importprojects.headlessimport", "-data", eclipseWorkspace.getPath(), "-import", jalviewDirAbsolutePath])
3302   if (eclipseDebug) {
3303     args += "-debug"
3304   }
3305   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
3306   if (!IN_ECLIPSE) {
3307     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
3308     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
3309   }
3310
3311   inputs.file("${jalviewDir}/.project")
3312   outputs.upToDateWhen { 
3313     file(projdir).exists()
3314   }
3315 }
3316
3317
3318 task jalviewjsTranspile(type: Exec) {
3319   dependsOn jalviewjsEclipseSetup 
3320   dependsOn jalviewjsProjectImport
3321   dependsOn jalviewjsEclipsePaths
3322   if (!IN_ECLIPSE) {
3323     dependsOn jalviewjsEnableAltFileProperty
3324   }
3325
3326   doFirst {
3327     // do not run a headless transpile when we claim to be in Eclipse
3328     if (IN_ECLIPSE) {
3329       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3330       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3331     } else {
3332       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3333     }
3334   }
3335
3336   executable(eclipseBinary)
3337   args(["-nosplash", "--launcher.suppressErrors", "-application", "org.eclipse.jdt.apt.core.aptBuild", "-data", eclipseWorkspace, "-${jalviewjs_eclipse_build_arg}", eclipse_project_name ])
3338   if (eclipseDebug) {
3339     args += "-debug"
3340   }
3341   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
3342   if (!IN_ECLIPSE) {
3343     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
3344     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
3345   }
3346
3347   def stdout
3348   def stderr
3349   doFirst {
3350     stdout = new ByteArrayOutputStream()
3351     stderr = new ByteArrayOutputStream()
3352
3353     def logOutFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}"
3354     def logOutFile = file(logOutFileName)
3355     logOutFile.createNewFile()
3356     logOutFile.text = """ROOT: ${jalviewjs_eclipse_root}
3357 BINARY: ${eclipseBinary}
3358 VERSION: ${eclipseVersion}
3359 WORKSPACE: ${eclipseWorkspace}
3360 DEBUG: ${eclipseDebug}
3361 ----
3362 """
3363     def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
3364     // combine stdout and stderr
3365     def logErrFOS = logOutFOS
3366
3367     if (jalviewjs_j2s_to_console.equals("true")) {
3368       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3369         new org.apache.tools.ant.util.TeeOutputStream(
3370           logOutFOS,
3371           stdout),
3372         System.out)
3373       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3374         new org.apache.tools.ant.util.TeeOutputStream(
3375           logErrFOS,
3376           stderr),
3377         System.err)
3378     } else {
3379       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3380         logOutFOS,
3381         stdout)
3382       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3383         logErrFOS,
3384         stderr)
3385     }
3386   }
3387
3388   doLast {
3389     if (stdout.toString().contains("Error processing ")) {
3390       // j2s did not complete transpile
3391       //throw new TaskExecutionException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3392       if (jalviewjs_ignore_transpile_errors.equals("true")) {
3393         println("IGNORING TRANSPILE ERRORS")
3394         println("See eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3395       } else {
3396         throw new GradleException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3397       }
3398     }
3399   }
3400
3401   inputs.dir("${jalviewDir}/${sourceDir}")
3402   outputs.dir("${jalviewDir}/${jalviewjsTransferSiteJsDir}")
3403   outputs.upToDateWhen( { file("${jalviewDir}/${jalviewjsTransferSiteJsDir}${jalviewjs_server_resource}").exists() } )
3404 }
3405
3406
3407 def jalviewjsCallCore(String name, FileCollection list, String prefixFile, String suffixFile, String jsfile, String zjsfile, File logOutFile, Boolean logOutConsole) {
3408
3409   def stdout = new ByteArrayOutputStream()
3410   def stderr = new ByteArrayOutputStream()
3411
3412   def coreFile = file(jsfile)
3413   def msg = ""
3414   msg = "Creating core for ${name}...\nGenerating ${jsfile}"
3415   println(msg)
3416   logOutFile.createNewFile()
3417   logOutFile.append(msg+"\n")
3418
3419   def coreTop = file(prefixFile)
3420   def coreBottom = file(suffixFile)
3421   coreFile.getParentFile().mkdirs()
3422   coreFile.createNewFile()
3423   coreFile.write( coreTop.getText("UTF-8") )
3424   list.each {
3425     f ->
3426     if (f.exists()) {
3427       def t = f.getText("UTF-8")
3428       t.replaceAll("Clazz\\.([^_])","Clazz_${1}")
3429       coreFile.append( t )
3430     } else {
3431       msg = "...file '"+f.getPath()+"' does not exist, skipping"
3432       println(msg)
3433       logOutFile.append(msg+"\n")
3434     }
3435   }
3436   coreFile.append( coreBottom.getText("UTF-8") )
3437
3438   msg = "Generating ${zjsfile}"
3439   println(msg)
3440   logOutFile.append(msg+"\n")
3441   def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
3442   def logErrFOS = logOutFOS
3443
3444   javaexec {
3445     classpath = files(["${jalviewDir}/${jalviewjs_closure_compiler}"])
3446     main = "com.google.javascript.jscomp.CommandLineRunner"
3447     jvmArgs = [ "-Dfile.encoding=UTF-8" ]
3448     args = [ "--compilation_level", "SIMPLE_OPTIMIZATIONS", "--warning_level", "QUIET", "--charset", "UTF-8", "--js", jsfile, "--js_output_file", zjsfile ]
3449     maxHeapSize = "2g"
3450
3451     msg = "\nRunning '"+commandLine.join(' ')+"'\n"
3452     println(msg)
3453     logOutFile.append(msg+"\n")
3454
3455     if (logOutConsole) {
3456       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3457         new org.apache.tools.ant.util.TeeOutputStream(
3458           logOutFOS,
3459           stdout),
3460         standardOutput)
3461         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3462           new org.apache.tools.ant.util.TeeOutputStream(
3463             logErrFOS,
3464             stderr),
3465           System.err)
3466     } else {
3467       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3468         logOutFOS,
3469         stdout)
3470         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3471           logErrFOS,
3472           stderr)
3473     }
3474   }
3475   msg = "--"
3476   println(msg)
3477   logOutFile.append(msg+"\n")
3478 }
3479
3480
3481 task jalviewjsBuildAllCores {
3482   group "JalviewJS"
3483   description "Build the core js lib closures listed in the classlists dir"
3484   dependsOn jalviewjsTranspile
3485   dependsOn jalviewjsTransferUnzipSwingJs
3486
3487   def j2sDir = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${jalviewjs_j2s_subdir}"
3488   def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_j2s_subdir}"
3489   def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteLibDir}/${jalviewjs_j2s_subdir}"
3490   def jsDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_js_subdir}"
3491   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
3492   def prefixFile = "${jsDir}/core/coretop2.js"
3493   def suffixFile = "${jsDir}/core/corebottom2.js"
3494
3495   inputs.file prefixFile
3496   inputs.file suffixFile
3497
3498   def classlistFiles = []
3499   // add the classlists found int the jalviewjs_classlists_dir
3500   fileTree(dir: "${jalviewDir}/${jalviewjs_classlists_dir}", include: "*.txt").each {
3501     file ->
3502     def name = file.getName() - ".txt"
3503     classlistFiles += [
3504       'file': file,
3505       'name': name
3506     ]
3507   }
3508
3509   // _jmol and _jalview cores. Add any other peculiar classlist.txt files here
3510   //classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jmol}"), 'name': "_jvjmol" ]
3511   classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jalview}"), 'name': jalviewjsJalviewCoreName ]
3512
3513   jalviewjsCoreClasslists = []
3514
3515   classlistFiles.each {
3516     hash ->
3517
3518     def file = hash['file']
3519     if (! file.exists()) {
3520       //println("...classlist file '"+file.getPath()+"' does not exist, skipping")
3521       return false // this is a "continue" in groovy .each closure
3522     }
3523     def name = hash['name']
3524     if (name == null) {
3525       name = file.getName() - ".txt"
3526     }
3527
3528     def filelist = []
3529     file.eachLine {
3530       line ->
3531         filelist += line
3532     }
3533     def list = fileTree(dir: j2sDir, includes: filelist)
3534
3535     def jsfile = "${outputDir}/core${name}.js"
3536     def zjsfile = "${outputDir}/core${name}.z.js"
3537
3538     jalviewjsCoreClasslists += [
3539       'jsfile': jsfile,
3540       'zjsfile': zjsfile,
3541       'list': list,
3542       'name': name
3543     ]
3544
3545     inputs.file(file)
3546     inputs.files(list)
3547     outputs.file(jsfile)
3548     outputs.file(zjsfile)
3549   }
3550   
3551   // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
3552   def stevesoftClasslistName = "_stevesoft"
3553   def stevesoftClasslist = [
3554     'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
3555     'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
3556     'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
3557     'name': stevesoftClasslistName
3558   ]
3559   jalviewjsCoreClasslists += stevesoftClasslist
3560   inputs.files(stevesoftClasslist['list'])
3561   outputs.file(stevesoftClasslist['jsfile'])
3562   outputs.file(stevesoftClasslist['zjsfile'])
3563
3564   // _all core
3565   def allClasslistName = "_all"
3566   def allJsFiles = fileTree(dir: j2sDir, include: "**/*.js")
3567   allJsFiles += fileTree(
3568     dir: libJ2sDir,
3569     include: "**/*.js",
3570     excludes: [
3571       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3572       "**/org/jmol/jvxl/readers/IsoIntersectFileReader.js",
3573       "**/org/jmol/export/JSExporter.js"
3574     ]
3575   )
3576   allJsFiles += fileTree(
3577     dir: swingJ2sDir,
3578     include: "**/*.js",
3579     excludes: [
3580       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3581       "**/sun/misc/Unsafe.js",
3582       "**/swingjs/jquery/jquery-editable-select.js",
3583       "**/swingjs/jquery/j2sComboBox.js",
3584       "**/sun/misc/FloatingDecimal.js"
3585     ]
3586   )
3587   def allClasslist = [
3588     'jsfile': "${outputDir}/core${allClasslistName}.js",
3589     'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
3590     'list': allJsFiles,
3591     'name': allClasslistName
3592   ]
3593   // not including this version of "all" core at the moment
3594   //jalviewjsCoreClasslists += allClasslist
3595   inputs.files(allClasslist['list'])
3596   outputs.file(allClasslist['jsfile'])
3597   outputs.file(allClasslist['zjsfile'])
3598
3599   doFirst {
3600     def logOutFile = file("${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_closure_stdout}")
3601     logOutFile.getParentFile().mkdirs()
3602     logOutFile.createNewFile()
3603     logOutFile.write(getDate("yyyy-MM-dd HH:mm:ss")+" jalviewjsBuildAllCores\n----\n")
3604
3605     jalviewjsCoreClasslists.each {
3606       jalviewjsCallCore(it.name, it.list, prefixFile, suffixFile, it.jsfile, it.zjsfile, logOutFile, jalviewjs_j2s_to_console.equals("true"))
3607     }
3608   }
3609
3610 }
3611
3612
3613 def jalviewjsPublishCoreTemplate(String coreName, String templateName, File inputFile, String outputFile) {
3614   copy {
3615     from inputFile
3616     into file(outputFile).getParentFile()
3617     rename { filename ->
3618       if (filename.equals(inputFile.getName())) {
3619         return file(outputFile).getName()
3620       }
3621       return null
3622     }
3623     filter(ReplaceTokens,
3624       beginToken: '_',
3625       endToken: '_',
3626       tokens: [
3627         'MAIN': '"'+main_class+'"',
3628         'CODE': "null",
3629         'NAME': jalviewjsJalviewTemplateName+" [core ${coreName}]",
3630         'COREKEY': jalviewjs_core_key,
3631         'CORENAME': coreName
3632       ]
3633     )
3634   }
3635 }
3636
3637
3638 task jalviewjsPublishCoreTemplates {
3639   dependsOn jalviewjsBuildAllCores
3640   def inputFileName = "${jalviewDir}/${j2s_coretemplate_html}"
3641   def inputFile = file(inputFileName)
3642   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3643
3644   def outputFiles = []
3645   jalviewjsCoreClasslists.each { cl ->
3646     def outputFile = "${outputDir}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
3647     cl['outputfile'] = outputFile
3648     outputFiles += outputFile
3649   }
3650
3651   doFirst {
3652     jalviewjsCoreClasslists.each { cl ->
3653       jalviewjsPublishCoreTemplate(cl.name, jalviewjsJalviewTemplateName, inputFile, cl.outputfile)
3654     }
3655   }
3656   inputs.file(inputFile)
3657   outputs.files(outputFiles)
3658 }
3659
3660
3661 task jalviewjsSyncCore (type: Sync) {
3662   dependsOn jalviewjsBuildAllCores
3663   dependsOn jalviewjsPublishCoreTemplates
3664   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
3665   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3666
3667   from inputFiles
3668   into outputDir
3669   def outputFiles = []
3670   rename { filename ->
3671     outputFiles += "${outputDir}/${filename}"
3672     null
3673   }
3674   preserve {
3675     include "**"
3676   }
3677   outputs.files outputFiles
3678   inputs.files inputFiles
3679 }
3680
3681
3682 // this Copy version of TransferSiteJs will delete anything else in the target dir
3683 task jalviewjsCopyTransferSiteJs(type: Copy) {
3684   dependsOn jalviewjsTranspile
3685   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3686   into "${jalviewDir}/${jalviewjsSiteDir}"
3687 }
3688
3689
3690 // this Sync version of TransferSite is used by buildship to keep the website automatically up to date when a file changes
3691 task jalviewjsSyncTransferSiteJs(type: Sync) {
3692   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3693   include "**/*.*"
3694   into "${jalviewDir}/${jalviewjsSiteDir}"
3695   preserve {
3696     include "**"
3697   }
3698 }
3699
3700
3701 jalviewjsSyncAllLibs.mustRunAfter jalviewjsCopyTransferSiteJs
3702 jalviewjsSyncResources.mustRunAfter jalviewjsCopyTransferSiteJs
3703 jalviewjsSyncSiteResources.mustRunAfter jalviewjsCopyTransferSiteJs
3704 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsCopyTransferSiteJs
3705
3706 jalviewjsSyncAllLibs.mustRunAfter jalviewjsSyncTransferSiteJs
3707 jalviewjsSyncResources.mustRunAfter jalviewjsSyncTransferSiteJs
3708 jalviewjsSyncSiteResources.mustRunAfter jalviewjsSyncTransferSiteJs
3709 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsSyncTransferSiteJs
3710
3711
3712 task jalviewjsPrepareSite {
3713   group "JalviewJS"
3714   description "Prepares the website folder including unzipping files and copying resources"
3715   dependsOn jalviewjsSyncAllLibs
3716   dependsOn jalviewjsSyncResources
3717   dependsOn jalviewjsSyncSiteResources
3718   dependsOn jalviewjsSyncBuildProperties
3719   dependsOn jalviewjsSyncCore
3720 }
3721
3722
3723 task jalviewjsBuildSite {
3724   group "JalviewJS"
3725   description "Builds the whole website including transpiled code"
3726   dependsOn jalviewjsCopyTransferSiteJs
3727   dependsOn jalviewjsPrepareSite
3728 }
3729
3730
3731 task cleanJalviewjsTransferSite {
3732   doFirst {
3733     delete "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3734     delete "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3735     delete "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3736     delete "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3737   }
3738 }
3739
3740
3741 task cleanJalviewjsSite {
3742   dependsOn cleanJalviewjsTransferSite
3743   doFirst {
3744     delete "${jalviewDir}/${jalviewjsSiteDir}"
3745   }
3746 }
3747
3748
3749 task jalviewjsSiteTar(type: Tar) {
3750   group "JalviewJS"
3751   description "Creates a tar.gz file for the website"
3752   dependsOn jalviewjsBuildSite
3753   def outputFilename = "jalviewjs-site-${JALVIEW_VERSION}.tar.gz"
3754   archiveFileName = outputFilename
3755
3756   compression Compression.GZIP
3757
3758   from "${jalviewDir}/${jalviewjsSiteDir}"
3759   into jalviewjs_site_dir // this is inside the tar file
3760
3761   inputs.dir("${jalviewDir}/${jalviewjsSiteDir}")
3762 }
3763
3764
3765 task jalviewjsServer {
3766   group "JalviewJS"
3767   def filename = "jalviewjsTest.html"
3768   description "Starts a webserver on localhost to test the website. See ${filename} to access local site on most recently used port."
3769   def htmlFile = "${jalviewDirAbsolutePath}/${filename}"
3770   doLast {
3771
3772     def factory
3773     try {
3774       def f = Class.forName("org.gradle.plugins.javascript.envjs.http.simple.SimpleHttpFileServerFactory")
3775       factory = f.newInstance()
3776     } catch (ClassNotFoundException e) {
3777       throw new GradleException("Unable to create SimpleHttpFileServerFactory")
3778     }
3779     def port = Integer.valueOf(jalviewjs_server_port)
3780     def start = port
3781     def running = false
3782     def url
3783     def jalviewjsServer
3784     while(port < start+1000 && !running) {
3785       try {
3786         def doc_root = new File("${jalviewDirAbsolutePath}/${jalviewjsSiteDir}")
3787         jalviewjsServer = factory.start(doc_root, port)
3788         running = true
3789         url = jalviewjsServer.getResourceUrl(jalviewjs_server_resource)
3790         println("SERVER STARTED with document root ${doc_root}.")
3791         println("Go to "+url+" . Run  gradle --stop  to stop (kills all gradle daemons).")
3792         println("For debug: "+url+"?j2sdebug")
3793         println("For verbose: "+url+"?j2sverbose")
3794       } catch (Exception e) {
3795         port++;
3796       }
3797     }
3798     def htmlText = """
3799       <p><a href="${url}">JalviewJS Test. &lt;${url}&gt;</a></p>
3800       <p><a href="${url}?j2sdebug">JalviewJS Test with debug. &lt;${url}?j2sdebug&gt;</a></p>
3801       <p><a href="${url}?j2sverbose">JalviewJS Test with verbose. &lt;${url}?j2sdebug&gt;</a></p>
3802       """
3803     jalviewjsCoreClasslists.each { cl ->
3804       def urlcore = jalviewjsServer.getResourceUrl(file(cl.outputfile).getName())
3805       htmlText += """
3806       <p><a href="${urlcore}">${jalviewjsJalviewTemplateName} [core ${cl.name}]. &lt;${urlcore}&gt;</a></p>
3807       """
3808       println("For core ${cl.name}: "+urlcore)
3809     }
3810
3811     file(htmlFile).text = htmlText
3812   }
3813
3814   outputs.file(htmlFile)
3815   outputs.upToDateWhen({false})
3816 }
3817
3818
3819 task cleanJalviewjsAll {
3820   group "JalviewJS"
3821   description "Delete all configuration and build artifacts to do with JalviewJS build"
3822   dependsOn cleanJalviewjsSite
3823   dependsOn jalviewjsEclipsePaths
3824   
3825   doFirst {
3826     delete "${jalviewDir}/${jalviewjsBuildDir}"
3827     delete "${jalviewDir}/${eclipse_bin_dir}"
3828     if (eclipseWorkspace != null && file(eclipseWorkspace.getAbsolutePath()+"/.metadata").exists()) {
3829       delete file(eclipseWorkspace.getAbsolutePath()+"/.metadata")
3830     }
3831     delete jalviewjsJ2sAltSettingsFileName
3832   }
3833
3834   outputs.upToDateWhen( { false } )
3835 }
3836
3837
3838 task jalviewjsIDE_checkJ2sPlugin {
3839   group "00 JalviewJS in Eclipse"
3840   description "Compare the swingjs/net.sf.j2s.core(-j11)?.jar file with the Eclipse IDE's plugin version (found in the 'dropins' dir)"
3841
3842   doFirst {
3843     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
3844     def j2sPluginFile = file(j2sPlugin)
3845     def eclipseHome = System.properties["eclipse.home.location"]
3846     if (eclipseHome == null || ! IN_ECLIPSE) {
3847       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. Skipping J2S Plugin Check.")
3848     }
3849     def eclipseJ2sPluginDirs = [ "${eclipseHome}/dropins" ]
3850     def altPluginsDir = System.properties["org.eclipse.equinox.p2.reconciler.dropins.directory"]
3851     if (altPluginsDir != null && file(altPluginsDir).exists()) {
3852       eclipseJ2sPluginDirs += altPluginsDir
3853     }
3854     def foundPlugin = false
3855     def j2sPluginFileName = j2sPluginFile.getName()
3856     def eclipseJ2sPlugin
3857     def eclipseJ2sPluginFile
3858     eclipseJ2sPluginDirs.any { dir ->
3859       eclipseJ2sPlugin = "${dir}/${j2sPluginFileName}"
3860       eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
3861       if (eclipseJ2sPluginFile.exists()) {
3862         foundPlugin = true
3863         return true
3864       }
3865     }
3866     if (!foundPlugin) {
3867       def msg = "Eclipse J2S Plugin is not installed (could not find '${j2sPluginFileName}' in\n"+eclipseJ2sPluginDirs.join("\n")+"\n)\nTry running task jalviewjsIDE_copyJ2sPlugin"
3868       System.err.println(msg)
3869       throw new StopExecutionException(msg)
3870     }
3871
3872     def digest = MessageDigest.getInstance("MD5")
3873
3874     digest.update(j2sPluginFile.text.bytes)
3875     def j2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
3876
3877     digest.update(eclipseJ2sPluginFile.text.bytes)
3878     def eclipseJ2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
3879      
3880     if (j2sPluginMd5 != eclipseJ2sPluginMd5) {
3881       def msg = "WARNING! Eclipse J2S Plugin '${eclipseJ2sPlugin}' is different to this commit's version '${j2sPlugin}'"
3882       System.err.println(msg)
3883       throw new StopExecutionException(msg)
3884     } else {
3885       def msg = "Eclipse J2S Plugin '${eclipseJ2sPlugin}' is the same as '${j2sPlugin}' (this is good)"
3886       println(msg)
3887     }
3888   }
3889 }
3890
3891 task jalviewjsIDE_copyJ2sPlugin {
3892   group "00 JalviewJS in Eclipse"
3893   description "Copy the swingjs/net.sf.j2s.core(-j11)?.jar file into the Eclipse IDE's 'dropins' dir"
3894
3895   doFirst {
3896     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
3897     def j2sPluginFile = file(j2sPlugin)
3898     def eclipseHome = System.properties["eclipse.home.location"]
3899     if (eclipseHome == null || ! IN_ECLIPSE) {
3900       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. NOT copying J2S Plugin.")
3901     }
3902     def eclipseJ2sPlugin = "${eclipseHome}/dropins/${j2sPluginFile.getName()}"
3903     def eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
3904     def msg = "WARNING! Copying this commit's j2s plugin '${j2sPlugin}' to Eclipse J2S Plugin '${eclipseJ2sPlugin}'\n* May require an Eclipse restart"
3905     System.err.println(msg)
3906     copy {
3907       from j2sPlugin
3908       eclipseJ2sPluginFile.getParentFile().mkdirs()
3909       into eclipseJ2sPluginFile.getParent()
3910     }
3911   }
3912 }
3913
3914
3915 task jalviewjsIDE_j2sFile {
3916   group "00 JalviewJS in Eclipse"
3917   description "Creates the .j2s file"
3918   dependsOn jalviewjsCreateJ2sSettings
3919 }
3920
3921
3922 task jalviewjsIDE_SyncCore {
3923   group "00 JalviewJS in Eclipse"
3924   description "Build the core js lib closures listed in the classlists dir and publish core html from template"
3925   dependsOn jalviewjsSyncCore
3926 }
3927
3928
3929 task jalviewjsIDE_SyncSiteAll {
3930   dependsOn jalviewjsSyncAllLibs
3931   dependsOn jalviewjsSyncResources
3932   dependsOn jalviewjsSyncSiteResources
3933   dependsOn jalviewjsSyncBuildProperties
3934 }
3935
3936
3937 cleanJalviewjsTransferSite.mustRunAfter jalviewjsIDE_SyncSiteAll
3938
3939
3940 task jalviewjsIDE_PrepareSite {
3941   group "00 JalviewJS in Eclipse"
3942   description "Sync libs and resources to site dir, but not closure cores"
3943
3944   dependsOn jalviewjsIDE_SyncSiteAll
3945   //dependsOn cleanJalviewjsTransferSite // not sure why this clean is here -- will slow down a re-run of this task
3946 }
3947
3948
3949 task jalviewjsIDE_AssembleSite {
3950   group "00 JalviewJS in Eclipse"
3951   description "Assembles unzipped supporting zipfiles, resources, site resources and closure cores into the Eclipse transpiled site"
3952   dependsOn jalviewjsPrepareSite
3953 }
3954
3955
3956 task jalviewjsIDE_SiteClean {
3957   group "00 JalviewJS in Eclipse"
3958   description "Deletes the Eclipse transpiled site"
3959   dependsOn cleanJalviewjsSite
3960 }
3961
3962
3963 task jalviewjsIDE_Server {
3964   group "00 JalviewJS in Eclipse"
3965   description "Starts a webserver on localhost to test the website"
3966   dependsOn jalviewjsServer
3967 }
3968
3969
3970 // buildship runs this at import or gradle refresh
3971 task eclipseSynchronizationTask {
3972   //dependsOn eclipseSetup
3973   dependsOn createBuildProperties
3974   if (J2S_ENABLED) {
3975     dependsOn jalviewjsIDE_j2sFile
3976     dependsOn jalviewjsIDE_checkJ2sPlugin
3977     dependsOn jalviewjsIDE_PrepareSite
3978   }
3979 }
3980
3981
3982 // buildship runs this at build time or project refresh
3983 task eclipseAutoBuildTask {
3984   //dependsOn jalviewjsIDE_checkJ2sPlugin
3985   //dependsOn jalviewjsIDE_PrepareSite
3986 }
3987
3988
3989 task jalviewjs {
3990   group "JalviewJS"
3991   description "Build the site"
3992   dependsOn jalviewjsBuildSite
3993 }
3994