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