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