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