First try at merging didn't go so well.
[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   jalviewjsJalviewCoreName = string(jalviewjs_core_name)
461   jalviewjsCoreClasslists = []
462   jalviewjsJalviewTemplateName = string(jalviewjs_name)
463   jalviewjsJ2sSettingsFileName = string("${jalviewDir}/${jalviewjs_j2s_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   def errFOS = outFOS
1303   standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
1304     outFOS,
1305     standardOutput)
1306   errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
1307     outFOS,
1308     errorOutput)
1309
1310   inputs.dir("${classesDir}/${help_dir}")
1311   outputs.file(helpLinksCheckerOutFile)
1312 }
1313
1314 // import the pubhtmlhelp target
1315 ant.properties.basedir = "${jalviewDir}"
1316 ant.properties.helpBuildDir = "${jalviewDirAbsolutePath}/${classes_dir}/${help_dir}"
1317 ant.importBuild "${utils_dir}/publishHelp.xml"
1318
1319
1320 task cleanPackageDir(type: Delete) {
1321   doFirst {
1322     delete fileTree(dir: "${jalviewDir}/${package_dir}", include: "*.jar")
1323   }
1324 }
1325
1326
1327 jar {
1328   dependsOn linkCheck
1329   dependsOn buildIndices
1330   dependsOn createBuildProperties
1331
1332   manifest {
1333     attributes "Main-Class": main_class,
1334     "Permissions": "all-permissions",
1335     "Application-Name": "Jalview Desktop",
1336     "Codebase": application_codebase
1337   }
1338
1339   destinationDir = file("${jalviewDir}/${package_dir}")
1340   archiveName = rootProject.name+".jar"
1341
1342   exclude "cache*/**"
1343   exclude "*.jar"
1344   exclude "*.jar.*"
1345   exclude "**/*.jar"
1346   exclude "**/*.jar.*"
1347
1348   inputs.dir(classesDir)
1349   outputs.file("${jalviewDir}/${package_dir}/${archiveName}")
1350 }
1351
1352
1353 task copyJars(type: Copy) {
1354   from fileTree(dir: classesDir, include: "**/*.jar").files
1355   into "${jalviewDir}/${package_dir}"
1356 }
1357
1358
1359 // doing a Sync instead of Copy as Copy doesn't deal with "outputs" very well
1360 task syncJars(type: Sync) {
1361   from fileTree(dir: "${jalviewDir}/${libDistDir}", include: "**/*.jar").files
1362   into "${jalviewDir}/${package_dir}"
1363   preserve {
1364     include jar.archiveName
1365   }
1366 }
1367
1368
1369 task makeDist {
1370   group = "build"
1371   description = "Put all required libraries in dist"
1372   // order of "cleanPackageDir", "copyJars", "jar" important!
1373   jar.mustRunAfter cleanPackageDir
1374   syncJars.mustRunAfter cleanPackageDir
1375   dependsOn cleanPackageDir
1376   dependsOn syncJars
1377   dependsOn jar
1378   outputs.dir("${jalviewDir}/${package_dir}")
1379 }
1380
1381
1382 task cleanDist {
1383   dependsOn cleanPackageDir
1384   dependsOn cleanTest
1385   dependsOn clean
1386 }
1387
1388 shadowJar {
1389   group = "distribution"
1390   description = "Create a single jar file with all dependency libraries merged. Can be run with java -jar"
1391   if (buildDist) {
1392     dependsOn makeDist
1393   }
1394   from ("${jalviewDir}/${libDistDir}") {
1395     include("*.jar")
1396   }
1397   manifest {
1398     attributes 'Implementation-Version': JALVIEW_VERSION
1399   }
1400   mainClassName = shadow_jar_main_class
1401   mergeServiceFiles()
1402   classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
1403   minimize()
1404 }
1405
1406
1407 task getdownWebsite() {
1408   group = "distribution"
1409   description = "Create the getdown minimal app folder, and website folder for this version of jalview. Website folder also used for offline app installer"
1410   if (buildDist) {
1411     dependsOn makeDist
1412   }
1413
1414   def getdownWebsiteResourceFilenames = []
1415   def getdownTextString = ""
1416   def getdownResourceDir = getdownResourceDir
1417   def getdownResourceFilenames = []
1418
1419   doFirst {
1420     // clean the getdown website and files dir before creating getdown folders
1421     delete getdownWebsiteDir
1422     delete getdownFilesDir
1423
1424     copy {
1425       from buildProperties
1426       rename(build_properties_file, getdown_build_properties)
1427       into getdownAppDir
1428     }
1429     getdownWebsiteResourceFilenames += "${getdownAppDistDir}/${getdown_build_properties}"
1430
1431     // set some getdown_txt_ properties then go through all properties looking for getdown_txt_...
1432     def props = project.properties.sort { it.key }
1433     if (getdownAltJavaMinVersion != null && getdownAltJavaMinVersion.length() > 0) {
1434       props.put("getdown_txt_java_min_version", getdownAltJavaMinVersion)
1435     }
1436     if (getdownAltJavaMaxVersion != null && getdownAltJavaMaxVersion.length() > 0) {
1437       props.put("getdown_txt_java_max_version", getdownAltJavaMaxVersion)
1438     }
1439     if (getdownAltMultiJavaLocation != null && getdownAltMultiJavaLocation.length() > 0) {
1440       props.put("getdown_txt_multi_java_location", getdownAltMultiJavaLocation)
1441     }
1442
1443     props.put("getdown_txt_title", jalview_name)
1444     props.put("getdown_txt_ui.name", install4jApplicationName)
1445
1446     // start with appbase
1447     getdownTextString += "appbase = ${getdownAppBase}\n"
1448     props.each{ prop, val ->
1449       if (prop.startsWith("getdown_txt_") && val != null) {
1450         if (prop.startsWith("getdown_txt_multi_")) {
1451           def key = prop.substring(18)
1452           val.split(",").each{ v ->
1453             def line = "${key} = ${v}\n"
1454             getdownTextString += line
1455           }
1456         } else {
1457           // file values rationalised
1458           if (val.indexOf('/') > -1 || prop.startsWith("getdown_txt_resource")) {
1459             def r = null
1460             if (val.indexOf('/') == 0) {
1461               // absolute path
1462               r = file(val)
1463             } else if (val.indexOf('/') > 0) {
1464               // relative path (relative to jalviewDir)
1465               r = file( "${jalviewDir}/${val}" )
1466             }
1467             if (r.exists()) {
1468               val = "${getdown_resource_dir}/" + r.getName()
1469               getdownWebsiteResourceFilenames += val
1470               getdownResourceFilenames += r.getPath()
1471             }
1472           }
1473           if (! prop.startsWith("getdown_txt_resource")) {
1474             def line = prop.substring(12) + " = ${val}\n"
1475             getdownTextString += line
1476           }
1477         }
1478       }
1479     }
1480
1481     getdownWebsiteResourceFilenames.each{ filename ->
1482       getdownTextString += "resource = ${filename}\n"
1483     }
1484     getdownResourceFilenames.each{ filename ->
1485       copy {
1486         from filename
1487         into getdownResourceDir
1488       }
1489     }
1490
1491     def codeFiles = []
1492     fileTree(file(package_dir)).each{ f ->
1493       if (f.isDirectory()) {
1494         def files = fileTree(dir: f, include: ["*"]).getFiles()
1495         codeFiles += files
1496       } else if (f.exists()) {
1497         codeFiles += f
1498       }
1499     }
1500     codeFiles.sort().each{f ->
1501       def name = f.getName()
1502       def line = "code = ${getdownAppDistDir}/${name}\n"
1503       getdownTextString += line
1504       copy {
1505         from f.getPath()
1506         into getdownAppDir
1507       }
1508     }
1509
1510     // NOT USING MODULES YET, EVERYTHING SHOULD BE IN dist
1511     /*
1512     if (JAVA_VERSION.equals("11")) {
1513     def j11libFiles = fileTree(dir: "${jalviewDir}/${j11libDir}", include: ["*.jar"]).getFiles()
1514     j11libFiles.sort().each{f ->
1515     def name = f.getName()
1516     def line = "code = ${getdown_j11lib_dir}/${name}\n"
1517     getdownTextString += line
1518     copy {
1519     from f.getPath()
1520     into getdownJ11libDir
1521     }
1522     }
1523     }
1524      */
1525
1526     // 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.
1527     //getdownTextString += "class = " + file(getdownLauncher).getName() + "\n"
1528     getdownTextString += "resource = ${getdown_launcher_new}\n"
1529     getdownTextString += "class = ${main_class}\n"
1530
1531     def getdown_txt = file("${getdownWebsiteDir}/getdown.txt")
1532     getdown_txt.write(getdownTextString)
1533
1534     def getdownLaunchJvl = getdown_launch_jvl_name + ( (jvlChannelName != null && jvlChannelName.length() > 0)?"-${jvlChannelName}":"" ) + ".jvl"
1535     def launchJvl = file("${getdownWebsiteDir}/${getdownLaunchJvl}")
1536     launchJvl.write("appbase=${getdownAppBase}")
1537
1538     copy {
1539       from getdownLauncher
1540       rename(file(getdownLauncher).getName(), getdown_launcher_new)
1541       into getdownWebsiteDir
1542     }
1543
1544     copy {
1545       from getdownLauncher
1546       if (file(getdownLauncher).getName() != getdown_launcher) {
1547         rename(file(getdownLauncher).getName(), getdown_launcher)
1548       }
1549       into getdownWebsiteDir
1550     }
1551
1552     if (! (CHANNEL.startsWith("ARCHIVE") || CHANNEL.startsWith("DEVELOP"))) {
1553       copy {
1554         from getdown_txt
1555         from getdownLauncher
1556         from "${getdownWebsiteDir}/${getdown_build_properties}"
1557         if (file(getdownLauncher).getName() != getdown_launcher) {
1558           rename(file(getdownLauncher).getName(), getdown_launcher)
1559         }
1560         into getdownInstallDir
1561       }
1562
1563       copy {
1564         from getdownInstallDir
1565         into getdownFilesInstallDir
1566       }
1567     }
1568
1569     copy {
1570       from getdown_txt
1571       from launchJvl
1572       from getdownLauncher
1573       from "${getdownWebsiteDir}/${getdown_build_properties}"
1574       if (file(getdownLauncher).getName() != getdown_launcher) {
1575         rename(file(getdownLauncher).getName(), getdown_launcher)
1576       }
1577       into getdownFilesDir
1578     }
1579
1580     copy {
1581       from getdownResourceDir
1582       into "${getdownFilesDir}/${getdown_resource_dir}"
1583     }
1584   }
1585
1586   if (buildDist) {
1587     inputs.dir("${jalviewDir}/${package_dir}")
1588   }
1589   outputs.dir(getdownWebsiteDir)
1590   outputs.dir(getdownFilesDir)
1591 }
1592
1593
1594 // a helper task to allow getdown digest of any dir: `gradle getdownDigestDir -PDIGESTDIR=/path/to/my/random/getdown/dir
1595 task getdownDigestDir(type: JavaExec) {
1596   group "Help"
1597   description "A task to run a getdown Digest on a dir with getdown.txt. Provide a DIGESTDIR property via -PDIGESTDIR=..."
1598
1599   def digestDirPropertyName = "DIGESTDIR"
1600   doFirst {
1601     classpath = files(getdownLauncher)
1602     def digestDir = findProperty(digestDirPropertyName)
1603     if (digestDir == null) {
1604       throw new GradleException("Must provide a DIGESTDIR value to produce an alternative getdown digest")
1605     }
1606     args digestDir
1607   }
1608   main = "com.threerings.getdown.tools.Digester"
1609 }
1610
1611
1612 task getdownDigest(type: JavaExec) {
1613   group = "distribution"
1614   description = "Digest the getdown website folder"
1615   dependsOn getdownWebsite
1616   doFirst {
1617     classpath = files(getdownLauncher)
1618   }
1619   main = "com.threerings.getdown.tools.Digester"
1620   args getdownWebsiteDir
1621   inputs.dir(getdownWebsiteDir)
1622   outputs.file("${getdownWebsiteDir}/digest2.txt")
1623 }
1624
1625
1626 task getdown() {
1627   group = "distribution"
1628   description = "Create the minimal and full getdown app folder for installers and website and create digest file"
1629   dependsOn getdownDigest
1630   doLast {
1631     if (reportRsyncCommand) {
1632       def fromDir = getdownWebsiteDir + (getdownWebsiteDir.endsWith('/')?'':'/')
1633       def toDir = "${getdown_rsync_dest}/${getdownDir}" + (getdownDir.endsWith('/')?'':'/')
1634       println "LIKELY RSYNC COMMAND:"
1635       println "mkdir -p '$toDir'\nrsync -avh --delete '$fromDir' '$toDir'"
1636       if (RUNRSYNC == "true") {
1637         exec {
1638           commandLine "mkdir", "-p", toDir
1639         }
1640         exec {
1641           commandLine "rsync", "-avh", "--delete", fromDir, toDir
1642         }
1643       }
1644     }
1645   }
1646 }
1647
1648
1649 tasks.withType(JavaCompile) {
1650         options.encoding = 'UTF-8'
1651 }
1652
1653
1654 clean {
1655   doFirst {
1656     delete getdownWebsiteDir
1657     delete getdownFilesDir
1658   }
1659 }
1660
1661
1662 install4j {
1663   if (file(install4jHomeDir).exists()) {
1664     // good to go!
1665   } else if (file(System.getProperty("user.home")+"/buildtools/install4j").exists()) {
1666     install4jHomeDir = System.getProperty("user.home")+"/buildtools/install4j"
1667   } else if (file("/Applications/install4j.app/Contents/Resources/app").exists()) {
1668     install4jHomeDir = "/Applications/install4j.app/Contents/Resources/app"
1669   }
1670   installDir(file(install4jHomeDir))
1671
1672   mediaTypes = Arrays.asList(install4j_media_types.split(","))
1673 }
1674
1675
1676 task copyInstall4jTemplate {
1677   def install4jTemplateFile = file("${install4jDir}/${install4j_template}")
1678   def install4jFileAssociationsFile = file("${install4jDir}/${install4j_installer_file_associations}")
1679   inputs.file(install4jTemplateFile)
1680   inputs.file(install4jFileAssociationsFile)
1681   inputs.property("CHANNEL", { CHANNEL })
1682   outputs.file(install4jConfFile)
1683
1684   doLast {
1685     def install4jConfigXml = new XmlParser().parse(install4jTemplateFile)
1686
1687     // turn off code signing if no OSX_KEYPASS
1688     if (OSX_KEYPASS == "") {
1689       install4jConfigXml.'**'.codeSigning.each { codeSigning ->
1690         codeSigning.'@macEnabled' = "false"
1691       }
1692       install4jConfigXml.'**'.windows.each { windows ->
1693         windows.'@runPostProcessor' = "false"
1694       }
1695     }
1696
1697     // turn off checksum creation for LOCAL channel
1698     def e = install4jConfigXml.application[0]
1699     if (CHANNEL == "LOCAL") {
1700       e.'@createChecksums' = "false"
1701     } else {
1702       e.'@createChecksums' = "true"
1703     }
1704
1705     // put file association actions where placeholder action is
1706     def install4jFileAssociationsText = install4jFileAssociationsFile.text
1707     def fileAssociationActions = new XmlParser().parseText("<actions>${install4jFileAssociationsText}</actions>")
1708     install4jConfigXml.'**'.action.any { a -> // .any{} stops after the first one that returns true
1709       if (a.'@name' == 'EXTENSIONS_REPLACED_BY_GRADLE') {
1710         def parent = a.parent()
1711         parent.remove(a)
1712         fileAssociationActions.each { faa ->
1713             parent.append(faa)
1714         }
1715         // don't need to continue in .any loop once replacements have been made
1716         return true
1717       }
1718     }
1719
1720     // use Windows Program Group with Examples folder for RELEASE, and Program Group without Examples for everything else
1721     // NB we're deleting the /other/ one!
1722     // Also remove the examples subdir from non-release versions
1723     def customizedIdToDelete = "PROGRAM_GROUP_RELEASE"
1724     // 2.11.1.0 NOT releasing with the Examples folder in the Program Group
1725     if (false && CHANNEL=="RELEASE") { // remove 'false && ' to include Examples folder in RELEASE channel
1726       customizedIdToDelete = "PROGRAM_GROUP_NON_RELEASE"
1727     } else {
1728       // remove the examples subdir from Full File Set
1729       def files = install4jConfigXml.files[0]
1730       def fileset = files.filesets.fileset.find { fs -> fs.'@customizedId' == "FULL_FILE_SET" }
1731       def root = files.roots.root.find { r -> r.'@fileset' == fileset.'@id' }
1732       def mountPoint = files.mountPoints.mountPoint.find { mp -> mp.'@root' == root.'@id' }
1733       def dirEntry = files.entries.dirEntry.find { de -> de.'@mountPoint' == mountPoint.'@id' && de.'@subDirectory' == "examples" }
1734       dirEntry.parent().remove(dirEntry)
1735     }
1736     install4jConfigXml.'**'.action.any { a ->
1737       if (a.'@customizedId' == customizedIdToDelete) {
1738         def parent = a.parent()
1739         parent.remove(a)
1740         return true
1741       }
1742     }
1743
1744     // remove the "Uninstall Old Jalview (optional)" symlink from DMG for non-release DS_Stores
1745     if (! (CHANNEL == "RELEASE" || CHANNEL == "TEST-RELEASE" ) ) {
1746       def symlink = install4jConfigXml.'**'.topLevelFiles.symlink.find { sl -> sl.'@name' == "Uninstall Old Jalview (optional).app" }
1747       symlink.parent().remove(symlink)
1748     }
1749
1750     // write install4j file
1751     install4jConfFile.text = XmlUtil.serialize(install4jConfigXml)
1752   }
1753 }
1754
1755
1756 clean {
1757   doFirst {
1758     delete install4jConfFile
1759   }
1760 }
1761
1762
1763 task installers(type: com.install4j.gradle.Install4jTask) {
1764   group = "distribution"
1765   description = "Create the install4j installers"
1766   dependsOn setGitVals
1767   dependsOn getdown
1768   dependsOn copyInstall4jTemplate
1769
1770   projectFile = install4jConfFile
1771
1772   // create an md5 for the input files to use as version for install4j conf file
1773   def digest = MessageDigest.getInstance("MD5")
1774   digest.update(
1775     (file("${install4jDir}/${install4j_template}").text + 
1776     file("${install4jDir}/${install4j_info_plist_file_associations}").text +
1777     file("${install4jDir}/${install4j_installer_file_associations}").text).bytes)
1778   def filesMd5 = new BigInteger(1, digest.digest()).toString(16)
1779   if (filesMd5.length() >= 8) {
1780     filesMd5 = filesMd5.substring(0,8)
1781   }
1782   def install4jTemplateVersion = "${JALVIEW_VERSION}_F${filesMd5}_C${gitHash}"
1783   // make install4jBuildDir relative to jalviewDir
1784   def install4jBuildDir = "${install4j_build_dir}/${JAVA_VERSION}"
1785
1786   variables = [
1787     'JALVIEW_NAME': jalview_name,
1788     'JALVIEW_APPLICATION_NAME': install4jApplicationName,
1789     'JALVIEW_DIR': "../..",
1790     'OSX_KEYSTORE': OSX_KEYSTORE,
1791     'JSIGN_SH': JSIGN_SH,
1792     'JRE_DIR': getdown_app_dir_java,
1793     'INSTALLER_TEMPLATE_VERSION': install4jTemplateVersion,
1794     'JALVIEW_VERSION': JALVIEW_VERSION,
1795     'JAVA_MIN_VERSION': JAVA_MIN_VERSION,
1796     'JAVA_MAX_VERSION': JAVA_MAX_VERSION,
1797     'JAVA_VERSION': JAVA_VERSION,
1798     'JAVA_INTEGER_VERSION': JAVA_INTEGER_VERSION,
1799     'VERSION': JALVIEW_VERSION,
1800     'MACOS_JAVA_VM_DIR': macosJavaVMDir,
1801     'WINDOWS_JAVA_VM_DIR': windowsJavaVMDir,
1802     'LINUX_JAVA_VM_DIR': linuxJavaVMDir,
1803     'MACOS_JAVA_VM_TGZ': macosJavaVMTgz,
1804     'WINDOWS_JAVA_VM_TGZ': windowsJavaVMTgz,
1805     'LINUX_JAVA_VM_TGZ': linuxJavaVMTgz,
1806     'COPYRIGHT_MESSAGE': install4j_copyright_message,
1807     'BUNDLE_ID': install4jBundleId,
1808     'INTERNAL_ID': install4jInternalId,
1809     'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
1810     'MACOS_DS_STORE': install4jDSStore,
1811     'MACOS_DMG_BG_IMAGE': install4jDMGBackgroundImage,
1812     'INSTALLER_NAME': install4jInstallerName,
1813     'INSTALL4J_UTILS_DIR': install4j_utils_dir,
1814     'GETDOWN_WEBSITE_DIR': getdown_website_dir,
1815     'GETDOWN_FILES_DIR': getdown_files_dir,
1816     'GETDOWN_RESOURCE_DIR': getdown_resource_dir,
1817     'GETDOWN_DIST_DIR': getdownAppDistDir,
1818     'GETDOWN_ALT_DIR': getdown_app_dir_alt,
1819     'GETDOWN_INSTALL_DIR': getdown_install_dir,
1820     'INFO_PLIST_FILE_ASSOCIATIONS_FILE': install4j_info_plist_file_associations,
1821     'BUILD_DIR': install4jBuildDir,
1822     'APPLICATION_CATEGORIES': install4j_application_categories,
1823     'APPLICATION_FOLDER': install4jApplicationFolder,
1824     'UNIX_APPLICATION_FOLDER': install4jUnixApplicationFolder,
1825     'EXECUTABLE_NAME': install4jExecutableName,
1826     'EXTRA_SCHEME': install4jExtraScheme,
1827   ]
1828
1829   //println("INSTALL4J VARIABLES:")
1830   //variables.each{k,v->println("${k}=${v}")}
1831
1832   destination = "${jalviewDir}/${install4jBuildDir}"
1833   buildSelected = true
1834
1835   if (install4j_faster.equals("true") || CHANNEL.startsWith("LOCAL")) {
1836     faster = true
1837     disableSigning = true
1838   }
1839
1840   if (OSX_KEYPASS) {
1841     macKeystorePassword = OSX_KEYPASS
1842   }
1843
1844   doFirst {
1845     println("Using projectFile "+projectFile)
1846   }
1847
1848   inputs.dir(getdownWebsiteDir)
1849   inputs.file(install4jConfFile)
1850   inputs.file("${install4jDir}/${install4j_info_plist_file_associations}")
1851   inputs.dir(macosJavaVMDir)
1852   inputs.dir(windowsJavaVMDir)
1853   outputs.dir("${jalviewDir}/${install4j_build_dir}/${JAVA_VERSION}")
1854 }
1855
1856
1857 spotless {
1858   java {
1859     eclipse().configFile(eclipse_codestyle_file)
1860   }
1861 }
1862
1863
1864 task sourceDist(type: Tar) {
1865   group "distribution"
1866   description "Create a source .tar.gz file for distribution"
1867   
1868   dependsOn convertMdFiles
1869
1870   def VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
1871   def outputFileName = "${project.name}_${VERSION_UNDERSCORES}.tar.gz"
1872   // cater for buildship < 3.1 [3.0.1 is max version in eclipse 2018-09]
1873   try {
1874     archiveFileName = outputFileName
1875   } catch (Exception e) {
1876     archiveName = outputFileName
1877   }
1878   
1879   compression Compression.GZIP
1880   
1881   into project.name
1882
1883   def EXCLUDE_FILES=[
1884     "build/*",
1885     "bin/*",
1886     "test-output/",
1887     "test-reports",
1888     "tests",
1889     "clover*/*",
1890     ".*",
1891     "benchmarking/*",
1892     "**/.*",
1893     "*.class",
1894     "**/*.class","$j11modDir/**/*.jar","appletlib","**/*locales",
1895     "*locales/**",
1896     "utils/InstallAnywhere",
1897     "**/*.log",
1898   ] 
1899   def PROCESS_FILES=[
1900     "AUTHORS",
1901     "CITATION",
1902     "FEATURETODO",
1903     "JAVA-11-README",
1904     "FEATURETODO",
1905     "LICENSE",
1906     "**/README",
1907     "RELEASE",
1908     "THIRDPARTYLIBS",
1909     "TESTNG",
1910     "build.gradle",
1911     "gradle.properties",
1912     "**/*.java",
1913     "**/*.html",
1914     "**/*.xml",
1915     "**/*.gradle",
1916     "**/*.groovy",
1917     "**/*.properties",
1918     "**/*.perl",
1919     "**/*.sh",
1920   ]
1921   def INCLUDE_FILES=[
1922     ".settings/org.eclipse.jdt.core.jalview.prefs",
1923   ]
1924
1925   from(jalviewDir) {
1926     exclude (EXCLUDE_FILES)
1927     include (PROCESS_FILES)
1928     filter(ReplaceTokens,
1929       beginToken: '$$',
1930       endToken: '$$',
1931       tokens: [
1932         'Version-Rel': JALVIEW_VERSION,
1933         'Year-Rel': getDate("yyyy")
1934       ]
1935     )
1936   }
1937   from(jalviewDir) {
1938     exclude (EXCLUDE_FILES)
1939     exclude (PROCESS_FILES)
1940     exclude ("appletlib")
1941     exclude ("**/*locales")
1942     exclude ("*locales/**")
1943     exclude ("utils/InstallAnywhere")
1944
1945     exclude (getdown_files_dir)
1946     exclude (getdown_website_dir)
1947
1948     // exluding these as not using jars as modules yet
1949     exclude ("${j11modDir}/**/*.jar")
1950   }
1951   from(jalviewDir) {
1952     include(INCLUDE_FILES)
1953   }
1954 //  from (jalviewDir) {
1955 //    // explicit includes for stuff that seemed to not get included
1956 //    include(fileTree("test/**/*."))
1957 //    exclude(EXCLUDE_FILES)
1958 //    exclude(PROCESS_FILES)
1959 //  }
1960 }
1961
1962
1963 task helppages {
1964   dependsOn copyHelp
1965   dependsOn pubhtmlhelp
1966   
1967   inputs.dir("${classesDir}/${help_dir}")
1968   outputs.dir("${buildDir}/distributions/${help_dir}")
1969 }
1970
1971
1972 task j2sSetHeadlessBuild {
1973   doFirst {
1974     IN_ECLIPSE = false
1975   }
1976 }
1977
1978
1979 task jalviewjsSetEclipseWorkspace {
1980   def propKey = "jalviewjs_eclipse_workspace"
1981   def propVal = null
1982   if (project.hasProperty(propKey)) {
1983     propVal = project.getProperty(propKey)
1984     if (propVal.startsWith("~/")) {
1985       propVal = System.getProperty("user.home") + propVal.substring(1)
1986     }
1987   }
1988   def propsFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_workspace_location_file}"
1989   def propsFile = file(propsFileName)
1990   def eclipseWsDir = propVal
1991   def props = new Properties()
1992
1993   def writeProps = true
1994   if (( eclipseWsDir == null || !file(eclipseWsDir).exists() ) && propsFile.exists()) {
1995     def ins = new FileInputStream(propsFileName)
1996     props.load(ins)
1997     ins.close()
1998     if (props.getProperty(propKey, null) != null) {
1999       eclipseWsDir = props.getProperty(propKey)
2000       writeProps = false
2001     }
2002   }
2003
2004   if (eclipseWsDir == null || !file(eclipseWsDir).exists()) {
2005     def tempDir = File.createTempDir()
2006     eclipseWsDir = tempDir.getAbsolutePath()
2007     writeProps = true
2008   }
2009   eclipseWorkspace = file(eclipseWsDir)
2010
2011   doFirst {
2012     // do not run a headless transpile when we claim to be in Eclipse
2013     if (IN_ECLIPSE) {
2014       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2015       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2016     } else {
2017       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2018     }
2019
2020     if (writeProps) {
2021       props.setProperty(propKey, eclipseWsDir)
2022       propsFile.parentFile.mkdirs()
2023       def bytes = new ByteArrayOutputStream()
2024       props.store(bytes, null)
2025       def propertiesString = bytes.toString()
2026       propsFile.text = propertiesString
2027       print("NEW ")
2028     } else {
2029       print("EXISTING ")
2030     }
2031
2032     println("ECLIPSE WORKSPACE: "+eclipseWorkspace.getPath())
2033   }
2034
2035   //inputs.property(propKey, eclipseWsDir) // eclipseWsDir only gets set once this task runs, so will be out-of-date
2036   outputs.file(propsFileName)
2037   outputs.upToDateWhen { eclipseWorkspace.exists() && propsFile.exists() }
2038 }
2039
2040
2041 task jalviewjsEclipsePaths {
2042   def eclipseProduct
2043
2044   def eclipseRoot = jalviewjs_eclipse_root
2045   if (eclipseRoot.startsWith("~/")) {
2046     eclipseRoot = System.getProperty("user.home") + eclipseRoot.substring(1)
2047   }
2048   if (OperatingSystem.current().isMacOsX()) {
2049     eclipseRoot += "/Eclipse.app"
2050     eclipseBinary = "${eclipseRoot}/Contents/MacOS/eclipse"
2051     eclipseProduct = "${eclipseRoot}/Contents/Eclipse/.eclipseproduct"
2052   } else if (OperatingSystem.current().isWindows()) { // check these paths!!
2053     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
2054       eclipseRoot += "/eclipse"
2055     }
2056     eclipseBinary = "${eclipseRoot}/eclipse.exe"
2057     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
2058   } else { // linux or unix
2059     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
2060       eclipseRoot += "/eclipse"
2061 println("eclipseDir exists")
2062     }
2063     eclipseBinary = "${eclipseRoot}/eclipse"
2064     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
2065   }
2066
2067   eclipseVersion = "4.13" // default
2068   def assumedVersion = true
2069   if (file(eclipseProduct).exists()) {
2070     def fis = new FileInputStream(eclipseProduct)
2071     def props = new Properties()
2072     props.load(fis)
2073     eclipseVersion = props.getProperty("version")
2074     fis.close()
2075     assumedVersion = false
2076   }
2077   
2078   def propKey = "eclipse_debug"
2079   eclipseDebug = (project.hasProperty(propKey) && project.getProperty(propKey).equals("true"))
2080
2081   doFirst {
2082     // do not run a headless transpile when we claim to be in Eclipse
2083     if (IN_ECLIPSE) {
2084       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2085       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2086     } else {
2087       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2088     }
2089
2090     if (!assumedVersion) {
2091       println("ECLIPSE VERSION=${eclipseVersion}")
2092     }
2093   }
2094 }
2095
2096
2097 task printProperties {
2098   group "Debug"
2099   description "Output to console all System.properties"
2100   doFirst {
2101     System.properties.each { key, val -> System.out.println("Property: ${key}=${val}") }
2102   }
2103 }
2104
2105
2106 task eclipseSetup {
2107   dependsOn eclipseProject
2108   dependsOn eclipseClasspath
2109   dependsOn eclipseJdt
2110 }
2111
2112
2113 // this version (type: Copy) will delete anything in the eclipse dropins folder that isn't in fromDropinsDir
2114 task jalviewjsEclipseCopyDropins(type: Copy) {
2115   dependsOn jalviewjsEclipsePaths
2116
2117   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_eclipse_dropins_dir}", include: "*.jar")
2118   inputFiles += file("${jalviewDir}/${jalviewjsJ2sPlugin}")
2119   def outputDir = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
2120
2121   from inputFiles
2122   into outputDir
2123 }
2124
2125
2126 // this eclipse -clean doesn't actually work
2127 task jalviewjsCleanEclipse(type: Exec) {
2128   dependsOn eclipseSetup
2129   dependsOn jalviewjsEclipsePaths
2130   dependsOn jalviewjsEclipseCopyDropins
2131
2132   executable(eclipseBinary)
2133   args(["-nosplash", "--launcher.suppressErrors", "-data", eclipseWorkspace.getPath(), "-clean", "-console", "-consoleLog"])
2134   if (eclipseDebug) {
2135     args += "-debug"
2136   }
2137   args += "-l"
2138
2139   def inputString = """exit
2140 y
2141 """
2142   def inputByteStream = new ByteArrayInputStream(inputString.getBytes())
2143   standardInput = inputByteStream
2144 }
2145
2146 /* not really working yet
2147 jalviewjsEclipseCopyDropins.finalizedBy jalviewjsCleanEclipse
2148 */
2149
2150
2151 task jalviewjsTransferUnzipSwingJs {
2152   def file_zip = "${jalviewDir}/${jalviewjs_swingjs_zip}"
2153
2154   doLast {
2155     copy {
2156       from zipTree(file_zip)
2157       into "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
2158     }
2159   }
2160
2161   inputs.file file_zip
2162   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
2163 }
2164
2165
2166 task jalviewjsTransferUnzipLib {
2167   def zipFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_libjs_dir}", include: "*.zip")
2168
2169   doLast {
2170     zipFiles.each { file_zip -> 
2171       copy {
2172         from zipTree(file_zip)
2173         into "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
2174       }
2175     }
2176   }
2177
2178   inputs.files zipFiles
2179   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
2180 }
2181
2182
2183 task jalviewjsTransferUnzipAllLibs {
2184   dependsOn jalviewjsTransferUnzipSwingJs
2185   dependsOn jalviewjsTransferUnzipLib
2186 }
2187
2188
2189 task jalviewjsCreateJ2sSettings(type: WriteProperties) {
2190   group "JalviewJS"
2191   description "Create the .j2s file from the j2s.* properties"
2192
2193   jalviewjsJ2sProps = project.properties.findAll { it.key.startsWith("j2s.") }.sort { it.key }
2194   def siteDirProperty = "j2s.site.directory"
2195   def setSiteDir = false
2196   jalviewjsJ2sProps.each { prop, val ->
2197     if (val != null) {
2198       if (prop == siteDirProperty) {
2199         if (!(val.startsWith('/') || val.startsWith("file://") )) {
2200           val = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${val}"
2201         }
2202         setSiteDir = true
2203       }
2204       property(prop,val)
2205     }
2206     if (!setSiteDir) { // default site location, don't override specifically set property
2207       property(siteDirProperty,"${jalviewDirRelativePath}/${jalviewjsTransferSiteJsDir}")
2208     }
2209   }
2210   outputFile = jalviewjsJ2sSettingsFileName
2211
2212   if (! IN_ECLIPSE) {
2213     inputs.properties(jalviewjsJ2sProps)
2214     outputs.file(jalviewjsJ2sSettingsFileName)
2215   }
2216 }
2217
2218
2219 task jalviewjsEclipseSetup {
2220   dependsOn jalviewjsEclipseCopyDropins
2221   dependsOn jalviewjsSetEclipseWorkspace
2222   dependsOn jalviewjsCreateJ2sSettings
2223 }
2224
2225
2226 task jalviewjsSyncAllLibs (type: Sync) {
2227   dependsOn jalviewjsTransferUnzipAllLibs
2228   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteLibDir}")
2229   inputFiles += fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
2230   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
2231
2232   from inputFiles
2233   into outputDir
2234   def outputFiles = []
2235   rename { filename ->
2236     outputFiles += "${outputDir}/${filename}"
2237     null
2238   }
2239   preserve {
2240     include "**"
2241   }
2242   outputs.files outputFiles
2243   inputs.files inputFiles
2244 }
2245
2246
2247 task jalviewjsSyncResources (type: Sync) {
2248   def inputFiles = fileTree(dir: resourceDir)
2249   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
2250
2251   from inputFiles
2252   into outputDir
2253   def outputFiles = []
2254   rename { filename ->
2255     outputFiles += "${outputDir}/${filename}"
2256     null
2257   }
2258   preserve {
2259     include "**"
2260   }
2261   outputs.files outputFiles
2262   inputs.files inputFiles
2263 }
2264
2265
2266 task jalviewjsSyncSiteResources (type: Sync) {
2267   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_site_resource_dir}")
2268   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
2269
2270   from inputFiles
2271   into outputDir
2272   def outputFiles = []
2273   rename { filename ->
2274     outputFiles += "${outputDir}/${filename}"
2275     null
2276   }
2277   preserve {
2278     include "**"
2279   }
2280   outputs.files outputFiles
2281   inputs.files inputFiles
2282 }
2283
2284
2285 task jalviewjsSyncBuildProperties (type: Sync) {
2286   dependsOn createBuildProperties
2287   def inputFiles = [file(buildProperties)]
2288   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
2289
2290   from inputFiles
2291   into outputDir
2292   def outputFiles = []
2293   rename { filename ->
2294     outputFiles += "${outputDir}/${filename}"
2295     null
2296   }
2297   preserve {
2298     include "**"
2299   }
2300   outputs.files outputFiles
2301   inputs.files inputFiles
2302 }
2303
2304
2305 task jalviewjsProjectImport(type: Exec) {
2306   dependsOn eclipseSetup
2307   dependsOn jalviewjsEclipsePaths
2308   dependsOn jalviewjsEclipseSetup
2309
2310   doFirst {
2311     // do not run a headless import when we claim to be in Eclipse
2312     if (IN_ECLIPSE) {
2313       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2314       throw new StopExecutionException("Not running headless import whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2315     } else {
2316       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2317     }
2318   }
2319
2320   //def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview/org.eclipse.jdt.core"
2321   def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview"
2322   executable(eclipseBinary)
2323   args(["-nosplash", "--launcher.suppressErrors", "-application", "com.seeq.eclipse.importprojects.headlessimport", "-data", eclipseWorkspace.getPath(), "-import", jalviewDirAbsolutePath])
2324   if (eclipseDebug) {
2325     args += "-debug"
2326   }
2327   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
2328   if (!IN_ECLIPSE) {
2329     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
2330   }
2331
2332   inputs.file("${jalviewDir}/.project")
2333   outputs.upToDateWhen { 
2334     file(projdir).exists()
2335   }
2336 }
2337
2338
2339 task jalviewjsTranspile(type: Exec) {
2340   dependsOn jalviewjsEclipseSetup 
2341   dependsOn jalviewjsProjectImport
2342   dependsOn jalviewjsEclipsePaths
2343
2344   doFirst {
2345     // do not run a headless transpile when we claim to be in Eclipse
2346     if (IN_ECLIPSE) {
2347       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2348       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2349     } else {
2350       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2351     }
2352   }
2353
2354   executable(eclipseBinary)
2355   args(["-nosplash", "--launcher.suppressErrors", "-application", "org.eclipse.jdt.apt.core.aptBuild", "-data", eclipseWorkspace, "-${jalviewjs_eclipse_build_arg}", eclipse_project_name ])
2356   if (eclipseDebug) {
2357     args += "-debug"
2358   }
2359   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
2360   if (!IN_ECLIPSE) {
2361     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
2362   }
2363
2364   def stdout
2365   def stderr
2366   doFirst {
2367     stdout = new ByteArrayOutputStream()
2368     stderr = new ByteArrayOutputStream()
2369
2370     def logOutFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}"
2371     def logOutFile = file(logOutFileName)
2372     logOutFile.createNewFile()
2373     logOutFile.text = """ROOT: ${jalviewjs_eclipse_root}
2374 BINARY: ${eclipseBinary}
2375 VERSION: ${eclipseVersion}
2376 WORKSPACE: ${eclipseWorkspace}
2377 DEBUG: ${eclipseDebug}
2378 ----
2379 """
2380     def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
2381     // combine stdout and stderr
2382     def logErrFOS = logOutFOS
2383
2384     if (jalviewjs_j2s_to_console.equals("true")) {
2385       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2386         new org.apache.tools.ant.util.TeeOutputStream(
2387           logOutFOS,
2388           stdout),
2389         standardOutput)
2390       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2391         new org.apache.tools.ant.util.TeeOutputStream(
2392           logErrFOS,
2393           stderr),
2394         errorOutput)
2395     } else {
2396       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2397         logOutFOS,
2398         stdout)
2399       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2400         logErrFOS,
2401         stderr)
2402     }
2403   }
2404
2405   doLast {
2406     if (stdout.toString().contains("Error processing ")) {
2407       // j2s did not complete transpile
2408       //throw new TaskExecutionException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
2409       if (jalviewjs_ignore_transpile_errors.equals("true")) {
2410         println("IGNORING TRANSPILE ERRORS")
2411         println("See eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
2412       } else {
2413         throw new GradleException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
2414       }
2415     }
2416   }
2417
2418   inputs.dir("${jalviewDir}/${sourceDir}")
2419   outputs.dir("${jalviewDir}/${jalviewjsTransferSiteJsDir}")
2420   outputs.upToDateWhen( { file("${jalviewDir}/${jalviewjsTransferSiteJsDir}${jalviewjs_server_resource}").exists() } )
2421 }
2422
2423
2424 def jalviewjsCallCore(String name, FileCollection list, String prefixFile, String suffixFile, String jsfile, String zjsfile, File logOutFile, Boolean logOutConsole) {
2425
2426   def stdout = new ByteArrayOutputStream()
2427   def stderr = new ByteArrayOutputStream()
2428
2429   def coreFile = file(jsfile)
2430   def msg = ""
2431   msg = "Creating core for ${name}...\nGenerating ${jsfile}"
2432   println(msg)
2433   logOutFile.createNewFile()
2434   logOutFile.append(msg+"\n")
2435
2436   def coreTop = file(prefixFile)
2437   def coreBottom = file(suffixFile)
2438   coreFile.getParentFile().mkdirs()
2439   coreFile.createNewFile()
2440   coreFile.write( coreTop.getText("UTF-8") )
2441   list.each {
2442     f ->
2443     if (f.exists()) {
2444       def t = f.getText("UTF-8")
2445       t.replaceAll("Clazz\\.([^_])","Clazz_${1}")
2446       coreFile.append( t )
2447     } else {
2448       msg = "...file '"+f.getPath()+"' does not exist, skipping"
2449       println(msg)
2450       logOutFile.append(msg+"\n")
2451     }
2452   }
2453   coreFile.append( coreBottom.getText("UTF-8") )
2454
2455   msg = "Generating ${zjsfile}"
2456   println(msg)
2457   logOutFile.append(msg+"\n")
2458   def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
2459   def logErrFOS = logOutFOS
2460
2461   javaexec {
2462     classpath = files(["${jalviewDir}/${jalviewjs_closure_compiler}"])
2463     main = "com.google.javascript.jscomp.CommandLineRunner"
2464     jvmArgs = [ "-Dfile.encoding=UTF-8" ]
2465     args = [ "--compilation_level", "SIMPLE_OPTIMIZATIONS", "--warning_level", "QUIET", "--charset", "UTF-8", "--js", jsfile, "--js_output_file", zjsfile ]
2466     maxHeapSize = "2g"
2467
2468     msg = "\nRunning '"+commandLine.join(' ')+"'\n"
2469     println(msg)
2470     logOutFile.append(msg+"\n")
2471
2472     if (logOutConsole) {
2473       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2474         new org.apache.tools.ant.util.TeeOutputStream(
2475           logOutFOS,
2476           stdout),
2477         standardOutput)
2478         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2479           new org.apache.tools.ant.util.TeeOutputStream(
2480             logErrFOS,
2481             stderr),
2482           errorOutput)
2483     } else {
2484       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2485         logOutFOS,
2486         stdout)
2487         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2488           logErrFOS,
2489           stderr)
2490     }
2491   }
2492   msg = "--"
2493   println(msg)
2494   logOutFile.append(msg+"\n")
2495 }
2496
2497
2498 task jalviewjsBuildAllCores {
2499   group "JalviewJS"
2500   description "Build the core js lib closures listed in the classlists dir"
2501   dependsOn jalviewjsTranspile
2502   dependsOn jalviewjsTransferUnzipSwingJs
2503
2504   def j2sDir = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${jalviewjs_j2s_subdir}"
2505   def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_j2s_subdir}"
2506   def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteLibDir}/${jalviewjs_j2s_subdir}"
2507   def jsDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_js_subdir}"
2508   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
2509   def prefixFile = "${jsDir}/core/coretop2.js"
2510   def suffixFile = "${jsDir}/core/corebottom2.js"
2511
2512   inputs.file prefixFile
2513   inputs.file suffixFile
2514
2515   def classlistFiles = []
2516   // add the classlists found int the jalviewjs_classlists_dir
2517   fileTree(dir: "${jalviewDir}/${jalviewjs_classlists_dir}", include: "*.txt").each {
2518     file ->
2519     def name = file.getName() - ".txt"
2520     classlistFiles += [
2521       'file': file,
2522       'name': name
2523     ]
2524   }
2525
2526   // _jmol and _jalview cores. Add any other peculiar classlist.txt files here
2527   //classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jmol}"), 'name': "_jvjmol" ]
2528   classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jalview}"), 'name': jalviewjsJalviewCoreName ]
2529
2530   jalviewjsCoreClasslists = []
2531
2532   classlistFiles.each {
2533     hash ->
2534
2535     def file = hash['file']
2536     if (! file.exists()) {
2537       //println("...classlist file '"+file.getPath()+"' does not exist, skipping")
2538       return false // this is a "continue" in groovy .each closure
2539     }
2540     def name = hash['name']
2541     if (name == null) {
2542       name = file.getName() - ".txt"
2543     }
2544
2545     def filelist = []
2546     file.eachLine {
2547       line ->
2548         filelist += line
2549     }
2550     def list = fileTree(dir: j2sDir, includes: filelist)
2551
2552     def jsfile = "${outputDir}/core${name}.js"
2553     def zjsfile = "${outputDir}/core${name}.z.js"
2554
2555     jalviewjsCoreClasslists += [
2556       'jsfile': jsfile,
2557       'zjsfile': zjsfile,
2558       'list': list,
2559       'name': name
2560     ]
2561
2562     inputs.file(file)
2563     inputs.files(list)
2564     outputs.file(jsfile)
2565     outputs.file(zjsfile)
2566   }
2567   
2568   // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
2569   def stevesoftClasslistName = "_stevesoft"
2570   def stevesoftClasslist = [
2571     'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
2572     'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
2573     'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
2574     'name': stevesoftClasslistName
2575   ]
2576   jalviewjsCoreClasslists += stevesoftClasslist
2577   inputs.files(stevesoftClasslist['list'])
2578   outputs.file(stevesoftClasslist['jsfile'])
2579   outputs.file(stevesoftClasslist['zjsfile'])
2580
2581   // _all core
2582   def allClasslistName = "_all"
2583   def allJsFiles = fileTree(dir: j2sDir, include: "**/*.js")
2584   allJsFiles += fileTree(
2585     dir: libJ2sDir,
2586     include: "**/*.js",
2587     excludes: [
2588       // these exlusions are files that the closure-compiler produces errors for. Should fix them
2589       "**/org/jmol/jvxl/readers/IsoIntersectFileReader.js",
2590       "**/org/jmol/export/JSExporter.js"
2591     ]
2592   )
2593   allJsFiles += fileTree(
2594     dir: swingJ2sDir,
2595     include: "**/*.js",
2596     excludes: [
2597       // these exlusions are files that the closure-compiler produces errors for. Should fix them
2598       "**/sun/misc/Unsafe.js",
2599       "**/swingjs/jquery/jquery-editable-select.js",
2600       "**/swingjs/jquery/j2sComboBox.js",
2601       "**/sun/misc/FloatingDecimal.js"
2602     ]
2603   )
2604   def allClasslist = [
2605     'jsfile': "${outputDir}/core${allClasslistName}.js",
2606     'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
2607     'list': allJsFiles,
2608     'name': allClasslistName
2609   ]
2610   // not including this version of "all" core at the moment
2611   //jalviewjsCoreClasslists += allClasslist
2612   inputs.files(allClasslist['list'])
2613   outputs.file(allClasslist['jsfile'])
2614   outputs.file(allClasslist['zjsfile'])
2615
2616   doFirst {
2617     def logOutFile = file("${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_closure_stdout}")
2618     logOutFile.getParentFile().mkdirs()
2619     logOutFile.createNewFile()
2620     logOutFile.write(getDate("yyyy-MM-dd HH:mm:ss")+" jalviewjsBuildAllCores\n----\n")
2621
2622     jalviewjsCoreClasslists.each {
2623       jalviewjsCallCore(it.name, it.list, prefixFile, suffixFile, it.jsfile, it.zjsfile, logOutFile, jalviewjs_j2s_to_console.equals("true"))
2624     }
2625   }
2626
2627 }
2628
2629
2630 def jalviewjsPublishCoreTemplate(String coreName, String templateName, File inputFile, String outputFile) {
2631   copy {
2632     from inputFile
2633     into file(outputFile).getParentFile()
2634     rename { filename ->
2635       if (filename.equals(inputFile.getName())) {
2636         return file(outputFile).getName()
2637       }
2638       return null
2639     }
2640     filter(ReplaceTokens,
2641       beginToken: '_',
2642       endToken: '_',
2643       tokens: [
2644         'MAIN': '"'+main_class+'"',
2645         'CODE': "null",
2646         'NAME': jalviewjsJalviewTemplateName+" [core ${coreName}]",
2647         'COREKEY': jalviewjs_core_key,
2648         'CORENAME': coreName
2649       ]
2650     )
2651   }
2652 }
2653
2654
2655 task jalviewjsPublishCoreTemplates {
2656   dependsOn jalviewjsBuildAllCores
2657   def inputFileName = "${jalviewDir}/${j2s_coretemplate_html}"
2658   def inputFile = file(inputFileName)
2659   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
2660
2661   def outputFiles = []
2662   jalviewjsCoreClasslists.each { cl ->
2663     def outputFile = "${outputDir}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
2664     cl['outputfile'] = outputFile
2665     outputFiles += outputFile
2666   }
2667
2668   doFirst {
2669     jalviewjsCoreClasslists.each { cl ->
2670       jalviewjsPublishCoreTemplate(cl.name, jalviewjsJalviewTemplateName, inputFile, cl.outputfile)
2671     }
2672   }
2673   inputs.file(inputFile)
2674   outputs.files(outputFiles)
2675 }
2676
2677
2678 task jalviewjsSyncCore (type: Sync) {
2679   dependsOn jalviewjsBuildAllCores
2680   dependsOn jalviewjsPublishCoreTemplates
2681   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
2682   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
2683
2684   from inputFiles
2685   into outputDir
2686   def outputFiles = []
2687   rename { filename ->
2688     outputFiles += "${outputDir}/${filename}"
2689     null
2690   }
2691   preserve {
2692     include "**"
2693   }
2694   outputs.files outputFiles
2695   inputs.files inputFiles
2696 }
2697
2698
2699 // this Copy version of TransferSiteJs will delete anything else in the target dir
2700 task jalviewjsCopyTransferSiteJs(type: Copy) {
2701   dependsOn jalviewjsTranspile
2702   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
2703   into "${jalviewDir}/${jalviewjsSiteDir}"
2704 }
2705
2706
2707 // this Sync version of TransferSite is used by buildship to keep the website automatically up to date when a file changes
2708 task jalviewjsSyncTransferSiteJs(type: Sync) {
2709   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
2710   include "**/*.*"
2711   into "${jalviewDir}/${jalviewjsSiteDir}"
2712   preserve {
2713     include "**"
2714   }
2715 }
2716
2717
2718 jalviewjsSyncAllLibs.mustRunAfter jalviewjsCopyTransferSiteJs
2719 jalviewjsSyncResources.mustRunAfter jalviewjsCopyTransferSiteJs
2720 jalviewjsSyncSiteResources.mustRunAfter jalviewjsCopyTransferSiteJs
2721 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsCopyTransferSiteJs
2722
2723 jalviewjsSyncAllLibs.mustRunAfter jalviewjsSyncTransferSiteJs
2724 jalviewjsSyncResources.mustRunAfter jalviewjsSyncTransferSiteJs
2725 jalviewjsSyncSiteResources.mustRunAfter jalviewjsSyncTransferSiteJs
2726 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsSyncTransferSiteJs
2727
2728
2729 task jalviewjsPrepareSite {
2730   group "JalviewJS"
2731   description "Prepares the website folder including unzipping files and copying resources"
2732   dependsOn jalviewjsSyncAllLibs
2733   dependsOn jalviewjsSyncResources
2734   dependsOn jalviewjsSyncSiteResources
2735   dependsOn jalviewjsSyncBuildProperties
2736   dependsOn jalviewjsSyncCore
2737 }
2738
2739
2740 task jalviewjsBuildSite {
2741   group "JalviewJS"
2742   description "Builds the whole website including transpiled code"
2743   dependsOn jalviewjsCopyTransferSiteJs
2744   dependsOn jalviewjsPrepareSite
2745 }
2746
2747
2748 task cleanJalviewjsTransferSite {
2749   doFirst {
2750     delete "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
2751     delete "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
2752     delete "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
2753     delete "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
2754   }
2755 }
2756
2757
2758 task cleanJalviewjsSite {
2759   dependsOn cleanJalviewjsTransferSite
2760   doFirst {
2761     delete "${jalviewDir}/${jalviewjsSiteDir}"
2762   }
2763 }
2764
2765
2766 task jalviewjsSiteTar(type: Tar) {
2767   group "JalviewJS"
2768   description "Creates a tar.gz file for the website"
2769   dependsOn jalviewjsBuildSite
2770   def outputFilename = "jalviewjs-site-${JALVIEW_VERSION}.tar.gz"
2771   try {
2772     archiveFileName = outputFilename
2773   } catch (Exception e) {
2774     archiveName = outputFilename
2775   }
2776
2777   compression Compression.GZIP
2778
2779   from "${jalviewDir}/${jalviewjsSiteDir}"
2780   into jalviewjs_site_dir // this is inside the tar file
2781
2782   inputs.dir("${jalviewDir}/${jalviewjsSiteDir}")
2783 }
2784
2785
2786 task jalviewjsServer {
2787   group "JalviewJS"
2788   def filename = "jalviewjsTest.html"
2789   description "Starts a webserver on localhost to test the website. See ${filename} to access local site on most recently used port."
2790   def htmlFile = "${jalviewDirAbsolutePath}/${filename}"
2791   doLast {
2792
2793     SimpleHttpFileServerFactory factory = new SimpleHttpFileServerFactory()
2794     def port = Integer.valueOf(jalviewjs_server_port)
2795     def start = port
2796     def running = false
2797     def url
2798     def jalviewjsServer
2799     while(port < start+1000 && !running) {
2800       try {
2801         def doc_root = new File("${jalviewDirAbsolutePath}/${jalviewjsSiteDir}")
2802         jalviewjsServer = factory.start(doc_root, port)
2803         running = true
2804         url = jalviewjsServer.getResourceUrl(jalviewjs_server_resource)
2805         println("SERVER STARTED with document root ${doc_root}.")
2806         println("Go to "+url+" . Run  gradle --stop  to stop (kills all gradle daemons).")
2807         println("For debug: "+url+"?j2sdebug")
2808         println("For verbose: "+url+"?j2sverbose")
2809       } catch (Exception e) {
2810         port++;
2811       }
2812     }
2813     def htmlText = """
2814       <p><a href="${url}">JalviewJS Test. &lt;${url}&gt;</a></p>
2815       <p><a href="${url}?j2sdebug">JalviewJS Test with debug. &lt;${url}?j2sdebug&gt;</a></p>
2816       <p><a href="${url}?j2sverbose">JalviewJS Test with verbose. &lt;${url}?j2sdebug&gt;</a></p>
2817       """
2818     jalviewjsCoreClasslists.each { cl ->
2819       def urlcore = jalviewjsServer.getResourceUrl(file(cl.outputfile).getName())
2820       htmlText += """
2821       <p><a href="${urlcore}">${jalviewjsJalviewTemplateName} [core ${cl.name}]. &lt;${urlcore}&gt;</a></p>
2822       """
2823       println("For core ${cl.name}: "+urlcore)
2824     }
2825
2826     file(htmlFile).text = htmlText
2827   }
2828
2829   outputs.file(htmlFile)
2830   outputs.upToDateWhen({false})
2831 }
2832
2833
2834 task cleanJalviewjsAll {
2835   group "JalviewJS"
2836   description "Delete all configuration and build artifacts to do with JalviewJS build"
2837   dependsOn cleanJalviewjsSite
2838   dependsOn jalviewjsEclipsePaths
2839   
2840   doFirst {
2841     delete "${jalviewDir}/${jalviewjsBuildDir}"
2842     delete "${jalviewDir}/${eclipse_bin_dir}"
2843     if (eclipseWorkspace != null && file(eclipseWorkspace.getAbsolutePath()+"/.metadata").exists()) {
2844       delete file(eclipseWorkspace.getAbsolutePath()+"/.metadata")
2845     }
2846     delete "${jalviewDir}/${jalviewjs_j2s_settings}"
2847   }
2848
2849   outputs.upToDateWhen( { false } )
2850 }
2851
2852
2853 task jalviewjsIDE_checkJ2sPlugin {
2854   group "00 JalviewJS in Eclipse"
2855   description "Compare the swingjs/net.sf.j2s.core(-j11)?.jar file with the Eclipse IDE's plugin version (found in the 'dropins' dir)"
2856
2857   doFirst {
2858     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
2859     def j2sPluginFile = file(j2sPlugin)
2860     def eclipseHome = System.properties["eclipse.home.location"]
2861     if (eclipseHome == null || ! IN_ECLIPSE) {
2862       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. Skipping J2S Plugin Check.")
2863     }
2864     def eclipseJ2sPlugin = "${eclipseHome}/dropins/${j2sPluginFile.getName()}"
2865     def eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
2866     if (!eclipseJ2sPluginFile.exists()) {
2867       def msg = "Eclipse J2S Plugin is not installed (could not find '${eclipseJ2sPlugin}')\nTry running task jalviewjsIDE_copyJ2sPlugin"
2868       System.err.println(msg)
2869       throw new StopExecutionException(msg)
2870     }
2871
2872     def digest = MessageDigest.getInstance("MD5")
2873
2874     digest.update(j2sPluginFile.text.bytes)
2875     def j2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
2876
2877     digest.update(eclipseJ2sPluginFile.text.bytes)
2878     def eclipseJ2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
2879      
2880     if (j2sPluginMd5 != eclipseJ2sPluginMd5) {
2881       def msg = "WARNING! Eclipse J2S Plugin '${eclipseJ2sPlugin}' is different to this commit's version '${j2sPlugin}'"
2882       System.err.println(msg)
2883       throw new StopExecutionException(msg)
2884     } else {
2885       def msg = "Eclipse J2S Plugin is the same as '${j2sPlugin}' (this is good)"
2886       println(msg)
2887     }
2888   }
2889 }
2890
2891 task jalviewjsIDE_copyJ2sPlugin {
2892   group "00 JalviewJS in Eclipse"
2893   description "Copy the swingjs/net.sf.j2s.core(-j11)?.jar file into the Eclipse IDE's 'dropins' dir"
2894
2895   doFirst {
2896     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
2897     def j2sPluginFile = file(j2sPlugin)
2898     def eclipseHome = System.properties["eclipse.home.location"]
2899     if (eclipseHome == null || ! IN_ECLIPSE) {
2900       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. NOT copying J2S Plugin.")
2901     }
2902     def eclipseJ2sPlugin = "${eclipseHome}/dropins/${j2sPluginFile.getName()}"
2903     def eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
2904     def msg = "WARNING! Copying this commit's j2s plugin '${j2sPlugin}' to Eclipse J2S Plugin '${eclipseJ2sPlugin}'\n* May require an Eclipse restart"
2905     System.err.println(msg)
2906     copy {
2907       from j2sPlugin
2908       eclipseJ2sPluginFile.getParentFile().mkdirs()
2909       into eclipseJ2sPluginFile.getParent()
2910     }
2911   }
2912 }
2913
2914
2915 task jalviewjsIDE_j2sFile {
2916   group "00 JalviewJS in Eclipse"
2917   description "Creates the .j2s file"
2918   dependsOn jalviewjsCreateJ2sSettings
2919 }
2920
2921
2922 task jalviewjsIDE_SyncCore {
2923   group "00 JalviewJS in Eclipse"
2924   description "Build the core js lib closures listed in the classlists dir and publish core html from template"
2925   dependsOn jalviewjsSyncCore
2926 }
2927
2928
2929 task jalviewjsIDE_SyncSiteAll {
2930   dependsOn jalviewjsSyncAllLibs
2931   dependsOn jalviewjsSyncResources
2932   dependsOn jalviewjsSyncSiteResources
2933   dependsOn jalviewjsSyncBuildProperties
2934 }
2935
2936
2937 cleanJalviewjsTransferSite.mustRunAfter jalviewjsIDE_SyncSiteAll
2938
2939
2940 task jalviewjsIDE_PrepareSite {
2941   group "00 JalviewJS in Eclipse"
2942   description "Sync libs and resources to site dir, but not closure cores"
2943
2944   dependsOn jalviewjsIDE_SyncSiteAll
2945   //dependsOn cleanJalviewjsTransferSite // not sure why this clean is here -- will slow down a re-run of this task
2946 }
2947
2948
2949 task jalviewjsIDE_AssembleSite {
2950   group "00 JalviewJS in Eclipse"
2951   description "Assembles unzipped supporting zipfiles, resources, site resources and closure cores into the Eclipse transpiled site"
2952   dependsOn jalviewjsPrepareSite
2953 }
2954
2955
2956 task jalviewjsIDE_SiteClean {
2957   group "00 JalviewJS in Eclipse"
2958   description "Deletes the Eclipse transpiled site"
2959   dependsOn cleanJalviewjsSite
2960 }
2961
2962
2963 task jalviewjsIDE_Server {
2964   group "00 JalviewJS in Eclipse"
2965   description "Starts a webserver on localhost to test the website"
2966   dependsOn jalviewjsServer
2967 }
2968
2969
2970 // buildship runs this at import or gradle refresh
2971 task eclipseSynchronizationTask {
2972   //dependsOn eclipseSetup
2973   dependsOn createBuildProperties
2974   if (J2S_ENABLED) {
2975     dependsOn jalviewjsIDE_j2sFile
2976     dependsOn jalviewjsIDE_checkJ2sPlugin
2977     dependsOn jalviewjsIDE_PrepareSite
2978   }
2979 }
2980
2981
2982 // buildship runs this at build time or project refresh
2983 task eclipseAutoBuildTask {
2984   //dependsOn jalviewjsIDE_checkJ2sPlugin
2985   //dependsOn jalviewjsIDE_PrepareSite
2986 }
2987
2988
2989 task jalviewjs {
2990   group "JalviewJS"
2991   description "Build the site"
2992   dependsOn jalviewjsBuildSite
2993 }