JAL-4004 gradle releasesTemplates builds releases.html
[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 task copyHelp(type: Copy) {
1203   def inputDir = helpSourceDir
1204   def outputDir = "${helpBuildDir}/${help_dir}"
1205   from(inputDir) {
1206     include('**/*.txt')
1207     include('**/*.md')
1208     include('**/*.html')
1209     include('**/*.hs')
1210     include('**/*.xml')
1211     include('**/*.jhm')
1212     filter(ReplaceTokens,
1213       beginToken: '$$',
1214       endToken: '$$',
1215       tokens: [
1216         'Version-Rel': JALVIEW_VERSION,
1217         'Year-Rel': getDate("yyyy")
1218       ]
1219     )
1220   }
1221   from(inputDir) {
1222     exclude('**/*.txt')
1223     exclude('**/*.md')
1224     exclude('**/*.html')
1225     exclude('**/*.hs')
1226     exclude('**/*.xml')
1227     exclude('**/*.jhm')
1228   }
1229   into outputDir
1230
1231   inputs.dir(inputDir)
1232   outputs.files(helpFile)
1233   outputs.dir(outputDir)
1234 }
1235
1236
1237 task copyResources(type: Copy) {
1238   group = "build"
1239   description = "Copy (and make text substitutions in) the resources dir to the build area"
1240
1241   def inputDir = resourceDir
1242   def outputDir = resourcesBuildDir
1243   from(inputDir) {
1244     include('**/*.txt')
1245     include('**/*.md')
1246     include('**/*.html')
1247     include('**/*.xml')
1248     filter(ReplaceTokens,
1249       beginToken: '$$',
1250       endToken: '$$',
1251       tokens: [
1252         'Version-Rel': JALVIEW_VERSION,
1253         'Year-Rel': getDate("yyyy")
1254       ]
1255     )
1256   }
1257   from(inputDir) {
1258     exclude('**/*.txt')
1259     exclude('**/*.md')
1260     exclude('**/*.html')
1261     exclude('**/*.xml')
1262   }
1263   into outputDir
1264
1265   inputs.dir(inputDir)
1266   outputs.dir(outputDir)
1267 }
1268
1269 task copyChannelResources(type: Copy) {
1270   dependsOn copyResources
1271   group = "build"
1272   description = "Copy the channel resources dir to the build resources area"
1273
1274   def inputDir = "${channelDir}/${resource_dir}"
1275   def outputDir = resourcesBuildDir
1276   from inputDir
1277   into outputDir
1278
1279   inputs.dir(inputDir)
1280   outputs.dir(outputDir)
1281 }
1282
1283 task createBuildProperties(type: WriteProperties) {
1284   dependsOn copyResources
1285   group = "build"
1286   description = "Create the ${buildProperties} file"
1287   
1288   inputs.dir(sourceDir)
1289   inputs.dir(resourcesBuildDir)
1290   outputFile (buildProperties)
1291   // taking time specific comment out to allow better incremental builds
1292   comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd HH:mm:ss")
1293   //comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd")
1294   property "BUILD_DATE", getDate("HH:mm:ss dd MMMM yyyy")
1295   property "VERSION", JALVIEW_VERSION
1296   property "INSTALLATION", INSTALLATION+" git-commit:"+gitHash+" ["+gitBranch+"]"
1297   if (getdownSetAppBaseProperty) {
1298     property "GETDOWNAPPBASE", getdownAppBase
1299     property "GETDOWNAPPDISTDIR", getdownAppDistDir
1300   }
1301   outputs.file(outputFile)
1302 }
1303
1304
1305 task buildIndices(type: JavaExec) {
1306   dependsOn copyHelp
1307   classpath = sourceSets.main.compileClasspath
1308   main = "com.sun.java.help.search.Indexer"
1309   workingDir = "${helpBuildDir}/${help_dir}"
1310   def argDir = "html"
1311   args = [ argDir ]
1312   inputs.dir("${workingDir}/${argDir}")
1313
1314   outputs.dir("${classesDir}/doc")
1315   outputs.dir("${classesDir}/help")
1316   outputs.file("${workingDir}/JavaHelpSearch/DOCS")
1317   outputs.file("${workingDir}/JavaHelpSearch/DOCS.TAB")
1318   outputs.file("${workingDir}/JavaHelpSearch/OFFSETS")
1319   outputs.file("${workingDir}/JavaHelpSearch/POSITIONS")
1320   outputs.file("${workingDir}/JavaHelpSearch/SCHEMA")
1321   outputs.file("${workingDir}/JavaHelpSearch/TMAP")
1322 }
1323
1324 task buildResources {
1325   dependsOn copyResources
1326   dependsOn copyChannelResources
1327   dependsOn createBuildProperties
1328 }
1329
1330 task prepare {
1331   dependsOn buildResources
1332   dependsOn copyDocs
1333   dependsOn copyHelp
1334   dependsOn convertMdFiles
1335   dependsOn buildIndices
1336 }
1337
1338
1339 compileJava.dependsOn prepare
1340 run.dependsOn compileJava
1341 //run.dependsOn prepare
1342
1343
1344 //testReportDirName = "test-reports" // note that test workingDir will be $jalviewDir
1345 test {
1346   dependsOn prepare
1347
1348   if (useClover) {
1349     dependsOn cloverClasses
1350    } else { //?
1351     dependsOn compileJava //?
1352   }
1353
1354   useTestNG() {
1355     includeGroups testng_groups
1356     excludeGroups testng_excluded_groups
1357     preserveOrder true
1358     useDefaultListeners=true
1359   }
1360
1361   maxHeapSize = "1024m"
1362
1363   workingDir = jalviewDir
1364   def testLaf = project.findProperty("test_laf")
1365   if (testLaf != null) {
1366     println("Setting Test LaF to '${testLaf}'")
1367     systemProperty "laf", testLaf
1368   }
1369   def testHiDPIScale = project.findProperty("test_HiDPIScale")
1370   if (testHiDPIScale != null) {
1371     println("Setting Test HiDPI Scale to '${testHiDPIScale}'")
1372     systemProperty "sun.java2d.uiScale", testHiDPIScale
1373   }
1374   sourceCompatibility = compile_source_compatibility
1375   targetCompatibility = compile_target_compatibility
1376   jvmArgs += additional_compiler_args
1377
1378   doFirst {
1379     if (useClover) {
1380       println("Running tests " + (useClover?"WITH":"WITHOUT") + " clover")
1381     }
1382   }
1383 }
1384
1385
1386 task compileLinkCheck(type: JavaCompile) {
1387   options.fork = true
1388   classpath = files("${jalviewDir}/${utils_dir}")
1389   destinationDir = file("${jalviewDir}/${utils_dir}")
1390   source = fileTree(dir: "${jalviewDir}/${utils_dir}", include: ["HelpLinksChecker.java", "BufferedLineReader.java"])
1391
1392   inputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.java")
1393   inputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.java")
1394   outputs.file("${jalviewDir}/${utils_dir}/HelpLinksChecker.class")
1395   outputs.file("${jalviewDir}/${utils_dir}/BufferedLineReader.class")
1396 }
1397
1398
1399 task linkCheck(type: JavaExec) {
1400   dependsOn prepare
1401   dependsOn compileLinkCheck
1402
1403   def helpLinksCheckerOutFile = file("${jalviewDir}/${utils_dir}/HelpLinksChecker.out")
1404   classpath = files("${jalviewDir}/${utils_dir}")
1405   main = "HelpLinksChecker"
1406   workingDir = jalviewDir
1407   args = [ "${helpBuildDir}/${help_dir}", "-nointernet" ]
1408
1409   def outFOS = new FileOutputStream(helpLinksCheckerOutFile, false) // false == don't append
1410   standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
1411     outFOS,
1412     System.out)
1413   errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
1414     outFOS,
1415     System.err)
1416
1417   inputs.dir(helpBuildDir)
1418   outputs.file(helpLinksCheckerOutFile)
1419 }
1420
1421
1422 // import the pubhtmlhelp target
1423 ant.properties.basedir = "${jalviewDir}"
1424 ant.properties.helpBuildDir = "${helpBuildDir}/${help_dir}"
1425 ant.importBuild "${utils_dir}/publishHelp.xml"
1426
1427
1428 task cleanPackageDir(type: Delete) {
1429   doFirst {
1430     delete fileTree(dir: "${jalviewDir}/${package_dir}", include: "*.jar")
1431   }
1432 }
1433
1434
1435 jar {
1436   dependsOn prepare
1437   dependsOn linkCheck
1438
1439   manifest {
1440     attributes "Main-Class": main_class,
1441     "Permissions": "all-permissions",
1442     "Application-Name": applicationName,
1443     "Codebase": application_codebase,
1444     "Implementation-Version": JALVIEW_VERSION
1445   }
1446
1447   def outputDir = "${jalviewDir}/${package_dir}"
1448   destinationDirectory = file(outputDir)
1449   archiveFileName = rootProject.name+".jar"
1450   duplicatesStrategy "EXCLUDE"
1451
1452
1453   exclude "cache*/**"
1454   exclude "*.jar"
1455   exclude "*.jar.*"
1456   exclude "**/*.jar"
1457   exclude "**/*.jar.*"
1458
1459   inputs.dir(sourceSets.main.java.outputDir)
1460   sourceSets.main.resources.srcDirs.each{ dir ->
1461     inputs.dir(dir)
1462   }
1463   outputs.file("${outputDir}/${archiveFileName}")
1464 }
1465
1466
1467 task copyJars(type: Copy) {
1468   from fileTree(dir: classesDir, include: "**/*.jar").files
1469   into "${jalviewDir}/${package_dir}"
1470 }
1471
1472
1473 // doing a Sync instead of Copy as Copy doesn't deal with "outputs" very well
1474 task syncJars(type: Sync) {
1475   dependsOn jar
1476   from fileTree(dir: "${jalviewDir}/${libDistDir}", include: "**/*.jar").files
1477   into "${jalviewDir}/${package_dir}"
1478   preserve {
1479     include jar.archiveFileName.getOrNull()
1480   }
1481 }
1482
1483
1484 task makeDist {
1485   group = "build"
1486   description = "Put all required libraries in dist"
1487   // order of "cleanPackageDir", "copyJars", "jar" important!
1488   jar.mustRunAfter cleanPackageDir
1489   syncJars.mustRunAfter cleanPackageDir
1490   dependsOn cleanPackageDir
1491   dependsOn syncJars
1492   dependsOn jar
1493   outputs.dir("${jalviewDir}/${package_dir}")
1494 }
1495
1496
1497 task cleanDist {
1498   dependsOn cleanPackageDir
1499   dependsOn cleanTest
1500   dependsOn clean
1501 }
1502
1503
1504 shadowJar {
1505   group = "distribution"
1506   description = "Create a single jar file with all dependency libraries merged. Can be run with java -jar"
1507   if (buildDist) {
1508     dependsOn makeDist
1509   }
1510   from ("${jalviewDir}/${libDistDir}") {
1511     include("*.jar")
1512   }
1513   manifest {
1514     attributes "Implementation-Version": JALVIEW_VERSION,
1515     "Application-Name": applicationName
1516   }
1517
1518   duplicatesStrategy "INCLUDE"
1519
1520   mainClassName = shadow_jar_main_class
1521   mergeServiceFiles()
1522   classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
1523   minimize()
1524 }
1525
1526
1527 task getdownWebsite() {
1528   group = "distribution"
1529   description = "Create the getdown minimal app folder, and website folder for this version of jalview. Website folder also used for offline app installer"
1530   if (buildDist) {
1531     dependsOn makeDist
1532   }
1533
1534   def getdownWebsiteResourceFilenames = []
1535   def getdownResourceDir = getdownResourceDir
1536   def getdownResourceFilenames = []
1537
1538   doFirst {
1539     // clean the getdown website and files dir before creating getdown folders
1540     delete getdownAppBaseDir
1541     delete getdownFilesDir
1542
1543     copy {
1544       from buildProperties
1545       rename(file(buildProperties).getName(), getdown_build_properties)
1546       into getdownAppDir
1547     }
1548     getdownWebsiteResourceFilenames += "${getdownAppDistDir}/${getdown_build_properties}"
1549
1550     copy {
1551       from channelPropsFile
1552       into getdownAppBaseDir
1553     }
1554     getdownWebsiteResourceFilenames += file(channelPropsFile).getName()
1555
1556     // set some getdownTxt_ properties then go through all properties looking for getdownTxt_...
1557     def props = project.properties.sort { it.key }
1558     if (getdownAltJavaMinVersion != null && getdownAltJavaMinVersion.length() > 0) {
1559       props.put("getdown_txt_java_min_version", getdownAltJavaMinVersion)
1560     }
1561     if (getdownAltJavaMaxVersion != null && getdownAltJavaMaxVersion.length() > 0) {
1562       props.put("getdown_txt_java_max_version", getdownAltJavaMaxVersion)
1563     }
1564     if (getdownAltMultiJavaLocation != null && getdownAltMultiJavaLocation.length() > 0) {
1565       props.put("getdown_txt_multi_java_location", getdownAltMultiJavaLocation)
1566     }
1567     if (getdownImagesDir != null && file(getdownImagesDir).exists()) {
1568       props.put("getdown_txt_ui.background_image", "${getdownImagesDir}/${getdown_background_image}")
1569       props.put("getdown_txt_ui.instant_background_image", "${getdownImagesDir}/${getdown_instant_background_image}")
1570       props.put("getdown_txt_ui.error_background", "${getdownImagesDir}/${getdown_error_background}")
1571       props.put("getdown_txt_ui.progress_image", "${getdownImagesDir}/${getdown_progress_image}")
1572       props.put("getdown_txt_ui.icon", "${getdownImagesDir}/${getdown_icon}")
1573       props.put("getdown_txt_ui.mac_dock_icon", "${getdownImagesDir}/${getdown_mac_dock_icon}")
1574     }
1575
1576     props.put("getdown_txt_title", jalview_name)
1577     props.put("getdown_txt_ui.name", applicationName)
1578
1579     // start with appbase
1580     getdownTextLines += "appbase = ${getdownAppBase}"
1581     props.each{ prop, val ->
1582       if (prop.startsWith("getdown_txt_") && val != null) {
1583         if (prop.startsWith("getdown_txt_multi_")) {
1584           def key = prop.substring(18)
1585           val.split(",").each{ v ->
1586             def line = "${key} = ${v}"
1587             getdownTextLines += line
1588           }
1589         } else {
1590           // file values rationalised
1591           if (val.indexOf('/') > -1 || prop.startsWith("getdown_txt_resource")) {
1592             def r = null
1593             if (val.indexOf('/') == 0) {
1594               // absolute path
1595               r = file(val)
1596             } else if (val.indexOf('/') > 0) {
1597               // relative path (relative to jalviewDir)
1598               r = file( "${jalviewDir}/${val}" )
1599             }
1600             if (r.exists()) {
1601               val = "${getdown_resource_dir}/" + r.getName()
1602               getdownWebsiteResourceFilenames += val
1603               getdownResourceFilenames += r.getPath()
1604             }
1605           }
1606           if (! prop.startsWith("getdown_txt_resource")) {
1607             def line = prop.substring(12) + " = ${val}"
1608             getdownTextLines += line
1609           }
1610         }
1611       }
1612     }
1613
1614     getdownWebsiteResourceFilenames.each{ filename ->
1615       getdownTextLines += "resource = ${filename}"
1616     }
1617     getdownResourceFilenames.each{ filename ->
1618       copy {
1619         from filename
1620         into getdownResourceDir
1621       }
1622     }
1623     
1624     def getdownWrapperScripts = [ getdown_bash_wrapper_script, getdown_powershell_wrapper_script, getdown_batch_wrapper_script ]
1625     getdownWrapperScripts.each{ script ->
1626       def s = file( "${jalviewDir}/utils/getdown/${getdown_wrapper_script_dir}/${script}" )
1627       if (s.exists()) {
1628         copy {
1629           from s
1630           into "${getdownAppBaseDir}/${getdown_wrapper_script_dir}"
1631         }
1632         getdownTextLines += "resource = ${getdown_wrapper_script_dir}/${script}"
1633       }
1634     }
1635
1636     def codeFiles = []
1637     fileTree(file(package_dir)).each{ f ->
1638       if (f.isDirectory()) {
1639         def files = fileTree(dir: f, include: ["*"]).getFiles()
1640         codeFiles += files
1641       } else if (f.exists()) {
1642         codeFiles += f
1643       }
1644     }
1645     def jalviewJar = jar.archiveFileName.getOrNull()
1646     // put jalview.jar first for CLASSPATH and .properties files reasons
1647     codeFiles.sort{a, b -> ( a.getName() == jalviewJar ? -1 : ( b.getName() == jalviewJar ? 1 : a <=> b ) ) }.each{f ->
1648       def name = f.getName()
1649       def line = "code = ${getdownAppDistDir}/${name}"
1650       getdownTextLines += line
1651       copy {
1652         from f.getPath()
1653         into getdownAppDir
1654       }
1655     }
1656
1657     // NOT USING MODULES YET, EVERYTHING SHOULD BE IN dist
1658     /*
1659     if (JAVA_VERSION.equals("11")) {
1660     def j11libFiles = fileTree(dir: "${jalviewDir}/${j11libDir}", include: ["*.jar"]).getFiles()
1661     j11libFiles.sort().each{f ->
1662     def name = f.getName()
1663     def line = "code = ${getdown_j11lib_dir}/${name}"
1664     getdownTextLines += line
1665     copy {
1666     from f.getPath()
1667     into getdownJ11libDir
1668     }
1669     }
1670     }
1671      */
1672
1673     // 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.
1674     //getdownTextLines += "class = " + file(getdownLauncher).getName()
1675     getdownTextLines += "resource = ${getdown_launcher_new}"
1676     getdownTextLines += "class = ${main_class}"
1677     // Not setting these properties in general so that getdownappbase and getdowndistdir will default to release version in jalview.bin.Cache
1678     if (getdownSetAppBaseProperty) {
1679       getdownTextLines += "jvmarg = -Dgetdowndistdir=${getdownAppDistDir}"
1680       getdownTextLines += "jvmarg = -Dgetdownappbase=${getdownAppBase}"
1681     }
1682
1683     def getdownTxt = file("${getdownAppBaseDir}/getdown.txt")
1684     getdownTxt.write(getdownTextLines.join("\n"))
1685
1686     getdownLaunchJvl = getdown_launch_jvl_name + ( (jvlChannelName != null && jvlChannelName.length() > 0)?"-${jvlChannelName}":"" ) + ".jvl"
1687     def launchJvl = file("${getdownAppBaseDir}/${getdownLaunchJvl}")
1688     launchJvl.write("appbase=${getdownAppBase}")
1689
1690     // files going into the getdown website dir: getdown-launcher.jar
1691     copy {
1692       from getdownLauncher
1693       rename(file(getdownLauncher).getName(), getdown_launcher_new)
1694       into getdownAppBaseDir
1695     }
1696
1697     // files going into the getdown website dir: getdown-launcher(-local).jar
1698     copy {
1699       from getdownLauncher
1700       if (file(getdownLauncher).getName() != getdown_launcher) {
1701         rename(file(getdownLauncher).getName(), getdown_launcher)
1702       }
1703       into getdownAppBaseDir
1704     }
1705
1706     // files going into the getdown website dir: ./install dir and files
1707     if (! (CHANNEL.startsWith("ARCHIVE") || CHANNEL.startsWith("DEVELOP"))) {
1708       copy {
1709         from getdownTxt
1710         from getdownLauncher
1711         from "${getdownAppDir}/${getdown_build_properties}"
1712         if (file(getdownLauncher).getName() != getdown_launcher) {
1713           rename(file(getdownLauncher).getName(), getdown_launcher)
1714         }
1715         into getdownInstallDir
1716       }
1717
1718       // and make a copy in the getdown files dir (these are not downloaded by getdown)
1719       copy {
1720         from getdownInstallDir
1721         into getdownFilesInstallDir
1722       }
1723     }
1724
1725     // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
1726     copy {
1727       from getdownTxt
1728       from launchJvl
1729       from getdownLauncher
1730       from "${getdownAppBaseDir}/${getdown_build_properties}"
1731       from "${getdownAppBaseDir}/${channel_props}"
1732       if (file(getdownLauncher).getName() != getdown_launcher) {
1733         rename(file(getdownLauncher).getName(), getdown_launcher)
1734       }
1735       into getdownFilesDir
1736     }
1737
1738     // and ./resource (not all downloaded by getdown)
1739     copy {
1740       from getdownResourceDir
1741       into "${getdownFilesDir}/${getdown_resource_dir}"
1742     }
1743   }
1744
1745   if (buildDist) {
1746     inputs.dir("${jalviewDir}/${package_dir}")
1747   }
1748   outputs.dir(getdownAppBaseDir)
1749   outputs.dir(getdownFilesDir)
1750 }
1751
1752
1753 // a helper task to allow getdown digest of any dir: `gradle getdownDigestDir -PDIGESTDIR=/path/to/my/random/getdown/dir
1754 task getdownDigestDir(type: JavaExec) {
1755   group "Help"
1756   description "A task to run a getdown Digest on a dir with getdown.txt. Provide a DIGESTDIR property via -PDIGESTDIR=..."
1757
1758   def digestDirPropertyName = "DIGESTDIR"
1759   doFirst {
1760     classpath = files(getdownLauncher)
1761     def digestDir = findProperty(digestDirPropertyName)
1762     if (digestDir == null) {
1763       throw new GradleException("Must provide a DIGESTDIR value to produce an alternative getdown digest")
1764     }
1765     args digestDir
1766   }
1767   main = "com.threerings.getdown.tools.Digester"
1768 }
1769
1770
1771 task getdownDigest(type: JavaExec) {
1772   group = "distribution"
1773   description = "Digest the getdown website folder"
1774   dependsOn getdownWebsite
1775   doFirst {
1776     classpath = files(getdownLauncher)
1777   }
1778   main = "com.threerings.getdown.tools.Digester"
1779   args getdownAppBaseDir
1780   inputs.dir(getdownAppBaseDir)
1781   outputs.file("${getdownAppBaseDir}/digest2.txt")
1782 }
1783
1784
1785 task getdown() {
1786   group = "distribution"
1787   description = "Create the minimal and full getdown app folder for installers and website and create digest file"
1788   dependsOn getdownDigest
1789   doLast {
1790     if (reportRsyncCommand) {
1791       def fromDir = getdownAppBaseDir + (getdownAppBaseDir.endsWith('/')?'':'/')
1792       def toDir = "${getdown_rsync_dest}/${getdownDir}" + (getdownDir.endsWith('/')?'':'/')
1793       println "LIKELY RSYNC COMMAND:"
1794       println "mkdir -p '$toDir'\nrsync -avh --delete '$fromDir' '$toDir'"
1795       if (RUNRSYNC == "true") {
1796         exec {
1797           commandLine "mkdir", "-p", toDir
1798         }
1799         exec {
1800           commandLine "rsync", "-avh", "--delete", fromDir, toDir
1801         }
1802       }
1803     }
1804   }
1805 }
1806
1807
1808 task getdownArchiveBuild() {
1809   group = "distribution"
1810   description = "Put files in the archive dir to go on the website"
1811
1812   dependsOn getdownWebsite
1813
1814   def v = "v${JALVIEW_VERSION_UNDERSCORES}"
1815   def vDir = "${getdownArchiveDir}/${v}"
1816   getdownFullArchiveDir = "${vDir}/getdown"
1817   getdownVersionLaunchJvl = "${vDir}/jalview-${v}.jvl"
1818
1819   def vAltDir = "alt_${v}"
1820   def archiveImagesDir = "${jalviewDir}/${channel_properties_dir}/old/images"
1821
1822   doFirst {
1823     // cleanup old "old" dir
1824     delete getdownArchiveDir
1825
1826     def getdownArchiveTxt = file("${getdownFullArchiveDir}/getdown.txt")
1827     getdownArchiveTxt.getParentFile().mkdirs()
1828     def getdownArchiveTextLines = []
1829     def getdownFullArchiveAppBase = "${getdownArchiveAppBase}${getdownArchiveAppBase.endsWith("/")?"":"/"}${v}/getdown/"
1830
1831     // the libdir
1832     copy {
1833       from "${getdownAppBaseDir}/${getdownAppDistDir}"
1834       into "${getdownFullArchiveDir}/${vAltDir}"
1835     }
1836
1837     getdownTextLines.each { line ->
1838       line = line.replaceAll("^(?<s>appbase\\s*=\\s*).*", '${s}'+getdownFullArchiveAppBase)
1839       line = line.replaceAll("^(?<s>(resource|code)\\s*=\\s*)${getdownAppDistDir}/", '${s}'+vAltDir+"/")
1840       line = line.replaceAll("^(?<s>ui.background_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background.png")
1841       line = line.replaceAll("^(?<s>ui.instant_background_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background_initialising.png")
1842       line = line.replaceAll("^(?<s>ui.error_background\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_background_error.png")
1843       line = line.replaceAll("^(?<s>ui.progress_image\\s*=\\s*).*\\.png", '${s}'+"${getdown_resource_dir}/jalview_archive_getdown_progress_bar.png")
1844       // remove the existing resource = resource/ or bin/ lines
1845       if (! line.matches("resource\\s*=\\s*(resource|bin)/.*")) {
1846         getdownArchiveTextLines += line
1847       }
1848     }
1849
1850     // the resource dir -- add these files as resource lines in getdown.txt
1851     copy {
1852       from "${archiveImagesDir}"
1853       into "${getdownFullArchiveDir}/${getdown_resource_dir}"
1854       eachFile { file ->
1855         getdownArchiveTextLines += "resource = ${getdown_resource_dir}/${file.getName()}"
1856       }
1857     }
1858
1859     getdownArchiveTxt.write(getdownArchiveTextLines.join("\n"))
1860
1861     def vLaunchJvl = file(getdownVersionLaunchJvl)
1862     vLaunchJvl.getParentFile().mkdirs()
1863     vLaunchJvl.write("appbase=${getdownFullArchiveAppBase}\n")
1864     def vLaunchJvlPath = vLaunchJvl.toPath().toAbsolutePath()
1865     def jvlLinkPath = file("${vDir}/jalview.jvl").toPath().toAbsolutePath()
1866     // for some reason filepath.relativize(fileInSameDirPath) gives a path to "../" which is wrong
1867     //java.nio.file.Files.createSymbolicLink(jvlLinkPath, jvlLinkPath.relativize(vLaunchJvlPath));
1868     java.nio.file.Files.createSymbolicLink(jvlLinkPath, java.nio.file.Paths.get(".",vLaunchJvl.getName()));
1869
1870     // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
1871     copy {
1872       from getdownLauncher
1873       from "${getdownAppBaseDir}/${getdownLaunchJvl}"
1874       from "${getdownAppBaseDir}/${getdown_launcher_new}"
1875       from "${getdownAppBaseDir}/${channel_props}"
1876       if (file(getdownLauncher).getName() != getdown_launcher) {
1877         rename(file(getdownLauncher).getName(), getdown_launcher)
1878       }
1879       into getdownFullArchiveDir
1880     }
1881
1882   }
1883 }
1884
1885 task getdownArchiveDigest(type: JavaExec) {
1886   group = "distribution"
1887   description = "Digest the getdown archive folder"
1888
1889   dependsOn getdownArchiveBuild
1890
1891   doFirst {
1892     classpath = files(getdownLauncher)
1893     args getdownFullArchiveDir
1894   }
1895   main = "com.threerings.getdown.tools.Digester"
1896   inputs.dir(getdownFullArchiveDir)
1897   outputs.file("${getdownFullArchiveDir}/digest2.txt")
1898 }
1899
1900 task getdownArchive() {
1901   group = "distribution"
1902   description = "Build the website archive dir with getdown digest"
1903
1904   dependsOn getdownArchiveBuild
1905   dependsOn getdownArchiveDigest
1906 }
1907
1908 tasks.withType(JavaCompile) {
1909         options.encoding = 'UTF-8'
1910 }
1911
1912
1913 clean {
1914   doFirst {
1915     delete getdownAppBaseDir
1916     delete getdownFilesDir
1917     delete getdownArchiveDir
1918   }
1919 }
1920
1921
1922 install4j {
1923   if (file(install4jHomeDir).exists()) {
1924     // good to go!
1925   } else if (file(System.getProperty("user.home")+"/buildtools/install4j").exists()) {
1926     install4jHomeDir = System.getProperty("user.home")+"/buildtools/install4j"
1927   } else if (file("/Applications/install4j.app/Contents/Resources/app").exists()) {
1928     install4jHomeDir = "/Applications/install4j.app/Contents/Resources/app"
1929   }
1930   installDir(file(install4jHomeDir))
1931
1932   mediaTypes = Arrays.asList(install4j_media_types.split(","))
1933 }
1934
1935
1936 task copyInstall4jTemplate {
1937   def install4jTemplateFile = file("${install4jDir}/${install4j_template}")
1938   def install4jFileAssociationsFile = file("${install4jDir}/${install4j_installer_file_associations}")
1939   inputs.file(install4jTemplateFile)
1940   inputs.file(install4jFileAssociationsFile)
1941   inputs.property("CHANNEL", { CHANNEL })
1942   outputs.file(install4jConfFile)
1943
1944   doLast {
1945     def install4jConfigXml = new XmlParser().parse(install4jTemplateFile)
1946
1947     // turn off code signing if no OSX_KEYPASS
1948     if (OSX_KEYPASS == "") {
1949       install4jConfigXml.'**'.codeSigning.each { codeSigning ->
1950         codeSigning.'@macEnabled' = "false"
1951       }
1952       install4jConfigXml.'**'.windows.each { windows ->
1953         windows.'@runPostProcessor' = "false"
1954       }
1955     }
1956
1957     // disable install screen for OSX dmg (for 2.11.2.0)
1958     install4jConfigXml.'**'.macosArchive.each { macosArchive -> 
1959       macosArchive.attributes().remove('executeSetupApp')
1960       macosArchive.attributes().remove('setupAppId')
1961     }
1962
1963     // turn off checksum creation for LOCAL channel
1964     def e = install4jConfigXml.application[0]
1965     e.'@createChecksums' = string(install4jCheckSums)
1966
1967     // put file association actions where placeholder action is
1968     def install4jFileAssociationsText = install4jFileAssociationsFile.text
1969     def fileAssociationActions = new XmlParser().parseText("<actions>${install4jFileAssociationsText}</actions>")
1970     install4jConfigXml.'**'.action.any { a -> // .any{} stops after the first one that returns true
1971       if (a.'@name' == 'EXTENSIONS_REPLACED_BY_GRADLE') {
1972         def parent = a.parent()
1973         parent.remove(a)
1974         fileAssociationActions.each { faa ->
1975             parent.append(faa)
1976         }
1977         // don't need to continue in .any loop once replacements have been made
1978         return true
1979       }
1980     }
1981
1982     // use Windows Program Group with Examples folder for RELEASE, and Program Group without Examples for everything else
1983     // NB we're deleting the /other/ one!
1984     // Also remove the examples subdir from non-release versions
1985     def customizedIdToDelete = "PROGRAM_GROUP_RELEASE"
1986     // 2.11.1.0 NOT releasing with the Examples folder in the Program Group
1987     if (false && CHANNEL=="RELEASE") { // remove 'false && ' to include Examples folder in RELEASE channel
1988       customizedIdToDelete = "PROGRAM_GROUP_NON_RELEASE"
1989     } else {
1990       // remove the examples subdir from Full File Set
1991       def files = install4jConfigXml.files[0]
1992       def fileset = files.filesets.fileset.find { fs -> fs.'@customizedId' == "FULL_FILE_SET" }
1993       def root = files.roots.root.find { r -> r.'@fileset' == fileset.'@id' }
1994       def mountPoint = files.mountPoints.mountPoint.find { mp -> mp.'@root' == root.'@id' }
1995       def dirEntry = files.entries.dirEntry.find { de -> de.'@mountPoint' == mountPoint.'@id' && de.'@subDirectory' == "examples" }
1996       dirEntry.parent().remove(dirEntry)
1997     }
1998     install4jConfigXml.'**'.action.any { a ->
1999       if (a.'@customizedId' == customizedIdToDelete) {
2000         def parent = a.parent()
2001         parent.remove(a)
2002         return true
2003       }
2004     }
2005
2006     // write install4j file
2007     install4jConfFile.text = XmlUtil.serialize(install4jConfigXml)
2008   }
2009 }
2010
2011
2012 clean {
2013   doFirst {
2014     delete install4jConfFile
2015   }
2016 }
2017
2018 task cleanInstallersDataFiles {
2019   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
2020   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
2021   def hugoDataJsonFile = file("${jalviewDir}/${install4jBuildDir}/installers-${JALVIEW_VERSION_UNDERSCORES}.json")
2022   doFirst {
2023     delete installersOutputTxt
2024     delete installersSha256
2025     delete hugoDataJsonFile
2026   }
2027 }
2028
2029 task installerFiles(type: com.install4j.gradle.Install4jTask) {
2030   group = "distribution"
2031   description = "Create the install4j installers"
2032   dependsOn getdown
2033   dependsOn copyInstall4jTemplate
2034   dependsOn cleanInstallersDataFiles
2035
2036   projectFile = install4jConfFile
2037
2038   // create an md5 for the input files to use as version for install4j conf file
2039   def digest = MessageDigest.getInstance("MD5")
2040   digest.update(
2041     (file("${install4jDir}/${install4j_template}").text + 
2042     file("${install4jDir}/${install4j_info_plist_file_associations}").text +
2043     file("${install4jDir}/${install4j_installer_file_associations}").text).bytes)
2044   def filesMd5 = new BigInteger(1, digest.digest()).toString(16)
2045   if (filesMd5.length() >= 8) {
2046     filesMd5 = filesMd5.substring(0,8)
2047   }
2048   def install4jTemplateVersion = "${JALVIEW_VERSION}_F${filesMd5}_C${gitHash}"
2049
2050   variables = [
2051     'JALVIEW_NAME': jalview_name,
2052     'JALVIEW_APPLICATION_NAME': applicationName,
2053     'JALVIEW_DIR': "../..",
2054     'OSX_KEYSTORE': OSX_KEYSTORE,
2055     'OSX_APPLEID': OSX_APPLEID,
2056     'OSX_ALTOOLPASS': OSX_ALTOOLPASS,
2057     'JSIGN_SH': JSIGN_SH,
2058     'JRE_DIR': getdown_app_dir_java,
2059     'INSTALLER_TEMPLATE_VERSION': install4jTemplateVersion,
2060     'JALVIEW_VERSION': JALVIEW_VERSION,
2061     'JAVA_MIN_VERSION': JAVA_MIN_VERSION,
2062     'JAVA_MAX_VERSION': JAVA_MAX_VERSION,
2063     'JAVA_VERSION': JAVA_VERSION,
2064     'JAVA_INTEGER_VERSION': JAVA_INTEGER_VERSION,
2065     'VERSION': JALVIEW_VERSION,
2066     'MACOS_JAVA_VM_DIR': macosJavaVMDir,
2067     'WINDOWS_JAVA_VM_DIR': windowsJavaVMDir,
2068     'LINUX_JAVA_VM_DIR': linuxJavaVMDir,
2069     'MACOS_JAVA_VM_TGZ': macosJavaVMTgz,
2070     'WINDOWS_JAVA_VM_TGZ': windowsJavaVMTgz,
2071     'LINUX_JAVA_VM_TGZ': linuxJavaVMTgz,
2072     'COPYRIGHT_MESSAGE': install4j_copyright_message,
2073     'BUNDLE_ID': install4jBundleId,
2074     'INTERNAL_ID': install4jInternalId,
2075     'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
2076     'MACOS_DMG_DS_STORE': install4jDMGDSStore,
2077     'MACOS_DMG_BG_IMAGE': install4jDMGBackgroundImage,
2078     'WRAPPER_LINK': getdownWrapperLink,
2079     'BASH_WRAPPER_SCRIPT': getdown_bash_wrapper_script,
2080     'POWERSHELL_WRAPPER_SCRIPT': getdown_powershell_wrapper_script,
2081     'WRAPPER_SCRIPT_BIN_DIR': getdown_wrapper_script_dir,
2082     'INSTALLER_NAME': install4jInstallerName,
2083     'INSTALL4J_UTILS_DIR': install4j_utils_dir,
2084     'GETDOWN_CHANNEL_DIR': getdownChannelDir,
2085     'GETDOWN_FILES_DIR': getdown_files_dir,
2086     'GETDOWN_RESOURCE_DIR': getdown_resource_dir,
2087     'GETDOWN_DIST_DIR': getdownAppDistDir,
2088     'GETDOWN_ALT_DIR': getdown_app_dir_alt,
2089     'GETDOWN_INSTALL_DIR': getdown_install_dir,
2090     'INFO_PLIST_FILE_ASSOCIATIONS_FILE': install4j_info_plist_file_associations,
2091     'BUILD_DIR': install4jBuildDir,
2092     'APPLICATION_CATEGORIES': install4j_application_categories,
2093     'APPLICATION_FOLDER': install4jApplicationFolder,
2094     'UNIX_APPLICATION_FOLDER': install4jUnixApplicationFolder,
2095     'EXECUTABLE_NAME': install4jExecutableName,
2096     'EXTRA_SCHEME': install4jExtraScheme,
2097     'MAC_ICONS_FILE': install4jMacIconsFile,
2098     'WINDOWS_ICONS_FILE': install4jWindowsIconsFile,
2099     'PNG_ICON_FILE': install4jPngIconFile,
2100     'BACKGROUND': install4jBackground,
2101
2102   ]
2103
2104   //println("INSTALL4J VARIABLES:")
2105   //variables.each{k,v->println("${k}=${v}")}
2106
2107   destination = "${jalviewDir}/${install4jBuildDir}"
2108   buildSelected = true
2109
2110   if (install4j_faster.equals("true") || CHANNEL.startsWith("LOCAL")) {
2111     faster = true
2112     disableSigning = true
2113     disableNotarization = true
2114   }
2115
2116   if (OSX_KEYPASS) {
2117     macKeystorePassword = OSX_KEYPASS
2118   } 
2119   
2120   if (OSX_ALTOOLPASS) {
2121     appleIdPassword = OSX_ALTOOLPASS
2122     disableNotarization = false
2123   } else {
2124     disableNotarization = true
2125   }
2126
2127   doFirst {
2128     println("Using projectFile "+projectFile)
2129     if (!disableNotarization) { println("Will notarize OSX App DMG") }
2130   }
2131   //verbose=true
2132
2133   inputs.dir(getdownAppBaseDir)
2134   inputs.file(install4jConfFile)
2135   inputs.file("${install4jDir}/${install4j_info_plist_file_associations}")
2136   inputs.dir(macosJavaVMDir)
2137   inputs.dir(windowsJavaVMDir)
2138   outputs.dir("${jalviewDir}/${install4j_build_dir}/${JAVA_VERSION}")
2139 }
2140
2141 def getDataHash(File myFile) {
2142   HashCode hash = Files.asByteSource(myFile).hash(Hashing.sha256())
2143   return myFile.exists()
2144   ? [
2145       "file" : myFile.getName(),
2146       "filesize" : myFile.length(),
2147       "sha256" : hash.toString()
2148     ]
2149   : null
2150 }
2151
2152 def writeDataJsonFile(File installersOutputTxt, File installersSha256, File dataJsonFile) {
2153   def hash = [
2154     "channel" : getdownChannelName,
2155     "date" : getDate("yyyy-MM-dd HH:mm:ss"),
2156     "git-commit" : "${gitHash} [${gitBranch}]",
2157     "version" : JALVIEW_VERSION
2158   ]
2159   // install4j installer files
2160   if (installersOutputTxt.exists()) {
2161     def idHash = [:]
2162     installersOutputTxt.readLines().each { def line ->
2163       if (line.startsWith("#")) {
2164         return;
2165       }
2166       line.replaceAll("\n","")
2167       def vals = line.split("\t")
2168       def filename = vals[3]
2169       def filesize = file(filename).length()
2170       filename = filename.replaceAll(/^.*\//, "")
2171       hash[vals[0]] = [ "id" : vals[0], "os" : vals[1], "name" : vals[2], "file" : filename, "filesize" : filesize ]
2172       idHash."${filename}" = vals[0]
2173     }
2174     if (install4jCheckSums && installersSha256.exists()) {
2175       installersSha256.readLines().each { def line ->
2176         if (line.startsWith("#")) {
2177           return;
2178         }
2179         line.replaceAll("\n","")
2180         def vals = line.split(/\s+\*?/)
2181         def filename = vals[1]
2182         def innerHash = (hash.(idHash."${filename}"))."sha256" = vals[0]
2183       }
2184     }
2185   }
2186
2187   [
2188     "JAR": shadowJar.archiveFile, // executable JAR
2189     "JVL": getdownVersionLaunchJvl, // version JVL
2190     "SOURCE": sourceDist.archiveFile // source TGZ
2191   ].each { key, value ->
2192     def file = file(value)
2193     if (file.exists()) {
2194       def fileHash = getDataHash(file)
2195       if (fileHash != null) {
2196         hash."${key}" = fileHash;
2197       }
2198     }
2199   }
2200   return dataJsonFile.write(new JsonBuilder(hash).toPrettyString())
2201 }
2202
2203 task staticMakeInstallersJsonFile {
2204   doFirst {
2205     def output = findProperty("i4j_output")
2206     def sha256 = findProperty("i4j_sha256")
2207     def json = findProperty("i4j_json")
2208     if (output == null || sha256 == null || json == null) {
2209       throw new GradleException("Must provide paths to all of output.txt, sha256sums, and output.json with '-Pi4j_output=... -Pi4j_sha256=... -Pi4j_json=...")
2210     }
2211     writeDataJsonFile(file(output), file(sha256), file(json))
2212   }
2213 }
2214
2215 task installers {
2216   dependsOn installerFiles
2217 }
2218
2219
2220 spotless {
2221   java {
2222     eclipse().configFile(eclipse_codestyle_file)
2223   }
2224 }
2225
2226 task createSourceReleaseProperties(type: WriteProperties) {
2227   group = "distribution"
2228   description = "Create the source RELEASE properties file"
2229   
2230   def sourceTarBuildDir = "${buildDir}/sourceTar"
2231   def sourceReleasePropertiesFile = "${sourceTarBuildDir}/RELEASE"
2232   outputFile (sourceReleasePropertiesFile)
2233
2234   doFirst {
2235     releaseProps.each{ key, val -> property key, val }
2236     property "git.branch", gitBranch
2237     property "git.hash", gitHash
2238   }
2239
2240   outputs.file(outputFile)
2241 }
2242
2243 task sourceDist(type: Tar) {
2244   group "distribution"
2245   description "Create a source .tar.gz file for distribution"
2246
2247   dependsOn createBuildProperties
2248   dependsOn convertMdFiles
2249   dependsOn eclipseAllPreferences
2250   dependsOn createSourceReleaseProperties
2251
2252
2253   def outputFileName = "${project.name}_${JALVIEW_VERSION_UNDERSCORES}.tar.gz"
2254   archiveFileName = outputFileName
2255   
2256   compression Compression.GZIP
2257   
2258   into project.name
2259
2260   def EXCLUDE_FILES=[
2261     "build/*",
2262     "bin/*",
2263     "test-output/",
2264     "test-reports",
2265     "tests",
2266     "clover*/*",
2267     ".*",
2268     "benchmarking/*",
2269     "**/.*",
2270     "*.class",
2271     "**/*.class","$j11modDir/**/*.jar","appletlib","**/*locales",
2272     "*locales/**",
2273     "utils/InstallAnywhere",
2274     "**/*.log",
2275     "RELEASE",
2276   ] 
2277   def PROCESS_FILES=[
2278     "AUTHORS",
2279     "CITATION",
2280     "FEATURETODO",
2281     "JAVA-11-README",
2282     "FEATURETODO",
2283     "LICENSE",
2284     "**/README",
2285     "THIRDPARTYLIBS",
2286     "TESTNG",
2287     "build.gradle",
2288     "gradle.properties",
2289     "**/*.java",
2290     "**/*.html",
2291     "**/*.xml",
2292     "**/*.gradle",
2293     "**/*.groovy",
2294     "**/*.properties",
2295     "**/*.perl",
2296     "**/*.sh",
2297   ]
2298   def INCLUDE_FILES=[
2299     ".classpath",
2300     ".settings/org.eclipse.buildship.core.prefs",
2301     ".settings/org.eclipse.jdt.core.prefs"
2302   ]
2303
2304   from(jalviewDir) {
2305     exclude (EXCLUDE_FILES)
2306     include (PROCESS_FILES)
2307     filter(ReplaceTokens,
2308       beginToken: '$$',
2309       endToken: '$$',
2310       tokens: [
2311         'Version-Rel': JALVIEW_VERSION,
2312         'Year-Rel': getDate("yyyy")
2313       ]
2314     )
2315   }
2316   from(jalviewDir) {
2317     exclude (EXCLUDE_FILES)
2318     exclude (PROCESS_FILES)
2319     exclude ("appletlib")
2320     exclude ("**/*locales")
2321     exclude ("*locales/**")
2322     exclude ("utils/InstallAnywhere")
2323
2324     exclude (getdown_files_dir)
2325     // getdown_website_dir and getdown_archive_dir moved to build/website/docroot/getdown
2326     //exclude (getdown_website_dir)
2327     //exclude (getdown_archive_dir)
2328
2329     // exluding these as not using jars as modules yet
2330     exclude ("${j11modDir}/**/*.jar")
2331   }
2332   from(jalviewDir) {
2333     include(INCLUDE_FILES)
2334   }
2335 //  from (jalviewDir) {
2336 //    // explicit includes for stuff that seemed to not get included
2337 //    include(fileTree("test/**/*."))
2338 //    exclude(EXCLUDE_FILES)
2339 //    exclude(PROCESS_FILES)
2340 //  }
2341
2342   from(file(buildProperties).getParent()) {
2343     include(file(buildProperties).getName())
2344     rename(file(buildProperties).getName(), "build_properties")
2345     filter({ line ->
2346       line.replaceAll("^INSTALLATION=.*\$","INSTALLATION=Source Release"+" git-commit\\\\:"+gitHash+" ["+gitBranch+"]")
2347     })
2348   }
2349
2350   def sourceTarBuildDir = "${buildDir}/sourceTar"
2351   from(sourceTarBuildDir) {
2352     // this includes the appended RELEASE properties file
2353   }
2354 }
2355
2356 task dataInstallersJson {
2357   group "website"
2358   description "Create the installers-VERSION.json data file for installer files created"
2359
2360   mustRunAfter installers
2361   mustRunAfter shadowJar
2362   mustRunAfter sourceDist
2363   mustRunAfter getdownArchive
2364
2365   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
2366   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
2367
2368   if (installersOutputTxt.exists()) {
2369     inputs.file(installersOutputTxt)
2370   }
2371   if (install4jCheckSums && installersSha256.exists()) {
2372     inputs.file(installersSha256)
2373   }
2374   [
2375     shadowJar.archiveFile, // executable JAR
2376     getdownVersionLaunchJvl, // version JVL
2377     sourceDist.archiveFile // source TGZ
2378   ].each { fileName ->
2379     if (file(fileName).exists()) {
2380       inputs.file(fileName)
2381     }
2382   }
2383
2384   outputs.file(hugoDataJsonFile)
2385
2386   doFirst {
2387     writeDataJsonFile(installersOutputTxt, installersSha256, hugoDataJsonFile)
2388   }
2389 }
2390
2391 def hugoTemplateSubstitutions(String input, Map extras=null) {
2392   def replacements = [
2393     DATE: getDate("yyyy-MM-dd"),
2394     CHANNEL: propertiesChannelName,
2395     APPLICATION_NAME: applicationName,
2396     GIT_HASH: gitHash,
2397     GIT_BRANCH: gitBranch,
2398     VERSION: JALVIEW_VERSION,
2399     JAVA_VERSION: JAVA_VERSION,
2400     VERSION_UNDERSCORES: JALVIEW_VERSION_UNDERSCORES,
2401     DRAFT: "false",
2402     JVL_HEADER: ""
2403   ]
2404   def output = input
2405   if (extras != null) {
2406     extras.each{ k, v ->
2407       output = output.replaceAll("__${k}__", ((v == null)?"":v))
2408     }
2409   }
2410   replacements.each{ k, v ->
2411     output = output.replaceAll("__${k}__", ((v == null)?"":v))
2412   }
2413   return output
2414 }
2415
2416 def mdFileComponents(File mdFile, def dateOnly=false) {
2417   def map = [:]
2418   def content = ""
2419   if (mdFile.exists()) {
2420     def inFrontMatter = false
2421     def firstLine = true
2422     mdFile.eachLine { line ->
2423       if (line.matches("---")) {
2424         def prev = inFrontMatter
2425         inFrontMatter = firstLine
2426         if (inFrontMatter != prev)
2427           return false
2428       }
2429       if (inFrontMatter) {
2430         def m = null
2431         if (m = line =~ /^date:\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/) {
2432           map["date"] = new Date().parse("yyyy-MM-dd HH:mm:ss", m[0][1])
2433         } else if (m = line =~ /^date:\s*(\d{4}-\d{2}-\d{2})/) {
2434           map["date"] = new Date().parse("yyyy-MM-dd", m[0][1])
2435         } else if (m = line =~ /^channel:\s*(\S+)/) {
2436           map["channel"] = m[0][1]
2437         } else if (m = line =~ /^version:\s*(\S+)/) {
2438           map["version"] = m[0][1]
2439         } else if (m = line =~ /^\s*([^:]+)\s*:\s*(\S.*)/) {
2440           map[ m[0][1] ] = m[0][2]
2441         }
2442         if (dateOnly && map["date"] != null) {
2443           return false
2444         }
2445       } else {
2446         if (dateOnly)
2447           return false
2448         content += line+"\n"
2449       }
2450       firstLine = false
2451     }
2452   }
2453   return dateOnly ? map["date"] : [map, content]
2454 }
2455
2456 task hugoTemplates {
2457   group "website"
2458   description "Create partially populated md pages for hugo website build"
2459
2460   def hugoTemplatesDir = file("${jalviewDir}/${hugo_templates_dir}")
2461   def hugoBuildDir = "${jalviewDir}/${hugo_build_dir}"
2462   def templateFiles = fileTree(dir: hugoTemplatesDir)
2463   def releaseMdFile = file("${jalviewDir}/${releases_dir}/release-${JALVIEW_VERSION_UNDERSCORES}.md")
2464   def whatsnewMdFile = file("${jalviewDir}/${whatsnew_dir}/whatsnew-${JALVIEW_VERSION_UNDERSCORES}.md")
2465   def oldJvlFile = file("${jalviewDir}/${hugo_old_jvl}")
2466   def jalviewjsFile = file("${jalviewDir}/${hugo_jalviewjs}")
2467
2468   doFirst {
2469     // specific release template for version archive
2470     def changes = ""
2471     def whatsnew = null
2472     def givenDate = null
2473     def givenChannel = null
2474     def givenVersion = null
2475     if (CHANNEL == "RELEASE") {
2476       def (map, content) = mdFileComponents(releaseMdFile)
2477       givenDate = map.date
2478       givenChannel = map.channel
2479       givenVersion = map.version
2480       changes = content
2481       if (givenVersion != null && givenVersion != JALVIEW_VERSION) {
2482         throw new GradleException("'version' header (${givenVersion}) found in ${releaseMdFile} does not match JALVIEW_VERSION (${JALVIEW_VERSION})")
2483       }
2484
2485       if (whatsnewMdFile.exists())
2486         whatsnew = whatsnewMdFile.text
2487     }
2488
2489     def oldJvl = oldJvlFile.exists() ? oldJvlFile.collect{it} : []
2490     def jalviewjsLink = jalviewjsFile.exists() ? jalviewjsFile.collect{it} : []
2491
2492     def changesHugo = null
2493     if (changes != null) {
2494       changesHugo = '<div class="release_notes">\n\n'
2495       def inSection = false
2496       changes.eachLine { line ->
2497         def m = null
2498         if (m = line =~ /^##([^#].*)$/) {
2499           if (inSection) {
2500             changesHugo += "</div>\n\n"
2501           }
2502           def section = m[0][1].trim()
2503           section = section.toLowerCase()
2504           section = section.replaceAll(/ +/, "_")
2505           section = section.replaceAll(/[^a-z0-9_\-]/, "")
2506           changesHugo += "<div class=\"${section}\">\n\n"
2507           inSection = true
2508         } else if (m = line =~ /^(\s*-\s*)<!--([^>]+)-->(.*?)(<br\/?>)?\s*$/) {
2509           def comment = m[0][2].trim()
2510           if (comment != "") {
2511             comment = comment.replaceAll('"', "&quot;")
2512             def issuekeys = []
2513             comment.eachMatch(/JAL-\d+/) { jal -> issuekeys += jal }
2514             def newline = m[0][1]
2515             if (comment.trim() != "")
2516               newline += "{{<comment>}}${comment}{{</comment>}}  "
2517             newline += m[0][3].trim()
2518             if (issuekeys.size() > 0)
2519               newline += "  {{< jal issue=\"${issuekeys.join(",")}\" alt=\"${comment}\" >}}"
2520             if (m[0][4] != null)
2521               newline += m[0][4]
2522             line = newline
2523           }
2524         }
2525         changesHugo += line+"\n"
2526       }
2527       if (inSection) {
2528         changesHugo += "\n</div>\n\n"
2529       }
2530       changesHugo += '</div>'
2531     }
2532
2533     templateFiles.each{ templateFile ->
2534       def newFileName = string(hugoTemplateSubstitutions(templateFile.getName()))
2535       def relPath = hugoTemplatesDir.toPath().relativize(templateFile.toPath()).getParent()
2536       def newRelPathName = hugoTemplateSubstitutions( relPath.toString() )
2537
2538       def outPathName = string("${hugoBuildDir}/$newRelPathName")
2539
2540       copy {
2541         from templateFile
2542         rename(templateFile.getName(), newFileName)
2543         into outPathName
2544       }
2545
2546       def newFile = file("${outPathName}/${newFileName}".toString())
2547       def content = newFile.text
2548       newFile.text = hugoTemplateSubstitutions(content,
2549         [
2550           WHATSNEW: whatsnew,
2551           CHANGES: changesHugo,
2552           DATE: givenDate == null ? "" : givenDate.format("yyyy-MM-dd"),
2553           DRAFT: givenDate == null ? "true" : "false",
2554           JALVIEWJSLINK: jalviewjsLink.contains(JALVIEW_VERSION) ? "true" : "false",
2555           JVL_HEADER: oldJvl.contains(JALVIEW_VERSION) ? "jvl: true" : ""
2556         ]
2557       )
2558     }
2559
2560   }
2561
2562   inputs.file(oldJvlFile)
2563   inputs.dir(hugoTemplatesDir)
2564   inputs.property("JALVIEW_VERSION", { JALVIEW_VERSION })
2565   inputs.property("CHANNEL", { CHANNEL })
2566 }
2567
2568 def getMdDate(File mdFile) {
2569   return mdFileComponents(mdFile, true)
2570 }
2571
2572 def getMdSections(String content) {
2573   def sections = [:]
2574   def sectionContent = ""
2575   def sectionName = null
2576   content.eachLine { line ->
2577     def m = null
2578     if (m = line =~ /^##([^#].*)$/) {
2579       if (sectionName != null) {
2580         sections[sectionName] = sectionContent
2581         sectionName = null
2582         sectionContent = ""
2583       }
2584       sectionName = m[0][1].trim()
2585       sectionName = sectionName.toLowerCase()
2586       sectionName = sectionName.replaceAll(/ +/, "_")
2587       sectionName = sectionName.replaceAll(/[^a-z0-9_\-]/, "")
2588     } else if (sectionName != null) {
2589       sectionContent += line+"\n"
2590     }
2591   }
2592   if (sectionContent != null) {
2593     sections[sectionName] = sectionContent
2594   }
2595   return sections
2596 }
2597
2598 task releasesTemplates {
2599   def releasesTemplateFile = file("${jalviewDir}/${releases_template}")
2600   def whatsnewTemplateFile = file("${jalviewDir}/${whatsnew_template}")
2601   def releasesHtmlFile = file("${helpSourceDir}/${releases_html}")
2602   def whatsnewHtmlFile = file("${helpSourceDir}/${whatsnew_html}")
2603   def releasesMdDir = "${jalviewDir}/${releases_dir}"
2604   def whatsnewMdDir = "${jalviewDir}/${whatsnew_dir}"
2605
2606   doFirst {
2607     def releaseFiles = fileTree(dir: releasesMdDir, include: "release-*.md")
2608     def releaseFilesDates = releaseFiles.collectEntries {
2609       [(it): getMdDate(it)]
2610     }
2611     releaseFiles = releaseFiles.sort { a,b -> releaseFilesDates[a].compareTo(releaseFilesDates[b]) }
2612
2613     def releasesTemplate = releasesTemplateFile.text
2614     def m = releasesTemplate =~ /(?s)__VERSION_LOOP_START__(.*)__VERSION_LOOP_END__/
2615     def versionTemplate = m[0][1]
2616
2617     MutableDataSet options = new MutableDataSet()
2618
2619     def extensions = new ArrayList<>()
2620     options.set(Parser.EXTENSIONS, extensions)
2621     options.set(Parser.HTML_BLOCK_COMMENT_ONLY_FULL_LINE, true)
2622
2623     Parser parser = Parser.builder(options).build()
2624     HtmlRenderer renderer = HtmlRenderer.builder(options).build()
2625
2626     def actualVersions = releaseFiles.collect { rf ->
2627       def (rfMap, rfContent) = mdFileComponents(rf)
2628       return rfMap.version
2629     }
2630     def versionsHtml = ""
2631     def linkedVersions = []
2632     releaseFiles.reverse().each { rFile ->
2633       def (rMap, rContent) = mdFileComponents(rFile)
2634
2635       def versionLink = ""
2636       def partialVersion = ""
2637       def firstPart = true
2638       rMap.version.split("\\.").each { part ->
2639         def displayPart = ( firstPart ? "" : "." ) + part
2640         partialVersion += displayPart
2641         if (
2642             linkedVersions.contains(partialVersion)
2643             || ( actualVersions.contains(partialVersion) && partialVersion != rMap.version )
2644             ) {
2645           versionLink += displayPart
2646         } else {
2647           versionLink += "<a id=\"Jalview.${partialVersion}\">${displayPart}</a>"
2648           linkedVersions += partialVersion
2649         }
2650         firstPart = false
2651       }
2652       def displayDate = releaseFilesDates[rFile].format("d MMMMM yyyy")
2653
2654       def rContentSections = getMdSections(rContent)
2655       def rVersion = versionTemplate
2656       if (rVersion != "") {
2657         Node newFeaturesNode = parser.parse(rContentSections["new_features"])
2658         String newFeaturesHtml = renderer.render(newFeaturesNode)
2659         Node issuesResolvedNode = parser.parse(rContentSections["issues_resolved"])
2660         String issuesResolvedHtml = renderer.render(issuesResolvedNode)
2661         rVersion = hugoTemplateSubstitutions(rVersion,
2662           [
2663             VERSION: rMap.version,
2664             VERSION_LINK: versionLink,
2665             DISPLAY_DATE: displayDate,
2666             NEW_FEATURES: newFeaturesHtml,
2667             ISSUES_RESOLVED: issuesResolvedHtml
2668           ]
2669         )
2670         versionsHtml += rVersion
2671       }
2672     }
2673
2674     releasesTemplate = releasesTemplate.replaceAll("(?s)__VERSION_LOOP_START__.*__VERSION_LOOP_END__", versionsHtml)
2675     releasesTemplate = hugoTemplateSubstitutions(releasesTemplate)
2676     releasesHtmlFile.text = releasesTemplate
2677   }
2678
2679   inputs.file(releasesTemplateFile)
2680   inputs.file(whatsnewTemplateFile)
2681   inputs.dir(releasesMdDir)
2682   inputs.dir(whatsnewMdDir)
2683   outputs.file(releasesHtmlFile)
2684   outputs.file(whatsnewHtmlFile)
2685 }
2686
2687
2688 task helppages {
2689   dependsOn copyHelp
2690   dependsOn pubhtmlhelp
2691   
2692   inputs.dir("${helpBuildDir}/${help_dir}")
2693   outputs.dir("${buildDir}/distributions/${help_dir}")
2694 }
2695
2696
2697 task j2sSetHeadlessBuild {
2698   doFirst {
2699     IN_ECLIPSE = false
2700   }
2701 }
2702
2703
2704 task jalviewjsEnableAltFileProperty(type: WriteProperties) {
2705   group "jalviewjs"
2706   description "Enable the alternative J2S Config file for headless build"
2707
2708   outputFile = jalviewjsJ2sSettingsFileName
2709   def j2sPropsFile = file(jalviewjsJ2sSettingsFileName)
2710   def j2sProps = new Properties()
2711   if (j2sPropsFile.exists()) {
2712     try {
2713       def j2sPropsFileFIS = new FileInputStream(j2sPropsFile)
2714       j2sProps.load(j2sPropsFileFIS)
2715       j2sPropsFileFIS.close()
2716
2717       j2sProps.each { prop, val ->
2718         property(prop, val)
2719       }
2720     } catch (Exception e) {
2721       println("Exception reading ${jalviewjsJ2sSettingsFileName}")
2722       e.printStackTrace()
2723     }
2724   }
2725   if (! j2sProps.stringPropertyNames().contains(jalviewjs_j2s_alt_file_property_config)) {
2726     property(jalviewjs_j2s_alt_file_property_config, jalviewjs_j2s_alt_file_property)
2727   }
2728 }
2729
2730
2731 task jalviewjsSetEclipseWorkspace {
2732   def propKey = "jalviewjs_eclipse_workspace"
2733   def propVal = null
2734   if (project.hasProperty(propKey)) {
2735     propVal = project.getProperty(propKey)
2736     if (propVal.startsWith("~/")) {
2737       propVal = System.getProperty("user.home") + propVal.substring(1)
2738     }
2739   }
2740   def propsFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_workspace_location_file}"
2741   def propsFile = file(propsFileName)
2742   def eclipseWsDir = propVal
2743   def props = new Properties()
2744
2745   def writeProps = true
2746   if (( eclipseWsDir == null || !file(eclipseWsDir).exists() ) && propsFile.exists()) {
2747     def ins = new FileInputStream(propsFileName)
2748     props.load(ins)
2749     ins.close()
2750     if (props.getProperty(propKey, null) != null) {
2751       eclipseWsDir = props.getProperty(propKey)
2752       writeProps = false
2753     }
2754   }
2755
2756   if (eclipseWsDir == null || !file(eclipseWsDir).exists()) {
2757     def tempDir = File.createTempDir()
2758     eclipseWsDir = tempDir.getAbsolutePath()
2759     writeProps = true
2760   }
2761   eclipseWorkspace = file(eclipseWsDir)
2762
2763   doFirst {
2764     // do not run a headless transpile when we claim to be in Eclipse
2765     if (IN_ECLIPSE) {
2766       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2767       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2768     } else {
2769       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2770     }
2771
2772     if (writeProps) {
2773       props.setProperty(propKey, eclipseWsDir)
2774       propsFile.parentFile.mkdirs()
2775       def bytes = new ByteArrayOutputStream()
2776       props.store(bytes, null)
2777       def propertiesString = bytes.toString()
2778       propsFile.text = propertiesString
2779       print("NEW ")
2780     } else {
2781       print("EXISTING ")
2782     }
2783
2784     println("ECLIPSE WORKSPACE: "+eclipseWorkspace.getPath())
2785   }
2786
2787   //inputs.property(propKey, eclipseWsDir) // eclipseWsDir only gets set once this task runs, so will be out-of-date
2788   outputs.file(propsFileName)
2789   outputs.upToDateWhen { eclipseWorkspace.exists() && propsFile.exists() }
2790 }
2791
2792
2793 task jalviewjsEclipsePaths {
2794   def eclipseProduct
2795
2796   def eclipseRoot = jalviewjs_eclipse_root
2797   if (eclipseRoot.startsWith("~/")) {
2798     eclipseRoot = System.getProperty("user.home") + eclipseRoot.substring(1)
2799   }
2800   if (OperatingSystem.current().isMacOsX()) {
2801     eclipseRoot += "/Eclipse.app"
2802     eclipseBinary = "${eclipseRoot}/Contents/MacOS/eclipse"
2803     eclipseProduct = "${eclipseRoot}/Contents/Eclipse/.eclipseproduct"
2804   } else if (OperatingSystem.current().isWindows()) { // check these paths!!
2805     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
2806       eclipseRoot += "/eclipse"
2807     }
2808     eclipseBinary = "${eclipseRoot}/eclipse.exe"
2809     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
2810   } else { // linux or unix
2811     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
2812       eclipseRoot += "/eclipse"
2813 println("eclipseDir exists")
2814     }
2815     eclipseBinary = "${eclipseRoot}/eclipse"
2816     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
2817   }
2818
2819   eclipseVersion = "4.13" // default
2820   def assumedVersion = true
2821   if (file(eclipseProduct).exists()) {
2822     def fis = new FileInputStream(eclipseProduct)
2823     def props = new Properties()
2824     props.load(fis)
2825     eclipseVersion = props.getProperty("version")
2826     fis.close()
2827     assumedVersion = false
2828   }
2829   
2830   def propKey = "eclipse_debug"
2831   eclipseDebug = (project.hasProperty(propKey) && project.getProperty(propKey).equals("true"))
2832
2833   doFirst {
2834     // do not run a headless transpile when we claim to be in Eclipse
2835     if (IN_ECLIPSE) {
2836       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2837       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2838     } else {
2839       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2840     }
2841
2842     if (!assumedVersion) {
2843       println("ECLIPSE VERSION=${eclipseVersion}")
2844     }
2845   }
2846 }
2847
2848
2849 task printProperties {
2850   group "Debug"
2851   description "Output to console all System.properties"
2852   doFirst {
2853     System.properties.each { key, val -> System.out.println("Property: ${key}=${val}") }
2854   }
2855 }
2856
2857
2858 task eclipseSetup {
2859   dependsOn eclipseProject
2860   dependsOn eclipseClasspath
2861   dependsOn eclipseJdt
2862 }
2863
2864
2865 // this version (type: Copy) will delete anything in the eclipse dropins folder that isn't in fromDropinsDir
2866 task jalviewjsEclipseCopyDropins(type: Copy) {
2867   dependsOn jalviewjsEclipsePaths
2868
2869   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_eclipse_dropins_dir}", include: "*.jar")
2870   inputFiles += file("${jalviewDir}/${jalviewjsJ2sPlugin}")
2871   def outputDir = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
2872
2873   from inputFiles
2874   into outputDir
2875 }
2876
2877
2878 // this eclipse -clean doesn't actually work
2879 task jalviewjsCleanEclipse(type: Exec) {
2880   dependsOn eclipseSetup
2881   dependsOn jalviewjsEclipsePaths
2882   dependsOn jalviewjsEclipseCopyDropins
2883
2884   executable(eclipseBinary)
2885   args(["-nosplash", "--launcher.suppressErrors", "-data", eclipseWorkspace.getPath(), "-clean", "-console", "-consoleLog"])
2886   if (eclipseDebug) {
2887     args += "-debug"
2888   }
2889   args += "-l"
2890
2891   def inputString = """exit
2892 y
2893 """
2894   def inputByteStream = new ByteArrayInputStream(inputString.getBytes())
2895   standardInput = inputByteStream
2896 }
2897
2898 /* not really working yet
2899 jalviewjsEclipseCopyDropins.finalizedBy jalviewjsCleanEclipse
2900 */
2901
2902
2903 task jalviewjsTransferUnzipSwingJs {
2904   def file_zip = "${jalviewDir}/${jalviewjs_swingjs_zip}"
2905
2906   doLast {
2907     copy {
2908       from zipTree(file_zip)
2909       into "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
2910     }
2911   }
2912
2913   inputs.file file_zip
2914   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
2915 }
2916
2917
2918 task jalviewjsTransferUnzipLib {
2919   def zipFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_libjs_dir}", include: "*.zip")
2920
2921   doLast {
2922     zipFiles.each { file_zip -> 
2923       copy {
2924         from zipTree(file_zip)
2925         into "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
2926       }
2927     }
2928   }
2929
2930   inputs.files zipFiles
2931   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
2932 }
2933
2934
2935 task jalviewjsTransferUnzipAllLibs {
2936   dependsOn jalviewjsTransferUnzipSwingJs
2937   dependsOn jalviewjsTransferUnzipLib
2938 }
2939
2940
2941 task jalviewjsCreateJ2sSettings(type: WriteProperties) {
2942   group "JalviewJS"
2943   description "Create the alternative j2s file from the j2s.* properties"
2944
2945   jalviewjsJ2sProps = project.properties.findAll { it.key.startsWith("j2s.") }.sort { it.key }
2946   def siteDirProperty = "j2s.site.directory"
2947   def setSiteDir = false
2948   jalviewjsJ2sProps.each { prop, val ->
2949     if (val != null) {
2950       if (prop == siteDirProperty) {
2951         if (!(val.startsWith('/') || val.startsWith("file://") )) {
2952           val = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${val}"
2953         }
2954         setSiteDir = true
2955       }
2956       property(prop,val)
2957     }
2958     if (!setSiteDir) { // default site location, don't override specifically set property
2959       property(siteDirProperty,"${jalviewDirRelativePath}/${jalviewjsTransferSiteJsDir}")
2960     }
2961   }
2962   outputFile = jalviewjsJ2sAltSettingsFileName
2963
2964   if (! IN_ECLIPSE) {
2965     inputs.properties(jalviewjsJ2sProps)
2966     outputs.file(jalviewjsJ2sAltSettingsFileName)
2967   }
2968 }
2969
2970
2971 task jalviewjsEclipseSetup {
2972   dependsOn jalviewjsEclipseCopyDropins
2973   dependsOn jalviewjsSetEclipseWorkspace
2974   dependsOn jalviewjsCreateJ2sSettings
2975 }
2976
2977
2978 task jalviewjsSyncAllLibs (type: Sync) {
2979   dependsOn jalviewjsTransferUnzipAllLibs
2980   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteLibDir}")
2981   inputFiles += fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
2982   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
2983
2984   from inputFiles
2985   into outputDir
2986   def outputFiles = []
2987   rename { filename ->
2988     outputFiles += "${outputDir}/${filename}"
2989     null
2990   }
2991   preserve {
2992     include "**"
2993   }
2994
2995   // should this be exclude really ?
2996   duplicatesStrategy "INCLUDE"
2997
2998   outputs.files outputFiles
2999   inputs.files inputFiles
3000 }
3001
3002
3003 task jalviewjsSyncResources (type: Sync) {
3004   dependsOn buildResources
3005
3006   def inputFiles = fileTree(dir: resourcesBuildDir)
3007   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
3008
3009   from inputFiles
3010   into outputDir
3011   def outputFiles = []
3012   rename { filename ->
3013     outputFiles += "${outputDir}/${filename}"
3014     null
3015   }
3016   preserve {
3017     include "**"
3018   }
3019   outputs.files outputFiles
3020   inputs.files inputFiles
3021 }
3022
3023
3024 task jalviewjsSyncSiteResources (type: Sync) {
3025   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_site_resource_dir}")
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   outputs.files outputFiles
3039   inputs.files inputFiles
3040 }
3041
3042
3043 task jalviewjsSyncBuildProperties (type: Sync) {
3044   dependsOn createBuildProperties
3045   def inputFiles = [file(buildProperties)]
3046   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
3047
3048   from inputFiles
3049   into outputDir
3050   def outputFiles = []
3051   rename { filename ->
3052     outputFiles += "${outputDir}/${filename}"
3053     null
3054   }
3055   preserve {
3056     include "**"
3057   }
3058   outputs.files outputFiles
3059   inputs.files inputFiles
3060 }
3061
3062
3063 task jalviewjsProjectImport(type: Exec) {
3064   dependsOn eclipseSetup
3065   dependsOn jalviewjsEclipsePaths
3066   dependsOn jalviewjsEclipseSetup
3067
3068   doFirst {
3069     // do not run a headless import when we claim to be in Eclipse
3070     if (IN_ECLIPSE) {
3071       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3072       throw new StopExecutionException("Not running headless import whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3073     } else {
3074       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3075     }
3076   }
3077
3078   //def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview/org.eclipse.jdt.core"
3079   def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview"
3080   executable(eclipseBinary)
3081   args(["-nosplash", "--launcher.suppressErrors", "-application", "com.seeq.eclipse.importprojects.headlessimport", "-data", eclipseWorkspace.getPath(), "-import", jalviewDirAbsolutePath])
3082   if (eclipseDebug) {
3083     args += "-debug"
3084   }
3085   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
3086   if (!IN_ECLIPSE) {
3087     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
3088     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
3089   }
3090
3091   inputs.file("${jalviewDir}/.project")
3092   outputs.upToDateWhen { 
3093     file(projdir).exists()
3094   }
3095 }
3096
3097
3098 task jalviewjsTranspile(type: Exec) {
3099   dependsOn jalviewjsEclipseSetup 
3100   dependsOn jalviewjsProjectImport
3101   dependsOn jalviewjsEclipsePaths
3102   if (!IN_ECLIPSE) {
3103     dependsOn jalviewjsEnableAltFileProperty
3104   }
3105
3106   doFirst {
3107     // do not run a headless transpile when we claim to be in Eclipse
3108     if (IN_ECLIPSE) {
3109       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3110       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
3111     } else {
3112       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
3113     }
3114   }
3115
3116   executable(eclipseBinary)
3117   args(["-nosplash", "--launcher.suppressErrors", "-application", "org.eclipse.jdt.apt.core.aptBuild", "-data", eclipseWorkspace, "-${jalviewjs_eclipse_build_arg}", eclipse_project_name ])
3118   if (eclipseDebug) {
3119     args += "-debug"
3120   }
3121   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
3122   if (!IN_ECLIPSE) {
3123     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
3124     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
3125   }
3126
3127   def stdout
3128   def stderr
3129   doFirst {
3130     stdout = new ByteArrayOutputStream()
3131     stderr = new ByteArrayOutputStream()
3132
3133     def logOutFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}"
3134     def logOutFile = file(logOutFileName)
3135     logOutFile.createNewFile()
3136     logOutFile.text = """ROOT: ${jalviewjs_eclipse_root}
3137 BINARY: ${eclipseBinary}
3138 VERSION: ${eclipseVersion}
3139 WORKSPACE: ${eclipseWorkspace}
3140 DEBUG: ${eclipseDebug}
3141 ----
3142 """
3143     def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
3144     // combine stdout and stderr
3145     def logErrFOS = logOutFOS
3146
3147     if (jalviewjs_j2s_to_console.equals("true")) {
3148       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3149         new org.apache.tools.ant.util.TeeOutputStream(
3150           logOutFOS,
3151           stdout),
3152         System.out)
3153       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3154         new org.apache.tools.ant.util.TeeOutputStream(
3155           logErrFOS,
3156           stderr),
3157         System.err)
3158     } else {
3159       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3160         logOutFOS,
3161         stdout)
3162       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3163         logErrFOS,
3164         stderr)
3165     }
3166   }
3167
3168   doLast {
3169     if (stdout.toString().contains("Error processing ")) {
3170       // j2s did not complete transpile
3171       //throw new TaskExecutionException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3172       if (jalviewjs_ignore_transpile_errors.equals("true")) {
3173         println("IGNORING TRANSPILE ERRORS")
3174         println("See eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3175       } else {
3176         throw new GradleException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
3177       }
3178     }
3179   }
3180
3181   inputs.dir("${jalviewDir}/${sourceDir}")
3182   outputs.dir("${jalviewDir}/${jalviewjsTransferSiteJsDir}")
3183   outputs.upToDateWhen( { file("${jalviewDir}/${jalviewjsTransferSiteJsDir}${jalviewjs_server_resource}").exists() } )
3184 }
3185
3186
3187 def jalviewjsCallCore(String name, FileCollection list, String prefixFile, String suffixFile, String jsfile, String zjsfile, File logOutFile, Boolean logOutConsole) {
3188
3189   def stdout = new ByteArrayOutputStream()
3190   def stderr = new ByteArrayOutputStream()
3191
3192   def coreFile = file(jsfile)
3193   def msg = ""
3194   msg = "Creating core for ${name}...\nGenerating ${jsfile}"
3195   println(msg)
3196   logOutFile.createNewFile()
3197   logOutFile.append(msg+"\n")
3198
3199   def coreTop = file(prefixFile)
3200   def coreBottom = file(suffixFile)
3201   coreFile.getParentFile().mkdirs()
3202   coreFile.createNewFile()
3203   coreFile.write( coreTop.getText("UTF-8") )
3204   list.each {
3205     f ->
3206     if (f.exists()) {
3207       def t = f.getText("UTF-8")
3208       t.replaceAll("Clazz\\.([^_])","Clazz_${1}")
3209       coreFile.append( t )
3210     } else {
3211       msg = "...file '"+f.getPath()+"' does not exist, skipping"
3212       println(msg)
3213       logOutFile.append(msg+"\n")
3214     }
3215   }
3216   coreFile.append( coreBottom.getText("UTF-8") )
3217
3218   msg = "Generating ${zjsfile}"
3219   println(msg)
3220   logOutFile.append(msg+"\n")
3221   def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
3222   def logErrFOS = logOutFOS
3223
3224   javaexec {
3225     classpath = files(["${jalviewDir}/${jalviewjs_closure_compiler}"])
3226     main = "com.google.javascript.jscomp.CommandLineRunner"
3227     jvmArgs = [ "-Dfile.encoding=UTF-8" ]
3228     args = [ "--compilation_level", "SIMPLE_OPTIMIZATIONS", "--warning_level", "QUIET", "--charset", "UTF-8", "--js", jsfile, "--js_output_file", zjsfile ]
3229     maxHeapSize = "2g"
3230
3231     msg = "\nRunning '"+commandLine.join(' ')+"'\n"
3232     println(msg)
3233     logOutFile.append(msg+"\n")
3234
3235     if (logOutConsole) {
3236       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3237         new org.apache.tools.ant.util.TeeOutputStream(
3238           logOutFOS,
3239           stdout),
3240         standardOutput)
3241         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3242           new org.apache.tools.ant.util.TeeOutputStream(
3243             logErrFOS,
3244             stderr),
3245           System.err)
3246     } else {
3247       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
3248         logOutFOS,
3249         stdout)
3250         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
3251           logErrFOS,
3252           stderr)
3253     }
3254   }
3255   msg = "--"
3256   println(msg)
3257   logOutFile.append(msg+"\n")
3258 }
3259
3260
3261 task jalviewjsBuildAllCores {
3262   group "JalviewJS"
3263   description "Build the core js lib closures listed in the classlists dir"
3264   dependsOn jalviewjsTranspile
3265   dependsOn jalviewjsTransferUnzipSwingJs
3266
3267   def j2sDir = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${jalviewjs_j2s_subdir}"
3268   def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_j2s_subdir}"
3269   def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteLibDir}/${jalviewjs_j2s_subdir}"
3270   def jsDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_js_subdir}"
3271   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
3272   def prefixFile = "${jsDir}/core/coretop2.js"
3273   def suffixFile = "${jsDir}/core/corebottom2.js"
3274
3275   inputs.file prefixFile
3276   inputs.file suffixFile
3277
3278   def classlistFiles = []
3279   // add the classlists found int the jalviewjs_classlists_dir
3280   fileTree(dir: "${jalviewDir}/${jalviewjs_classlists_dir}", include: "*.txt").each {
3281     file ->
3282     def name = file.getName() - ".txt"
3283     classlistFiles += [
3284       'file': file,
3285       'name': name
3286     ]
3287   }
3288
3289   // _jmol and _jalview cores. Add any other peculiar classlist.txt files here
3290   //classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jmol}"), 'name': "_jvjmol" ]
3291   classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jalview}"), 'name': jalviewjsJalviewCoreName ]
3292
3293   jalviewjsCoreClasslists = []
3294
3295   classlistFiles.each {
3296     hash ->
3297
3298     def file = hash['file']
3299     if (! file.exists()) {
3300       //println("...classlist file '"+file.getPath()+"' does not exist, skipping")
3301       return false // this is a "continue" in groovy .each closure
3302     }
3303     def name = hash['name']
3304     if (name == null) {
3305       name = file.getName() - ".txt"
3306     }
3307
3308     def filelist = []
3309     file.eachLine {
3310       line ->
3311         filelist += line
3312     }
3313     def list = fileTree(dir: j2sDir, includes: filelist)
3314
3315     def jsfile = "${outputDir}/core${name}.js"
3316     def zjsfile = "${outputDir}/core${name}.z.js"
3317
3318     jalviewjsCoreClasslists += [
3319       'jsfile': jsfile,
3320       'zjsfile': zjsfile,
3321       'list': list,
3322       'name': name
3323     ]
3324
3325     inputs.file(file)
3326     inputs.files(list)
3327     outputs.file(jsfile)
3328     outputs.file(zjsfile)
3329   }
3330   
3331   // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
3332   def stevesoftClasslistName = "_stevesoft"
3333   def stevesoftClasslist = [
3334     'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
3335     'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
3336     'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
3337     'name': stevesoftClasslistName
3338   ]
3339   jalviewjsCoreClasslists += stevesoftClasslist
3340   inputs.files(stevesoftClasslist['list'])
3341   outputs.file(stevesoftClasslist['jsfile'])
3342   outputs.file(stevesoftClasslist['zjsfile'])
3343
3344   // _all core
3345   def allClasslistName = "_all"
3346   def allJsFiles = fileTree(dir: j2sDir, include: "**/*.js")
3347   allJsFiles += fileTree(
3348     dir: libJ2sDir,
3349     include: "**/*.js",
3350     excludes: [
3351       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3352       "**/org/jmol/jvxl/readers/IsoIntersectFileReader.js",
3353       "**/org/jmol/export/JSExporter.js"
3354     ]
3355   )
3356   allJsFiles += fileTree(
3357     dir: swingJ2sDir,
3358     include: "**/*.js",
3359     excludes: [
3360       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3361       "**/sun/misc/Unsafe.js",
3362       "**/swingjs/jquery/jquery-editable-select.js",
3363       "**/swingjs/jquery/j2sComboBox.js",
3364       "**/sun/misc/FloatingDecimal.js"
3365     ]
3366   )
3367   def allClasslist = [
3368     'jsfile': "${outputDir}/core${allClasslistName}.js",
3369     'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
3370     'list': allJsFiles,
3371     'name': allClasslistName
3372   ]
3373   // not including this version of "all" core at the moment
3374   //jalviewjsCoreClasslists += allClasslist
3375   inputs.files(allClasslist['list'])
3376   outputs.file(allClasslist['jsfile'])
3377   outputs.file(allClasslist['zjsfile'])
3378
3379   doFirst {
3380     def logOutFile = file("${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_closure_stdout}")
3381     logOutFile.getParentFile().mkdirs()
3382     logOutFile.createNewFile()
3383     logOutFile.write(getDate("yyyy-MM-dd HH:mm:ss")+" jalviewjsBuildAllCores\n----\n")
3384
3385     jalviewjsCoreClasslists.each {
3386       jalviewjsCallCore(it.name, it.list, prefixFile, suffixFile, it.jsfile, it.zjsfile, logOutFile, jalviewjs_j2s_to_console.equals("true"))
3387     }
3388   }
3389
3390 }
3391
3392
3393 def jalviewjsPublishCoreTemplate(String coreName, String templateName, File inputFile, String outputFile) {
3394   copy {
3395     from inputFile
3396     into file(outputFile).getParentFile()
3397     rename { filename ->
3398       if (filename.equals(inputFile.getName())) {
3399         return file(outputFile).getName()
3400       }
3401       return null
3402     }
3403     filter(ReplaceTokens,
3404       beginToken: '_',
3405       endToken: '_',
3406       tokens: [
3407         'MAIN': '"'+main_class+'"',
3408         'CODE': "null",
3409         'NAME': jalviewjsJalviewTemplateName+" [core ${coreName}]",
3410         'COREKEY': jalviewjs_core_key,
3411         'CORENAME': coreName
3412       ]
3413     )
3414   }
3415 }
3416
3417
3418 task jalviewjsPublishCoreTemplates {
3419   dependsOn jalviewjsBuildAllCores
3420   def inputFileName = "${jalviewDir}/${j2s_coretemplate_html}"
3421   def inputFile = file(inputFileName)
3422   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3423
3424   def outputFiles = []
3425   jalviewjsCoreClasslists.each { cl ->
3426     def outputFile = "${outputDir}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
3427     cl['outputfile'] = outputFile
3428     outputFiles += outputFile
3429   }
3430
3431   doFirst {
3432     jalviewjsCoreClasslists.each { cl ->
3433       jalviewjsPublishCoreTemplate(cl.name, jalviewjsJalviewTemplateName, inputFile, cl.outputfile)
3434     }
3435   }
3436   inputs.file(inputFile)
3437   outputs.files(outputFiles)
3438 }
3439
3440
3441 task jalviewjsSyncCore (type: Sync) {
3442   dependsOn jalviewjsBuildAllCores
3443   dependsOn jalviewjsPublishCoreTemplates
3444   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
3445   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3446
3447   from inputFiles
3448   into outputDir
3449   def outputFiles = []
3450   rename { filename ->
3451     outputFiles += "${outputDir}/${filename}"
3452     null
3453   }
3454   preserve {
3455     include "**"
3456   }
3457   outputs.files outputFiles
3458   inputs.files inputFiles
3459 }
3460
3461
3462 // this Copy version of TransferSiteJs will delete anything else in the target dir
3463 task jalviewjsCopyTransferSiteJs(type: Copy) {
3464   dependsOn jalviewjsTranspile
3465   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3466   into "${jalviewDir}/${jalviewjsSiteDir}"
3467 }
3468
3469
3470 // this Sync version of TransferSite is used by buildship to keep the website automatically up to date when a file changes
3471 task jalviewjsSyncTransferSiteJs(type: Sync) {
3472   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3473   include "**/*.*"
3474   into "${jalviewDir}/${jalviewjsSiteDir}"
3475   preserve {
3476     include "**"
3477   }
3478 }
3479
3480
3481 jalviewjsSyncAllLibs.mustRunAfter jalviewjsCopyTransferSiteJs
3482 jalviewjsSyncResources.mustRunAfter jalviewjsCopyTransferSiteJs
3483 jalviewjsSyncSiteResources.mustRunAfter jalviewjsCopyTransferSiteJs
3484 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsCopyTransferSiteJs
3485
3486 jalviewjsSyncAllLibs.mustRunAfter jalviewjsSyncTransferSiteJs
3487 jalviewjsSyncResources.mustRunAfter jalviewjsSyncTransferSiteJs
3488 jalviewjsSyncSiteResources.mustRunAfter jalviewjsSyncTransferSiteJs
3489 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsSyncTransferSiteJs
3490
3491
3492 task jalviewjsPrepareSite {
3493   group "JalviewJS"
3494   description "Prepares the website folder including unzipping files and copying resources"
3495   dependsOn jalviewjsSyncAllLibs
3496   dependsOn jalviewjsSyncResources
3497   dependsOn jalviewjsSyncSiteResources
3498   dependsOn jalviewjsSyncBuildProperties
3499   dependsOn jalviewjsSyncCore
3500 }
3501
3502
3503 task jalviewjsBuildSite {
3504   group "JalviewJS"
3505   description "Builds the whole website including transpiled code"
3506   dependsOn jalviewjsCopyTransferSiteJs
3507   dependsOn jalviewjsPrepareSite
3508 }
3509
3510
3511 task cleanJalviewjsTransferSite {
3512   doFirst {
3513     delete "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3514     delete "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3515     delete "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3516     delete "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3517   }
3518 }
3519
3520
3521 task cleanJalviewjsSite {
3522   dependsOn cleanJalviewjsTransferSite
3523   doFirst {
3524     delete "${jalviewDir}/${jalviewjsSiteDir}"
3525   }
3526 }
3527
3528
3529 task jalviewjsSiteTar(type: Tar) {
3530   group "JalviewJS"
3531   description "Creates a tar.gz file for the website"
3532   dependsOn jalviewjsBuildSite
3533   def outputFilename = "jalviewjs-site-${JALVIEW_VERSION}.tar.gz"
3534   archiveFileName = outputFilename
3535
3536   compression Compression.GZIP
3537
3538   from "${jalviewDir}/${jalviewjsSiteDir}"
3539   into jalviewjs_site_dir // this is inside the tar file
3540
3541   inputs.dir("${jalviewDir}/${jalviewjsSiteDir}")
3542 }
3543
3544
3545 task jalviewjsServer {
3546   group "JalviewJS"
3547   def filename = "jalviewjsTest.html"
3548   description "Starts a webserver on localhost to test the website. See ${filename} to access local site on most recently used port."
3549   def htmlFile = "${jalviewDirAbsolutePath}/${filename}"
3550   doLast {
3551
3552     def factory
3553     try {
3554       def f = Class.forName("org.gradle.plugins.javascript.envjs.http.simple.SimpleHttpFileServerFactory")
3555       factory = f.newInstance()
3556     } catch (ClassNotFoundException e) {
3557       throw new GradleException("Unable to create SimpleHttpFileServerFactory")
3558     }
3559     def port = Integer.valueOf(jalviewjs_server_port)
3560     def start = port
3561     def running = false
3562     def url
3563     def jalviewjsServer
3564     while(port < start+1000 && !running) {
3565       try {
3566         def doc_root = new File("${jalviewDirAbsolutePath}/${jalviewjsSiteDir}")
3567         jalviewjsServer = factory.start(doc_root, port)
3568         running = true
3569         url = jalviewjsServer.getResourceUrl(jalviewjs_server_resource)
3570         println("SERVER STARTED with document root ${doc_root}.")
3571         println("Go to "+url+" . Run  gradle --stop  to stop (kills all gradle daemons).")
3572         println("For debug: "+url+"?j2sdebug")
3573         println("For verbose: "+url+"?j2sverbose")
3574       } catch (Exception e) {
3575         port++;
3576       }
3577     }
3578     def htmlText = """
3579       <p><a href="${url}">JalviewJS Test. &lt;${url}&gt;</a></p>
3580       <p><a href="${url}?j2sdebug">JalviewJS Test with debug. &lt;${url}?j2sdebug&gt;</a></p>
3581       <p><a href="${url}?j2sverbose">JalviewJS Test with verbose. &lt;${url}?j2sdebug&gt;</a></p>
3582       """
3583     jalviewjsCoreClasslists.each { cl ->
3584       def urlcore = jalviewjsServer.getResourceUrl(file(cl.outputfile).getName())
3585       htmlText += """
3586       <p><a href="${urlcore}">${jalviewjsJalviewTemplateName} [core ${cl.name}]. &lt;${urlcore}&gt;</a></p>
3587       """
3588       println("For core ${cl.name}: "+urlcore)
3589     }
3590
3591     file(htmlFile).text = htmlText
3592   }
3593
3594   outputs.file(htmlFile)
3595   outputs.upToDateWhen({false})
3596 }
3597
3598
3599 task cleanJalviewjsAll {
3600   group "JalviewJS"
3601   description "Delete all configuration and build artifacts to do with JalviewJS build"
3602   dependsOn cleanJalviewjsSite
3603   dependsOn jalviewjsEclipsePaths
3604   
3605   doFirst {
3606     delete "${jalviewDir}/${jalviewjsBuildDir}"
3607     delete "${jalviewDir}/${eclipse_bin_dir}"
3608     if (eclipseWorkspace != null && file(eclipseWorkspace.getAbsolutePath()+"/.metadata").exists()) {
3609       delete file(eclipseWorkspace.getAbsolutePath()+"/.metadata")
3610     }
3611     delete jalviewjsJ2sAltSettingsFileName
3612   }
3613
3614   outputs.upToDateWhen( { false } )
3615 }
3616
3617
3618 task jalviewjsIDE_checkJ2sPlugin {
3619   group "00 JalviewJS in Eclipse"
3620   description "Compare the swingjs/net.sf.j2s.core(-j11)?.jar file with the Eclipse IDE's plugin version (found in the 'dropins' dir)"
3621
3622   doFirst {
3623     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
3624     def j2sPluginFile = file(j2sPlugin)
3625     def eclipseHome = System.properties["eclipse.home.location"]
3626     if (eclipseHome == null || ! IN_ECLIPSE) {
3627       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. Skipping J2S Plugin Check.")
3628     }
3629     def eclipseJ2sPluginDirs = [ "${eclipseHome}/dropins" ]
3630     def altPluginsDir = System.properties["org.eclipse.equinox.p2.reconciler.dropins.directory"]
3631     if (altPluginsDir != null && file(altPluginsDir).exists()) {
3632       eclipseJ2sPluginDirs += altPluginsDir
3633     }
3634     def foundPlugin = false
3635     def j2sPluginFileName = j2sPluginFile.getName()
3636     def eclipseJ2sPlugin
3637     def eclipseJ2sPluginFile
3638     eclipseJ2sPluginDirs.any { dir ->
3639       eclipseJ2sPlugin = "${dir}/${j2sPluginFileName}"
3640       eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
3641       if (eclipseJ2sPluginFile.exists()) {
3642         foundPlugin = true
3643         return true
3644       }
3645     }
3646     if (!foundPlugin) {
3647       def msg = "Eclipse J2S Plugin is not installed (could not find '${j2sPluginFileName}' in\n"+eclipseJ2sPluginDirs.join("\n")+"\n)\nTry running task jalviewjsIDE_copyJ2sPlugin"
3648       System.err.println(msg)
3649       throw new StopExecutionException(msg)
3650     }
3651
3652     def digest = MessageDigest.getInstance("MD5")
3653
3654     digest.update(j2sPluginFile.text.bytes)
3655     def j2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
3656
3657     digest.update(eclipseJ2sPluginFile.text.bytes)
3658     def eclipseJ2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
3659      
3660     if (j2sPluginMd5 != eclipseJ2sPluginMd5) {
3661       def msg = "WARNING! Eclipse J2S Plugin '${eclipseJ2sPlugin}' is different to this commit's version '${j2sPlugin}'"
3662       System.err.println(msg)
3663       throw new StopExecutionException(msg)
3664     } else {
3665       def msg = "Eclipse J2S Plugin '${eclipseJ2sPlugin}' is the same as '${j2sPlugin}' (this is good)"
3666       println(msg)
3667     }
3668   }
3669 }
3670
3671 task jalviewjsIDE_copyJ2sPlugin {
3672   group "00 JalviewJS in Eclipse"
3673   description "Copy the swingjs/net.sf.j2s.core(-j11)?.jar file into the Eclipse IDE's 'dropins' dir"
3674
3675   doFirst {
3676     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
3677     def j2sPluginFile = file(j2sPlugin)
3678     def eclipseHome = System.properties["eclipse.home.location"]
3679     if (eclipseHome == null || ! IN_ECLIPSE) {
3680       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. NOT copying J2S Plugin.")
3681     }
3682     def eclipseJ2sPlugin = "${eclipseHome}/dropins/${j2sPluginFile.getName()}"
3683     def eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
3684     def msg = "WARNING! Copying this commit's j2s plugin '${j2sPlugin}' to Eclipse J2S Plugin '${eclipseJ2sPlugin}'\n* May require an Eclipse restart"
3685     System.err.println(msg)
3686     copy {
3687       from j2sPlugin
3688       eclipseJ2sPluginFile.getParentFile().mkdirs()
3689       into eclipseJ2sPluginFile.getParent()
3690     }
3691   }
3692 }
3693
3694
3695 task jalviewjsIDE_j2sFile {
3696   group "00 JalviewJS in Eclipse"
3697   description "Creates the .j2s file"
3698   dependsOn jalviewjsCreateJ2sSettings
3699 }
3700
3701
3702 task jalviewjsIDE_SyncCore {
3703   group "00 JalviewJS in Eclipse"
3704   description "Build the core js lib closures listed in the classlists dir and publish core html from template"
3705   dependsOn jalviewjsSyncCore
3706 }
3707
3708
3709 task jalviewjsIDE_SyncSiteAll {
3710   dependsOn jalviewjsSyncAllLibs
3711   dependsOn jalviewjsSyncResources
3712   dependsOn jalviewjsSyncSiteResources
3713   dependsOn jalviewjsSyncBuildProperties
3714 }
3715
3716
3717 cleanJalviewjsTransferSite.mustRunAfter jalviewjsIDE_SyncSiteAll
3718
3719
3720 task jalviewjsIDE_PrepareSite {
3721   group "00 JalviewJS in Eclipse"
3722   description "Sync libs and resources to site dir, but not closure cores"
3723
3724   dependsOn jalviewjsIDE_SyncSiteAll
3725   //dependsOn cleanJalviewjsTransferSite // not sure why this clean is here -- will slow down a re-run of this task
3726 }
3727
3728
3729 task jalviewjsIDE_AssembleSite {
3730   group "00 JalviewJS in Eclipse"
3731   description "Assembles unzipped supporting zipfiles, resources, site resources and closure cores into the Eclipse transpiled site"
3732   dependsOn jalviewjsPrepareSite
3733 }
3734
3735
3736 task jalviewjsIDE_SiteClean {
3737   group "00 JalviewJS in Eclipse"
3738   description "Deletes the Eclipse transpiled site"
3739   dependsOn cleanJalviewjsSite
3740 }
3741
3742
3743 task jalviewjsIDE_Server {
3744   group "00 JalviewJS in Eclipse"
3745   description "Starts a webserver on localhost to test the website"
3746   dependsOn jalviewjsServer
3747 }
3748
3749
3750 // buildship runs this at import or gradle refresh
3751 task eclipseSynchronizationTask {
3752   //dependsOn eclipseSetup
3753   dependsOn createBuildProperties
3754   if (J2S_ENABLED) {
3755     dependsOn jalviewjsIDE_j2sFile
3756     dependsOn jalviewjsIDE_checkJ2sPlugin
3757     dependsOn jalviewjsIDE_PrepareSite
3758   }
3759 }
3760
3761
3762 // buildship runs this at build time or project refresh
3763 task eclipseAutoBuildTask {
3764   //dependsOn jalviewjsIDE_checkJ2sPlugin
3765   //dependsOn jalviewjsIDE_PrepareSite
3766 }
3767
3768
3769 task jalviewjs {
3770   group "JalviewJS"
3771   description "Build the site"
3772   dependsOn jalviewjsBuildSite
3773 }