JAL-3989 fix a build date once in build.gradle, not each time getDate() is called.
[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   date = new Date()
101
102   getdownChannelName = CHANNEL.toLowerCase()
103   // default to "default". Currently only has different cosmetics for "develop", "release", "default"
104   propertiesChannelName = ["develop", "release", "test-release", "jalviewjs", "jalviewjs-release" ].contains(getdownChannelName) ? getdownChannelName : "default"
105   // Import channel_properties
106   channelDir = string("${jalviewDir}/${channel_properties_dir}/${propertiesChannelName}")
107   channelGradleProperties = string("${channelDir}/channel_gradle.properties")
108   channelPropsFile = string("${channelDir}/${resource_dir}/${channel_props}")
109   overrideProperties(channelGradleProperties, false)
110   // local build environment properties
111   // can be "projectDir/local.properties"
112   overrideProperties("${projectDir}/local.properties", true)
113   // or "../projectDir_local.properties"
114   overrideProperties(projectDir.getParent() + "/" + projectDir.getName() + "_local.properties", true)
115
116   ////  
117   // Import releaseProps from the RELEASE file
118   // or a file specified via JALVIEW_RELEASE_FILE if defined
119   // Expect jalview.version and target release branch in jalview.release        
120   releaseProps = new Properties();
121   def releasePropFile = findProperty("JALVIEW_RELEASE_FILE");
122   def defaultReleasePropFile = "${jalviewDirAbsolutePath}/RELEASE";
123   try {
124     (new File(releasePropFile!=null ? releasePropFile : defaultReleasePropFile)).withInputStream { 
125      releaseProps.load(it)
126     }
127   } catch (Exception fileLoadError) {
128     throw new Error("Couldn't load release properties file "+(releasePropFile==null ? defaultReleasePropFile : "from custom location: releasePropFile"),fileLoadError);
129   }
130   ////
131   // Set JALVIEW_VERSION if it is not already set
132   if (findProperty("JALVIEW_VERSION")==null || "".equals(JALVIEW_VERSION)) {
133     JALVIEW_VERSION = releaseProps.get("jalview.version")
134   }
135   
136   // this property set when running Eclipse headlessly
137   j2sHeadlessBuildProperty = string("net.sf.j2s.core.headlessbuild")
138   // this property set by Eclipse
139   eclipseApplicationProperty = string("eclipse.application")
140   // CHECK IF RUNNING FROM WITHIN ECLIPSE
141   def eclipseApplicationPropertyVal = System.properties[eclipseApplicationProperty]
142   IN_ECLIPSE = eclipseApplicationPropertyVal != null && eclipseApplicationPropertyVal.startsWith("org.eclipse.ui.")
143   // BUT WITHOUT THE HEADLESS BUILD PROPERTY SET
144   if (System.properties[j2sHeadlessBuildProperty].equals("true")) {
145     println("Setting IN_ECLIPSE to ${IN_ECLIPSE} as System.properties['${j2sHeadlessBuildProperty}'] == '${System.properties[j2sHeadlessBuildProperty]}'")
146     IN_ECLIPSE = false
147   }
148   if (IN_ECLIPSE) {
149     println("WITHIN ECLIPSE IDE")
150   } else {
151     println("HEADLESS BUILD")
152   }
153   
154   J2S_ENABLED = (project.hasProperty('j2s.compiler.status') && project['j2s.compiler.status'] != null && project['j2s.compiler.status'] == "enable")
155   if (J2S_ENABLED) {
156     println("J2S ENABLED")
157   } 
158   /* *-/
159   System.properties.sort { it.key }.each {
160     key, val -> println("SYSTEM PROPERTY ${key}='${val}'")
161   }
162   /-* *-/
163   if (false && IN_ECLIPSE) {
164     jalviewDir = jalviewDirAbsolutePath
165   }
166   */
167
168   // datestamp
169   buildDate = new Date().format("yyyyMMdd")
170
171   // essentials
172   bareSourceDir = string(source_dir)
173   sourceDir = string("${jalviewDir}/${bareSourceDir}")
174   resourceDir = string("${jalviewDir}/${resource_dir}")
175   bareTestSourceDir = string(test_source_dir)
176   testDir = string("${jalviewDir}/${bareTestSourceDir}")
177
178   classesDir = string("${jalviewDir}/${classes_dir}")
179
180   // clover
181   useClover = clover.equals("true")
182   cloverBuildDir = "${buildDir}/clover"
183   cloverInstrDir = file("${cloverBuildDir}/clover-instr")
184   cloverClassesDir = file("${cloverBuildDir}/clover-classes")
185   cloverReportDir = file("${buildDir}/reports/clover")
186   cloverTestInstrDir = file("${cloverBuildDir}/clover-test-instr")
187   cloverTestClassesDir = file("${cloverBuildDir}/clover-test-classes")
188   //cloverTestClassesDir = cloverClassesDir
189   cloverDb = string("${cloverBuildDir}/clover.db")
190
191   testSourceDir = useClover ? cloverTestInstrDir : testDir
192   testClassesDir = useClover ? cloverTestClassesDir : "${jalviewDir}/${test_output_dir}"
193
194   getdownWebsiteDir = string("${jalviewDir}/${getdown_website_dir}/${JAVA_VERSION}")
195   getdownArchiveDir = string("${jalviewDir}/${getdown_archive_dir}")
196   getdownFullArchiveDir = null
197   getdownTextLines = []
198   getdownLaunchJvl = null
199   buildDist = true
200   buildProperties = null
201
202   // the following values might be overridden by the CHANNEL switch
203   getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
204   getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
205   getdownArchiveAppBase = null
206   getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher}")
207   getdownAppDistDir = getdown_app_dir_alt
208   getdownImagesDir = string("${jalviewDir}/${getdown_images_dir}")
209   getdownSetAppBaseProperty = false // whether to pass the appbase and appdistdir to the application
210   reportRsyncCommand = false
211   jvlChannelName = CHANNEL.toLowerCase()
212   install4jSuffix = CHANNEL.substring(0, 1).toUpperCase() + CHANNEL.substring(1).toLowerCase(); // BUILD -> Build
213   install4jDMGDSStore = "${install4j_images_dir}/${install4j_dmg_ds_store}"
214   install4jDMGBackgroundImage = "${install4j_images_dir}/${install4j_dmg_background}"
215   install4jInstallerName = "${jalview_name} Non-Release Installer"
216   install4jExecutableName = install4j_executable_name
217   install4jExtraScheme = "jalviewx"
218   install4jMacIconsFile = string("${install4j_images_dir}/${install4j_mac_icons_file}")
219   install4jWindowsIconsFile = string("${install4j_images_dir}/${install4j_windows_icons_file}")
220   install4jPngIconFile = string("${install4j_images_dir}/${install4j_png_icon_file}")
221   install4jBackground = string("${install4j_images_dir}/${install4j_background}")
222   install4jBuildDir = "${install4j_build_dir}/${JAVA_VERSION}"
223   install4jCheckSums = true
224   switch (CHANNEL) {
225
226     case "BUILD":
227     // TODO: get bamboo build artifact URL for getdown artifacts
228     getdown_channel_base = bamboo_channelbase
229     getdownChannelName = string("${bamboo_planKey}/${JAVA_VERSION}")
230     getdownAppBase = string("${bamboo_channelbase}/${bamboo_planKey}${bamboo_getdown_channel_suffix}/${JAVA_VERSION}")
231     jvlChannelName += "_${getdownChannelName}"
232     // automatically add the test group Not-bamboo for exclusion 
233     if ("".equals(testng_excluded_groups)) { 
234       testng_excluded_groups = "Not-bamboo"
235     }
236     install4jExtraScheme = "jalviewb"
237     break
238
239     case [ "RELEASE", "JALVIEWJS-RELEASE" ]:
240     getdownAppDistDir = getdown_app_dir_release
241     getdownSetAppBaseProperty = true
242     reportRsyncCommand = true
243     install4jSuffix = ""
244     install4jInstallerName = "${jalview_name} Installer"
245     getdownArchiveAppBase = getdown_archive_base
246     break
247
248     case "ARCHIVE":
249     getdownChannelName = CHANNEL.toLowerCase()+"/${JALVIEW_VERSION}"
250     getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
251     getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
252     if (!file("${ARCHIVEDIR}/${package_dir}").exists()) {
253       throw new GradleException("Must provide an ARCHIVEDIR value to produce an archive distribution")
254     } else {
255       package_dir = string("${ARCHIVEDIR}/${package_dir}")
256       buildProperties = string("${ARCHIVEDIR}/${classes_dir}/${build_properties_file}")
257       buildDist = false
258     }
259     reportRsyncCommand = true
260     install4jExtraScheme = "jalviewa"
261     break
262
263     case "ARCHIVELOCAL":
264     getdownChannelName = string("archive/${JALVIEW_VERSION}")
265     getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
266     getdownAppBase = file(getdownWebsiteDir).toURI().toString()
267     if (!file("${ARCHIVEDIR}/${package_dir}").exists()) {
268       throw new GradleException("Must provide an ARCHIVEDIR value to produce an archive distribution")
269     } else {
270       package_dir = string("${ARCHIVEDIR}/${package_dir}")
271       buildProperties = string("${ARCHIVEDIR}/${classes_dir}/${build_properties_file}")
272       buildDist = false
273     }
274     reportRsyncCommand = true
275     getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
276     install4jSuffix = "Archive"
277     install4jExtraScheme = "jalviewa"
278     break
279
280     case "DEVELOP":
281     reportRsyncCommand = true
282     getdownSetAppBaseProperty = true
283     // DEVELOP-RELEASE is usually associated with a Jalview release series so set the version
284     JALVIEW_VERSION=JALVIEW_VERSION+"-d${buildDate}"
285     
286     install4jSuffix = "Develop"
287     install4jExtraScheme = "jalviewd"
288     install4jInstallerName = "${jalview_name} Develop Installer"
289     break
290
291     case "TEST-RELEASE":
292     reportRsyncCommand = true
293     getdownSetAppBaseProperty = true
294     // Don't ignore transpile errors for release build
295     if (jalviewjs_ignore_transpile_errors.equals("true")) {
296       jalviewjs_ignore_transpile_errors = "false"
297       println("Setting jalviewjs_ignore_transpile_errors to 'false'")
298     }
299     JALVIEW_VERSION = JALVIEW_VERSION+"-test"
300     install4jSuffix = "Test"
301     install4jExtraScheme = "jalviewt"
302     install4jInstallerName = "${jalview_name} Test Installer"
303     break
304
305     case ~/^SCRATCH(|-[-\w]*)$/:
306     getdownChannelName = CHANNEL
307     JALVIEW_VERSION = JALVIEW_VERSION+"-"+CHANNEL
308     
309     getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
310     getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
311     reportRsyncCommand = true
312     install4jSuffix = "Scratch"
313     break
314
315     case "TEST-LOCAL":
316     if (!file("${LOCALDIR}").exists()) {
317       throw new GradleException("Must provide a LOCALDIR value to produce a local distribution")
318     } else {
319       getdownAppBase = file(file("${LOCALDIR}").getAbsolutePath()).toURI().toString()
320       getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
321     }
322     JALVIEW_VERSION = "TEST"
323     install4jSuffix = "Test-Local"
324     install4jExtraScheme = "jalviewt"
325     install4jInstallerName = "${jalview_name} Test Installer"
326     break
327
328     case [ "LOCAL", "JALVIEWJS" ]:
329     JALVIEW_VERSION = "TEST"
330     getdownAppBase = file(getdownWebsiteDir).toURI().toString()
331     getdownArchiveAppBase = file("${jalviewDir}/${getdown_archive_dir}").toURI().toString()
332     getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
333     install4jExtraScheme = "jalviewl"
334     install4jCheckSums = false
335     break
336
337     default: // something wrong specified
338     throw new GradleException("CHANNEL must be one of BUILD, RELEASE, ARCHIVE, DEVELOP, TEST-RELEASE, SCRATCH-..., LOCAL [default]")
339     break
340
341   }
342   JALVIEW_VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
343   // override getdownAppBase if requested
344   if (findProperty("getdown_appbase_override") != null) {
345     // revert to LOCAL if empty string
346     if (string(getdown_appbase_override) == "") {
347       getdownAppBase = file(getdownWebsiteDir).toURI().toString()
348       getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
349     } else if (string(getdown_appbase_override).startsWith("file://")) {
350       getdownAppBase = string(getdown_appbase_override)
351       getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
352     } else {
353       getdownAppBase = string(getdown_appbase_override)
354     }
355     println("Overriding getdown appbase with '${getdownAppBase}'")
356   }
357   // sanitise file name for jalview launcher file for this channel
358   jvlChannelName = jvlChannelName.replaceAll("[^\\w\\-]+", "_")
359   // install4j application and folder names
360   if (install4jSuffix == "") {
361     install4jApplicationName = "${jalview_name}"
362     install4jBundleId = "${install4j_bundle_id}"
363     install4jWinApplicationId = install4j_release_win_application_id
364   } else {
365     install4jApplicationName = "${jalview_name} ${install4jSuffix}"
366     install4jBundleId = "${install4j_bundle_id}-" + install4jSuffix.toLowerCase()
367     // add int hash of install4jSuffix to the last part of the application_id
368     def id = install4j_release_win_application_id
369     def idsplitreverse = id.split("-").reverse()
370     idsplitreverse[0] = idsplitreverse[0].toInteger() + install4jSuffix.hashCode()
371     install4jWinApplicationId = idsplitreverse.reverse().join("-")
372   }
373   // sanitise folder and id names
374   // install4jApplicationFolder = e.g. "Jalview Build"
375   install4jApplicationFolder = install4jApplicationName
376                                     .replaceAll("[\"'~:/\\\\\\s]", "_") // replace all awkward filename chars " ' ~ : / \
377                                     .replaceAll("_+", "_") // collapse __
378   install4jInternalId = install4jApplicationName
379                                     .replaceAll(" ","_")
380                                     .replaceAll("[^\\w\\-\\.]", "_") // replace other non [alphanumeric,_,-,.]
381                                     .replaceAll("_+", "") // collapse __
382                                     //.replaceAll("_*-_*", "-") // collapse _-_
383   install4jUnixApplicationFolder = install4jApplicationName
384                                     .replaceAll(" ","_")
385                                     .replaceAll("[^\\w\\-\\.]", "_") // replace other non [alphanumeric,_,-,.]
386                                     .replaceAll("_+", "_") // collapse __
387                                     .replaceAll("_*-_*", "-") // collapse _-_
388                                     .toLowerCase()
389
390   getdownWrapperLink = install4jUnixApplicationFolder // e.g. "jalview_local"
391   getdownAppDir = string("${getdownWebsiteDir}/${getdownAppDistDir}")
392   //getdownJ11libDir = "${getdownWebsiteDir}/${getdown_j11lib_dir}"
393   getdownResourceDir = string("${getdownWebsiteDir}/${getdown_resource_dir}")
394   getdownInstallDir = string("${getdownWebsiteDir}/${getdown_install_dir}")
395   getdownFilesDir = string("${jalviewDir}/${getdown_files_dir}/${JAVA_VERSION}/")
396   getdownFilesInstallDir = string("${getdownFilesDir}/${getdown_install_dir}")
397   /* compile without modules -- using classpath libraries
398   modules_compileClasspath = fileTree(dir: "${jalviewDir}/${j11modDir}", include: ["*.jar"])
399   modules_runtimeClasspath = modules_compileClasspath
400   */
401
402   gitHash = "SOURCE"
403   gitBranch = "Source"
404   try {
405     apply plugin: "com.palantir.git-version"
406     def details = versionDetails()
407     gitHash = details.gitHash
408     gitBranch = details.branchName
409   } catch(org.gradle.api.internal.plugins.PluginApplicationException e) {
410     println("Not in a git repository. Using git values from RELEASE properties file.")
411     gitHash = releaseProps.getProperty("git.hash")
412     gitBranch = releaseProps.getProperty("git.branch")
413   } catch(java.lang.RuntimeException e1) {
414     throw new GradleException("Error with git-version plugin.  Directory '.git' exists but versionDetails() cannot be found.")
415   }
416
417   println("Using a ${CHANNEL} profile.")
418
419   additional_compiler_args = []
420   // configure classpath/args for j8/j11 compilation
421   if (JAVA_VERSION.equals("1.8")) {
422     JAVA_INTEGER_VERSION = string("8")
423     //libDir = j8libDir
424     libDir = j11libDir
425     libDistDir = j8libDir
426     compile_source_compatibility = 1.8
427     compile_target_compatibility = 1.8
428     // these are getdown.txt properties defined dependent on the JAVA_VERSION
429     getdownAltJavaMinVersion = string(findProperty("getdown_alt_java8_min_version"))
430     getdownAltJavaMaxVersion = string(findProperty("getdown_alt_java8_max_version"))
431     // this property is assigned below and expanded to multiple lines in the getdown task
432     getdownAltMultiJavaLocation = string(findProperty("getdown_alt_java8_txt_multi_java_location"))
433     // this property is for the Java library used in eclipse
434     eclipseJavaRuntimeName = string("JavaSE-1.8")
435   } else if (JAVA_VERSION.equals("11")) {
436     JAVA_INTEGER_VERSION = string("11")
437     libDir = j11libDir
438     libDistDir = j11libDir
439     compile_source_compatibility = 11
440     compile_target_compatibility = 11
441     getdownAltJavaMinVersion = string(findProperty("getdown_alt_java11_min_version"))
442     getdownAltJavaMaxVersion = string(findProperty("getdown_alt_java11_max_version"))
443     getdownAltMultiJavaLocation = string(findProperty("getdown_alt_java11_txt_multi_java_location"))
444     eclipseJavaRuntimeName = string("JavaSE-11")
445     /* compile without modules -- using classpath libraries
446     additional_compiler_args += [
447     '--module-path', modules_compileClasspath.asPath,
448     '--add-modules', j11modules
449     ]
450      */
451   } else if (JAVA_VERSION.equals("17")) {
452     JAVA_INTEGER_VERSION = string("17")
453     libDir = j17libDir
454     libDistDir = j17libDir
455     compile_source_compatibility = 17
456     compile_target_compatibility = 17
457     getdownAltJavaMinVersion = string(findProperty("getdown_alt_java11_min_version"))
458     getdownAltJavaMaxVersion = string(findProperty("getdown_alt_java11_max_version"))
459     getdownAltMultiJavaLocation = string(findProperty("getdown_alt_java11_txt_multi_java_location"))
460     eclipseJavaRuntimeName = string("JavaSE-17")
461     /* compile without modules -- using classpath libraries
462     additional_compiler_args += [
463     '--module-path', modules_compileClasspath.asPath,
464     '--add-modules', j11modules
465     ]
466      */
467   } else {
468     throw new GradleException("JAVA_VERSION=${JAVA_VERSION} not currently supported by Jalview")
469   }
470
471
472   // for install4j
473   JAVA_MIN_VERSION = JAVA_VERSION
474   JAVA_MAX_VERSION = JAVA_VERSION
475   def jreInstallsDir = string(jre_installs_dir)
476   if (jreInstallsDir.startsWith("~/")) {
477     jreInstallsDir = System.getProperty("user.home") + jreInstallsDir.substring(1)
478   }
479   macosJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-mac-x64/jre")
480   windowsJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-windows-x64/jre")
481   linuxJavaVMDir = string("${jreInstallsDir}/jre-${JAVA_INTEGER_VERSION}-linux-x64/jre")
482   macosJavaVMTgz = string("${jreInstallsDir}/tgz/jre_${JAVA_INTEGER_VERSION}_mac_x64.tar.gz")
483   windowsJavaVMTgz = string("${jreInstallsDir}/tgz/jre_${JAVA_INTEGER_VERSION}_windows_x64.tar.gz")
484   linuxJavaVMTgz = string("${jreInstallsDir}/tgz/jre_${JAVA_INTEGER_VERSION}_linux_x64.tar.gz")
485   install4jDir = string("${jalviewDir}/${install4j_utils_dir}")
486   install4jConfFileName = string("jalview-install4j-conf.install4j")
487   install4jConfFile = file("${install4jDir}/${install4jConfFileName}")
488   install4jHomeDir = install4j_home_dir
489   if (install4jHomeDir.startsWith("~/")) {
490     install4jHomeDir = System.getProperty("user.home") + install4jHomeDir.substring(1)
491   }
492
493   resourceBuildDir = string("${buildDir}/resources")
494   resourcesBuildDir = string("${resourceBuildDir}/resources_build")
495   helpBuildDir = string("${resourceBuildDir}/help_build")
496   docBuildDir = string("${resourceBuildDir}/doc_build")
497
498   if (buildProperties == null) {
499     buildProperties = string("${resourcesBuildDir}/${build_properties_file}")
500   }
501   buildingHTML = string("${jalviewDir}/${doc_dir}/building.html")
502   helpParentDir = string("${jalviewDir}/${help_parent_dir}")
503   helpSourceDir = string("${helpParentDir}/${help_dir}")
504   helpFile = string("${helpBuildDir}/${help_dir}/help.jhm")
505
506
507   relativeBuildDir = file(jalviewDirAbsolutePath).toPath().relativize(buildDir.toPath())
508   jalviewjsBuildDir = string("${relativeBuildDir}/jalviewjs")
509   jalviewjsSiteDir = string("${jalviewjsBuildDir}/${jalviewjs_site_dir}")
510   if (IN_ECLIPSE) {
511     jalviewjsTransferSiteJsDir = string(jalviewjsSiteDir)
512   } else {
513     jalviewjsTransferSiteJsDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_js")
514   }
515   jalviewjsTransferSiteLibDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_lib")
516   jalviewjsTransferSiteSwingJsDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_swingjs")
517   jalviewjsTransferSiteCoreDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_core")
518   jalviewjsJalviewCoreHtmlFile = string("")
519   jalviewjsJalviewCoreName = string(jalviewjs_core_name)
520   jalviewjsCoreClasslists = []
521   jalviewjsJalviewTemplateName = string(jalviewjs_name)
522   jalviewjsJ2sSettingsFileName = string("${jalviewDir}/${jalviewjs_j2s_settings}")
523   jalviewjsJ2sAltSettingsFileName = string("${jalviewDir}/${jalviewjs_j2s_alt_settings}")
524   jalviewjsJ2sProps = null
525   jalviewjsJ2sPlugin = jalviewjs_j2s_plugin
526
527   eclipseWorkspace = null
528   eclipseBinary = string("")
529   eclipseVersion = string("")
530   eclipseDebug = false
531   // ENDEXT
532 }
533
534
535 sourceSets {
536   main {
537     java {
538       srcDirs sourceDir
539       outputDir = file(classesDir)
540     }
541
542     resources {
543       srcDirs = [ resourcesBuildDir, docBuildDir, helpBuildDir ]
544     }
545
546     compileClasspath = files(sourceSets.main.java.outputDir)
547     compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
548
549     runtimeClasspath = compileClasspath
550     runtimeClasspath += files(sourceSets.main.resources.srcDirs)
551   }
552
553   clover {
554     java {
555       srcDirs cloverInstrDir
556       outputDir = cloverClassesDir
557     }
558
559     resources {
560       srcDirs = sourceSets.main.resources.srcDirs
561     }
562
563     compileClasspath = files( sourceSets.clover.java.outputDir )
564     //compileClasspath += files( testClassesDir )
565     compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
566     compileClasspath += fileTree(dir: "${jalviewDir}/${clover_lib_dir}", include: ["*.jar"])
567     compileClasspath += fileTree(dir: "${jalviewDir}/${utils_dir}/testnglibs", include: ["**/*.jar"])
568
569     runtimeClasspath = compileClasspath
570   }
571
572   test {
573     java {
574       srcDirs testSourceDir
575       outputDir = file(testClassesDir)
576     }
577
578     resources {
579       srcDirs = useClover ? sourceSets.clover.resources.srcDirs : sourceSets.main.resources.srcDirs
580     }
581
582     compileClasspath = files( sourceSets.test.java.outputDir )
583     compileClasspath += useClover ? sourceSets.clover.compileClasspath : sourceSets.main.compileClasspath
584     compileClasspath += fileTree(dir: "${jalviewDir}/${utils_dir}/testnglibs", include: ["**/*.jar"])
585
586     runtimeClasspath = compileClasspath
587     runtimeClasspath += files(sourceSets.test.resources.srcDirs)
588   }
589
590 }
591
592
593 // eclipse project and settings files creation, also used by buildship
594 eclipse {
595   project {
596     name = eclipse_project_name
597
598     natures 'org.eclipse.jdt.core.javanature',
599     'org.eclipse.jdt.groovy.core.groovyNature',
600     'org.eclipse.buildship.core.gradleprojectnature'
601
602     buildCommand 'org.eclipse.jdt.core.javabuilder'
603     buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder'
604   }
605
606   classpath {
607     //defaultOutputDir = sourceSets.main.java.outputDir
608     configurations.each{ c->
609       if (c.isCanBeResolved()) {
610         minusConfigurations += [c]
611       }
612     }
613
614     plusConfigurations = [ ]
615     file {
616
617       whenMerged { cp ->
618         def removeTheseToo = []
619         HashMap<String, Boolean> alreadyAddedSrcPath = new HashMap<>();
620         cp.entries.each { entry ->
621           // This conditional removes all src classpathentries that a) have already been added or b) aren't "src" or "test".
622           // e.g. this removes the resources dir being copied into bin/main, bin/test AND bin/clover
623           // we add the resources and help/help dirs in as libs afterwards (see below)
624           if (entry.kind == 'src') {
625             if (alreadyAddedSrcPath.getAt(entry.path) || !(entry.path == bareSourceDir || entry.path == bareTestSourceDir)) {
626               removeTheseToo += entry
627             } else {
628               alreadyAddedSrcPath.putAt(entry.path, true)
629             }
630           }
631
632         }
633         cp.entries.removeAll(removeTheseToo)
634
635         //cp.entries += new Output("${eclipse_bin_dir}/main")
636         if (file(helpParentDir).isDirectory()) {
637           cp.entries += new Library(fileReference(helpParentDir))
638         }
639         if (file(resourceDir).isDirectory()) {
640           cp.entries += new Library(fileReference(resourceDir))
641         }
642
643         HashMap<String, Boolean> alreadyAddedLibPath = new HashMap<>();
644
645         sourceSets.main.compileClasspath.findAll { it.name.endsWith(".jar") }.any {
646           //don't want to add outputDir as eclipse is using its own output dir in bin/main
647           if (it.isDirectory() || ! it.exists()) {
648             // don't add dirs to classpath, especially if they don't exist
649             return false // groovy "continue" in .any closure
650           }
651           def itPath = it.toString()
652           if (itPath.startsWith("${jalviewDirAbsolutePath}/")) {
653             // make relative path
654             itPath = itPath.substring(jalviewDirAbsolutePath.length()+1)
655           }
656           if (alreadyAddedLibPath.get(itPath)) {
657             //println("Not adding duplicate entry "+itPath)
658           } else {
659             //println("Adding entry "+itPath)
660             cp.entries += new Library(fileReference(itPath))
661             alreadyAddedLibPath.put(itPath, true)
662           }
663         }
664
665         sourceSets.test.compileClasspath.findAll { it.name.endsWith(".jar") }.any {
666           //no longer want to add outputDir as eclipse is using its own output dir in bin/main
667           if (it.isDirectory() || ! it.exists()) {
668             // don't add dirs to classpath
669             return false // groovy "continue" in .any closure
670           }
671
672           def itPath = it.toString()
673           if (itPath.startsWith("${jalviewDirAbsolutePath}/")) {
674             itPath = itPath.substring(jalviewDirAbsolutePath.length()+1)
675           }
676           if (alreadyAddedLibPath.get(itPath)) {
677             // don't duplicate
678           } else {
679             def lib = new Library(fileReference(itPath))
680             lib.entryAttributes["test"] = "true"
681             cp.entries += lib
682             alreadyAddedLibPath.put(itPath, true)
683           }
684         }
685
686       } // whenMerged
687
688     } // file
689
690     containers 'org.eclipse.buildship.core.gradleclasspathcontainer'
691
692   } // classpath
693
694   jdt {
695     // for the IDE, use java 11 compatibility
696     sourceCompatibility = compile_source_compatibility
697     targetCompatibility = compile_target_compatibility
698     javaRuntimeName = eclipseJavaRuntimeName
699
700     // add in jalview project specific properties/preferences into eclipse core preferences
701     file {
702       withProperties { props ->
703         def jalview_prefs = new Properties()
704         def ins = new FileInputStream("${jalviewDirAbsolutePath}/${eclipse_extra_jdt_prefs_file}")
705         jalview_prefs.load(ins)
706         ins.close()
707         jalview_prefs.forEach { t, v ->
708           if (props.getAt(t) == null) {
709             props.putAt(t, v)
710           }
711         }
712         // codestyle file -- overrides previous formatter prefs
713         def csFile = file("${jalviewDirAbsolutePath}/${eclipse_codestyle_file}")
714         if (csFile.exists()) {
715           XmlParser parser = new XmlParser()
716           def profiles = parser.parse(csFile)
717           def profile = profiles.'profile'.find { p -> (p.'@kind' == "CodeFormatterProfile" && p.'@name' == "Jalview") }
718           if (profile != null) {
719             profile.'setting'.each { s ->
720               def id = s.'@id'
721               def value = s.'@value'
722               if (id != null && value != null) {
723                 props.putAt(id, value)
724               }
725             }
726           }
727         }
728       }
729     }
730
731   } // jdt
732
733   if (IN_ECLIPSE) {
734     // Don't want these to be activated if in headless build
735     synchronizationTasks "eclipseSynchronizationTask"
736     //autoBuildTasks "eclipseAutoBuildTask"
737
738   }
739 }
740
741
742 /* hack to change eclipse prefs in .settings files other than org.eclipse.jdt.core.prefs */
743 // Class to allow updating arbitrary properties files
744 class PropertiesFile extends PropertiesPersistableConfigurationObject {
745   public PropertiesFile(PropertiesTransformer t) { super(t); }
746   @Override protected void load(Properties properties) { }
747   @Override protected void store(Properties properties) { }
748   @Override protected String getDefaultResourceName() { return ""; }
749   // This is necessary, because PropertiesPersistableConfigurationObject fails
750   // if no default properties file exists.
751   @Override public void loadDefaults() { load(new StringBufferInputStream("")); }
752 }
753
754 // Task to update arbitrary properties files (set outputFile)
755 class PropertiesFileTask extends PropertiesGeneratorTask<PropertiesFile> {
756   private final PropertiesFileContentMerger file;
757   public PropertiesFileTask() { file = new PropertiesFileContentMerger(getTransformer()); }
758   protected PropertiesFile create() { return new PropertiesFile(getTransformer()); }
759   protected void configure(PropertiesFile props) {
760     file.getBeforeMerged().execute(props); file.getWhenMerged().execute(props);
761   }
762   public void file(Closure closure) { ConfigureUtil.configure(closure, file); }
763 }
764
765 task eclipseUIPreferences(type: PropertiesFileTask) {
766   description = "Generate Eclipse additional settings"
767   def filename = "org.eclipse.jdt.ui.prefs"
768   outputFile = "$projectDir/.settings/${filename}" as File
769   file {
770     withProperties {
771       it.load new FileInputStream("$projectDir/utils/eclipse/${filename}" as String)
772     }
773   }
774 }
775
776 task eclipseGroovyCorePreferences(type: PropertiesFileTask) {
777   description = "Generate Eclipse additional settings"
778   def filename = "org.eclipse.jdt.groovy.core.prefs"
779   outputFile = "$projectDir/.settings/${filename}" as File
780   file {
781     withProperties {
782       it.load new FileInputStream("$projectDir/utils/eclipse/${filename}" as String)
783     }
784   }
785 }
786
787 task eclipseAllPreferences {
788   dependsOn eclipseJdt
789   dependsOn eclipseUIPreferences
790   dependsOn eclipseGroovyCorePreferences
791 }
792
793 eclipseUIPreferences.mustRunAfter eclipseJdt
794 eclipseGroovyCorePreferences.mustRunAfter eclipseJdt
795
796 /* end of eclipse preferences hack */
797
798
799 // clover bits
800
801
802 task cleanClover {
803   doFirst {
804     delete cloverBuildDir
805     delete cloverReportDir
806   }
807 }
808
809
810 task cloverInstrJava(type: JavaExec) {
811   group = "Verification"
812   description = "Create clover instrumented source java files"
813
814   dependsOn cleanClover
815
816   inputs.files(sourceSets.main.allJava)
817   outputs.dir(cloverInstrDir)
818
819   //classpath = fileTree(dir: "${jalviewDir}/${clover_lib_dir}", include: ["*.jar"])
820   classpath = sourceSets.clover.compileClasspath
821   main = "com.atlassian.clover.CloverInstr"
822
823   def argsList = [
824     "--encoding",
825     "UTF-8",
826     "--initstring",
827     cloverDb,
828     "--destdir",
829     cloverInstrDir.getPath(),
830   ]
831   def srcFiles = sourceSets.main.allJava.files
832   argsList.addAll(
833     srcFiles.collect(
834       { file -> file.absolutePath }
835     )
836   )
837   args argsList.toArray()
838
839   doFirst {
840     delete cloverInstrDir
841     println("Clover: About to instrument "+srcFiles.size() +" files")
842   }
843 }
844
845
846 task cloverInstrTests(type: JavaExec) {
847   group = "Verification"
848   description = "Create clover instrumented source test files"
849
850   dependsOn cleanClover
851
852   inputs.files(testDir)
853   outputs.dir(cloverTestInstrDir)
854
855   classpath = sourceSets.clover.compileClasspath
856   main = "com.atlassian.clover.CloverInstr"
857
858   def argsList = [
859     "--encoding",
860     "UTF-8",
861     "--initstring",
862     cloverDb,
863     "--srcdir",
864     testDir,
865     "--destdir",
866     cloverTestInstrDir.getPath(),
867   ]
868   args argsList.toArray()
869
870   doFirst {
871     delete cloverTestInstrDir
872     println("Clover: About to instrument test files")
873   }
874 }
875
876
877 task cloverInstr {
878   group = "Verification"
879   description = "Create clover instrumented all source files"
880
881   dependsOn cloverInstrJava
882   dependsOn cloverInstrTests
883 }
884
885
886 cloverClasses.dependsOn cloverInstr
887
888
889 task cloverConsoleReport(type: JavaExec) {
890   group = "Verification"
891   description = "Creates clover console report"
892
893   onlyIf {
894     file(cloverDb).exists()
895   }
896
897   inputs.dir cloverClassesDir
898
899   classpath = sourceSets.clover.runtimeClasspath
900   main = "com.atlassian.clover.reporters.console.ConsoleReporter"
901
902   if (cloverreport_mem.length() > 0) {
903     maxHeapSize = cloverreport_mem
904   }
905   if (cloverreport_jvmargs.length() > 0) {
906     jvmArgs Arrays.asList(cloverreport_jvmargs.split(" "))
907   }
908
909   def argsList = [
910     "--alwaysreport",
911     "--initstring",
912     cloverDb,
913     "--unittests"
914   ]
915
916   args argsList.toArray()
917 }
918
919
920 task cloverHtmlReport(type: JavaExec) {
921   group = "Verification"
922   description = "Creates clover HTML report"
923
924   onlyIf {
925     file(cloverDb).exists()
926   }
927
928   def cloverHtmlDir = cloverReportDir
929   inputs.dir cloverClassesDir
930   outputs.dir cloverHtmlDir
931
932   classpath = sourceSets.clover.runtimeClasspath
933   main = "com.atlassian.clover.reporters.html.HtmlReporter"
934
935   if (cloverreport_mem.length() > 0) {
936     maxHeapSize = cloverreport_mem
937   }
938   if (cloverreport_jvmargs.length() > 0) {
939     jvmArgs Arrays.asList(cloverreport_jvmargs.split(" "))
940   }
941
942   def argsList = [
943     "--alwaysreport",
944     "--initstring",
945     cloverDb,
946     "--outputdir",
947     cloverHtmlDir
948   ]
949
950   if (cloverreport_html_options.length() > 0) {
951     argsList += cloverreport_html_options.split(" ")
952   }
953
954   args argsList.toArray()
955 }
956
957
958 task cloverXmlReport(type: JavaExec) {
959   group = "Verification"
960   description = "Creates clover XML report"
961
962   onlyIf {
963     file(cloverDb).exists()
964   }
965
966   def cloverXmlFile = "${cloverReportDir}/clover.xml"
967   inputs.dir cloverClassesDir
968   outputs.file cloverXmlFile
969
970   classpath = sourceSets.clover.runtimeClasspath
971   main = "com.atlassian.clover.reporters.xml.XMLReporter"
972
973   if (cloverreport_mem.length() > 0) {
974     maxHeapSize = cloverreport_mem
975   }
976   if (cloverreport_jvmargs.length() > 0) {
977     jvmArgs Arrays.asList(cloverreport_jvmargs.split(" "))
978   }
979
980   def argsList = [
981     "--alwaysreport",
982     "--initstring",
983     cloverDb,
984     "--outfile",
985     cloverXmlFile
986   ]
987
988   if (cloverreport_xml_options.length() > 0) {
989     argsList += cloverreport_xml_options.split(" ")
990   }
991
992   args argsList.toArray()
993 }
994
995
996 task cloverReport {
997   group = "Verification"
998   description = "Creates clover reports"
999
1000   dependsOn cloverXmlReport
1001   dependsOn cloverHtmlReport
1002 }
1003
1004
1005 compileCloverJava {
1006
1007   doFirst {
1008     sourceCompatibility = compile_source_compatibility
1009     targetCompatibility = compile_target_compatibility
1010     options.compilerArgs += additional_compiler_args
1011     print ("Setting target compatibility to "+targetCompatibility+"\n")
1012   }
1013   //classpath += configurations.cloverRuntime
1014 }
1015 // end clover bits
1016
1017
1018 compileJava {
1019   // JBP->BS should the print statement in doFirst refer to compile_target_compatibility ?
1020   sourceCompatibility = compile_source_compatibility
1021   targetCompatibility = compile_target_compatibility
1022   options.compilerArgs = additional_compiler_args
1023   options.encoding = "UTF-8"
1024   doFirst {
1025     print ("Setting target compatibility to "+compile_target_compatibility+"\n")
1026   }
1027
1028 }
1029
1030
1031 compileTestJava {
1032   sourceCompatibility = compile_source_compatibility
1033   targetCompatibility = compile_target_compatibility
1034   options.compilerArgs = additional_compiler_args
1035   doFirst {
1036     print ("Setting target compatibility to "+targetCompatibility+"\n")
1037   }
1038 }
1039
1040
1041 clean {
1042   doFirst {
1043     delete sourceSets.main.java.outputDir
1044   }
1045 }
1046
1047
1048 cleanTest {
1049   dependsOn cleanClover
1050   doFirst {
1051     delete sourceSets.test.java.outputDir
1052   }
1053 }
1054
1055
1056 // format is a string like date.format("dd MMMM yyyy")
1057 def getDate(format) {
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 = ["channel":getdownChannelName,"date":getDate("yyyy-MM-dd HH:mm:ss")]
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
2173 /*
2174 ALSO COMPILE AND ADD IN TO THE JSON FILE INFO FOR EXECUTABLE JAR, VERSION JVL FILE, SOURCE TAR FILE
2175 */
2176
2177
2178
2179
2180
2181
2182 task makeInstallersJsonFile {
2183   dependsOn installerFiles
2184
2185   def installersOutputTxt = file("${jalviewDir}/${install4jBuildDir}/output.txt")
2186   def installersSha256 = file("${jalviewDir}/${install4jBuildDir}/sha256sums")
2187   def VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
2188   def installersJsonFile = file("${jalviewDir}/${install4jBuildDir}/installers-${VERSION_UNDERSCORES}.json")
2189
2190   inputs.file(installersOutputTxt)
2191   if (install4jCheckSums) {
2192     inputs.file(installersSha256)
2193   }
2194   outputs.file(installersJsonFile)
2195
2196   doFirst {
2197     writeInstallersJsonFile(installersOutputTxt, installersSha256, installersJsonFile)
2198   }
2199 }
2200
2201 task staticMakeInstallersJsonFile {
2202   doFirst {
2203     def output = findProperty("i4j_output")
2204     def sha256 = findProperty("i4j_sha256")
2205     def json = findProperty("i4j_json")
2206     if (output == null || sha256 == null || json == null) {
2207       throw new GradleException("Must provide paths to all of output.txt, sha256sums, and output.json with '-Pi4j_output=... -Pi4j_sha256=... -Pi4j_json=...")
2208     }
2209     writeInstallersJsonFile(file(output), file(sha256), file(json))
2210   }
2211 }
2212
2213 task installers {
2214   dependsOn installerFiles
2215   dependsOn makeInstallersJsonFile
2216 }
2217
2218
2219 spotless {
2220   java {
2221     eclipse().configFile(eclipse_codestyle_file)
2222   }
2223 }
2224
2225 task createSourceReleaseProperties(type: WriteProperties) {
2226   group = "distribution"
2227   description = "Create the source RELEASE properties file"
2228   
2229   def sourceTarBuildDir = "${buildDir}/sourceTar"
2230   def sourceReleasePropertiesFile = "${sourceTarBuildDir}/RELEASE"
2231   outputFile (sourceReleasePropertiesFile)
2232
2233   doFirst {
2234     releaseProps.each{ key, val -> property key, val }
2235     property "git.branch", gitBranch
2236     property "git.hash", gitHash
2237   }
2238
2239   outputs.file(outputFile)
2240 }
2241
2242 task sourceDist(type: Tar) {
2243   group "distribution"
2244   description "Create a source .tar.gz file for distribution"
2245
2246   dependsOn createBuildProperties
2247   dependsOn convertMdFiles
2248   dependsOn eclipseAllPreferences
2249   dependsOn createSourceReleaseProperties
2250
2251
2252   def outputFileName = "${project.name}_${JALVIEW_VERSION_UNDERSCORES}.tar.gz"
2253   archiveFileName = outputFileName
2254   
2255   compression Compression.GZIP
2256   
2257   into project.name
2258
2259   def EXCLUDE_FILES=[
2260     "build/*",
2261     "bin/*",
2262     "test-output/",
2263     "test-reports",
2264     "tests",
2265     "clover*/*",
2266     ".*",
2267     "benchmarking/*",
2268     "**/.*",
2269     "*.class",
2270     "**/*.class","$j11modDir/**/*.jar","appletlib","**/*locales",
2271     "*locales/**",
2272     "utils/InstallAnywhere",
2273     "**/*.log",
2274     "RELEASE",
2275   ] 
2276   def PROCESS_FILES=[
2277     "AUTHORS",
2278     "CITATION",
2279     "FEATURETODO",
2280     "JAVA-11-README",
2281     "FEATURETODO",
2282     "LICENSE",
2283     "**/README",
2284     "THIRDPARTYLIBS",
2285     "TESTNG",
2286     "build.gradle",
2287     "gradle.properties",
2288     "**/*.java",
2289     "**/*.html",
2290     "**/*.xml",
2291     "**/*.gradle",
2292     "**/*.groovy",
2293     "**/*.properties",
2294     "**/*.perl",
2295     "**/*.sh",
2296   ]
2297   def INCLUDE_FILES=[
2298     ".classpath",
2299     ".settings/org.eclipse.buildship.core.prefs",
2300     ".settings/org.eclipse.jdt.core.prefs"
2301   ]
2302
2303   from(jalviewDir) {
2304     exclude (EXCLUDE_FILES)
2305     include (PROCESS_FILES)
2306     filter(ReplaceTokens,
2307       beginToken: '$$',
2308       endToken: '$$',
2309       tokens: [
2310         'Version-Rel': JALVIEW_VERSION,
2311         'Year-Rel': getDate("yyyy")
2312       ]
2313     )
2314   }
2315   from(jalviewDir) {
2316     exclude (EXCLUDE_FILES)
2317     exclude (PROCESS_FILES)
2318     exclude ("appletlib")
2319     exclude ("**/*locales")
2320     exclude ("*locales/**")
2321     exclude ("utils/InstallAnywhere")
2322
2323     exclude (getdown_files_dir)
2324     exclude (getdown_website_dir)
2325
2326     // exluding these as not using jars as modules yet
2327     exclude ("${j11modDir}/**/*.jar")
2328   }
2329   from(jalviewDir) {
2330     include(INCLUDE_FILES)
2331   }
2332 //  from (jalviewDir) {
2333 //    // explicit includes for stuff that seemed to not get included
2334 //    include(fileTree("test/**/*."))
2335 //    exclude(EXCLUDE_FILES)
2336 //    exclude(PROCESS_FILES)
2337 //  }
2338
2339   from(file(buildProperties).getParent()) {
2340     include(file(buildProperties).getName())
2341     rename(file(buildProperties).getName(), "build_properties")
2342     filter({ line ->
2343       line.replaceAll("^INSTALLATION=.*\$","INSTALLATION=Source Release"+" git-commit\\\\:"+gitHash+" ["+gitBranch+"]")
2344     })
2345   }
2346
2347   def sourceTarBuildDir = "${buildDir}/sourceTar"
2348   from(sourceTarBuildDir) {
2349     // this includes the appended RELEASE properties file
2350   }
2351 }
2352
2353
2354 task helppages {
2355   dependsOn copyHelp
2356   dependsOn pubhtmlhelp
2357   
2358   inputs.dir("${helpBuildDir}/${help_dir}")
2359   outputs.dir("${buildDir}/distributions/${help_dir}")
2360 }
2361
2362
2363 task j2sSetHeadlessBuild {
2364   doFirst {
2365     IN_ECLIPSE = false
2366   }
2367 }
2368
2369
2370 task jalviewjsEnableAltFileProperty(type: WriteProperties) {
2371   group "jalviewjs"
2372   description "Enable the alternative J2S Config file for headless build"
2373
2374   outputFile = jalviewjsJ2sSettingsFileName
2375   def j2sPropsFile = file(jalviewjsJ2sSettingsFileName)
2376   def j2sProps = new Properties()
2377   if (j2sPropsFile.exists()) {
2378     try {
2379       def j2sPropsFileFIS = new FileInputStream(j2sPropsFile)
2380       j2sProps.load(j2sPropsFileFIS)
2381       j2sPropsFileFIS.close()
2382
2383       j2sProps.each { prop, val ->
2384         property(prop, val)
2385       }
2386     } catch (Exception e) {
2387       println("Exception reading ${jalviewjsJ2sSettingsFileName}")
2388       e.printStackTrace()
2389     }
2390   }
2391   if (! j2sProps.stringPropertyNames().contains(jalviewjs_j2s_alt_file_property_config)) {
2392     property(jalviewjs_j2s_alt_file_property_config, jalviewjs_j2s_alt_file_property)
2393   }
2394 }
2395
2396
2397 task jalviewjsSetEclipseWorkspace {
2398   def propKey = "jalviewjs_eclipse_workspace"
2399   def propVal = null
2400   if (project.hasProperty(propKey)) {
2401     propVal = project.getProperty(propKey)
2402     if (propVal.startsWith("~/")) {
2403       propVal = System.getProperty("user.home") + propVal.substring(1)
2404     }
2405   }
2406   def propsFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_workspace_location_file}"
2407   def propsFile = file(propsFileName)
2408   def eclipseWsDir = propVal
2409   def props = new Properties()
2410
2411   def writeProps = true
2412   if (( eclipseWsDir == null || !file(eclipseWsDir).exists() ) && propsFile.exists()) {
2413     def ins = new FileInputStream(propsFileName)
2414     props.load(ins)
2415     ins.close()
2416     if (props.getProperty(propKey, null) != null) {
2417       eclipseWsDir = props.getProperty(propKey)
2418       writeProps = false
2419     }
2420   }
2421
2422   if (eclipseWsDir == null || !file(eclipseWsDir).exists()) {
2423     def tempDir = File.createTempDir()
2424     eclipseWsDir = tempDir.getAbsolutePath()
2425     writeProps = true
2426   }
2427   eclipseWorkspace = file(eclipseWsDir)
2428
2429   doFirst {
2430     // do not run a headless transpile when we claim to be in Eclipse
2431     if (IN_ECLIPSE) {
2432       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2433       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2434     } else {
2435       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2436     }
2437
2438     if (writeProps) {
2439       props.setProperty(propKey, eclipseWsDir)
2440       propsFile.parentFile.mkdirs()
2441       def bytes = new ByteArrayOutputStream()
2442       props.store(bytes, null)
2443       def propertiesString = bytes.toString()
2444       propsFile.text = propertiesString
2445       print("NEW ")
2446     } else {
2447       print("EXISTING ")
2448     }
2449
2450     println("ECLIPSE WORKSPACE: "+eclipseWorkspace.getPath())
2451   }
2452
2453   //inputs.property(propKey, eclipseWsDir) // eclipseWsDir only gets set once this task runs, so will be out-of-date
2454   outputs.file(propsFileName)
2455   outputs.upToDateWhen { eclipseWorkspace.exists() && propsFile.exists() }
2456 }
2457
2458
2459 task jalviewjsEclipsePaths {
2460   def eclipseProduct
2461
2462   def eclipseRoot = jalviewjs_eclipse_root
2463   if (eclipseRoot.startsWith("~/")) {
2464     eclipseRoot = System.getProperty("user.home") + eclipseRoot.substring(1)
2465   }
2466   if (OperatingSystem.current().isMacOsX()) {
2467     eclipseRoot += "/Eclipse.app"
2468     eclipseBinary = "${eclipseRoot}/Contents/MacOS/eclipse"
2469     eclipseProduct = "${eclipseRoot}/Contents/Eclipse/.eclipseproduct"
2470   } else if (OperatingSystem.current().isWindows()) { // check these paths!!
2471     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
2472       eclipseRoot += "/eclipse"
2473     }
2474     eclipseBinary = "${eclipseRoot}/eclipse.exe"
2475     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
2476   } else { // linux or unix
2477     if (file("${eclipseRoot}/eclipse").isDirectory() && file("${eclipseRoot}/eclipse/.eclipseproduct").exists()) {
2478       eclipseRoot += "/eclipse"
2479 println("eclipseDir exists")
2480     }
2481     eclipseBinary = "${eclipseRoot}/eclipse"
2482     eclipseProduct = "${eclipseRoot}/.eclipseproduct"
2483   }
2484
2485   eclipseVersion = "4.13" // default
2486   def assumedVersion = true
2487   if (file(eclipseProduct).exists()) {
2488     def fis = new FileInputStream(eclipseProduct)
2489     def props = new Properties()
2490     props.load(fis)
2491     eclipseVersion = props.getProperty("version")
2492     fis.close()
2493     assumedVersion = false
2494   }
2495   
2496   def propKey = "eclipse_debug"
2497   eclipseDebug = (project.hasProperty(propKey) && project.getProperty(propKey).equals("true"))
2498
2499   doFirst {
2500     // do not run a headless transpile when we claim to be in Eclipse
2501     if (IN_ECLIPSE) {
2502       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2503       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2504     } else {
2505       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2506     }
2507
2508     if (!assumedVersion) {
2509       println("ECLIPSE VERSION=${eclipseVersion}")
2510     }
2511   }
2512 }
2513
2514
2515 task printProperties {
2516   group "Debug"
2517   description "Output to console all System.properties"
2518   doFirst {
2519     System.properties.each { key, val -> System.out.println("Property: ${key}=${val}") }
2520   }
2521 }
2522
2523
2524 task eclipseSetup {
2525   dependsOn eclipseProject
2526   dependsOn eclipseClasspath
2527   dependsOn eclipseJdt
2528 }
2529
2530
2531 // this version (type: Copy) will delete anything in the eclipse dropins folder that isn't in fromDropinsDir
2532 task jalviewjsEclipseCopyDropins(type: Copy) {
2533   dependsOn jalviewjsEclipsePaths
2534
2535   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_eclipse_dropins_dir}", include: "*.jar")
2536   inputFiles += file("${jalviewDir}/${jalviewjsJ2sPlugin}")
2537   def outputDir = "${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}"
2538
2539   from inputFiles
2540   into outputDir
2541 }
2542
2543
2544 // this eclipse -clean doesn't actually work
2545 task jalviewjsCleanEclipse(type: Exec) {
2546   dependsOn eclipseSetup
2547   dependsOn jalviewjsEclipsePaths
2548   dependsOn jalviewjsEclipseCopyDropins
2549
2550   executable(eclipseBinary)
2551   args(["-nosplash", "--launcher.suppressErrors", "-data", eclipseWorkspace.getPath(), "-clean", "-console", "-consoleLog"])
2552   if (eclipseDebug) {
2553     args += "-debug"
2554   }
2555   args += "-l"
2556
2557   def inputString = """exit
2558 y
2559 """
2560   def inputByteStream = new ByteArrayInputStream(inputString.getBytes())
2561   standardInput = inputByteStream
2562 }
2563
2564 /* not really working yet
2565 jalviewjsEclipseCopyDropins.finalizedBy jalviewjsCleanEclipse
2566 */
2567
2568
2569 task jalviewjsTransferUnzipSwingJs {
2570   def file_zip = "${jalviewDir}/${jalviewjs_swingjs_zip}"
2571
2572   doLast {
2573     copy {
2574       from zipTree(file_zip)
2575       into "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
2576     }
2577   }
2578
2579   inputs.file file_zip
2580   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
2581 }
2582
2583
2584 task jalviewjsTransferUnzipLib {
2585   def zipFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_libjs_dir}", include: "*.zip")
2586
2587   doLast {
2588     zipFiles.each { file_zip -> 
2589       copy {
2590         from zipTree(file_zip)
2591         into "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
2592       }
2593     }
2594   }
2595
2596   inputs.files zipFiles
2597   outputs.dir "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
2598 }
2599
2600
2601 task jalviewjsTransferUnzipAllLibs {
2602   dependsOn jalviewjsTransferUnzipSwingJs
2603   dependsOn jalviewjsTransferUnzipLib
2604 }
2605
2606
2607 task jalviewjsCreateJ2sSettings(type: WriteProperties) {
2608   group "JalviewJS"
2609   description "Create the alternative j2s file from the j2s.* properties"
2610
2611   jalviewjsJ2sProps = project.properties.findAll { it.key.startsWith("j2s.") }.sort { it.key }
2612   def siteDirProperty = "j2s.site.directory"
2613   def setSiteDir = false
2614   jalviewjsJ2sProps.each { prop, val ->
2615     if (val != null) {
2616       if (prop == siteDirProperty) {
2617         if (!(val.startsWith('/') || val.startsWith("file://") )) {
2618           val = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${val}"
2619         }
2620         setSiteDir = true
2621       }
2622       property(prop,val)
2623     }
2624     if (!setSiteDir) { // default site location, don't override specifically set property
2625       property(siteDirProperty,"${jalviewDirRelativePath}/${jalviewjsTransferSiteJsDir}")
2626     }
2627   }
2628   outputFile = jalviewjsJ2sAltSettingsFileName
2629
2630   if (! IN_ECLIPSE) {
2631     inputs.properties(jalviewjsJ2sProps)
2632     outputs.file(jalviewjsJ2sAltSettingsFileName)
2633   }
2634 }
2635
2636
2637 task jalviewjsEclipseSetup {
2638   dependsOn jalviewjsEclipseCopyDropins
2639   dependsOn jalviewjsSetEclipseWorkspace
2640   dependsOn jalviewjsCreateJ2sSettings
2641 }
2642
2643
2644 task jalviewjsSyncAllLibs (type: Sync) {
2645   dependsOn jalviewjsTransferUnzipAllLibs
2646   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteLibDir}")
2647   inputFiles += fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}")
2648   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
2649
2650   from inputFiles
2651   into outputDir
2652   def outputFiles = []
2653   rename { filename ->
2654     outputFiles += "${outputDir}/${filename}"
2655     null
2656   }
2657   preserve {
2658     include "**"
2659   }
2660
2661   // should this be exclude really ?
2662   duplicatesStrategy "INCLUDE"
2663
2664   outputs.files outputFiles
2665   inputs.files inputFiles
2666 }
2667
2668
2669 task jalviewjsSyncResources (type: Sync) {
2670   dependsOn buildResources
2671
2672   def inputFiles = fileTree(dir: resourcesBuildDir)
2673   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
2674
2675   from inputFiles
2676   into outputDir
2677   def outputFiles = []
2678   rename { filename ->
2679     outputFiles += "${outputDir}/${filename}"
2680     null
2681   }
2682   preserve {
2683     include "**"
2684   }
2685   outputs.files outputFiles
2686   inputs.files inputFiles
2687 }
2688
2689
2690 task jalviewjsSyncSiteResources (type: Sync) {
2691   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjs_site_resource_dir}")
2692   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
2693
2694   from inputFiles
2695   into outputDir
2696   def outputFiles = []
2697   rename { filename ->
2698     outputFiles += "${outputDir}/${filename}"
2699     null
2700   }
2701   preserve {
2702     include "**"
2703   }
2704   outputs.files outputFiles
2705   inputs.files inputFiles
2706 }
2707
2708
2709 task jalviewjsSyncBuildProperties (type: Sync) {
2710   dependsOn createBuildProperties
2711   def inputFiles = [file(buildProperties)]
2712   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
2713
2714   from inputFiles
2715   into outputDir
2716   def outputFiles = []
2717   rename { filename ->
2718     outputFiles += "${outputDir}/${filename}"
2719     null
2720   }
2721   preserve {
2722     include "**"
2723   }
2724   outputs.files outputFiles
2725   inputs.files inputFiles
2726 }
2727
2728
2729 task jalviewjsProjectImport(type: Exec) {
2730   dependsOn eclipseSetup
2731   dependsOn jalviewjsEclipsePaths
2732   dependsOn jalviewjsEclipseSetup
2733
2734   doFirst {
2735     // do not run a headless import when we claim to be in Eclipse
2736     if (IN_ECLIPSE) {
2737       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2738       throw new StopExecutionException("Not running headless import whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2739     } else {
2740       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2741     }
2742   }
2743
2744   //def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview/org.eclipse.jdt.core"
2745   def projdir = eclipseWorkspace.getPath()+"/.metadata/.plugins/org.eclipse.core.resources/.projects/jalview"
2746   executable(eclipseBinary)
2747   args(["-nosplash", "--launcher.suppressErrors", "-application", "com.seeq.eclipse.importprojects.headlessimport", "-data", eclipseWorkspace.getPath(), "-import", jalviewDirAbsolutePath])
2748   if (eclipseDebug) {
2749     args += "-debug"
2750   }
2751   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
2752   if (!IN_ECLIPSE) {
2753     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
2754     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
2755   }
2756
2757   inputs.file("${jalviewDir}/.project")
2758   outputs.upToDateWhen { 
2759     file(projdir).exists()
2760   }
2761 }
2762
2763
2764 task jalviewjsTranspile(type: Exec) {
2765   dependsOn jalviewjsEclipseSetup 
2766   dependsOn jalviewjsProjectImport
2767   dependsOn jalviewjsEclipsePaths
2768   if (!IN_ECLIPSE) {
2769     dependsOn jalviewjsEnableAltFileProperty
2770   }
2771
2772   doFirst {
2773     // do not run a headless transpile when we claim to be in Eclipse
2774     if (IN_ECLIPSE) {
2775       println("Skipping task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2776       throw new StopExecutionException("Not running headless transpile whilst IN_ECLIPSE is '${IN_ECLIPSE}'")
2777     } else {
2778       println("Running task ${name} as IN_ECLIPSE=${IN_ECLIPSE}")
2779     }
2780   }
2781
2782   executable(eclipseBinary)
2783   args(["-nosplash", "--launcher.suppressErrors", "-application", "org.eclipse.jdt.apt.core.aptBuild", "-data", eclipseWorkspace, "-${jalviewjs_eclipse_build_arg}", eclipse_project_name ])
2784   if (eclipseDebug) {
2785     args += "-debug"
2786   }
2787   args += [ "--launcher.appendVmargs", "-vmargs", "-Dorg.eclipse.equinox.p2.reconciler.dropins.directory=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_eclipse_tmp_dropins_dir}" ]
2788   if (!IN_ECLIPSE) {
2789     args += [ "-D${j2sHeadlessBuildProperty}=true" ]
2790     args += [ "-D${jalviewjs_j2s_alt_file_property}=${jalviewjsJ2sAltSettingsFileName}" ]
2791   }
2792
2793   def stdout
2794   def stderr
2795   doFirst {
2796     stdout = new ByteArrayOutputStream()
2797     stderr = new ByteArrayOutputStream()
2798
2799     def logOutFileName = "${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}"
2800     def logOutFile = file(logOutFileName)
2801     logOutFile.createNewFile()
2802     logOutFile.text = """ROOT: ${jalviewjs_eclipse_root}
2803 BINARY: ${eclipseBinary}
2804 VERSION: ${eclipseVersion}
2805 WORKSPACE: ${eclipseWorkspace}
2806 DEBUG: ${eclipseDebug}
2807 ----
2808 """
2809     def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
2810     // combine stdout and stderr
2811     def logErrFOS = logOutFOS
2812
2813     if (jalviewjs_j2s_to_console.equals("true")) {
2814       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2815         new org.apache.tools.ant.util.TeeOutputStream(
2816           logOutFOS,
2817           stdout),
2818         System.out)
2819       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2820         new org.apache.tools.ant.util.TeeOutputStream(
2821           logErrFOS,
2822           stderr),
2823         System.err)
2824     } else {
2825       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2826         logOutFOS,
2827         stdout)
2828       errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2829         logErrFOS,
2830         stderr)
2831     }
2832   }
2833
2834   doLast {
2835     if (stdout.toString().contains("Error processing ")) {
2836       // j2s did not complete transpile
2837       //throw new TaskExecutionException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
2838       if (jalviewjs_ignore_transpile_errors.equals("true")) {
2839         println("IGNORING TRANSPILE ERRORS")
2840         println("See eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
2841       } else {
2842         throw new GradleException("Error during transpilation:\n${stderr}\nSee eclipse transpile log file '${jalviewDir}/${jalviewjsBuildDir}/${jalviewjs_j2s_transpile_stdout}'")
2843       }
2844     }
2845   }
2846
2847   inputs.dir("${jalviewDir}/${sourceDir}")
2848   outputs.dir("${jalviewDir}/${jalviewjsTransferSiteJsDir}")
2849   outputs.upToDateWhen( { file("${jalviewDir}/${jalviewjsTransferSiteJsDir}${jalviewjs_server_resource}").exists() } )
2850 }
2851
2852
2853 def jalviewjsCallCore(String name, FileCollection list, String prefixFile, String suffixFile, String jsfile, String zjsfile, File logOutFile, Boolean logOutConsole) {
2854
2855   def stdout = new ByteArrayOutputStream()
2856   def stderr = new ByteArrayOutputStream()
2857
2858   def coreFile = file(jsfile)
2859   def msg = ""
2860   msg = "Creating core for ${name}...\nGenerating ${jsfile}"
2861   println(msg)
2862   logOutFile.createNewFile()
2863   logOutFile.append(msg+"\n")
2864
2865   def coreTop = file(prefixFile)
2866   def coreBottom = file(suffixFile)
2867   coreFile.getParentFile().mkdirs()
2868   coreFile.createNewFile()
2869   coreFile.write( coreTop.getText("UTF-8") )
2870   list.each {
2871     f ->
2872     if (f.exists()) {
2873       def t = f.getText("UTF-8")
2874       t.replaceAll("Clazz\\.([^_])","Clazz_${1}")
2875       coreFile.append( t )
2876     } else {
2877       msg = "...file '"+f.getPath()+"' does not exist, skipping"
2878       println(msg)
2879       logOutFile.append(msg+"\n")
2880     }
2881   }
2882   coreFile.append( coreBottom.getText("UTF-8") )
2883
2884   msg = "Generating ${zjsfile}"
2885   println(msg)
2886   logOutFile.append(msg+"\n")
2887   def logOutFOS = new FileOutputStream(logOutFile, true) // true == append
2888   def logErrFOS = logOutFOS
2889
2890   javaexec {
2891     classpath = files(["${jalviewDir}/${jalviewjs_closure_compiler}"])
2892     main = "com.google.javascript.jscomp.CommandLineRunner"
2893     jvmArgs = [ "-Dfile.encoding=UTF-8" ]
2894     args = [ "--compilation_level", "SIMPLE_OPTIMIZATIONS", "--warning_level", "QUIET", "--charset", "UTF-8", "--js", jsfile, "--js_output_file", zjsfile ]
2895     maxHeapSize = "2g"
2896
2897     msg = "\nRunning '"+commandLine.join(' ')+"'\n"
2898     println(msg)
2899     logOutFile.append(msg+"\n")
2900
2901     if (logOutConsole) {
2902       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2903         new org.apache.tools.ant.util.TeeOutputStream(
2904           logOutFOS,
2905           stdout),
2906         standardOutput)
2907         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2908           new org.apache.tools.ant.util.TeeOutputStream(
2909             logErrFOS,
2910             stderr),
2911           System.err)
2912     } else {
2913       standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
2914         logOutFOS,
2915         stdout)
2916         errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
2917           logErrFOS,
2918           stderr)
2919     }
2920   }
2921   msg = "--"
2922   println(msg)
2923   logOutFile.append(msg+"\n")
2924 }
2925
2926
2927 task jalviewjsBuildAllCores {
2928   group "JalviewJS"
2929   description "Build the core js lib closures listed in the classlists dir"
2930   dependsOn jalviewjsTranspile
2931   dependsOn jalviewjsTransferUnzipSwingJs
2932
2933   def j2sDir = "${jalviewDir}/${jalviewjsTransferSiteJsDir}/${jalviewjs_j2s_subdir}"
2934   def swingJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_j2s_subdir}"
2935   def libJ2sDir = "${jalviewDir}/${jalviewjsTransferSiteLibDir}/${jalviewjs_j2s_subdir}"
2936   def jsDir = "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}/${jalviewjs_js_subdir}"
2937   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}/${jalviewjs_j2s_subdir}/core"
2938   def prefixFile = "${jsDir}/core/coretop2.js"
2939   def suffixFile = "${jsDir}/core/corebottom2.js"
2940
2941   inputs.file prefixFile
2942   inputs.file suffixFile
2943
2944   def classlistFiles = []
2945   // add the classlists found int the jalviewjs_classlists_dir
2946   fileTree(dir: "${jalviewDir}/${jalviewjs_classlists_dir}", include: "*.txt").each {
2947     file ->
2948     def name = file.getName() - ".txt"
2949     classlistFiles += [
2950       'file': file,
2951       'name': name
2952     ]
2953   }
2954
2955   // _jmol and _jalview cores. Add any other peculiar classlist.txt files here
2956   //classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jmol}"), 'name': "_jvjmol" ]
2957   classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jalview}"), 'name': jalviewjsJalviewCoreName ]
2958
2959   jalviewjsCoreClasslists = []
2960
2961   classlistFiles.each {
2962     hash ->
2963
2964     def file = hash['file']
2965     if (! file.exists()) {
2966       //println("...classlist file '"+file.getPath()+"' does not exist, skipping")
2967       return false // this is a "continue" in groovy .each closure
2968     }
2969     def name = hash['name']
2970     if (name == null) {
2971       name = file.getName() - ".txt"
2972     }
2973
2974     def filelist = []
2975     file.eachLine {
2976       line ->
2977         filelist += line
2978     }
2979     def list = fileTree(dir: j2sDir, includes: filelist)
2980
2981     def jsfile = "${outputDir}/core${name}.js"
2982     def zjsfile = "${outputDir}/core${name}.z.js"
2983
2984     jalviewjsCoreClasslists += [
2985       'jsfile': jsfile,
2986       'zjsfile': zjsfile,
2987       'list': list,
2988       'name': name
2989     ]
2990
2991     inputs.file(file)
2992     inputs.files(list)
2993     outputs.file(jsfile)
2994     outputs.file(zjsfile)
2995   }
2996   
2997   // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
2998   def stevesoftClasslistName = "_stevesoft"
2999   def stevesoftClasslist = [
3000     'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
3001     'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
3002     'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
3003     'name': stevesoftClasslistName
3004   ]
3005   jalviewjsCoreClasslists += stevesoftClasslist
3006   inputs.files(stevesoftClasslist['list'])
3007   outputs.file(stevesoftClasslist['jsfile'])
3008   outputs.file(stevesoftClasslist['zjsfile'])
3009
3010   // _all core
3011   def allClasslistName = "_all"
3012   def allJsFiles = fileTree(dir: j2sDir, include: "**/*.js")
3013   allJsFiles += fileTree(
3014     dir: libJ2sDir,
3015     include: "**/*.js",
3016     excludes: [
3017       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3018       "**/org/jmol/jvxl/readers/IsoIntersectFileReader.js",
3019       "**/org/jmol/export/JSExporter.js"
3020     ]
3021   )
3022   allJsFiles += fileTree(
3023     dir: swingJ2sDir,
3024     include: "**/*.js",
3025     excludes: [
3026       // these exlusions are files that the closure-compiler produces errors for. Should fix them
3027       "**/sun/misc/Unsafe.js",
3028       "**/swingjs/jquery/jquery-editable-select.js",
3029       "**/swingjs/jquery/j2sComboBox.js",
3030       "**/sun/misc/FloatingDecimal.js"
3031     ]
3032   )
3033   def allClasslist = [
3034     'jsfile': "${outputDir}/core${allClasslistName}.js",
3035     'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
3036     'list': allJsFiles,
3037     'name': allClasslistName
3038   ]
3039   // not including this version of "all" core at the moment
3040   //jalviewjsCoreClasslists += allClasslist
3041   inputs.files(allClasslist['list'])
3042   outputs.file(allClasslist['jsfile'])
3043   outputs.file(allClasslist['zjsfile'])
3044
3045   doFirst {
3046     def logOutFile = file("${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_j2s_closure_stdout}")
3047     logOutFile.getParentFile().mkdirs()
3048     logOutFile.createNewFile()
3049     logOutFile.write(getDate("yyyy-MM-dd HH:mm:ss")+" jalviewjsBuildAllCores\n----\n")
3050
3051     jalviewjsCoreClasslists.each {
3052       jalviewjsCallCore(it.name, it.list, prefixFile, suffixFile, it.jsfile, it.zjsfile, logOutFile, jalviewjs_j2s_to_console.equals("true"))
3053     }
3054   }
3055
3056 }
3057
3058
3059 def jalviewjsPublishCoreTemplate(String coreName, String templateName, File inputFile, String outputFile) {
3060   copy {
3061     from inputFile
3062     into file(outputFile).getParentFile()
3063     rename { filename ->
3064       if (filename.equals(inputFile.getName())) {
3065         return file(outputFile).getName()
3066       }
3067       return null
3068     }
3069     filter(ReplaceTokens,
3070       beginToken: '_',
3071       endToken: '_',
3072       tokens: [
3073         'MAIN': '"'+main_class+'"',
3074         'CODE': "null",
3075         'NAME': jalviewjsJalviewTemplateName+" [core ${coreName}]",
3076         'COREKEY': jalviewjs_core_key,
3077         'CORENAME': coreName
3078       ]
3079     )
3080   }
3081 }
3082
3083
3084 task jalviewjsPublishCoreTemplates {
3085   dependsOn jalviewjsBuildAllCores
3086   def inputFileName = "${jalviewDir}/${j2s_coretemplate_html}"
3087   def inputFile = file(inputFileName)
3088   def outputDir = "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3089
3090   def outputFiles = []
3091   jalviewjsCoreClasslists.each { cl ->
3092     def outputFile = "${outputDir}/${jalviewjsJalviewTemplateName}_${cl.name}.html"
3093     cl['outputfile'] = outputFile
3094     outputFiles += outputFile
3095   }
3096
3097   doFirst {
3098     jalviewjsCoreClasslists.each { cl ->
3099       jalviewjsPublishCoreTemplate(cl.name, jalviewjsJalviewTemplateName, inputFile, cl.outputfile)
3100     }
3101   }
3102   inputs.file(inputFile)
3103   outputs.files(outputFiles)
3104 }
3105
3106
3107 task jalviewjsSyncCore (type: Sync) {
3108   dependsOn jalviewjsBuildAllCores
3109   dependsOn jalviewjsPublishCoreTemplates
3110   def inputFiles = fileTree(dir: "${jalviewDir}/${jalviewjsTransferSiteCoreDir}")
3111   def outputDir = "${jalviewDir}/${jalviewjsSiteDir}"
3112
3113   from inputFiles
3114   into outputDir
3115   def outputFiles = []
3116   rename { filename ->
3117     outputFiles += "${outputDir}/${filename}"
3118     null
3119   }
3120   preserve {
3121     include "**"
3122   }
3123   outputs.files outputFiles
3124   inputs.files inputFiles
3125 }
3126
3127
3128 // this Copy version of TransferSiteJs will delete anything else in the target dir
3129 task jalviewjsCopyTransferSiteJs(type: Copy) {
3130   dependsOn jalviewjsTranspile
3131   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3132   into "${jalviewDir}/${jalviewjsSiteDir}"
3133 }
3134
3135
3136 // this Sync version of TransferSite is used by buildship to keep the website automatically up to date when a file changes
3137 task jalviewjsSyncTransferSiteJs(type: Sync) {
3138   from "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3139   include "**/*.*"
3140   into "${jalviewDir}/${jalviewjsSiteDir}"
3141   preserve {
3142     include "**"
3143   }
3144 }
3145
3146
3147 jalviewjsSyncAllLibs.mustRunAfter jalviewjsCopyTransferSiteJs
3148 jalviewjsSyncResources.mustRunAfter jalviewjsCopyTransferSiteJs
3149 jalviewjsSyncSiteResources.mustRunAfter jalviewjsCopyTransferSiteJs
3150 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsCopyTransferSiteJs
3151
3152 jalviewjsSyncAllLibs.mustRunAfter jalviewjsSyncTransferSiteJs
3153 jalviewjsSyncResources.mustRunAfter jalviewjsSyncTransferSiteJs
3154 jalviewjsSyncSiteResources.mustRunAfter jalviewjsSyncTransferSiteJs
3155 jalviewjsSyncBuildProperties.mustRunAfter jalviewjsSyncTransferSiteJs
3156
3157
3158 task jalviewjsPrepareSite {
3159   group "JalviewJS"
3160   description "Prepares the website folder including unzipping files and copying resources"
3161   dependsOn jalviewjsSyncAllLibs
3162   dependsOn jalviewjsSyncResources
3163   dependsOn jalviewjsSyncSiteResources
3164   dependsOn jalviewjsSyncBuildProperties
3165   dependsOn jalviewjsSyncCore
3166 }
3167
3168
3169 task jalviewjsBuildSite {
3170   group "JalviewJS"
3171   description "Builds the whole website including transpiled code"
3172   dependsOn jalviewjsCopyTransferSiteJs
3173   dependsOn jalviewjsPrepareSite
3174 }
3175
3176
3177 task cleanJalviewjsTransferSite {
3178   doFirst {
3179     delete "${jalviewDir}/${jalviewjsTransferSiteJsDir}"
3180     delete "${jalviewDir}/${jalviewjsTransferSiteLibDir}"
3181     delete "${jalviewDir}/${jalviewjsTransferSiteSwingJsDir}"
3182     delete "${jalviewDir}/${jalviewjsTransferSiteCoreDir}"
3183   }
3184 }
3185
3186
3187 task cleanJalviewjsSite {
3188   dependsOn cleanJalviewjsTransferSite
3189   doFirst {
3190     delete "${jalviewDir}/${jalviewjsSiteDir}"
3191   }
3192 }
3193
3194
3195 task jalviewjsSiteTar(type: Tar) {
3196   group "JalviewJS"
3197   description "Creates a tar.gz file for the website"
3198   dependsOn jalviewjsBuildSite
3199   def outputFilename = "jalviewjs-site-${JALVIEW_VERSION}.tar.gz"
3200   archiveFileName = outputFilename
3201
3202   compression Compression.GZIP
3203
3204   from "${jalviewDir}/${jalviewjsSiteDir}"
3205   into jalviewjs_site_dir // this is inside the tar file
3206
3207   inputs.dir("${jalviewDir}/${jalviewjsSiteDir}")
3208 }
3209
3210
3211 task jalviewjsServer {
3212   group "JalviewJS"
3213   def filename = "jalviewjsTest.html"
3214   description "Starts a webserver on localhost to test the website. See ${filename} to access local site on most recently used port."
3215   def htmlFile = "${jalviewDirAbsolutePath}/${filename}"
3216   doLast {
3217
3218     def factory
3219     try {
3220       def f = Class.forName("org.gradle.plugins.javascript.envjs.http.simple.SimpleHttpFileServerFactory")
3221       factory = f.newInstance()
3222     } catch (ClassNotFoundException e) {
3223       throw new GradleException("Unable to create SimpleHttpFileServerFactory")
3224     }
3225     def port = Integer.valueOf(jalviewjs_server_port)
3226     def start = port
3227     def running = false
3228     def url
3229     def jalviewjsServer
3230     while(port < start+1000 && !running) {
3231       try {
3232         def doc_root = new File("${jalviewDirAbsolutePath}/${jalviewjsSiteDir}")
3233         jalviewjsServer = factory.start(doc_root, port)
3234         running = true
3235         url = jalviewjsServer.getResourceUrl(jalviewjs_server_resource)
3236         println("SERVER STARTED with document root ${doc_root}.")
3237         println("Go to "+url+" . Run  gradle --stop  to stop (kills all gradle daemons).")
3238         println("For debug: "+url+"?j2sdebug")
3239         println("For verbose: "+url+"?j2sverbose")
3240       } catch (Exception e) {
3241         port++;
3242       }
3243     }
3244     def htmlText = """
3245       <p><a href="${url}">JalviewJS Test. &lt;${url}&gt;</a></p>
3246       <p><a href="${url}?j2sdebug">JalviewJS Test with debug. &lt;${url}?j2sdebug&gt;</a></p>
3247       <p><a href="${url}?j2sverbose">JalviewJS Test with verbose. &lt;${url}?j2sdebug&gt;</a></p>
3248       """
3249     jalviewjsCoreClasslists.each { cl ->
3250       def urlcore = jalviewjsServer.getResourceUrl(file(cl.outputfile).getName())
3251       htmlText += """
3252       <p><a href="${urlcore}">${jalviewjsJalviewTemplateName} [core ${cl.name}]. &lt;${urlcore}&gt;</a></p>
3253       """
3254       println("For core ${cl.name}: "+urlcore)
3255     }
3256
3257     file(htmlFile).text = htmlText
3258   }
3259
3260   outputs.file(htmlFile)
3261   outputs.upToDateWhen({false})
3262 }
3263
3264
3265 task cleanJalviewjsAll {
3266   group "JalviewJS"
3267   description "Delete all configuration and build artifacts to do with JalviewJS build"
3268   dependsOn cleanJalviewjsSite
3269   dependsOn jalviewjsEclipsePaths
3270   
3271   doFirst {
3272     delete "${jalviewDir}/${jalviewjsBuildDir}"
3273     delete "${jalviewDir}/${eclipse_bin_dir}"
3274     if (eclipseWorkspace != null && file(eclipseWorkspace.getAbsolutePath()+"/.metadata").exists()) {
3275       delete file(eclipseWorkspace.getAbsolutePath()+"/.metadata")
3276     }
3277     delete jalviewjsJ2sAltSettingsFileName
3278   }
3279
3280   outputs.upToDateWhen( { false } )
3281 }
3282
3283
3284 task jalviewjsIDE_checkJ2sPlugin {
3285   group "00 JalviewJS in Eclipse"
3286   description "Compare the swingjs/net.sf.j2s.core(-j11)?.jar file with the Eclipse IDE's plugin version (found in the 'dropins' dir)"
3287
3288   doFirst {
3289     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
3290     def j2sPluginFile = file(j2sPlugin)
3291     def eclipseHome = System.properties["eclipse.home.location"]
3292     if (eclipseHome == null || ! IN_ECLIPSE) {
3293       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. Skipping J2S Plugin Check.")
3294     }
3295     def eclipseJ2sPluginDirs = [ "${eclipseHome}/dropins" ]
3296     def altPluginsDir = System.properties["org.eclipse.equinox.p2.reconciler.dropins.directory"]
3297     if (altPluginsDir != null && file(altPluginsDir).exists()) {
3298       eclipseJ2sPluginDirs += altPluginsDir
3299     }
3300     def foundPlugin = false
3301     def j2sPluginFileName = j2sPluginFile.getName()
3302     def eclipseJ2sPlugin
3303     def eclipseJ2sPluginFile
3304     eclipseJ2sPluginDirs.any { dir ->
3305       eclipseJ2sPlugin = "${dir}/${j2sPluginFileName}"
3306       eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
3307       if (eclipseJ2sPluginFile.exists()) {
3308         foundPlugin = true
3309         return true
3310       }
3311     }
3312     if (!foundPlugin) {
3313       def msg = "Eclipse J2S Plugin is not installed (could not find '${j2sPluginFileName}' in\n"+eclipseJ2sPluginDirs.join("\n")+"\n)\nTry running task jalviewjsIDE_copyJ2sPlugin"
3314       System.err.println(msg)
3315       throw new StopExecutionException(msg)
3316     }
3317
3318     def digest = MessageDigest.getInstance("MD5")
3319
3320     digest.update(j2sPluginFile.text.bytes)
3321     def j2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
3322
3323     digest.update(eclipseJ2sPluginFile.text.bytes)
3324     def eclipseJ2sPluginMd5 = new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
3325      
3326     if (j2sPluginMd5 != eclipseJ2sPluginMd5) {
3327       def msg = "WARNING! Eclipse J2S Plugin '${eclipseJ2sPlugin}' is different to this commit's version '${j2sPlugin}'"
3328       System.err.println(msg)
3329       throw new StopExecutionException(msg)
3330     } else {
3331       def msg = "Eclipse J2S Plugin '${eclipseJ2sPlugin}' is the same as '${j2sPlugin}' (this is good)"
3332       println(msg)
3333     }
3334   }
3335 }
3336
3337 task jalviewjsIDE_copyJ2sPlugin {
3338   group "00 JalviewJS in Eclipse"
3339   description "Copy the swingjs/net.sf.j2s.core(-j11)?.jar file into the Eclipse IDE's 'dropins' dir"
3340
3341   doFirst {
3342     def j2sPlugin = string("${jalviewDir}/${jalviewjsJ2sPlugin}")
3343     def j2sPluginFile = file(j2sPlugin)
3344     def eclipseHome = System.properties["eclipse.home.location"]
3345     if (eclipseHome == null || ! IN_ECLIPSE) {
3346       throw new StopExecutionException("Cannot find running Eclipse home from System.properties['eclipse.home.location']. NOT copying J2S Plugin.")
3347     }
3348     def eclipseJ2sPlugin = "${eclipseHome}/dropins/${j2sPluginFile.getName()}"
3349     def eclipseJ2sPluginFile = file(eclipseJ2sPlugin)
3350     def msg = "WARNING! Copying this commit's j2s plugin '${j2sPlugin}' to Eclipse J2S Plugin '${eclipseJ2sPlugin}'\n* May require an Eclipse restart"
3351     System.err.println(msg)
3352     copy {
3353       from j2sPlugin
3354       eclipseJ2sPluginFile.getParentFile().mkdirs()
3355       into eclipseJ2sPluginFile.getParent()
3356     }
3357   }
3358 }
3359
3360
3361 task jalviewjsIDE_j2sFile {
3362   group "00 JalviewJS in Eclipse"
3363   description "Creates the .j2s file"
3364   dependsOn jalviewjsCreateJ2sSettings
3365 }
3366
3367
3368 task jalviewjsIDE_SyncCore {
3369   group "00 JalviewJS in Eclipse"
3370   description "Build the core js lib closures listed in the classlists dir and publish core html from template"
3371   dependsOn jalviewjsSyncCore
3372 }
3373
3374
3375 task jalviewjsIDE_SyncSiteAll {
3376   dependsOn jalviewjsSyncAllLibs
3377   dependsOn jalviewjsSyncResources
3378   dependsOn jalviewjsSyncSiteResources
3379   dependsOn jalviewjsSyncBuildProperties
3380 }
3381
3382
3383 cleanJalviewjsTransferSite.mustRunAfter jalviewjsIDE_SyncSiteAll
3384
3385
3386 task jalviewjsIDE_PrepareSite {
3387   group "00 JalviewJS in Eclipse"
3388   description "Sync libs and resources to site dir, but not closure cores"
3389
3390   dependsOn jalviewjsIDE_SyncSiteAll
3391   //dependsOn cleanJalviewjsTransferSite // not sure why this clean is here -- will slow down a re-run of this task
3392 }
3393
3394
3395 task jalviewjsIDE_AssembleSite {
3396   group "00 JalviewJS in Eclipse"
3397   description "Assembles unzipped supporting zipfiles, resources, site resources and closure cores into the Eclipse transpiled site"
3398   dependsOn jalviewjsPrepareSite
3399 }
3400
3401
3402 task jalviewjsIDE_SiteClean {
3403   group "00 JalviewJS in Eclipse"
3404   description "Deletes the Eclipse transpiled site"
3405   dependsOn cleanJalviewjsSite
3406 }
3407
3408
3409 task jalviewjsIDE_Server {
3410   group "00 JalviewJS in Eclipse"
3411   description "Starts a webserver on localhost to test the website"
3412   dependsOn jalviewjsServer
3413 }
3414
3415
3416 // buildship runs this at import or gradle refresh
3417 task eclipseSynchronizationTask {
3418   //dependsOn eclipseSetup
3419   dependsOn createBuildProperties
3420   if (J2S_ENABLED) {
3421     dependsOn jalviewjsIDE_j2sFile
3422     dependsOn jalviewjsIDE_checkJ2sPlugin
3423     dependsOn jalviewjsIDE_PrepareSite
3424   }
3425 }
3426
3427
3428 // buildship runs this at build time or project refresh
3429 task eclipseAutoBuildTask {
3430   //dependsOn jalviewjsIDE_checkJ2sPlugin
3431   //dependsOn jalviewjsIDE_PrepareSite
3432 }
3433
3434
3435 task jalviewjs {
3436   group "JalviewJS"
3437   description "Build the site"
3438   dependsOn jalviewjsBuildSite
3439 }