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