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