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