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