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