JAL-3588 Auto insert of Examples folder into DMG and an attempt to style both the...
[jalview.git] / build.gradle
1 import org.apache.tools.ant.filters.ReplaceTokens
2 import org.gradle.internal.os.OperatingSystem
3 import org.gradle.plugins.ide.eclipse.model.Output
4 import org.gradle.plugins.ide.eclipse.model.Library
5 import java.security.MessageDigest
6 import groovy.transform.ExternalizeMethods
7 import groovy.util.XmlParser
8 import groovy.xml.XmlUtil
9
10
11 buildscript {
12   repositories {
13     mavenCentral()
14     mavenLocal()
15   }
16   dependencies {
17     classpath 'org.openclover:clover:4.4.1'
18   }
19 }
20
21
22 plugins {
23   id 'java'
24   id 'application'
25   id 'eclipse'
26   id 'com.github.johnrengelman.shadow' version '4.0.3'
27   id 'com.install4j.gradle' version '8.0.4'
28   id 'com.dorongold.task-tree' version '1.5' // only needed to display task dependency tree with  gradle task1 [task2 ...] taskTree
29 }
30
31 repositories {
32   jcenter()
33   mavenCentral()
34   mavenLocal()
35 }
36
37
38 // in ext the values are cast to Object. Ensure string values are cast as String (and not GStringImpl) for later use
39 def string(Object o) {
40   return o == null ? "" : o.toString()
41 }
42
43
44 ext {
45   jalviewDirAbsolutePath = file(jalviewDir).getAbsolutePath()
46   jalviewDirRelativePath = jalviewDir
47
48   // local build environment properties
49   // can be "projectDir/local.properties"
50   def localProps = "${projectDir}/local.properties"
51   def propsFile = null;
52   if (file(localProps).exists()) {
53     propsFile = localProps
54   }
55   // or "../projectDir_local.properties"
56   def dirLocalProps = projectDir.getParent() + "/" + projectDir.getName() + "_local.properties"
57   if (file(dirLocalProps).exists()) {
58     propsFile = dirLocalProps
59   }
60   if (propsFile != null) {
61     try {
62       def p = new Properties()
63       def localPropsFIS = new FileInputStream(propsFile)
64       p.load(localPropsFIS)
65       localPropsFIS.close()
66       p.each {
67         key, val -> 
68           def oldval = findProperty(key)
69           setProperty(key, val)
70           if (oldval != null) {
71             println("Overriding property '${key}' ('${oldval}') with ${file(propsFile).getName()} value '${val}'")
72           } else {
73             println("Setting unknown property '${key}' with ${file(propsFile).getName()}s value '${val}'")
74           }
75       }
76     } catch (Exception e) {
77       System.out.println("Exception reading local.properties")
78     }
79   }
80
81   // this property set when running Eclipse headlessly
82   j2sHeadlessBuildProperty = string("net.sf.j2s.core.headlessbuild")
83   // this property set by Eclipse
84   eclipseApplicationProperty = string("eclipse.application")
85   // CHECK IF RUNNING FROM WITHIN ECLIPSE
86   def eclipseApplicationPropertyVal = System.properties[eclipseApplicationProperty]
87   IN_ECLIPSE = eclipseApplicationPropertyVal != null && eclipseApplicationPropertyVal.startsWith("org.eclipse.ui.")
88   // BUT WITHOUT THE HEADLESS BUILD PROPERTY SET
89   if (System.properties[j2sHeadlessBuildProperty].equals("true")) {
90     println("Setting IN_ECLIPSE to ${IN_ECLIPSE} as System.properties['${j2sHeadlessBuildProperty}'] == '${System.properties[j2sHeadlessBuildProperty]}'")
91     IN_ECLIPSE = false
92   }
93   if (IN_ECLIPSE) {
94     println("WITHIN ECLIPSE IDE")
95   } else {
96     println("HEADLESS BUILD")
97   }
98   /* *-/
99   System.properties.sort { it.key }.each {
100     key, val -> println("SYSTEM PROPERTY ${key}='${val}'")
101   }
102   /-* *-/
103   if (false && IN_ECLIPSE) {
104     jalviewDir = jalviewDirAbsolutePath
105   }
106   */
107
108   // essentials
109   bareSourceDir = string(source_dir)
110   sourceDir = string("${jalviewDir}/${bareSourceDir}")
111   resourceDir = string("${jalviewDir}/${resource_dir}")
112   bareTestSourceDir = string(test_source_dir)
113   testSourceDir = string("${jalviewDir}/${bareTestSourceDir}")
114
115   // clover
116   cloverInstrDir = file("${buildDir}/${cloverSourcesInstrDir}")
117   cloverDb = string("${buildDir}/clover/clover.db")
118   classesDir = string("${jalviewDir}/${classes_dir}")
119   if (clover.equals("true")) {
120     use_clover = true
121     classesDir = string("${buildDir}/${cloverClassesDir}")
122   } else {
123     use_clover = false
124     classesDir = string("${jalviewDir}/${classes_dir}")
125   }
126
127   classes = classesDir
128
129   getdownWebsiteDir = string("${jalviewDir}/${getdown_website_dir}/${JAVA_VERSION}")
130   buildDist = true
131
132   // the following values might be overridden by the CHANNEL switch
133   getdownChannelName = CHANNEL.toLowerCase()
134   getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
135   getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
136   getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher}")
137   getdownAppDistDir = getdown_app_dir_alt
138   buildProperties = string("${classesDir}/${build_properties_file}")
139   reportRsyncCommand = false
140   jvlChannelName = CHANNEL.toLowerCase()
141   install4jSuffix = CHANNEL.substring(0, 1).toUpperCase() + CHANNEL.substring(1).toLowerCase(); // BUILD -> Build
142   install4jDSStore = "DS_Store-NON-RELEASE"
143   install4jDMGBackgroundImage = "jalview_dmg_background-NON-RELEASE.png"
144   install4jInstallerName = "${jalview_name} Non-Release Installer"
145   install4jExecutableName = jalview_name.replaceAll("[^\\w]+", "_").toLowerCase()
146   install4jExtraScheme = "jalviewx"
147   switch (CHANNEL) {
148
149     case "BUILD":
150     // TODO: get bamboo build artifact URL for getdown artifacts
151     getdown_channel_base = bamboo_channelbase
152     getdownChannelName = string("${bamboo_planKey}/${JAVA_VERSION}")
153     getdownAppBase = string("${bamboo_channelbase}/${bamboo_planKey}${bamboo_getdown_channel_suffix}/${JAVA_VERSION}")
154     jvlChannelName += "_${getdownChannelName}"
155     // automatically add the test group Not-bamboo for exclusion 
156     if ("".equals(testngExcludedGroups)) { 
157       testngExcludedGroups = "Not-bamboo"
158     }
159     install4jExtraScheme = "jalviewb"
160     break
161
162     case "RELEASE":
163     getdownAppDistDir = getdown_app_dir_release
164     reportRsyncCommand = true
165     install4jSuffix = ""
166     install4jDSStore = "DS_Store"
167     install4jDMGBackgroundImage = "jalview_dmg_background.png"
168     install4jInstallerName = "${jalview_name} Installer"
169     break
170
171     case "ARCHIVE":
172     getdownChannelName = CHANNEL.toLowerCase()+"/${JALVIEW_VERSION}"
173     getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
174     getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
175     if (!file("${ARCHIVEDIR}/${packageDir}").exists()) {
176       throw new GradleException("Must provide an ARCHIVEDIR value to produce an archive distribution")
177     } else {
178       packageDir = string("${ARCHIVEDIR}/${packageDir}")
179       buildProperties = string("${ARCHIVEDIR}/${classes_dir}/${build_properties_file}")
180       buildDist = false
181     }
182     reportRsyncCommand = true
183     install4jExtraScheme = "jalviewa"
184     break
185
186     case "ARCHIVELOCAL":
187     getdownChannelName = string("archive/${JALVIEW_VERSION}")
188     getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
189     getdownAppBase = file(getdownWebsiteDir).toURI().toString()
190     if (!file("${ARCHIVEDIR}/${packageDir}").exists()) {
191       throw new GradleException("Must provide an ARCHIVEDIR value to produce an archive distribution")
192     } else {
193       packageDir = string("${ARCHIVEDIR}/${packageDir}")
194       buildProperties = string("${ARCHIVEDIR}/${classes_dir}/${build_properties_file}")
195       buildDist = false
196     }
197     reportRsyncCommand = true
198     getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
199     install4jSuffix = "Archive"
200     install4jExtraScheme = "jalviewa"
201     break
202
203     case "DEVELOP":
204     reportRsyncCommand = true
205     install4jDSStore = "DS_Store-DEVELOP"
206     install4jDMGBackgroundImage = "jalview_dmg_background-DEVELOP.png"
207     install4jExtraScheme = "jalviewd"
208     install4jInstallerName = "${jalview_name} Develop Installer"
209     break
210
211     case "TEST-RELEASE":
212     reportRsyncCommand = true
213     JALVIEW_VERSION = "TEST"
214     install4jSuffix = "Test"
215     install4jDSStore = "DS_Store-TEST-RELEASE"
216     install4jDMGBackgroundImage = "jalview_dmg_background-TEST.png"
217     install4jExtraScheme = "jalviewt"
218     install4jInstallerName = "${jalview_name} Test Installer"
219     break
220
221     case ~/^SCRATCH(|-[-\w]*)$/:
222     getdownChannelName = CHANNEL
223     getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
224     getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
225     reportRsyncCommand = true
226     install4jSuffix = "Scratch"
227     break
228
229     case "TEST-LOCAL":
230     if (!file("${LOCALDIR}").exists()) {
231       throw new GradleException("Must provide a LOCALDIR value to produce a local distribution")
232     } else {
233       getdownAppBase = file(file("${LOCALDIR}").getAbsolutePath()).toURI().toString()
234       getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
235     }
236     JALVIEW_VERSION = "TEST"
237     install4jSuffix = "Test-Local"
238     install4jDSStore = "DS_Store-TEST-RELEASE"
239     install4jDMGBackgroundImage = "jalview_dmg_background-TEST.png"
240     install4jExtraScheme = "jalviewt"
241     install4jInstallerName = "${jalview_name} Test Installer"
242     break
243
244     case "LOCAL":
245     getdownAppBase = file(getdownWebsiteDir).toURI().toString()
246     getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
247     install4jExtraScheme = "jalviewl"
248     break
249
250     default: // something wrong specified
251     throw new GradleException("CHANNEL must be one of BUILD, RELEASE, ARCHIVE, DEVELOP, TEST-RELEASE, SCRATCH-..., LOCAL [default]")
252     break
253
254   }
255   // override getdownAppBase if requested
256   if (findProperty("getdown_appbase_override") != null) {
257     getdownAppBase = string(getProperty("getdown_appbase_override"))
258     println("Overriding getdown appbase with '${getdownAppBase}'")
259   }
260   // sanitise file name for jalview launcher file for this channel
261   jvlChannelName = jvlChannelName.replaceAll("[^\\w\\-]+", "_")
262   // install4j application and folder names
263   if (install4jSuffix == "") {
264     install4jApplicationName = "${jalview_name}"
265     install4jBundleId = "${install4j_bundle_id}"
266     install4jWinApplicationId = install4j_release_win_application_id
267   } else {
268     install4jApplicationName = "${jalview_name} ${install4jSuffix}"
269     install4jBundleId = "${install4j_bundle_id}-" + install4jSuffix.toLowerCase()
270     // add int hash of install4jSuffix to the last part of the application_id
271     def id = install4j_release_win_application_id
272     def idsplitreverse = id.split("-").reverse()
273     idsplitreverse[0] = idsplitreverse[0].toInteger() + install4jSuffix.hashCode()
274     install4jWinApplicationId = idsplitreverse.reverse().join("-")
275   }
276   // sanitise folder and id names
277   // install4jApplicationFolder = e.g. "Jalview Build"
278   install4jApplicationFolder = install4jApplicationName
279                                     .replaceAll("[\"'~:/\\\\\\s]", "_") // replace all awkward filename chars " ' ~ : / \
280                                     .replaceAll("_+", "_") // collapse __
281   install4jInternalId = install4jApplicationName
282                                     .replaceAll(" ","_")
283                                     .replaceAll("[^\\w\\-\\.]", "_") // replace other non [alphanumeric,_,-,.]
284                                     .replaceAll("_+", "") // collapse __
285                                     //.replaceAll("_*-_*", "-") // collapse _-_
286   install4jUnixApplicationFolder = install4jApplicationName
287                                     .replaceAll(" ","_")
288                                     .replaceAll("[^\\w\\-\\.]", "_") // replace other non [alphanumeric,_,-,.]
289                                     .replaceAll("_+", "_") // collapse __
290                                     .replaceAll("_*-_*", "-") // collapse _-_
291                                     .toLowerCase()
292
293   getdownAppDir = string("${getdownWebsiteDir}/${getdownAppDistDir}")
294   //getdownJ11libDir = "${getdownWebsiteDir}/${getdown_j11lib_dir}"
295   getdownResourceDir = string("${getdownWebsiteDir}/${getdown_resource_dir}")
296   getdownInstallDir = string("${getdownWebsiteDir}/${getdown_install_dir}")
297   getdownFilesDir = string("${jalviewDir}/${getdown_files_dir}/${JAVA_VERSION}/")
298   getdownFilesInstallDir = string("${getdownFilesDir}/${getdown_install_dir}")
299   /* compile without modules -- using classpath libraries
300   modules_compileClasspath = fileTree(dir: "${jalviewDir}/${j11modDir}", include: ["*.jar"])
301   modules_runtimeClasspath = modules_compileClasspath
302   */
303   gitHash = string("")
304   gitBranch = string("")
305
306   println("Using a ${CHANNEL} profile.")
307
308   additional_compiler_args = []
309   // configure classpath/args for j8/j11 compilation
310   if (JAVA_VERSION.equals("1.8")) {
311     JAVA_INTEGER_VERSION = string("8")
312     //libDir = j8libDir
313     libDir = j11libDir
314     libDistDir = j8libDir
315     compile_source_compatibility = 1.8
316     compile_target_compatibility = 1.8
317     // these are getdown.txt properties defined dependent on the JAVA_VERSION
318     getdownAltJavaMinVersion = string(findProperty("getdown_alt_java8_min_version"))
319     getdownAltJavaMaxVersion = string(findProperty("getdown_alt_java8_max_version"))
320     // this property is assigned below and expanded to multiple lines in the getdown task
321     getdownAltMultiJavaLocation = string(findProperty("getdown_alt_java8_txt_multi_java_location"))
322     // this property is for the Java library used in eclipse
323     eclipseJavaRuntimeName = string("JavaSE-1.8")
324   } else if (JAVA_VERSION.equals("11")) {
325     JAVA_INTEGER_VERSION = string("11")
326     libDir = j11libDir
327     libDistDir = j11libDir
328     compile_source_compatibility = 11
329     compile_target_compatibility = 11
330     getdownAltJavaMinVersion = string(findProperty("getdown_alt_java11_min_version"))
331     getdownAltJavaMaxVersion = string(findProperty("getdown_alt_java11_max_version"))
332     getdownAltMultiJavaLocation = string(findProperty("getdown_alt_java11_txt_multi_java_location"))
333     eclipseJavaRuntimeName = string("JavaSE-11")
334     /* compile without modules -- using classpath libraries
335     additional_compiler_args += [
336     '--module-path', modules_compileClasspath.asPath,
337     '--add-modules', j11modules
338     ]
339      */
340   } else if (JAVA_VERSION.equals("12") || JAVA_VERSION.equals("13")) {
341     JAVA_INTEGER_VERSION = JAVA_VERSION
342     libDir = j11libDir
343     libDistDir = j11libDir
344     compile_source_compatibility = JAVA_VERSION
345     compile_target_compatibility = JAVA_VERSION
346     getdownAltJavaMinVersion = string(findProperty("getdown_alt_java11_min_version"))
347     getdownAltJavaMaxVersion = string(findProperty("getdown_alt_java11_max_version"))
348     getdownAltMultiJavaLocation = string(findProperty("getdown_alt_java11_txt_multi_java_location"))
349     eclipseJavaRuntimeName = string("JavaSE-11")
350     /* compile without modules -- using classpath libraries
351     additional_compiler_args += [
352     '--module-path', modules_compileClasspath.asPath,
353     '--add-modules', j11modules
354     ]
355      */
356   } else {
357     throw new GradleException("JAVA_VERSION=${JAVA_VERSION} not currently supported by Jalview")
358   }
359
360
361   // for install4j
362   JAVA_MIN_VERSION = JAVA_VERSION
363   JAVA_MAX_VERSION = JAVA_VERSION
364   def jreInstallsDir = string(jre_installs_dir)
365   if (jreInstallsDir.startsWith("~/")) {
366     jreInstallsDir = System.getProperty("user.home") + jreInstallsDir.substring(1)
367   }
368   macosJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-mac-x64/jre")
369   macosJavaVMTgz = string("${jreInstallsDir}/tgz/jre-${JAVA_INTEGER_VERSION}-mac-x64.tar.gz")
370   windowsJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-windows-x64/jre")
371   windowsJavaVMTgz = string("${jreInstallsDir}/tgz/jre-${JAVA_INTEGER_VERSION}-windows-x64.tar.gz")
372   linuxJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-linux-x64/jre")
373   linuxJavaVMTgz = string("${jreInstallsDir}/tgz/jre-${JAVA_INTEGER_VERSION}-linux-x64.tar.gz")
374   install4jDir = string("${jalviewDir}/${install4j_utils_dir}")
375   install4jConfFileName = string("jalview-install4j-conf.install4j")
376   install4jConfFile = file("${install4jDir}/${install4jConfFileName}")
377   install4jHomeDir = install4j_home_dir
378   if (install4jHomeDir.startsWith("~/")) {
379     install4jHomeDir = System.getProperty("user.home") + install4jHomeDir.substring(1)
380   }
381
382
383
384   buildingHTML = string("${jalviewDir}/${docDir}/building.html")
385   helpFile = string("${classesDir}/${help_dir}/help.jhm")
386   helpParentDir = string("${jalviewDir}/${help_parent_dir}")
387   helpSourceDir = string("${helpParentDir}/${help_dir}")
388
389
390   relativeBuildDir = file(jalviewDirAbsolutePath).toPath().relativize(buildDir.toPath())
391   jalviewjsBuildDir = string("${relativeBuildDir}/jalviewjs")
392   jalviewjsSiteDir = string("${jalviewjsBuildDir}/${jalviewjs_site_dir}")
393   if (IN_ECLIPSE) {
394     jalviewjsTransferSiteJsDir = string(jalviewjsSiteDir)
395   } else {
396     jalviewjsTransferSiteJsDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_js")
397   }
398   jalviewjsTransferSiteLibDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_lib")
399   jalviewjsTransferSiteSwingJsDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_swingjs")
400   jalviewjsTransferSiteCoreDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_core")
401   jalviewjsJalviewCoreHtmlFile = string("")
402   jalviewjsJalviewCoreName = string(jalviewjs_core_name)
403   jalviewjsCoreClasslists = []
404   jalviewjsJalviewTemplateName = string(jalviewjs_name)
405   jalviewjsJ2sSettingsFileName = string("${jalviewDir}/${jalviewjs_j2s_settings}")
406   jalviewjsJ2sProps = null
407
408   eclipseWorkspace = null
409   eclipseBinary = string("")
410   eclipseVersion = string("")
411   eclipseDebug = false
412   // ENDEXT
413 }
414
415
416 sourceSets {
417   main {
418     java {
419       srcDirs sourceDir
420       outputDir = file(classesDir)
421     }
422
423     resources {
424       srcDirs resourceDir
425       srcDirs += helpParentDir
426     }
427
428     jar.destinationDir = file("${jalviewDir}/${packageDir}")
429
430     compileClasspath = files(sourceSets.main.java.outputDir)
431     //compileClasspath += files(sourceSets.main.resources.srcDirs)
432     compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
433
434     runtimeClasspath = compileClasspath
435   }
436
437   clover {
438     java {
439       srcDirs = [ cloverInstrDir ]
440       outputDir = file("${buildDir}/${cloverClassesDir}")
441     }
442
443     resources {
444       srcDirs = sourceSets.main.resources.srcDirs
445     }
446     compileClasspath = configurations.cloverRuntime + files( sourceSets.clover.java.outputDir )
447     compileClasspath += files(sourceSets.main.java.outputDir)
448     compileClasspath += sourceSets.main.compileClasspath
449     compileClasspath += fileTree(dir: "${jalviewDir}/${utilsDir}", include: ["**/*.jar"])
450     compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
451
452     runtimeClasspath = compileClasspath
453   }
454
455   test {
456     java {
457       srcDirs testSourceDir
458       outputDir = file("${jalviewDir}/${testOutputDir}")
459     }
460
461     resources {
462       srcDirs = sourceSets.main.resources.srcDirs
463     }
464
465     compileClasspath = files( sourceSets.test.java.outputDir )
466
467     if (use_clover) {
468       compileClasspath = sourceSets.clover.compileClasspath
469     } else {
470       compileClasspath += files(sourceSets.main.java.outputDir)
471     }
472
473     compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
474     compileClasspath += fileTree(dir: "${jalviewDir}/${utilsDir}/testnglibs", include: ["**/*.jar"])
475     compileClasspath += fileTree(dir: "${jalviewDir}/${utilsDir}/testlibs", include: ["**/*.jar"])
476
477     runtimeClasspath = compileClasspath
478   }
479 }
480
481
482 // clover bits
483 dependencies {
484   if (use_clover) {
485     cloverCompile 'org.openclover:clover:4.4.1'
486     testCompile 'org.openclover:clover:4.4.1'
487   }
488 }
489
490 configurations {
491   cloverRuntime
492   cloverRuntime.extendsFrom cloverCompile
493 }
494
495
496 // eclipse project and settings files creation, also used by buildship
497 eclipse {
498   project {
499     name = eclipse_project_name
500
501     natures 'org.eclipse.jdt.core.javanature',
502     'org.eclipse.jdt.groovy.core.groovyNature',
503     'org.eclipse.buildship.core.gradleprojectnature'
504
505     buildCommand 'org.eclipse.jdt.core.javabuilder'
506     buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder'
507   }
508
509   classpath {
510     //defaultOutputDir = sourceSets.main.java.outputDir
511     def removeThese = []
512     configurations.each{
513       if (it.isCanBeResolved()) {
514         removeThese += it
515       }
516     }
517
518     minusConfigurations += removeThese
519     plusConfigurations = [ ]
520     file {
521
522       whenMerged { cp ->
523         def removeTheseToo = []
524         HashMap<String, Boolean> alreadyAddedSrcPath = new HashMap<>();
525         cp.entries.each { entry ->
526           // This conditional removes all src classpathentries that a) have already been added or b) aren't "src" or "test".
527           // e.g. this removes the resources dir being copied into bin/main, bin/test AND bin/clover
528           // we add the resources and help/help dirs in as libs afterwards (see below)
529           if (entry.kind == 'src') {
530             if (alreadyAddedSrcPath.getAt(entry.path) || !(entry.path == bareSourceDir || entry.path == bareTestSourceDir)) {
531               removeTheseToo += entry
532             } else {
533               alreadyAddedSrcPath.putAt(entry.path, true)
534             }
535           }
536
537         }
538         cp.entries.removeAll(removeTheseToo)
539
540         //cp.entries += new Output("${eclipse_bin_dir}/main")
541         if (file(helpParentDir).isDirectory()) {
542           cp.entries += new Library(fileReference(helpParentDir))
543         }
544         if (file(resourceDir).isDirectory()) {
545           cp.entries += new Library(fileReference(resourceDir))
546         }
547
548         HashMap<String, Boolean> alreadyAddedLibPath = new HashMap<>();
549
550         sourceSets.main.compileClasspath.findAll { it.name.endsWith(".jar") }.any {
551           //don't want to add outputDir as eclipse is using its own output dir in bin/main
552           if (it.isDirectory() || ! it.exists()) {
553             // don't add dirs to classpath, especially if they don't exist
554             return false // groovy "continue" in .any closure
555           }
556           def itPath = it.toString()
557           if (itPath.startsWith("${jalviewDirAbsolutePath}/")) {
558             // make relative path
559             itPath = itPath.substring(jalviewDirAbsolutePath.length()+1)
560           }
561           if (alreadyAddedLibPath.get(itPath)) {
562             //println("Not adding duplicate entry "+itPath)
563           } else {
564             //println("Adding entry "+itPath)
565             cp.entries += new Library(fileReference(itPath))
566             alreadyAddedLibPath.put(itPath, true)
567           }
568         }
569
570         sourceSets.test.compileClasspath.findAll { it.name.endsWith(".jar") }.any {
571           //no longer want to add outputDir as eclipse is using its own output dir in bin/main
572           if (it.isDirectory() || ! it.exists()) {
573             // don't add dirs to classpath
574             return false // groovy "continue" in .any closure
575           }
576
577           def itPath = it.toString()
578           if (itPath.startsWith("${jalviewDirAbsolutePath}/")) {
579             itPath = itPath.substring(jalviewDirAbsolutePath.length()+1)
580           }
581           if (alreadyAddedLibPath.get(itPath)) {
582             // don't duplicate
583           } else {
584             def lib = new Library(fileReference(itPath))
585             lib.entryAttributes["test"] = "true"
586             cp.entries += lib
587             alreadyAddedLibPath.put(itPath, true)
588           }
589         }
590
591       } // whenMerged
592
593     } // file
594
595     containers 'org.eclipse.buildship.core.gradleclasspathcontainer'
596
597   } // classpath
598
599   jdt {
600     // for the IDE, use java 11 compatibility
601     sourceCompatibility = compile_source_compatibility
602     targetCompatibility = compile_target_compatibility
603     javaRuntimeName = eclipseJavaRuntimeName
604
605     // add in jalview project specific properties/preferences into eclipse core preferences
606     file {
607       withProperties { props ->
608         def jalview_prefs = new Properties()
609         def ins = new FileInputStream("${jalviewDirAbsolutePath}/${eclipse_extra_jdt_prefs_file}")
610         jalview_prefs.load(ins)
611         ins.close()
612         jalview_prefs.forEach { t, v ->
613           if (props.getAt(t) == null) {
614             props.putAt(t, v)
615           }
616         }
617       }
618     }
619
620   } // jdt
621
622   if (IN_ECLIPSE) {
623     // Don't want these to be activated if in headless build
624     synchronizationTasks "eclipseSynchronizationTask"
625     autoBuildTasks "eclipseAutoBuildTask"
626
627   }
628 }
629
630
631 task cloverInstr {
632   // only instrument source, we build test classes as normal
633   inputs.files files (sourceSets.main.allJava,sourceSets.test.allJava) // , fileTree(dir:"$jalviewDir/$testSourceDir", include: ["**/*.java"]))
634   outputs.dir cloverInstrDir
635
636   doFirst {
637     delete cloverInstrDir
638     def argsList = [
639       "--initstring",
640       cloverDb,
641       "-d",
642       cloverInstrDir.getPath(),
643     ]
644     argsList.addAll(
645       inputs.files.files.collect(
646         { file -> file.absolutePath }
647       )
648     )
649     String[] args = argsList.toArray()
650     println("About to instrument "+args.length +" files")
651     com.atlassian.clover.CloverInstr.mainImpl(args)
652   }
653 }
654
655
656 cloverClasses.dependsOn cloverInstr
657
658
659 task cloverReport {
660   group = "Verification"
661   description = "Creates the Clover report"
662   inputs.dir "${buildDir}/clover"
663   outputs.dir "${reportsDir}/clover"
664   onlyIf {
665     file(cloverDb).exists()
666   }
667   doFirst {
668     def argsList = [
669       "--initstring",
670       cloverDb,
671       "-o",
672       "${reportsDir}/clover"
673     ]
674     String[] args = argsList.toArray()
675     com.atlassian.clover.reporters.html.HtmlReporter.runReport(args)
676
677     // and generate ${reportsDir}/clover/clover.xml
678     args = [
679       "--initstring",
680       cloverDb,
681       "-o",
682       "${reportsDir}/clover/clover.xml"
683     ].toArray()
684     com.atlassian.clover.reporters.xml.XMLReporter.runReport(args)
685   }
686 }
687
688
689 compileCloverJava {
690
691   doFirst {
692     sourceCompatibility = compile_source_compatibility
693     targetCompatibility = compile_target_compatibility
694     options.compilerArgs += additional_compiler_args
695     print ("Setting target compatibility to "+targetCompatibility+"\n")
696   }
697   classpath += configurations.cloverRuntime
698 }
699
700
701 task cleanClover {
702   doFirst {
703     delete cloverInstrDir
704     delete cloverDb
705   }
706 }
707 // end clover bits
708
709
710 compileJava {
711
712   doFirst {
713     sourceCompatibility = compile_source_compatibility
714     targetCompatibility = compile_target_compatibility
715     options.compilerArgs = additional_compiler_args
716     print ("Setting target compatibility to "+targetCompatibility+"\n")
717   }
718
719 }
720
721
722 compileTestJava {
723   if (use_clover) {
724     dependsOn compileCloverJava
725     classpath += configurations.cloverRuntime
726   } else {
727     classpath += sourceSets.main.runtimeClasspath
728   }
729   doFirst {
730     sourceCompatibility = compile_source_compatibility
731     targetCompatibility = compile_target_compatibility
732     options.compilerArgs = additional_compiler_args
733     print ("Setting target compatibility to "+targetCompatibility+"\n")
734   }
735 }
736
737
738 clean {
739   doFirst {
740     delete sourceSets.main.java.outputDir
741   }
742 }
743
744
745 cleanTest {
746   dependsOn cleanClover
747   doFirst {
748     delete sourceSets.test.java.outputDir
749   }
750 }
751
752
753 // format is a string like date.format("dd MMMM yyyy")
754 def getDate(format) {
755   def date = new Date()
756   return date.format(format)
757 }
758
759
760 task setGitVals {
761   def hashStdOut = new ByteArrayOutputStream()
762   exec {
763     commandLine "git", "rev-parse", "--short", "HEAD"
764     standardOutput = hashStdOut
765     ignoreExitValue true
766   }
767
768   def branchStdOut = new ByteArrayOutputStream()
769   exec {
770     commandLine "git", "rev-parse", "--abbrev-ref", "HEAD"
771     standardOutput = branchStdOut
772     ignoreExitValue true
773   }
774
775   gitHash = hashStdOut.toString().trim()
776   gitBranch = branchStdOut.toString().trim()
777
778   outputs.upToDateWhen { false }
779 }
780
781
782 task createBuildProperties(type: WriteProperties) {
783   dependsOn setGitVals
784   inputs.dir(sourceDir)
785   inputs.dir(resourceDir)
786   file(buildProperties).getParentFile().mkdirs()
787   outputFile (buildProperties)
788   // taking time specific comment out to allow better incremental builds
789   comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd HH:mm:ss")
790   //comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd")
791   property "BUILD_DATE", getDate("HH:mm:ss dd MMMM yyyy")
792   property "VERSION", JALVIEW_VERSION
793   property "INSTALLATION", INSTALLATION+" git-commit:"+gitHash+" ["+gitBranch+"]"
794   outputs.file(outputFile)
795 }
796
797
798 task cleanBuildingHTML(type: Delete) {
799   doFirst {
800     delete buildingHTML
801   }
802 }
803
804
805 task convertBuildingMD(type: Exec) {
806   dependsOn cleanBuildingHTML
807   def buildingMD = "${jalviewDir}/${docDir}/building.md"
808   def css = "${jalviewDir}/${docDir}/github.css"
809
810   def pandoc = null
811   pandoc_exec.split(",").each {
812     if (file(it.trim()).exists()) {
813       pandoc = it.trim()
814       return true
815     }
816   }
817
818   def hostname = "hostname".execute().text.trim()
819   def buildtoolsPandoc = System.getProperty("user.home")+"/buildtools/pandoc/bin/pandoc"
820   if ((pandoc == null || ! file(pandoc).exists()) && file(buildtoolsPandoc).exists()) {
821     pandoc = System.getProperty("user.home")+"/buildtools/pandoc/bin/pandoc"
822   }
823
824   doFirst {
825     if (pandoc != null && file(pandoc).exists()) {
826         commandLine pandoc, '-s', '-o', buildingHTML, '--metadata', 'pagetitle="Building Jalview from Source"', '--toc', '-H', css, buildingMD
827     } else {
828         println("Cannot find pandoc. Skipping convert building.md to HTML")
829         throw new StopExecutionException("Cannot find pandoc. Skipping convert building.md to HTML")
830     }
831   }
832
833   ignoreExitValue true
834
835   inputs.file(buildingMD)
836   inputs.file(css)
837   outputs.file(buildingHTML)
838 }
839
840
841 clean {
842   doFirst {
843     delete buildingHTML
844   }
845 }
846
847
848 task syncDocs(type: Sync) {
849   dependsOn convertBuildingMD
850   def syncDir = "${classesDir}/${docDir}"
851   from fileTree("${jalviewDir}/${docDir}")
852   into syncDir
853
854 }
855
856
857 task copyHelp(type: Copy) {
858   def inputDir = helpSourceDir
859   def outputDir = "${classesDir}/${help_dir}"
860   from(inputDir) {
861     exclude '**/*.gif'
862     exclude '**/*.jpg'
863     exclude '**/*.png'
864     filter(ReplaceTokens,
865       beginToken: '$$',
866       endToken: '$$',
867       tokens: [
868         'Version-Rel': JALVIEW_VERSION,
869         'Year-Rel': getDate("yyyy")
870       ]
871     )
872   }
873   from(inputDir) {
874     include '**/*.gif'
875     include '**/*.jpg'
876     include '**/*.png'
877   }
878   into outputDir
879
880   inputs.dir(inputDir)
881   outputs.files(helpFile)
882   outputs.dir(outputDir)
883 }
884
885
886 task syncLib(type: Sync) {
887   def syncDir = "${classesDir}/${libDistDir}"
888   from fileTree("${jalviewDir}/${libDistDir}")
889   into syncDir
890 }
891
892
893 task syncResources(type: Sync) {
894   from resourceDir
895   include "**/*.*"
896   into "${classesDir}"
897   preserve {
898     include "**"
899   }
900 }
901
902
903 task prepare {
904   dependsOn syncResources
905   dependsOn syncDocs
906   dependsOn copyHelp
907 }
908
909
910 //testReportDirName = "test-reports" // note that test workingDir will be $jalviewDir
911 test {
912   dependsOn prepare
913   dependsOn compileJava
914   if (use_clover) {
915     dependsOn cloverInstr
916   }
917
918   if (use_clover) {
919     print("Running tests " + (use_clover?"WITH":"WITHOUT") + " clover [clover="+use_clover+"]\n")
920   }
921
922   useTestNG() {
923     includeGroups testngGroups
924     excludeGroups testngExcludedGroups
925     preserveOrder true
926     useDefaultListeners=true
927   }
928
929   maxHeapSize = "1024m"
930
931   workingDir = jalviewDir
932   //systemProperties 'clover.jar' System.properties.clover.jar
933   sourceCompatibility = compile_source_compatibility
934   targetCompatibility = compile_target_compatibility
935   jvmArgs += additional_compiler_args
936
937 }
938
939
940 task buildIndices(type: JavaExec) {
941   dependsOn copyHelp
942   classpath = sourceSets.main.compileClasspath
943   main = "com.sun.java.help.search.Indexer"
944   workingDir = "${classesDir}/${help_dir}"
945   def argDir = "html"
946   args = [ argDir ]
947   inputs.dir("${workingDir}/${argDir}")
948
949   outputs.dir("${classesDir}/doc")
950   outputs.dir("${classesDir}/help")
951   outputs.file("${workingDir}/JavaHelpSearch/DOCS")
952   outputs.file("${workingDir}/JavaHelpSearch/DOCS.TAB")
953   outputs.file("${workingDir}/JavaHelpSearch/OFFSETS")
954   outputs.file("${workingDir}/JavaHelpSearch/POSITIONS")
955   outputs.file("${workingDir}/JavaHelpSearch/SCHEMA")
956   outputs.file("${workingDir}/JavaHelpSearch/TMAP")
957 }
958
959
960 task compileLinkCheck(type: JavaCompile) {
961   options.fork = true
962   classpath = files("${jalviewDir}/${utilsDir}")
963   destinationDir = file("${jalviewDir}/${utilsDir}")
964   source = fileTree(dir: "${jalviewDir}/${utilsDir}", include: ["HelpLinksChecker.java", "BufferedLineReader.java"])
965
966   inputs.file("${jalviewDir}/${utilsDir}/HelpLinksChecker.java")
967   inputs.file("${jalviewDir}/${utilsDir}/HelpLinksChecker.java")
968   outputs.file("${jalviewDir}/${utilsDir}/HelpLinksChecker.class")
969   outputs.file("${jalviewDir}/${utilsDir}/BufferedLineReader.class")
970 }
971
972
973 task linkCheck(type: JavaExec) {
974   dependsOn prepare, compileLinkCheck
975
976   def helpLinksCheckerOutFile = file("${jalviewDir}/${utilsDir}/HelpLinksChecker.out")
977   classpath = files("${jalviewDir}/${utilsDir}")
978   main = "HelpLinksChecker"
979   workingDir = jalviewDir
980   args = [ "${classesDir}/${help_dir}", "-nointernet" ]
981
982   def outFOS = new FileOutputStream(helpLinksCheckerOutFile, false) // false == don't append
983   def errFOS = outFOS
984   standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
985     outFOS,
986     standardOutput)
987   errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
988     outFOS,
989     errorOutput)
990
991   inputs.dir("${classesDir}/${help_dir}")
992   outputs.file(helpLinksCheckerOutFile)
993 }
994
995 // import the pubhtmlhelp target
996 ant.properties.basedir = "${jalviewDir}"
997 ant.properties.helpBuildDir = "${jalviewDirAbsolutePath}/${classes_dir}/${help_dir}"
998 ant.importBuild "${utilsDir}/publishHelp.xml"
999
1000
1001 task cleanPackageDir(type: Delete) {
1002   doFirst {
1003     delete fileTree(dir: "${jalviewDir}/${packageDir}", include: "*.jar")
1004   }
1005 }
1006
1007 jar {
1008   dependsOn linkCheck
1009   dependsOn buildIndices
1010   dependsOn createBuildProperties
1011
1012   manifest {
1013     attributes "Main-Class": mainClass,
1014     "Permissions": "all-permissions",
1015     "Application-Name": "Jalview Desktop",
1016     "Codebase": application_codebase
1017   }
1018
1019   destinationDir = file("${jalviewDir}/${packageDir}")
1020   archiveName = rootProject.name+".jar"
1021
1022   exclude "cache*/**"
1023   exclude "*.jar"
1024   exclude "*.jar.*"
1025   exclude "**/*.jar"
1026   exclude "**/*.jar.*"
1027
1028   inputs.dir(classesDir)
1029   outputs.file("${jalviewDir}/${packageDir}/${archiveName}")
1030 }
1031
1032
1033 task copyJars(type: Copy) {
1034   from fileTree(dir: classesDir, include: "**/*.jar").files
1035   into "${jalviewDir}/${packageDir}"
1036 }
1037
1038
1039 // doing a Sync instead of Copy as Copy doesn't deal with "outputs" very well
1040 task syncJars(type: Sync) {
1041   from fileTree(dir: "${jalviewDir}/${libDistDir}", include: "**/*.jar").files
1042   into "${jalviewDir}/${packageDir}"
1043   preserve {
1044     include jar.archiveName
1045   }
1046 }
1047
1048
1049 task makeDist {
1050   group = "build"
1051   description = "Put all required libraries in dist"
1052   // order of "cleanPackageDir", "copyJars", "jar" important!
1053   jar.mustRunAfter cleanPackageDir
1054   syncJars.mustRunAfter cleanPackageDir
1055   dependsOn cleanPackageDir
1056   dependsOn syncJars
1057   dependsOn jar
1058   outputs.dir("${jalviewDir}/${packageDir}")
1059 }
1060
1061
1062 task cleanDist {
1063   dependsOn cleanPackageDir
1064   dependsOn cleanTest
1065   dependsOn clean
1066 }
1067
1068 shadowJar {
1069   group = "distribution"
1070   if (buildDist) {
1071     dependsOn makeDist
1072   }
1073   from ("${jalviewDir}/${libDistDir}") {
1074     include("*.jar")
1075   }
1076   manifest {
1077     attributes 'Implementation-Version': JALVIEW_VERSION
1078   }
1079   mainClassName = shadowJarMainClass
1080   mergeServiceFiles()
1081   classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
1082   minimize()
1083 }
1084
1085
1086 task getdownWebsite() {
1087   group = "distribution"
1088   description = "Create the getdown minimal app folder, and website folder for this version of jalview. Website folder also used for offline app installer"
1089   if (buildDist) {
1090     dependsOn makeDist
1091   }
1092
1093   def getdownWebsiteResourceFilenames = []
1094   def getdownTextString = ""
1095   def getdownResourceDir = getdownResourceDir
1096   def getdownResourceFilenames = []
1097
1098   doFirst {
1099     // clean the getdown website and files dir before creating getdown folders
1100     delete getdownWebsiteDir
1101     delete getdownFilesDir
1102
1103     copy {
1104       from buildProperties
1105       rename(build_properties_file, getdown_build_properties)
1106       into getdownAppDir
1107     }
1108     getdownWebsiteResourceFilenames += "${getdownAppDistDir}/${getdown_build_properties}"
1109
1110     // set some getdown_txt_ properties then go through all properties looking for getdown_txt_...
1111     def props = project.properties.sort { it.key }
1112     if (getdownAltJavaMinVersion != null && getdownAltJavaMinVersion.length() > 0) {
1113       props.put("getdown_txt_java_min_version", getdownAltJavaMinVersion)
1114     }
1115     if (getdownAltJavaMaxVersion != null && getdownAltJavaMaxVersion.length() > 0) {
1116       props.put("getdown_txt_java_max_version", getdownAltJavaMaxVersion)
1117     }
1118     if (getdownAltMultiJavaLocation != null && getdownAltMultiJavaLocation.length() > 0) {
1119       props.put("getdown_txt_multi_java_location", getdownAltMultiJavaLocation)
1120     }
1121
1122     props.put("getdown_txt_title", jalview_name)
1123     props.put("getdown_txt_ui.name", install4jApplicationName)
1124
1125     // start with appbase
1126     getdownTextString += "appbase = ${getdownAppBase}\n"
1127     props.each{ prop, val ->
1128       if (prop.startsWith("getdown_txt_") && val != null) {
1129         if (prop.startsWith("getdown_txt_multi_")) {
1130           def key = prop.substring(18)
1131           val.split(",").each{ v ->
1132             def line = "${key} = ${v}\n"
1133             getdownTextString += line
1134           }
1135         } else {
1136           // file values rationalised
1137           if (val.indexOf('/') > -1 || prop.startsWith("getdown_txt_resource")) {
1138             def r = null
1139             if (val.indexOf('/') == 0) {
1140               // absolute path
1141               r = file(val)
1142             } else if (val.indexOf('/') > 0) {
1143               // relative path (relative to jalviewDir)
1144               r = file( "${jalviewDir}/${val}" )
1145             }
1146             if (r.exists()) {
1147               val = "${getdown_resource_dir}/" + r.getName()
1148               getdownWebsiteResourceFilenames += val
1149               getdownResourceFilenames += r.getPath()
1150             }
1151           }
1152           if (! prop.startsWith("getdown_txt_resource")) {
1153             def line = prop.substring(12) + " = ${val}\n"
1154             getdownTextString += line
1155           }
1156         }
1157       }
1158     }
1159
1160     getdownWebsiteResourceFilenames.each{ filename ->
1161       getdownTextString += "resource = ${filename}\n"
1162     }
1163     getdownResourceFilenames.each{ filename ->
1164       copy {
1165         from filename
1166         into getdownResourceDir
1167       }
1168     }
1169
1170     def codeFiles = []
1171     fileTree(file(packageDir)).each{ f ->
1172       if (f.isDirectory()) {
1173         def files = fileTree(dir: f, include: ["*"]).getFiles()
1174         codeFiles += files
1175       } else if (f.exists()) {
1176         codeFiles += f
1177       }
1178     }
1179     codeFiles.sort().each{f ->
1180       def name = f.getName()
1181       def line = "code = ${getdownAppDistDir}/${name}\n"
1182       getdownTextString += line
1183       copy {
1184         from f.getPath()
1185         into getdownAppDir
1186       }
1187     }
1188
1189     // NOT USING MODULES YET, EVERYTHING SHOULD BE IN dist
1190     /*
1191     if (JAVA_VERSION.equals("11")) {
1192     def j11libFiles = fileTree(dir: "${jalviewDir}/${j11libDir}", include: ["*.jar"]).getFiles()
1193     j11libFiles.sort().each{f ->
1194     def name = f.getName()
1195     def line = "code = ${getdown_j11lib_dir}/${name}\n"
1196     getdownTextString += line
1197     copy {
1198     from f.getPath()
1199     into getdownJ11libDir
1200     }
1201     }
1202     }
1203      */
1204
1205     // 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.
1206     //getdownTextString += "class = " + file(getdownLauncher).getName() + "\n"
1207     getdownTextString += "resource = ${getdown_launcher_new}\n"
1208     getdownTextString += "class = ${mainClass}\n"
1209
1210     def getdown_txt = file("${getdownWebsiteDir}/getdown.txt")
1211     getdown_txt.write(getdownTextString)
1212
1213     def getdownLaunchJvl = getdown_launch_jvl_name + ( (jvlChannelName != null && jvlChannelName.length() > 0)?"-${jvlChannelName}":"" ) + ".jvl"
1214     def launchJvl = file("${getdownWebsiteDir}/${getdownLaunchJvl}")
1215     launchJvl.write("appbase=${getdownAppBase}")
1216
1217     copy {
1218       from getdownLauncher
1219       rename(file(getdownLauncher).getName(), getdown_launcher_new)
1220       into getdownWebsiteDir
1221     }
1222
1223     copy {
1224       from getdownLauncher
1225       if (file(getdownLauncher).getName() != getdown_launcher) {
1226         rename(file(getdownLauncher).getName(), getdown_launcher)
1227       }
1228       into getdownWebsiteDir
1229     }
1230
1231     if (! (CHANNEL.startsWith("ARCHIVE") || CHANNEL.startsWith("DEVELOP"))) {
1232       copy {
1233         from getdown_txt
1234         from getdownLauncher
1235         from "${getdownWebsiteDir}/${getdown_build_properties}"
1236         if (file(getdownLauncher).getName() != getdown_launcher) {
1237           rename(file(getdownLauncher).getName(), getdown_launcher)
1238         }
1239         into getdownInstallDir
1240       }
1241
1242       copy {
1243         from getdownInstallDir
1244         into getdownFilesInstallDir
1245       }
1246     }
1247
1248     copy {
1249       from getdown_txt
1250       from launchJvl
1251       from getdownLauncher
1252       from "${getdownWebsiteDir}/${getdown_build_properties}"
1253       if (file(getdownLauncher).getName() != getdown_launcher) {
1254         rename(file(getdownLauncher).getName(), getdown_launcher)
1255       }
1256       into getdownFilesDir
1257     }
1258
1259     copy {
1260       from getdownResourceDir
1261       into "${getdownFilesDir}/${getdown_resource_dir}"
1262     }
1263   }
1264
1265   if (buildDist) {
1266     inputs.dir("${jalviewDir}/${packageDir}")
1267   }
1268   outputs.dir(getdownWebsiteDir)
1269   outputs.dir(getdownFilesDir)
1270 }
1271
1272
1273 // a helper task to allow getdown digest of any dir: `gradle getdownDigestDir -PDIGESTDIR=/path/to/my/random/getdown/dir
1274 task getdownDigestDir(type: JavaExec) {
1275   def digestDirPropertyName = "DIGESTDIR"
1276   description = "Digest a local dir (-P${digestDirPropertyName}=...) for getdown"
1277   doFirst {
1278     classpath = files(getdownLauncher)
1279     def digestDir = findProperty(digestDirPropertyName)
1280     if (digestDir == null) {
1281       throw new GradleException("Must provide a DIGESTDIR value to produce an alternative getdown digest")
1282     }
1283     args digestDir
1284   }
1285   main = "com.threerings.getdown.tools.Digester"
1286 }
1287
1288
1289 task getdownDigest(type: JavaExec) {
1290   group = "distribution"
1291   description = "Digest the getdown website folder"
1292   dependsOn getdownWebsite
1293   doFirst {
1294     classpath = files(getdownLauncher)
1295   }
1296   main = "com.threerings.getdown.tools.Digester"
1297   args getdownWebsiteDir
1298   inputs.dir(getdownWebsiteDir)
1299   outputs.file("${getdownWebsiteDir}/digest2.txt")
1300 }
1301
1302
1303 task getdown() {
1304   group = "distribution"
1305   description = "Create the minimal and full getdown app folder for installers and website and create digest file"
1306   dependsOn getdownDigest
1307   doLast {
1308     if (reportRsyncCommand) {
1309       def fromDir = getdownWebsiteDir + (getdownWebsiteDir.endsWith('/')?'':'/')
1310       def toDir = "${getdown_rsync_dest}/${getdownDir}" + (getdownDir.endsWith('/')?'':'/')
1311       println "LIKELY RSYNC COMMAND:"
1312       println "mkdir -p '$toDir'\nrsync -avh --delete '$fromDir' '$toDir'"
1313       if (RUNRSYNC == "true") {
1314         exec {
1315           commandLine "mkdir", "-p", toDir
1316         }
1317         exec {
1318           commandLine "rsync", "-avh", "--delete", fromDir, toDir
1319         }
1320       }
1321     }
1322   }
1323 }
1324
1325
1326 clean {
1327   doFirst {
1328     delete getdownWebsiteDir
1329     delete getdownFilesDir
1330   }
1331 }
1332
1333
1334 install4j {
1335   if (file(install4jHomeDir).exists()) {
1336     // good to go!
1337   } else if (file(System.getProperty("user.home")+"/buildtools/install4j").exists()) {
1338     install4jHomeDir = System.getProperty("user.home")+"/buildtools/install4j"
1339   } else if (file("/Applications/install4j.app/Contents/Resources/app").exists()) {
1340     install4jHomeDir = "/Applications/install4j.app/Contents/Resources/app"
1341   }
1342   installDir(file(install4jHomeDir))
1343
1344   mediaTypes = Arrays.asList(install4j_media_types.split(","))
1345 }
1346
1347
1348 task copyInstall4jTemplate {
1349   def install4jTemplateFile = file("${install4jDir}/${install4j_template}")
1350   def install4jFileAssociationsFile = file("${install4jDir}/${install4j_installer_file_associations}")
1351   inputs.file(install4jTemplateFile)
1352   inputs.file(install4jFileAssociationsFile)
1353   inputs.property("CHANNEL", { CHANNEL })
1354   outputs.file(install4jConfFile)
1355
1356   doLast {
1357     def install4jConfigXml = new XmlParser().parse(install4jTemplateFile)
1358
1359     // turn off code signing if no OSX_KEYPASS
1360     if (OSX_KEYPASS == "") {
1361       install4jConfigXml.'**'.codeSigning.each { codeSigning ->
1362         codeSigning.'@macEnabled' = "false"
1363       }
1364       install4jConfigXml.'**'.windows.each { windows ->
1365         windows.'@runPostProcessor' = "false"
1366       }
1367     }
1368
1369     // turn off checksum creation for LOCAL channel
1370     def e = install4jConfigXml.application[0]
1371     if (CHANNEL == "LOCAL") {
1372       e.'@createChecksums' = "false"
1373     } else {
1374       e.'@createChecksums' = "true"
1375     }
1376
1377     // find largest @id value
1378     def maxId = 0
1379     /* NOT WORKING
1380     install4jConfigXml.'**'.each { element ->
1381       if ( element.'@id' != null && element.'@id'.toInteger() > maxId ) {
1382         maxId = element.'@id'.toInteger()
1383       }
1384     }
1385     */
1386     maxId = 10000
1387     def idCount = maxId + 1
1388
1389     // put file association actions where placeholder action is
1390     def install4jFileAssociationsText = install4jFileAssociationsFile.text
1391     def fileAssociationActions = new XmlParser().parseText("<actions>${install4jFileAssociationsText}</actions>")
1392     install4jConfigXml.'**'.action.any { a -> // .any{} stops after the first one that returns true
1393       if (a.'@name' == 'EXTENSIONS_REPLACED_BY_GRADLE') {
1394         def parent = a.parent()
1395         parent.remove(a)
1396         fileAssociationActions.each { faa ->
1397             if ( faa.'@id' != null ) {
1398               faa.'@id' = idCount++
1399             }
1400             parent.append(faa)
1401         }
1402         // don't need to continue in .any loop once replacements have been made
1403         return true
1404       }
1405     }
1406
1407     // use Windows Program Group with Examples folder for RELEASE, and Program Group without Examples for everything else
1408     // NB we're deleting the /other/ one!
1409     // Also remove the examples subdir from non-release versions
1410     def customizedIdToDelete = "PROGRAM_GROUP_RELEASE"
1411     def macosArchive = install4jConfigXml.'**'.macosArchive.find { ma -> ma.'@customizedId' == "MACOS_DISK_IMAGE" }
1412     def examplesFolderPlaceholder = macosArchive.topLevelFiles.file.find { f -> f.'@file' == "EXAMPLES_FOLDER_PLACEHOLDER" }
1413     if (CHANNEL=="RELEASE") {
1414       customizedIdToDelete = "PROGRAM_GROUP_NON_RELEASE"
1415
1416       // add all of examples folder
1417       def tlf = examplesFolderPlaceholder.parent()
1418       tlf.remove(examplesFolderPlaceholder)
1419       def examplesFiles = fileTree(file(examples_dir)).findAll { f -> f.isFile() }
1420       examplesFiles.each { f ->
1421         def relativeFile = file(examples_dir).toPath().relativize(f.toPath())
1422         tlf.append( new XmlParser().parseText(
1423           "<file name='\${compiler:EXAMPLES_FOLDER}/${relativeFile}' file='\${compiler:JALVIEW_DIR}/${examples_dir}/${relativeFile}' />"
1424         ) )
1425       }
1426       // let's see if we can add the snazzy Jalview Folder icon and Volume icon. Note the weird filename
1427       tlf.append( new XmlParser().parseText(
1428           "<file name='\${compiler:EXAMPLES_FOLDER}/Icon&#13;' file='\${compiler:JALVIEW_DIR}/${install4j_macos_examples_folder_icon}' />"
1429       ) )
1430       tlf.append( new XmlParser().parseText(
1431           "<file name='.VolumeIcon.icns' file='\${compiler:JALVIEW_DIR}/${install4j_dmg_volume_icons}' />"
1432       ) )
1433
1434     } else {
1435       // remove the examples subdir from Full File Set
1436       def files = install4jConfigXml.files[0]
1437       def fileset = files.filesets.fileset.find { fs -> fs.'@customizedId' == "FULL_FILE_SET" }
1438       def root = files.roots.root.find { r -> r.'@fileset' == fileset.'@id' }
1439       def mountPoint = files.mountPoints.mountPoint.find { mp -> mp.'@root' == root.'@id' }
1440       def dirEntry = files.entries.dirEntry.find { de -> de.'@mountPoint' == mountPoint.'@id' && de.'@subDirectory' == "examples" }
1441       dirEntry.parent().remove(dirEntry)
1442       // remove Examples folder from DMG
1443       examplesFolderPlaceholder.parent().remove(examplesFolderPlaceholder)
1444     }
1445     install4jConfigXml.'**'.action.any { a ->
1446       if (a.'@customizedId' == customizedIdToDelete) {
1447         a.parent().remove(a)
1448         return true
1449       }
1450     }
1451
1452     // remove the "Uninstall Old Jalview (optional)" symlink from DMG for non-release DS_Stores
1453     if (! (CHANNEL == "RELEASE" || CHANNEL == "TEST-RELEASE" ) ) {
1454       def symlink = install4jConfigXml.'**'.topLevelFiles.symlink.find { sl -> sl.'@name' == "Uninstall Old Jalview (optional).app" }
1455       symlink.parent().remove(symlink)
1456     }
1457
1458     // write install4j file
1459     install4jConfFile.text = XmlUtil.serialize(install4jConfigXml)
1460   }
1461 }
1462
1463
1464 clean {
1465   doFirst {
1466     delete install4jConfFile
1467   }
1468 }
1469
1470
1471 task installers(type: com.install4j.gradle.Install4jTask) {
1472   group = "distribution"
1473   description = "Create the install4j installers"
1474   dependsOn setGitVals
1475   dependsOn getdown
1476   dependsOn copyInstall4jTemplate
1477
1478   projectFile = install4jConfFile
1479
1480   // create an md5 for the input files to use as version for install4j conf file
1481   def digest = MessageDigest.getInstance("MD5")
1482   digest.update(
1483     (file("${install4jDir}/${install4j_template}").text + 
1484     file("${install4jDir}/${install4j_info_plist_file_associations}").text +
1485     file("${install4jDir}/${install4j_installer_file_associations}").text).bytes)
1486   def filesMd5 = new BigInteger(1, digest.digest()).toString(16)
1487   if (filesMd5.length() >= 8) {
1488     filesMd5 = filesMd5.substring(0,8)
1489   }
1490   def install4jTemplateVersion = "${JALVIEW_VERSION}_F${filesMd5}_C${gitHash}"
1491   // make install4jBuildDir relative to jalviewDir
1492   def install4jBuildDir = "${install4j_build_dir}/${JAVA_VERSION}"
1493
1494   variables = [
1495     'JALVIEW_NAME': jalview_name,
1496     'JALVIEW_APPLICATION_NAME': install4jApplicationName,
1497     'JALVIEW_DIR': "../..",
1498     'OSX_KEYSTORE': OSX_KEYSTORE,
1499     'JSIGN_SH': JSIGN_SH,
1500     'JRE_DIR': getdown_app_dir_java,
1501     'INSTALLER_TEMPLATE_VERSION': install4jTemplateVersion,
1502     'JALVIEW_VERSION': JALVIEW_VERSION,
1503     'JAVA_MIN_VERSION': JAVA_MIN_VERSION,
1504     'JAVA_MAX_VERSION': JAVA_MAX_VERSION,
1505     'JAVA_VERSION': JAVA_VERSION,
1506     'JAVA_INTEGER_VERSION': JAVA_INTEGER_VERSION,
1507     'VERSION': JALVIEW_VERSION,
1508     'MACOS_JAVA_VM_DIR': macosJavaVMDir,
1509     'WINDOWS_JAVA_VM_DIR': windowsJavaVMDir,
1510     'LINUX_JAVA_VM_DIR': linuxJavaVMDir,
1511     'MACOS_JAVA_VM_TGZ': macosJavaVMTgz,
1512     'WINDOWS_JAVA_VM_TGZ': windowsJavaVMTgz,
1513     'LINUX_JAVA_VM_TGZ': linuxJavaVMTgz,
1514     'COPYRIGHT_MESSAGE': install4j_copyright_message,
1515     'BUNDLE_ID': install4jBundleId,
1516     'INTERNAL_ID': install4jInternalId,
1517     'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
1518     'MACOS_DS_STORE': install4jDSStore,
1519     'MACOS_DMG_BG_IMAGE': install4jDMGBackgroundImage,
1520     'EXAMPLES_FOLDER': install4j_examples_folder,
1521     'INSTALLER_NAME': install4jInstallerName,
1522     'INSTALL4J_UTILS_DIR': install4j_utils_dir,
1523     'GETDOWN_WEBSITE_DIR': getdown_website_dir,
1524     'GETDOWN_FILES_DIR': getdown_files_dir,
1525     'GETDOWN_RESOURCE_DIR': getdown_resource_dir,
1526     'GETDOWN_DIST_DIR': getdownAppDistDir,
1527     'GETDOWN_ALT_DIR': getdown_app_dir_alt,
1528     'GETDOWN_INSTALL_DIR': getdown_install_dir,
1529     'INFO_PLIST_FILE_ASSOCIATIONS_FILE': install4j_info_plist_file_associations,
1530     'BUILD_DIR': install4jBuildDir,
1531     'APPLICATION_CATEGORIES': install4j_application_categories,
1532     'APPLICATION_FOLDER': install4jApplicationFolder,
1533     'UNIX_APPLICATION_FOLDER': install4jUnixApplicationFolder,
1534     'EXECUTABLE_NAME': install4jExecutableName,
1535     'EXTRA_SCHEME': install4jExtraScheme,
1536   ]
1537
1538   //println("INSTALL4J VARIABLES:")
1539   //variables.each{k,v->println("${k}=${v}")}
1540
1541   destination = "${jalviewDir}/${install4jBuildDir}"
1542   buildSelected = true
1543
1544   if (install4j_faster.equals("true") || CHANNEL.startsWith("LOCAL")) {
1545     faster = true
1546     disableSigning = true
1547   }
1548
1549   if (OSX_KEYPASS) {
1550     macKeystorePassword = OSX_KEYPASS
1551   }
1552
1553   doFirst {
1554     println("Using projectFile "+projectFile)
1555   }
1556
1557   inputs.dir(getdownWebsiteDir)
1558   inputs.file(install4jConfFile)
1559   inputs.file("${install4jDir}/${install4j_info_plist_file_associations}")
1560   inputs.dir(macosJavaVMDir)
1561   inputs.dir(windowsJavaVMDir)
1562   outputs.dir("${jalviewDir}/${install4j_build_dir}/${JAVA_VERSION}")
1563 }
1564
1565
1566 task sourceDist(type: Tar) {
1567   
1568   def VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
1569   def outputFileName = "${project.name}_${VERSION_UNDERSCORES}.tar.gz"
1570   // cater for buildship < 3.1 [3.0.1 is max version in eclipse 2018-09]
1571   try {
1572     archiveFileName = outputFileName
1573   } catch (Exception e) {
1574     archiveName = outputFileName
1575   }
1576   
1577   compression Compression.GZIP
1578   
1579   into project.name
1580
1581   def EXCLUDE_FILES=[
1582     "build/*",
1583     "bin/*",
1584     "test-output/",
1585     "test-reports",
1586     "tests",
1587     "clover*/*",
1588     ".*",
1589     "benchmarking/*",
1590     "**/.*",
1591     "*.class",
1592     "**/*.class","$j11modDir/**/*.jar","appletlib","**/*locales",
1593     "*locales/**",
1594     "utils/InstallAnywhere",
1595     "**/*.log",
1596   ] 
1597   def PROCESS_FILES=[
1598     "AUTHORS",
1599     "CITATION",
1600     "FEATURETODO",
1601     "JAVA-11-README",
1602     "FEATURETODO",
1603     "LICENSE",
1604     "**/README",
1605     "RELEASE",
1606     "THIRDPARTYLIBS",
1607     "TESTNG",
1608     "build.gradle",
1609     "gradle.properties",
1610     "**/*.java",
1611     "**/*.html",
1612     "**/*.xml",
1613     "**/*.gradle",
1614     "**/*.groovy",
1615     "**/*.properties",
1616     "**/*.perl",
1617     "**/*.sh",
1618   ]
1619   def INCLUDE_FILES=[
1620     ".settings/org.eclipse.jdt.core.jalview.prefs",
1621   ]
1622
1623   from(jalviewDir) {
1624     exclude (EXCLUDE_FILES)
1625     include (PROCESS_FILES)
1626     filter(ReplaceTokens,
1627       beginToken: '$$',
1628       endToken: '$$',
1629       tokens: [
1630         'Version-Rel': JALVIEW_VERSION,
1631         'Year-Rel': getDate("yyyy")
1632       ]
1633     )
1634   }
1635   from(jalviewDir) {
1636     exclude (EXCLUDE_FILES)
1637     exclude (PROCESS_FILES)
1638     exclude ("appletlib")
1639     exclude ("**/*locales")
1640     exclude ("*locales/**")
1641     exclude ("utils/InstallAnywhere")
1642
1643     exclude (getdown_files_dir)
1644     exclude (getdown_website_dir)
1645
1646     // exluding these as not using jars as modules yet
1647     exclude ("$j11modDir/**/*.jar")
1648   }
1649   from(jalviewDir) {
1650     include(INCLUDE_FILES)
1651   }
1652 //  from (jalviewDir) {
1653 //    // explicit includes for stuff that seemed to not get included
1654 //    include(fileTree("test/**/*."))
1655 //    exclude(EXCLUDE_FILES)
1656 //    exclude(PROCESS_FILES)
1657 //  }
1658 }
1659
1660
1661 task helppages {
1662   dependsOn copyHelp
1663   dependsOn pubhtmlhelp
1664   
1665   inputs.dir("${classesDir}/${help_dir}")
1666   outputs.dir("${buildDir}/distributions/${help_dir}")
1667 }
1668
1669 // LARGE AMOUNT OF JALVIEWJS STUFF DELETED HERE
1670 task eclipseAutoBuildTask {}
1671 task eclipseSynchronizationTask {}