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