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