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