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