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