76ccefdc9ecfbfa417f8d77c4211dbde6f842190
[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 VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
2022   def hugoDataJsonFile = file("${jalviewDir}/${install4jBuildDir}/installers-${VERSION_UNDERSCORES}.json")
2023   doFirst {
2024     delete installersOutputTxt
2025     delete installersSha256
2026     delete hugoDataJsonFile
2027   }
2028 }
2029
2030 task installerFiles(type: com.install4j.gradle.Install4jTask) {
2031   group = "distribution"
2032   description = "Create the install4j installers"
2033   dependsOn getdown
2034   dependsOn copyInstall4jTemplate
2035   dependsOn cleanInstallersDataFiles
2036
2037   projectFile = install4jConfFile
2038
2039   // create an md5 for the input files to use as version for install4j conf file
2040   def digest = MessageDigest.getInstance("MD5")
2041   digest.update(
2042     (file("${install4jDir}/${install4j_template}").text + 
2043     file("${install4jDir}/${install4j_info_plist_file_associations}").text +
2044     file("${install4jDir}/${install4j_installer_file_associations}").text).bytes)
2045   def filesMd5 = new BigInteger(1, digest.digest()).toString(16)
2046   if (filesMd5.length() >= 8) {
2047     filesMd5 = filesMd5.substring(0,8)
2048   }
2049   def install4jTemplateVersion = "${JALVIEW_VERSION}_F${filesMd5}_C${gitHash}"
2050
2051   variables = [
2052     'JALVIEW_NAME': jalview_name,
2053     'JALVIEW_APPLICATION_NAME': install4jApplicationName,
2054     'JALVIEW_DIR': "../..",
2055     'OSX_KEYSTORE': OSX_KEYSTORE,
2056     'OSX_APPLEID': OSX_APPLEID,
2057     'OSX_ALTOOLPASS': OSX_ALTOOLPASS,
2058     'JSIGN_SH': JSIGN_SH,
2059     'JRE_DIR': getdown_app_dir_java,
2060     'INSTALLER_TEMPLATE_VERSION': install4jTemplateVersion,
2061     'JALVIEW_VERSION': JALVIEW_VERSION,
2062     'JAVA_MIN_VERSION': JAVA_MIN_VERSION,
2063     'JAVA_MAX_VERSION': JAVA_MAX_VERSION,
2064     'JAVA_VERSION': JAVA_VERSION,
2065     'JAVA_INTEGER_VERSION': JAVA_INTEGER_VERSION,
2066     'VERSION': JALVIEW_VERSION,
2067     'MACOS_JAVA_VM_DIR': macosJavaVMDir,
2068     'WINDOWS_JAVA_VM_DIR': windowsJavaVMDir,
2069     'LINUX_JAVA_VM_DIR': linuxJavaVMDir,
2070     'MACOS_JAVA_VM_TGZ': macosJavaVMTgz,
2071     'WINDOWS_JAVA_VM_TGZ': windowsJavaVMTgz,
2072     'LINUX_JAVA_VM_TGZ': linuxJavaVMTgz,
2073     'COPYRIGHT_MESSAGE': install4j_copyright_message,
2074     'BUNDLE_ID': install4jBundleId,
2075     'INTERNAL_ID': install4jInternalId,
2076     'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
2077     'MACOS_DMG_DS_STORE': install4jDMGDSStore,
2078     'MACOS_DMG_BG_IMAGE': install4jDMGBackgroundImage,
2079     'WRAPPER_LINK': getdownWrapperLink,
2080     'BASH_WRAPPER_SCRIPT': getdown_bash_wrapper_script,
2081     'POWERSHELL_WRAPPER_SCRIPT': getdown_powershell_wrapper_script,
2082     'WRAPPER_SCRIPT_BIN_DIR': getdown_wrapper_script_dir,
2083     'INSTALLER_NAME': install4jInstallerName,
2084     'INSTALL4J_UTILS_DIR': install4j_utils_dir,
2085     'GETDOWN_WEBSITE_DIR': getdown_website_dir,
2086     'GETDOWN_FILES_DIR': getdown_files_dir,
2087     'GETDOWN_RESOURCE_DIR': getdown_resource_dir,
2088     'GETDOWN_DIST_DIR': getdownAppDistDir,
2089     'GETDOWN_ALT_DIR': getdown_app_dir_alt,
2090     'GETDOWN_INSTALL_DIR': getdown_install_dir,
2091     'INFO_PLIST_FILE_ASSOCIATIONS_FILE': install4j_info_plist_file_associations,
2092     'BUILD_DIR': install4jBuildDir,
2093     'APPLICATION_CATEGORIES': install4j_application_categories,
2094     'APPLICATION_FOLDER': install4jApplicationFolder,
2095     'UNIX_APPLICATION_FOLDER': install4jUnixApplicationFolder,
2096     'EXECUTABLE_NAME': install4jExecutableName,
2097     'EXTRA_SCHEME': install4jExtraScheme,
2098     'MAC_ICONS_FILE': install4jMacIconsFile,
2099     'WINDOWS_ICONS_FILE': install4jWindowsIconsFile,
2100     'PNG_ICON_FILE': install4jPngIconFile,
2101     'BACKGROUND': install4jBackground,
2102
2103   ]
2104
2105   //println("INSTALL4J VARIABLES:")
2106   //variables.each{k,v->println("${k}=${v}")}
2107
2108   destination = "${jalviewDir}/${install4jBuildDir}"
2109   buildSelected = true
2110
2111   if (install4j_faster.equals("true") || CHANNEL.startsWith("LOCAL")) {
2112     faster = true
2113     disableSigning = true
2114     disableNotarization = true
2115   }
2116
2117   if (OSX_KEYPASS) {
2118     macKeystorePassword = OSX_KEYPASS
2119   } 
2120   
2121   if (OSX_ALTOOLPASS) {
2122     appleIdPassword = OSX_ALTOOLPASS
2123     disableNotarization = false
2124   } else {
2125     disableNotarization = true
2126   }
2127
2128   doFirst {
2129     println("Using projectFile "+projectFile)
2130     if (!disableNotarization) { println("Will notarize OSX App DMG") }
2131   }
2132   //verbose=true
2133
2134   inputs.dir(getdownWebsiteDir)
2135   inputs.file(install4jConfFile)
2136   inputs.file("${install4jDir}/${install4j_info_plist_file_associations}")
2137   inputs.dir(macosJavaVMDir)
2138   inputs.dir(windowsJavaVMDir)
2139   outputs.dir("${jalviewDir}/${install4j_build_dir}/${JAVA_VERSION}")
2140 }
2141
2142 def getDataHash(File myFile) {
2143   HashCode hash = Files.asByteSource(myFile).hash(Hashing.sha256())
2144   return myFile.exists()
2145   ? [
2146       "filename" : myFile.getName(),
2147       "filesize" : myFile.length(),
2148       "sha256" : hash.toString()
2149     ]
2150   : null
2151 }
2152
2153 def writeDataJsonFile(File installersOutputTxt, File installersSha256, File hugoDataJsonFile) {
2154   if (!installersOutputTxt.exists()) {
2155     throw new GradleException("Required input file '${installersOutputTxt.getPath()}' doesn't exist.")
2156     return null;
2157   }
2158   if (install4jCheckSums && (!installersSha256)) {
2159     throw new GradleException("Required input file '${installersSha256.getPath()}' doesn't exist.")
2160     return null;
2161   }
2162   def hash = [
2163     "channel" : getdownChannelName,
2164     "date" : getDate("yyyy-MM-dd HH:mm:ss"),
2165     "git-commit" : "${gitHash} [${gitBranch}]",
2166     "version" : JALVIEW_VERSION
2167   ]
2168   def idHash = [:]
2169   installersOutputTxt.readLines().each { def line ->
2170     if (line.startsWith("#")) {
2171       return;
2172     }
2173     line.replaceAll("\n","")
2174     def vals = line.split("\t")
2175     def filename = vals[3]
2176     def filesize = file(filename).length()
2177     filename = filename.replaceAll(/^.*\//, "")
2178     hash[vals[0]] = [ "id" : vals[0], "os" : vals[1], "name" : vals[2], "file" : filename, "filesize" : filesize ]
2179     idHash."${filename}" = vals[0]
2180   }
2181   if (install4jCheckSums) {
2182     installersSha256.readLines().each { def line ->
2183       if (line.startsWith("#")) {
2184         return;
2185       }
2186       line.replaceAll("\n","")
2187       def vals = line.split(/\s+\*?/)
2188       def filename = vals[1]
2189       def innerHash = (hash.(idHash."${filename}"))."sha256" = vals[0]
2190     }
2191   }
2192
2193   // executable JAR
2194   def jarHash = getDataHash(file(shadowJar.archiveFile))
2195   if (jarHash != null) {
2196     hash."JAR" = jarHash;
2197   }
2198
2199   // version JVL
2200   def jvlHash =getDataHash(file(getdownVersionLaunchJvl))
2201   if (jvlHash != null) {
2202     hash."JVL" = jvlHash;
2203   }
2204
2205   // source TGZ
2206   def tarHash = getDataHash(file(sourceDist.archiveFile))
2207   if (tarHash != null) {
2208     hash."SOURCE" = tarHash;
2209   }
2210
2211   return hugoDataJsonFile.write(new JsonBuilder(hash).toPrettyString())
2212 }
2213
2214 task staticMakeInstallersJsonFile {
2215   doFirst {
2216     def output = findProperty("i4j_output")
2217     def sha256 = findProperty("i4j_sha256")
2218     def json = findProperty("i4j_json")
2219     if (output == null || sha256 == null || json == null) {
2220       throw new GradleException("Must provide paths to all of output.txt, sha256sums, and output.json with '-Pi4j_output=... -Pi4j_sha256=... -Pi4j_json=...")
2221     }
2222     writeDataJsonFile(file(output), file(sha256), file(json))
2223   }
2224 }
2225
2226 task installers {
2227   dependsOn installerFiles
2228 }
2229
2230
2231 spotless {
2232   java {
2233     eclipse().configFile(eclipse_codestyle_file)
2234   }
2235 }
2236
2237 task createSourceReleaseProperties(type: WriteProperties) {
2238   group = "distribution"
2239   description = "Create the source RELEASE properties file"
2240   
2241   def sourceTarBuildDir = "${buildDir}/sourceTar"
2242   def sourceReleasePropertiesFile = "${sourceTarBuildDir}/RELEASE"
2243   outputFile (sourceReleasePropertiesFile)
2244
2245   doFirst {
2246     releaseProps.each{ key, val -> property key, val }
2247     property "git.branch", gitBranch
2248     property "git.hash", gitHash
2249   }
2250
2251   outputs.file(outputFile)
2252 }
2253
2254 task sourceDist(type: Tar) {
2255   group "distribution"
2256   description "Create a source .tar.gz file for distribution"
2257
2258   dependsOn createBuildProperties
2259   dependsOn convertMdFiles
2260   dependsOn eclipseAllPreferences
2261   dependsOn createSourceReleaseProperties
2262
2263
2264   def outputFileName = "${project.name}_${JALVIEW_VERSION_UNDERSCORES}.tar.gz"
2265   archiveFileName = outputFileName
2266   
2267   compression Compression.GZIP
2268   
2269   into project.name
2270
2271   def EXCLUDE_FILES=[
2272     "build/*",
2273     "bin/*",
2274     "test-output/",
2275     "test-reports",
2276     "tests",
2277     "clover*/*",
2278     ".*",
2279     "benchmarking/*",
2280     "**/.*",
2281     "*.class",
2282     "**/*.class","$j11modDir/**/*.jar","appletlib","**/*locales",
2283     "*locales/**",
2284     "utils/InstallAnywhere",
2285     "**/*.log",
2286     "RELEASE",
2287   ] 
2288   def PROCESS_FILES=[
2289     "AUTHORS",
2290     "CITATION",
2291     "FEATURETODO",
2292     "JAVA-11-README",
2293     "FEATURETODO",
2294     "LICENSE",
2295     "**/README",
2296     "THIRDPARTYLIBS",
2297     "TESTNG",
2298     "build.gradle",
2299     "gradle.properties",
2300     "**/*.java",
2301     "**/*.html",
2302     "**/*.xml",
2303     "**/*.gradle",
2304     "**/*.groovy",
2305     "**/*.properties",
2306     "**/*.perl",
2307     "**/*.sh",
2308   ]
2309   def INCLUDE_FILES=[
2310     ".classpath",
2311     ".settings/org.eclipse.buildship.core.prefs",
2312     ".settings/org.eclipse.jdt.core.prefs"
2313   ]
2314
2315   from(jalviewDir) {
2316     exclude (EXCLUDE_FILES)
2317     include (PROCESS_FILES)
2318     filter(ReplaceTokens,
2319       beginToken: '$$',
2320       endToken: '$$',
2321       tokens: [
2322         'Version-Rel': JALVIEW_VERSION,
2323         'Year-Rel': getDate("yyyy")
2324       ]
2325     )
2326   }
2327   from(jalviewDir) {
2328     exclude (EXCLUDE_FILES)
2329     exclude (PROCESS_FILES)
2330     exclude ("appletlib")
2331     exclude ("**/*locales")
2332     exclude ("*locales/**")
2333     exclude ("utils/InstallAnywhere")
2334
2335     exclude (getdown_files_dir)
2336     exclude (getdown_website_dir)
2337
2338     // exluding these as not using jars as modules yet
2339     exclude ("${j11modDir}/**/*.jar")
2340   }
2341   from(jalviewDir) {
2342     include(INCLUDE_FILES)
2343   }
2344 //  from (jalviewDir) {
2345 //    // explicit includes for stuff that seemed to not get included
2346 //    include(fileTree("test/**/*."))
2347 //    exclude(EXCLUDE_FILES)
2348 //    exclude(PROCESS_FILES)
2349 //  }
2350
2351   from(file(buildProperties).getParent()) {
2352     include(file(buildProperties).getName())
2353     rename(file(buildProperties).getName(), "build_properties")
2354     filter({ line ->
2355       line.replaceAll("^INSTALLATION=.*\$","INSTALLATION=Source Release"+" git-commit\\\\:"+gitHash+" ["+gitBranch+"]")
2356     })
2357   }
2358
2359   def sourceTarBuildDir = "${buildDir}/sourceTar"
2360   from(sourceTarBuildDir) {
2361     // this includes the appended RELEASE properties file
2362   }
2363 }
2364
2365 task makeDataJsonFile {
2366   dependsOn installerFiles
2367   dependsOn sourceDist
2368   dependsOn getdownArchive
2369   dependsOn shadowJar
2370
2371   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
2372   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
2373   def VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
2374   def hugoDataJsonFile = file("${jalviewDir}/${hugo_build_dir}/data/installers-${VERSION_UNDERSCORES}.json")
2375
2376   inputs.file(installersOutputTxt)
2377   if (install4jCheckSums) {
2378     inputs.file(installersSha256)
2379   }
2380   outputs.file(hugoDataJsonFile)
2381
2382   doFirst {
2383     writeDataJsonFile(installersOutputTxt, installersSha256, hugoDataJsonFile)
2384   }
2385 }
2386
2387 task helppages {
2388   dependsOn copyHelp
2389   dependsOn pubhtmlhelp
2390   
2391   inputs.dir("${helpBuildDir}/${help_dir}")
2392   outputs.dir("${buildDir}/distributions/${help_dir}")
2393 }
2394
2395
2396 task j2sSetHeadlessBuild {
2397   doFirst {
2398     IN_ECLIPSE = false
2399   }
2400 }
2401
2402
2403 task jalviewjsEnableAltFileProperty(type: WriteProperties) {
2404   group "jalviewjs"
2405   description "Enable the alternative J2S Config file for headless build"
2406
2407   outputFile = jalviewjsJ2sSettingsFileName
2408   def j2sPropsFile = file(jalviewjsJ2sSettingsFileName)
2409   def j2sProps = new Properties()
2410   if (j2sPropsFile.exists()) {
2411     try {
2412       def j2sPropsFileFIS = new FileInputStream(j2sPropsFile)
2413       j2sProps.load(j2sPropsFileFIS)
2414       j2sPropsFileFIS.close()
2415
2416       j2sProps.each { prop, val ->
2417         property(prop, val)
2418       }
2419     } catch (Exception e) {
2420       println("Exception reading ${jalviewjsJ2sSettingsFileName}")
2421       e.printStackTrace()
2422     }
2423   }
2424   if (! j2sProps.stringPropertyNames().contains(jalviewjs_j2s_alt_file_property_config)) {
2425     property(jalviewjs_j2s_alt_file_property_config, jalviewjs_j2s_alt_file_property)
2426   }
2427 }
2428
2429
2430 task jalviewjsSetEclipseWorkspace {
2431   def propKey = "jalviewjs_eclipse_workspace"
2432   def propVal = null
2433   if (project.hasProperty(propKey)) {
2434     propVal = project.getProperty(propKey)
2435     if (propVal.startsWith("~/")) {
2436       propVal = System.getProperty("user.home") + propVal.substring(1)
2437     }
2438   }
2439   def propsFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_workspace_location_file}"
2440   def propsFile = file(propsFileName)
2441   def eclipseWsDir = propVal
2442   def props = new Properties()
2443
2444   def writeProps = true
2445   if (( eclipseWsDir == null || !file(eclipseWsDir).exists() ) && propsFile.exists()) {
2446     def ins = new FileInputStream(propsFileName)
2447     props.load(ins)
2448     ins.close()
2449     if (props.getProperty(propKey, null) != null) {
2450       eclipseWsDir = props.getProperty(propKey)
2451       writeProps = false
2452     }
2453   }
2454
2455   if (eclipseWsDir == null || !file(eclipseWsDir).exists()) {
2456     def tempDir = File.createTempDir()
2457     eclipseWsDir = tempDir.getAbsolutePath()
2458     writeProps = true
2459   }
2460   eclipseWorkspace = file(eclipseWsDir)
2461
2462   doFirst {
2463     // do not run a headless transpile when we claim to be in Eclipse
2464     if (IN_ECLIPSE) {
2465       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2466       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2467     } else {
2468       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2469     }
2470
2471     if (writeProps) {
2472       props.setProperty(propKey, eclipseWsDir)
2473       propsFile.parentFile.mkdirs()
2474       def bytes = new ByteArrayOutputStream()
2475       props.store(bytes, null)
2476       def propertiesString = bytes.toString()
2477       propsFile.text = propertiesString
2478       print("NEW ")
2479     } else {
2480       print("EXISTING ")
2481     }
2482
2483     println("ECLIPSE WORKSPACE: "+eclipseWorkspace.getPath())
2484   }
2485
2486   //inputs.property(propKey, eclipseWsDir) // eclipseWsDir only gets set once this task runs, so will be out-of-date
2487   outputs.file(propsFileName)
2488   outputs.upToDateWhen { eclipseWorkspace.exists() && propsFile.exists() }
2489 }
2490
2491
2492 task jalviewjsEclipsePaths {
2493   def eclipseProduct
2494
2495   def eclipseRoot = jalviewjs_eclipse_root
2496   if (eclipseRoot.startsWith("~/")) {
2497     eclipseRoot = System.getProperty("user.home") + eclipseRoot.substring(1)
2498   }
2499   if (OperatingSystem.current().isMacOsX()) {
2500     eclipseRoot += "/Eclipse.app"
2501     eclipseBinary = "${eclipseRoot}/Contents/MacOS/eclipse"
2502     eclipseProduct = "${eclipseRoot}/Contents/Eclipse/.eclipseproduct"
2503   } else if (OperatingSystem.current().isWindows()) { // check these paths!!
2504     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
2505       eclipseRoot += "/eclipse"
2506     }
2507     eclipseBinary = "${eclipseRoot}/eclipse.exe"
2508     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
2509   } else { // linux or unix
2510     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
2511       eclipseRoot += "/eclipse"
2512 println("eclipseDir exists")
2513     }
2514     eclipseBinary = "${eclipseRoot}/eclipse"
2515     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
2516   }
2517
2518   eclipseVersion = "4.13" // default
2519   def assumedVersion = true
2520   if (file(eclipseProduct).exists()) {
2521     def fis = new FileInputStream(eclipseProduct)
2522     def props = new Properties()
2523     props.load(fis)
2524     eclipseVersion = props.getProperty("version")
2525     fis.close()
2526     assumedVersion = false
2527   }
2528   
2529   def propKey = "eclipse_debug"
2530   eclipseDebug = (project.hasProperty(propKey) && project.getProperty(propKey).equals("true"))
2531
2532   doFirst {
2533     // do not run a headless transpile when we claim to be in Eclipse
2534     if (IN_ECLIPSE) {
2535       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2536       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2537     } else {
2538       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2539     }
2540
2541     if (!assumedVersion) {
2542       println("ECLIPSE VERSION=${eclipseVersion}")
2543     }
2544   }
2545 }
2546
2547
2548 task printProperties {
2549   group "Debug"
2550   description "Output to console all System.properties"
2551   doFirst {
2552     System.properties.each { key, val -> System.out.println("Property: ${key}=${val}") }
2553   }
2554 }
2555
2556
2557 task eclipseSetup {
2558   dependsOn eclipseProject
2559   dependsOn eclipseClasspath
2560   dependsOn eclipseJdt
2561 }
2562
2563
2564 // this version (type: Copy) will delete anything in the eclipse dropins folder that isn't in fromDropinsDir
2565 task jalviewjsEclipseCopyDropins(type: Copy) {
2566   dependsOn jalviewjsEclipsePaths
2567
2568   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_eclipse_dropins_dir}", include: "*.jar")
2569   inputFiles += file("${jalviewDir}/${jalviewjsJ2sPlugin}")
2570   def outputDir = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
2571
2572   from inputFiles
2573   into outputDir
2574 }
2575
2576
2577 // this eclipse -clean doesn't actually work
2578 task jalviewjsCleanEclipse(type: Exec) {
2579   dependsOn eclipseSetup
2580   dependsOn jalviewjsEclipsePaths
2581   dependsOn jalviewjsEclipseCopyDropins
2582
2583   executable(eclipseBinary)
2584   args(["-nosplash", "--launcher.suppressErrors", "-data", eclipseWorkspace.getPath(), "-clean", "-console", "-consoleLog"])
2585   if (eclipseDebug) {
2586     args += "-debug"
2587   }
2588   args += "-l"
2589
2590   def inputString = """exit
2591 y
2592 """
2593   def inputByteStream = new ByteArrayInputStream(inputString.getBytes())
2594   standardInput = inputByteStream
2595 }
2596
2597 /* not really working yet
2598 jalviewjsEclipseCopyDropins.finalizedBy jalviewjsCleanEclipse
2599 */
2600
2601
2602 task jalviewjsTransferUnzipSwingJs {
2603   def file_zip = "${jalviewDir}/${jalviewjs_swingjs_zip}"
2604
2605   doLast {
2606     copy {
2607       from zipTree(file_zip)
2608       into "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
2609     }
2610   }
2611
2612   inputs.file file_zip
2613   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
2614 }
2615
2616
2617 task jalviewjsTransferUnzipLib {
2618   def zipFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_libjs_dir}", include: "*.zip")
2619
2620   doLast {
2621     zipFiles.each { file_zip -> 
2622       copy {
2623         from zipTree(file_zip)
2624         into "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
2625       }
2626     }
2627   }
2628
2629   inputs.files zipFiles
2630   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
2631 }
2632
2633
2634 task jalviewjsTransferUnzipAllLibs {
2635   dependsOn jalviewjsTransferUnzipSwingJs
2636   dependsOn jalviewjsTransferUnzipLib
2637 }
2638
2639
2640 task jalviewjsCreateJ2sSettings(type: WriteProperties) {
2641   group "JalviewJS"
2642   description "Create the alternative j2s file from the j2s.* properties"
2643
2644   jalviewjsJ2sProps = project.properties.findAll { it.key.startsWith("j2s.") }.sort { it.key }
2645   def siteDirProperty = "j2s.site.directory"
2646   def setSiteDir = false
2647   jalviewjsJ2sProps.each { prop, val ->
2648     if (val != null) {
2649       if (prop == siteDirProperty) {
2650         if (!(val.startsWith('/') || val.startsWith("file://") )) {
2651           val = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${val}"
2652         }
2653         setSiteDir = true
2654       }
2655       property(prop,val)
2656     }
2657     if (!setSiteDir) { // default site location, don't override specifically set property
2658       property(siteDirProperty,"${jalviewDirRelativePath}/${jalviewjsTransferSiteJsDir}")
2659     }
2660   }
2661   outputFile = jalviewjsJ2sAltSettingsFileName
2662
2663   if (! IN_ECLIPSE) {
2664     inputs.properties(jalviewjsJ2sProps)
2665     outputs.file(jalviewjsJ2sAltSettingsFileName)
2666   }
2667 }
2668
2669
2670 task jalviewjsEclipseSetup {
2671   dependsOn jalviewjsEclipseCopyDropins
2672   dependsOn jalviewjsSetEclipseWorkspace
2673   dependsOn jalviewjsCreateJ2sSettings
2674 }
2675
2676
2677 task jalviewjsSyncAllLibs (type: Sync) {
2678   dependsOn jalviewjsTransferUnzipAllLibs
2679   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteLibDir}")
2680   inputFiles += fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
2681   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
2682
2683   from inputFiles
2684   into outputDir
2685   def outputFiles = []
2686   rename { filename ->
2687     outputFiles += "${outputDir}/${filename}"
2688     null
2689   }
2690   preserve {
2691     include "**"
2692   }
2693
2694   // should this be exclude really ?
2695   duplicatesStrategy "INCLUDE"
2696
2697   outputs.files outputFiles
2698   inputs.files inputFiles
2699 }
2700
2701
2702 task jalviewjsSyncResources (type: Sync) {
2703   dependsOn buildResources
2704
2705   def inputFiles = fileTree(dir: resourcesBuildDir)
2706   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
2707
2708   from inputFiles
2709   into outputDir
2710   def outputFiles = []
2711   rename { filename ->
2712     outputFiles += "${outputDir}/${filename}"
2713     null
2714   }
2715   preserve {
2716     include "**"
2717   }
2718   outputs.files outputFiles
2719   inputs.files inputFiles
2720 }
2721
2722
2723 task jalviewjsSyncSiteResources (type: Sync) {
2724   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_site_resource_dir}")
2725   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
2726
2727   from inputFiles
2728   into outputDir
2729   def outputFiles = []
2730   rename { filename ->
2731     outputFiles += "${outputDir}/${filename}"
2732     null
2733   }
2734   preserve {
2735     include "**"
2736   }
2737   outputs.files outputFiles
2738   inputs.files inputFiles
2739 }
2740
2741
2742 task jalviewjsSyncBuildProperties (type: Sync) {
2743   dependsOn createBuildProperties
2744   def inputFiles = [file(buildProperties)]
2745   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
2746
2747   from inputFiles
2748   into outputDir
2749   def outputFiles = []
2750   rename { filename ->
2751     outputFiles += "${outputDir}/${filename}"
2752     null
2753   }
2754   preserve {
2755     include "**"
2756   }
2757   outputs.files outputFiles
2758   inputs.files inputFiles
2759 }
2760
2761
2762 task jalviewjsProjectImport(type: Exec) {
2763   dependsOn eclipseSetup
2764   dependsOn jalviewjsEclipsePaths
2765   dependsOn jalviewjsEclipseSetup
2766
2767   doFirst {
2768     // do not run a headless import when we claim to be in Eclipse
2769     if (IN_ECLIPSE) {
2770       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2771       throw new StopExecutionException("Not running headless import whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2772     } else {
2773       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2774     }
2775   }
2776
2777   //def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview/org.eclipse.jdt.core"
2778   def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview"
2779   executable(eclipseBinary)
2780   args(["-nosplash", "--launcher.suppressErrors", "-application", "com.seeq.eclipse.importprojects.headlessimport", "-data", eclipseWorkspace.getPath(), "-import", jalviewDirAbsolutePath])
2781   if (eclipseDebug) {
2782     args += "-debug"
2783   }
2784   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
2785   if (!IN_ECLIPSE) {
2786     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
2787     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
2788   }
2789
2790   inputs.file("${jalviewDir}/.project")
2791   outputs.upToDateWhen { 
2792     file(projdir).exists()
2793   }
2794 }
2795
2796
2797 task jalviewjsTranspile(type: Exec) {
2798   dependsOn jalviewjsEclipseSetup 
2799   dependsOn jalviewjsProjectImport
2800   dependsOn jalviewjsEclipsePaths
2801   if (!IN_ECLIPSE) {
2802     dependsOn jalviewjsEnableAltFileProperty
2803   }
2804
2805   doFirst {
2806     // do not run a headless transpile when we claim to be in Eclipse
2807     if (IN_ECLIPSE) {
2808       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2809       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2810     } else {
2811       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2812     }
2813   }
2814
2815   executable(eclipseBinary)
2816   args(["-nosplash", "--launcher.suppressErrors", "-application", "org.eclipse.jdt.apt.core.aptBuild", "-data", eclipseWorkspace, "-${jalviewjs_eclipse_build_arg}", eclipse_project_name ])
2817   if (eclipseDebug) {
2818     args += "-debug"
2819   }
2820   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
2821   if (!IN_ECLIPSE) {
2822     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
2823     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
2824   }
2825
2826   def stdout
2827   def stderr
2828   doFirst {
2829     stdout = new ByteArrayOutputStream()
2830     stderr = new ByteArrayOutputStream()
2831
2832     def logOutFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}"
2833     def logOutFile = file(logOutFileName)
2834     logOutFile.createNewFile()
2835     logOutFile.text = """ROOT: ${jalviewjs_eclipse_root}
2836 BINARY: ${eclipseBinary}
2837 VERSION: ${eclipseVersion}
2838 WORKSPACE: ${eclipseWorkspace}
2839 DEBUG: ${eclipseDebug}
2840 ----
2841 """
2842     def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
2843     // combine stdout and stderr
2844     def logErrFOS = logOutFOS
2845
2846     if (jalviewjs_j2s_to_console.equals("true")) {
2847       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2848         new org.apache.tools.ant.util.TeeOutputStream(
2849           logOutFOS,
2850           stdout),
2851         System.out)
2852       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2853         new org.apache.tools.ant.util.TeeOutputStream(
2854           logErrFOS,
2855           stderr),
2856         System.err)
2857     } else {
2858       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2859         logOutFOS,
2860         stdout)
2861       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2862         logErrFOS,
2863         stderr)
2864     }
2865   }
2866
2867   doLast {
2868     if (stdout.toString().contains("Error processing ")) {
2869       // j2s did not complete transpile
2870       //throw new TaskExecutionException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
2871       if (jalviewjs_ignore_transpile_errors.equals("true")) {
2872         println("IGNORING TRANSPILE ERRORS")
2873         println("See eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
2874       } else {
2875         throw new GradleException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
2876       }
2877     }
2878   }
2879
2880   inputs.dir("${jalviewDir}/${sourceDir}")
2881   outputs.dir("${jalviewDir}/${jalviewjsTransferSiteJsDir}")
2882   outputs.upToDateWhen( { file("${jalviewDir}/${jalviewjsTransferSiteJsDir}${jalviewjs_server_resource}").exists() } )
2883 }
2884
2885
2886 def jalviewjsCallCore(String name, FileCollection list, String prefixFile, String suffixFile, String jsfile, String zjsfile, File logOutFile, Boolean logOutConsole) {
2887
2888   def stdout = new ByteArrayOutputStream()
2889   def stderr = new ByteArrayOutputStream()
2890
2891   def coreFile = file(jsfile)
2892   def msg = ""
2893   msg = "Creating core for ${name}...\nGenerating ${jsfile}"
2894   println(msg)
2895   logOutFile.createNewFile()
2896   logOutFile.append(msg+"\n")
2897
2898   def coreTop = file(prefixFile)
2899   def coreBottom = file(suffixFile)
2900   coreFile.getParentFile().mkdirs()
2901   coreFile.createNewFile()
2902   coreFile.write( coreTop.getText("UTF-8") )
2903   list.each {
2904     f ->
2905     if (f.exists()) {
2906       def t = f.getText("UTF-8")
2907       t.replaceAll("Clazz\\.([^_])","Clazz_${1}")
2908       coreFile.append( t )
2909     } else {
2910       msg = "...file '"+f.getPath()+"' does not exist, skipping"
2911       println(msg)
2912       logOutFile.append(msg+"\n")
2913     }
2914   }
2915   coreFile.append( coreBottom.getText("UTF-8") )
2916
2917   msg = "Generating ${zjsfile}"
2918   println(msg)
2919   logOutFile.append(msg+"\n")
2920   def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
2921   def logErrFOS = logOutFOS
2922
2923   javaexec {
2924     classpath = files(["${jalviewDir}/${jalviewjs_closure_compiler}"])
2925     main = "com.google.javascript.jscomp.CommandLineRunner"
2926     jvmArgs = [ "-Dfile.encoding=UTF-8" ]
2927     args = [ "--compilation_level", "SIMPLE_OPTIMIZATIONS", "--warning_level", "QUIET", "--charset", "UTF-8", "--js", jsfile, "--js_output_file", zjsfile ]
2928     maxHeapSize = "2g"
2929
2930     msg = "\nRunning '"+commandLine.join(' ')+"'\n"
2931     println(msg)
2932     logOutFile.append(msg+"\n")
2933
2934     if (logOutConsole) {
2935       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2936         new org.apache.tools.ant.util.TeeOutputStream(
2937           logOutFOS,
2938           stdout),
2939         standardOutput)
2940         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2941           new org.apache.tools.ant.util.TeeOutputStream(
2942             logErrFOS,
2943             stderr),
2944           System.err)
2945     } else {
2946       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2947         logOutFOS,
2948         stdout)
2949         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2950           logErrFOS,
2951           stderr)
2952     }
2953   }
2954   msg = "--"
2955   println(msg)
2956   logOutFile.append(msg+"\n")
2957 }
2958
2959
2960 task jalviewjsBuildAllCores {
2961   group "JalviewJS"
2962   description "Build the core js lib closures listed in the classlists dir"
2963   dependsOn jalviewjsTranspile
2964   dependsOn jalviewjsTransferUnzipSwingJs
2965
2966   def j2sDir = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${jalviewjs_j2s_subdir}"
2967   def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_j2s_subdir}"
2968   def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteLibDir}/${jalviewjs_j2s_subdir}"
2969   def jsDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_js_subdir}"
2970   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
2971   def prefixFile = "${jsDir}/core/coretop2.js"
2972   def suffixFile = "${jsDir}/core/corebottom2.js"
2973
2974   inputs.file prefixFile
2975   inputs.file suffixFile
2976
2977   def classlistFiles = []
2978   // add the classlists found int the jalviewjs_classlists_dir
2979   fileTree(dir: "${jalviewDir}/${jalviewjs_classlists_dir}", include: "*.txt").each {
2980     file ->
2981     def name = file.getName() - ".txt"
2982     classlistFiles += [
2983       'file': file,
2984       'name': name
2985     ]
2986   }
2987
2988   // _jmol and _jalview cores. Add any other peculiar classlist.txt files here
2989   //classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jmol}"), 'name': "_jvjmol" ]
2990   classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jalview}"), 'name': jalviewjsJalviewCoreName ]
2991
2992   jalviewjsCoreClasslists = []
2993
2994   classlistFiles.each {
2995     hash ->
2996
2997     def file = hash['file']
2998     if (! file.exists()) {
2999       //println("...classlist file '"+file.getPath()+"' does not exist, skipping")
3000       return false // this is a "continue" in groovy .each closure
3001     }
3002     def name = hash['name']
3003     if (name == null) {
3004       name = file.getName() - ".txt"
3005     }
3006
3007     def filelist = []
3008     file.eachLine {
3009       line ->
3010         filelist += line
3011     }
3012     def list = fileTree(dir: j2sDir, includes: filelist)
3013
3014     def jsfile = "${outputDir}/core${name}.js"
3015     def zjsfile = "${outputDir}/core${name}.z.js"
3016
3017     jalviewjsCoreClasslists += [
3018       'jsfile': jsfile,
3019       'zjsfile': zjsfile,
3020       'list': list,
3021       'name': name
3022     ]
3023
3024     inputs.file(file)
3025     inputs.files(list)
3026     outputs.file(jsfile)
3027     outputs.file(zjsfile)
3028   }
3029   
3030   // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
3031   def stevesoftClasslistName = "_stevesoft"
3032   def stevesoftClasslist = [
3033     'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
3034     'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
3035     'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
3036     'name': stevesoftClasslistName
3037   ]
3038   jalviewjsCoreClasslists += stevesoftClasslist
3039   inputs.files(stevesoftClasslist['list'])
3040   outputs.file(stevesoftClasslist['jsfile'])
3041   outputs.file(stevesoftClasslist['zjsfile'])
3042
3043   // _all core
3044   def allClasslistName = "_all"
3045   def allJsFiles = fileTree(dir: j2sDir, include: "**/*.js")
3046   allJsFiles += fileTree(
3047     dir: libJ2sDir,
3048     include: "**/*.js",
3049     excludes: [
3050       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3051       "**/org/jmol/jvxl/readers/IsoIntersectFileReader.js",
3052       "**/org/jmol/export/JSExporter.js"
3053     ]
3054   )
3055   allJsFiles += fileTree(
3056     dir: swingJ2sDir,
3057     include: "**/*.js",
3058     excludes: [
3059       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3060       "**/sun/misc/Unsafe.js",
3061       "**/swingjs/jquery/jquery-editable-select.js",
3062       "**/swingjs/jquery/j2sComboBox.js",
3063       "**/sun/misc/FloatingDecimal.js"
3064     ]
3065   )
3066   def allClasslist = [
3067     'jsfile': "${outputDir}/core${allClasslistName}.js",
3068     'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
3069     'list': allJsFiles,
3070     'name': allClasslistName
3071   ]
3072   // not including this version of "all" core at the moment
3073   //jalviewjsCoreClasslists += allClasslist
3074   inputs.files(allClasslist['list'])
3075   outputs.file(allClasslist['jsfile'])
3076   outputs.file(allClasslist['zjsfile'])
3077
3078   doFirst {
3079     def logOutFile = file("${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_closure_stdout}")
3080     logOutFile.getParentFile().mkdirs()
3081     logOutFile.createNewFile()
3082     logOutFile.write(getDate("yyyy-MM-dd HH:mm:ss")+" jalviewjsBuildAllCores\n----\n")
3083
3084     jalviewjsCoreClasslists.each {
3085       jalviewjsCallCore(it.name, it.list, prefixFile, suffixFile, it.jsfile, it.zjsfile, logOutFile, jalviewjs_j2s_to_console.equals("true"))
3086     }
3087   }
3088
3089 }
3090
3091
3092 def jalviewjsPublishCoreTemplate(String coreName, String templateName, File inputFile, String outputFile) {
3093   copy {
3094     from inputFile
3095     into file(outputFile).getParentFile()
3096     rename { filename ->
3097       if (filename.equals(inputFile.getName())) {
3098         return file(outputFile).getName()
3099       }
3100       return null
3101     }
3102     filter(ReplaceTokens,
3103       beginToken: '_',
3104       endToken: '_',
3105       tokens: [
3106         'MAIN': '"'+main_class+'"',
3107         'CODE': "null",
3108         'NAME': jalviewjsJalviewTemplateName+" [core ${coreName}]",
3109         'COREKEY': jalviewjs_core_key,
3110         'CORENAME': coreName
3111       ]
3112     )
3113   }
3114 }
3115
3116
3117 task jalviewjsPublishCoreTemplates {
3118   dependsOn jalviewjsBuildAllCores
3119   def inputFileName = "${jalviewDir}/${j2s_coretemplate_html}"
3120   def inputFile = file(inputFileName)
3121   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3122
3123   def outputFiles = []
3124   jalviewjsCoreClasslists.each { cl ->
3125     def outputFile = "${outputDir}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
3126     cl['outputfile'] = outputFile
3127     outputFiles += outputFile
3128   }
3129
3130   doFirst {
3131     jalviewjsCoreClasslists.each { cl ->
3132       jalviewjsPublishCoreTemplate(cl.name, jalviewjsJalviewTemplateName, inputFile, cl.outputfile)
3133     }
3134   }
3135   inputs.file(inputFile)
3136   outputs.files(outputFiles)
3137 }
3138
3139
3140 task jalviewjsSyncCore (type: Sync) {
3141   dependsOn jalviewjsBuildAllCores
3142   dependsOn jalviewjsPublishCoreTemplates
3143   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
3144   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3145
3146   from inputFiles
3147   into outputDir
3148   def outputFiles = []
3149   rename { filename ->
3150     outputFiles += "${outputDir}/${filename}"
3151     null
3152   }
3153   preserve {
3154     include "**"
3155   }
3156   outputs.files outputFiles
3157   inputs.files inputFiles
3158 }
3159
3160
3161 // this Copy version of TransferSiteJs will delete anything else in the target dir
3162 task jalviewjsCopyTransferSiteJs(type: Copy) {
3163   dependsOn jalviewjsTranspile
3164   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3165   into "${jalviewDir}/${jalviewjsSiteDir}"
3166 }
3167
3168
3169 // this Sync version of TransferSite is used by buildship to keep the website automatically up to date when a file changes
3170 task jalviewjsSyncTransferSiteJs(type: Sync) {
3171   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3172   include "**/*.*"
3173   into "${jalviewDir}/${jalviewjsSiteDir}"
3174   preserve {
3175     include "**"
3176   }
3177 }
3178
3179
3180 jalviewjsSyncAllLibs.mustRunAfter jalviewjsCopyTransferSiteJs
3181 jalviewjsSyncResources.mustRunAfter jalviewjsCopyTransferSiteJs
3182 jalviewjsSyncSiteResources.mustRunAfter jalviewjsCopyTransferSiteJs
3183 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsCopyTransferSiteJs
3184
3185 jalviewjsSyncAllLibs.mustRunAfter jalviewjsSyncTransferSiteJs
3186 jalviewjsSyncResources.mustRunAfter jalviewjsSyncTransferSiteJs
3187 jalviewjsSyncSiteResources.mustRunAfter jalviewjsSyncTransferSiteJs
3188 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsSyncTransferSiteJs
3189
3190
3191 task jalviewjsPrepareSite {
3192   group "JalviewJS"
3193   description "Prepares the website folder including unzipping files and copying resources"
3194   dependsOn jalviewjsSyncAllLibs
3195   dependsOn jalviewjsSyncResources
3196   dependsOn jalviewjsSyncSiteResources
3197   dependsOn jalviewjsSyncBuildProperties
3198   dependsOn jalviewjsSyncCore
3199 }
3200
3201
3202 task jalviewjsBuildSite {
3203   group "JalviewJS"
3204   description "Builds the whole website including transpiled code"
3205   dependsOn jalviewjsCopyTransferSiteJs
3206   dependsOn jalviewjsPrepareSite
3207 }
3208
3209
3210 task cleanJalviewjsTransferSite {
3211   doFirst {
3212     delete "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3213     delete "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3214     delete "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3215     delete "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3216   }
3217 }
3218
3219
3220 task cleanJalviewjsSite {
3221   dependsOn cleanJalviewjsTransferSite
3222   doFirst {
3223     delete "${jalviewDir}/${jalviewjsSiteDir}"
3224   }
3225 }
3226
3227
3228 task jalviewjsSiteTar(type: Tar) {
3229   group "JalviewJS"
3230   description "Creates a tar.gz file for the website"
3231   dependsOn jalviewjsBuildSite
3232   def outputFilename = "jalviewjs-site-${JALVIEW_VERSION}.tar.gz"
3233   archiveFileName = outputFilename
3234
3235   compression Compression.GZIP
3236
3237   from "${jalviewDir}/${jalviewjsSiteDir}"
3238   into jalviewjs_site_dir // this is inside the tar file
3239
3240   inputs.dir("${jalviewDir}/${jalviewjsSiteDir}")
3241 }
3242
3243
3244 task jalviewjsServer {
3245   group "JalviewJS"
3246   def filename = "jalviewjsTest.html"
3247   description "Starts a webserver on localhost to test the website. See ${filename} to access local site on most recently used port."
3248   def htmlFile = "${jalviewDirAbsolutePath}/${filename}"
3249   doLast {
3250
3251     def factory
3252     try {
3253       def f = Class.forName("org.gradle.plugins.javascript.envjs.http.simple.SimpleHttpFileServerFactory")
3254       factory = f.newInstance()
3255     } catch (ClassNotFoundException e) {
3256       throw new GradleException("Unable to create SimpleHttpFileServerFactory")
3257     }
3258     def port = Integer.valueOf(jalviewjs_server_port)
3259     def start = port
3260     def running = false
3261     def url
3262     def jalviewjsServer
3263     while(port < start+1000 && !running) {
3264       try {
3265         def doc_root = new File("${jalviewDirAbsolutePath}/${jalviewjsSiteDir}")
3266         jalviewjsServer = factory.start(doc_root, port)
3267         running = true
3268         url = jalviewjsServer.getResourceUrl(jalviewjs_server_resource)
3269         println("SERVER STARTED with document root ${doc_root}.")
3270         println("Go to "+url+" . Run  gradle --stop  to stop (kills all gradle daemons).")
3271         println("For debug: "+url+"?j2sdebug")
3272         println("For verbose: "+url+"?j2sverbose")
3273       } catch (Exception e) {
3274         port++;
3275       }
3276     }
3277     def htmlText = """
3278       <p><a href="${url}">JalviewJS Test. &lt;${url}&gt;</a></p>
3279       <p><a href="${url}?j2sdebug">JalviewJS Test with debug. &lt;${url}?j2sdebug&gt;</a></p>
3280       <p><a href="${url}?j2sverbose">JalviewJS Test with verbose. &lt;${url}?j2sdebug&gt;</a></p>
3281       """
3282     jalviewjsCoreClasslists.each { cl ->
3283       def urlcore = jalviewjsServer.getResourceUrl(file(cl.outputfile).getName())
3284       htmlText += """
3285       <p><a href="${urlcore}">${jalviewjsJalviewTemplateName} [core ${cl.name}]. &lt;${urlcore}&gt;</a></p>
3286       """
3287       println("For core ${cl.name}: "+urlcore)
3288     }
3289
3290     file(htmlFile).text = htmlText
3291   }
3292
3293   outputs.file(htmlFile)
3294   outputs.upToDateWhen({false})
3295 }
3296
3297
3298 task cleanJalviewjsAll {
3299   group "JalviewJS"
3300   description "Delete all configuration and build artifacts to do with JalviewJS build"
3301   dependsOn cleanJalviewjsSite
3302   dependsOn jalviewjsEclipsePaths
3303   
3304   doFirst {
3305     delete "${jalviewDir}/${jalviewjsBuildDir}"
3306     delete "${jalviewDir}/${eclipse_bin_dir}"
3307     if (eclipseWorkspace != null && file(eclipseWorkspace.getAbsolutePath()+"/.metadata").exists()) {
3308       delete file(eclipseWorkspace.getAbsolutePath()+"/.metadata")
3309     }
3310     delete jalviewjsJ2sAltSettingsFileName
3311   }
3312
3313   outputs.upToDateWhen( { false } )
3314 }
3315
3316
3317 task jalviewjsIDE_checkJ2sPlugin {
3318   group "00 JalviewJS in Eclipse"
3319   description "Compare the swingjs/net.sf.j2s.core(-j11)?.jar file with the Eclipse IDE's plugin version (found in the 'dropins' dir)"
3320
3321   doFirst {
3322     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
3323     def j2sPluginFile = file(j2sPlugin)
3324     def eclipseHome = System.properties["eclipse.home.location"]
3325     if (eclipseHome == null || ! IN_ECLIPSE) {
3326       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. Skipping J2S Plugin Check.")
3327     }
3328     def eclipseJ2sPluginDirs = [ "${eclipseHome}/dropins" ]
3329     def altPluginsDir = System.properties["org.eclipse.equinox.p2.reconciler.dropins.directory"]
3330     if (altPluginsDir != null && file(altPluginsDir).exists()) {
3331       eclipseJ2sPluginDirs += altPluginsDir
3332     }
3333     def foundPlugin = false
3334     def j2sPluginFileName = j2sPluginFile.getName()
3335     def eclipseJ2sPlugin
3336     def eclipseJ2sPluginFile
3337     eclipseJ2sPluginDirs.any { dir ->
3338       eclipseJ2sPlugin = "${dir}/${j2sPluginFileName}"
3339       eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
3340       if (eclipseJ2sPluginFile.exists()) {
3341         foundPlugin = true
3342         return true
3343       }
3344     }
3345     if (!foundPlugin) {
3346       def msg = "Eclipse J2S Plugin is not installed (could not find '${j2sPluginFileName}' in\n"+eclipseJ2sPluginDirs.join("\n")+"\n)\nTry running task jalviewjsIDE_copyJ2sPlugin"
3347       System.err.println(msg)
3348       throw new StopExecutionException(msg)
3349     }
3350
3351     def digest = MessageDigest.getInstance("MD5")
3352
3353     digest.update(j2sPluginFile.text.bytes)
3354     def j2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
3355
3356     digest.update(eclipseJ2sPluginFile.text.bytes)
3357     def eclipseJ2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
3358      
3359     if (j2sPluginMd5 != eclipseJ2sPluginMd5) {
3360       def msg = "WARNING! Eclipse J2S Plugin '${eclipseJ2sPlugin}' is different to this commit's version '${j2sPlugin}'"
3361       System.err.println(msg)
3362       throw new StopExecutionException(msg)
3363     } else {
3364       def msg = "Eclipse J2S Plugin '${eclipseJ2sPlugin}' is the same as '${j2sPlugin}' (this is good)"
3365       println(msg)
3366     }
3367   }
3368 }
3369
3370 task jalviewjsIDE_copyJ2sPlugin {
3371   group "00 JalviewJS in Eclipse"
3372   description "Copy the swingjs/net.sf.j2s.core(-j11)?.jar file into the Eclipse IDE's 'dropins' dir"
3373
3374   doFirst {
3375     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
3376     def j2sPluginFile = file(j2sPlugin)
3377     def eclipseHome = System.properties["eclipse.home.location"]
3378     if (eclipseHome == null || ! IN_ECLIPSE) {
3379       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. NOT copying J2S Plugin.")
3380     }
3381     def eclipseJ2sPlugin = "${eclipseHome}/dropins/${j2sPluginFile.getName()}"
3382     def eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
3383     def msg = "WARNING! Copying this commit's j2s plugin '${j2sPlugin}' to Eclipse J2S Plugin '${eclipseJ2sPlugin}'\n* May require an Eclipse restart"
3384     System.err.println(msg)
3385     copy {
3386       from j2sPlugin
3387       eclipseJ2sPluginFile.getParentFile().mkdirs()
3388       into eclipseJ2sPluginFile.getParent()
3389     }
3390   }
3391 }
3392
3393
3394 task jalviewjsIDE_j2sFile {
3395   group "00 JalviewJS in Eclipse"
3396   description "Creates the .j2s file"
3397   dependsOn jalviewjsCreateJ2sSettings
3398 }
3399
3400
3401 task jalviewjsIDE_SyncCore {
3402   group "00 JalviewJS in Eclipse"
3403   description "Build the core js lib closures listed in the classlists dir and publish core html from template"
3404   dependsOn jalviewjsSyncCore
3405 }
3406
3407
3408 task jalviewjsIDE_SyncSiteAll {
3409   dependsOn jalviewjsSyncAllLibs
3410   dependsOn jalviewjsSyncResources
3411   dependsOn jalviewjsSyncSiteResources
3412   dependsOn jalviewjsSyncBuildProperties
3413 }
3414
3415
3416 cleanJalviewjsTransferSite.mustRunAfter jalviewjsIDE_SyncSiteAll
3417
3418
3419 task jalviewjsIDE_PrepareSite {
3420   group "00 JalviewJS in Eclipse"
3421   description "Sync libs and resources to site dir, but not closure cores"
3422
3423   dependsOn jalviewjsIDE_SyncSiteAll
3424   //dependsOn cleanJalviewjsTransferSite // not sure why this clean is here -- will slow down a re-run of this task
3425 }
3426
3427
3428 task jalviewjsIDE_AssembleSite {
3429   group "00 JalviewJS in Eclipse"
3430   description "Assembles unzipped supporting zipfiles, resources, site resources and closure cores into the Eclipse transpiled site"
3431   dependsOn jalviewjsPrepareSite
3432 }
3433
3434
3435 task jalviewjsIDE_SiteClean {
3436   group "00 JalviewJS in Eclipse"
3437   description "Deletes the Eclipse transpiled site"
3438   dependsOn cleanJalviewjsSite
3439 }
3440
3441
3442 task jalviewjsIDE_Server {
3443   group "00 JalviewJS in Eclipse"
3444   description "Starts a webserver on localhost to test the website"
3445   dependsOn jalviewjsServer
3446 }
3447
3448
3449 // buildship runs this at import or gradle refresh
3450 task eclipseSynchronizationTask {
3451   //dependsOn eclipseSetup
3452   dependsOn createBuildProperties
3453   if (J2S_ENABLED) {
3454     dependsOn jalviewjsIDE_j2sFile
3455     dependsOn jalviewjsIDE_checkJ2sPlugin
3456     dependsOn jalviewjsIDE_PrepareSite
3457   }
3458 }
3459
3460
3461 // buildship runs this at build time or project refresh
3462 task eclipseAutoBuildTask {
3463   //dependsOn jalviewjsIDE_checkJ2sPlugin
3464   //dependsOn jalviewjsIDE_PrepareSite
3465 }
3466
3467
3468 task jalviewjs {
3469   group "JalviewJS"
3470   description "Build the site"
3471   dependsOn jalviewjsBuildSite
3472 }