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