X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=build.gradle;h=a917a0df69f19cf79f27888cb6d60b1c220016bc;hb=d17a8d70dedafa76dd0bb6c7c8a280a516464dac;hp=3b06476cd470aa3b41d3a9d7276d8e5e2b625c35;hpb=f87f6b83fe704bb2eb73ada71897138a33d75c15;p=jalview.git diff --git a/build.gradle b/build.gradle index 3b06476..a917a0d 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,10 @@ import org.gradle.plugins.ide.eclipse.model.Output import org.gradle.plugins.ide.eclipse.model.Library import java.security.MessageDigest import java.util.regex.Matcher +import java.util.concurrent.Executors +import java.util.concurrent.Future +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit import groovy.transform.ExternalizeMethods import groovy.util.XmlParser import groovy.xml.XmlUtil @@ -47,8 +51,9 @@ plugins { id 'java' id 'application' id 'eclipse' + id "com.diffplug.spotless" version "6.18.0" //.gradle.spotless" "3.28.0" + id 'com.github.johnrengelman.shadow' version '8.1.1' // was 4.0.3 id "com.diffplug.gradle.spotless" version "3.28.0" - id 'com.github.johnrengelman.shadow' version '4.0.3' id 'com.install4j.gradle' version '10.0.3' id 'com.dorongold.task-tree' version '2.1.1' // only needed to display task dependency tree with gradle task1 [task2 ...] taskTree id 'com.palantir.git-version' version '0.13.0' apply false @@ -189,6 +194,7 @@ ext { testDir = string("${jalviewDir}/${bareTestSourceDir}") classesDir = string("${jalviewDir}/${classes_dir}") + outputDir = file(classesDir) // clover useClover = clover.equals("true") @@ -568,11 +574,16 @@ ext { jalviewjsJ2sAltSettingsFileName = string("${jalviewDir}/${jalviewjs_j2s_alt_settings}") jalviewjsJ2sProps = null jalviewjsJ2sPlugin = jalviewjs_j2s_plugin + jalviewjsStderrLaunchFilename = "${jalviewjsSiteDir}/"+(file(jalviewjs_stderr_launch).getName()) eclipseWorkspace = null eclipseBinary = string("") eclipseVersion = string("") eclipseDebug = false + + jalviewjsChromiumUserDir = "${jalviewjsBuildDir}/${jalviewjs_chromium_user_dir}" + jalviewjsChromiumProfileDir = "${ext.jalviewjsChromiumUserDir}/${jalviewjs_chromium_profile_name}" + // ENDEXT } @@ -1274,15 +1285,15 @@ def mdFileComponents(File mdFile, def dateOnly=false) { } if (inFrontMatter) { def m = null - if (m = line =~ /^date:\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/) { + if (m == line =~ /^date:\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/) { map["date"] = new Date().parse("yyyy-MM-dd HH:mm:ss", m[0][1]) - } else if (m = line =~ /^date:\s*(\d{4}-\d{2}-\d{2})/) { + } else if (m == line =~ /^date:\s*(\d{4}-\d{2}-\d{2})/) { map["date"] = new Date().parse("yyyy-MM-dd", m[0][1]) - } else if (m = line =~ /^channel:\s*(\S+)/) { + } else if (m == line =~ /^channel:\s*(\S+)/) { map["channel"] = m[0][1] - } else if (m = line =~ /^version:\s*(\S+)/) { + } else if (m == line =~ /^version:\s*(\S+)/) { map["version"] = m[0][1] - } else if (m = line =~ /^\s*([^:]+)\s*:\s*(\S.*)/) { + } else if (m == line =~ /^\s*([^:]+)\s*:\s*(\S.*)/) { map[ m[0][1] ] = m[0][2] } if (dateOnly && map["date"] != null) { @@ -1341,7 +1352,7 @@ task hugoTemplates { def inSection = false changes.eachLine { line -> def m = null - if (m = line =~ /^##([^#].*)$/) { + if (m == line =~ /^##([^#].*)$/) { if (inSection) { changesHugo += "\n\n" } @@ -1351,7 +1362,7 @@ task hugoTemplates { section = section.replaceAll(/[^a-z0-9_\-]/, "") changesHugo += "
\n\n" inSection = true - } else if (m = line =~ /^(\s*-\s*)(.*?)()?\s*$/) { + } else if (m == line =~ /^(\s*-\s*)(.*?)()?\s*$/) { def comment = m[0][2].trim() if (comment != "") { comment = comment.replaceAll('"', """) @@ -1421,7 +1432,7 @@ def getMdSections(String content) { def sectionName = null content.eachLine { line -> def m = null - if (m = line =~ /^##([^#].*)$/) { + if (m == line =~ /^##([^#].*)$/) { if (sectionName != null) { sections[sectionName] = sectionContent sectionName = null @@ -1477,6 +1488,7 @@ task copyHelp(type: Copy) { } +/* task releasesTemplates { group "help" description "Recreate whatsNew.html and releases.html from markdown files and templates in help" @@ -1553,9 +1565,9 @@ task releasesTemplates { def lm = null def rContentProcessed = "" rContent.eachLine { line -> - if (lm = line =~ /^(\s*-)(\s*)(.*)$/) { + if (lm == line =~ /^(\s*-)(\s*)(.*)$/) { line = "${lm[0][1]}${lm[0][3]}${lm[0][2]}" - } else if (lm = line =~ /^###([^#]+.*)$/) { + } else if (lm == line =~ /^###([^#]+.*)$/) { line = "_${lm[0][1].trim()}_" } rContentProcessed += line + "\n" @@ -1613,6 +1625,7 @@ task releasesTemplates { outputs.file(whatsnewHtmlFile) } +*/ task copyResources(type: Copy) { group = "build" @@ -1674,6 +1687,7 @@ task copyChannelResources(type: Copy) { task createBuildProperties(type: WriteProperties) { dependsOn copyResources + dependsOn copyChannelResources group = "build" description = "Create the ${buildProperties} file" @@ -1697,6 +1711,7 @@ task createBuildProperties(type: WriteProperties) { task buildIndices(type: JavaExec) { dependsOn copyHelp + //dependsOn releasesTemplates classpath = sourceSets.main.compileClasspath main = "com.sun.java.help.search.Indexer" workingDir = "${helpBuildDir}/${help_dir}" @@ -1724,18 +1739,38 @@ task prepare { dependsOn buildResources dependsOn copyDocs dependsOn copyHelp - dependsOn releasesTemplates + //dependsOn releasesTemplates dependsOn convertMdFiles dependsOn buildIndices } +// random block of dependencies compileJava.dependsOn prepare run.dependsOn compileJava -compileTestJava.dependsOn compileJava +//run.dependsOn prepare +compileTestJava.dependsOn compileJava // +compileTestJava.dependsOn buildIndices // +processResources.dependsOn copyChannelResources // +processResources.dependsOn copyResources // +processResources.dependsOn createBuildProperties // +processResources.dependsOn copyDocs // +processResources.dependsOn convertMdFiles // +processResources.dependsOn copyHelp // +processResources.dependsOn buildIndices // +test { + group = "Verification" + description = "Runs all testTaskN tasks)" + if (useClover) { + dependsOn cloverClasses + } else { //? + dependsOn testClasses + } -ext.testsFailed = false + // not running tests in this task + exclude "**/*" +} /* testTask0 is the main test task */ task testTask0(type: Test) { group = "Verification" @@ -1761,6 +1796,20 @@ task testTask1(type: Test) { } } +/* insert more testTaskNs here -- change N to next digit or other string */ +/* +task testTaskN(type: Test) { + group = "Verification" + description = "Tests that need to be isolated from the main test run" + useTestNG() { + includeGroups name + excludeGroups testng_excluded_groups.split(",") + preserveOrder true + useDefaultListeners=true + } +} +*/ + /* * adapted from https://medium.com/@wasyl/pretty-tests-summary-in-gradle-744804dd676c * to summarise test results from all Test tasks @@ -1785,35 +1834,30 @@ tasks.withType(Test).matching {t -> t.getName().startsWith("testTask")}.all { te testTask.mustRunAfter "testTask0" testTask.testLogging { logging -> - events TestLogEvent.FAILED, - TestLogEvent.SKIPPED, - TestLogEvent.STANDARD_OUT, - TestLogEvent.STANDARD_ERROR + events TestLogEvent.FAILED +// TestLogEvent.SKIPPED, +// TestLogEvent.STANDARD_OUT, +// TestLogEvent.STANDARD_ERROR exceptionFormat TestExceptionFormat.FULL showExceptions true showCauses true showStackTraces true + + info.events = [ TestLogEvent.FAILED ] } + + ignoreFailures = true // Always try to run all tests for all modules afterSuite { desc, result -> + if (desc.parent) + return // Only summarize results for whole modules - if (desc.parent) return // Only summarize results for whole modules - - def summary = [testTask.project.name, testTask.name, result, TimeCategory.minus(new Date(result.endTime), new Date(result.startTime)), testTask.reports.html.entryPoint] + def resultsInfo = [testTask.project.name, testTask.name, result, TimeCategory.minus(new Date(result.endTime), new Date(result.startTime)), testTask.reports.html.entryPoint] - // Add reports in `testsResults`, keep failed suites at the end - if (result.resultType == TestResult.ResultType.SUCCESS) { - rootProject.ext.testsResults.add(0, summary) - } else { - rootProject.ext.testsResults.add(summary) - } - - if (result.resultType == TestResult.ResultType.FAILURE) { - testsFailed = true - } + rootProject.ext.testsResults.add(resultsInfo) } // from original test task @@ -1835,11 +1879,25 @@ tasks.withType(Test).matching {t -> t.getName().startsWith("testTask")}.all { te jvmArgs += additional_compiler_args doFirst { + // this is not perfect yet -- we should only add the commandLineIncludePatterns to the + // testTasks that include the tests, and exclude all from the others. + // get --test argument + filter.commandLineIncludePatterns = test.filter.commandLineIncludePatterns + // do something with testTask.getCandidateClassFiles() to see if the test should silently finish because of the + // commandLineIncludePatterns not matching anything. Instead we are doing setFailOnNoMatchingTests(false) below + + if (useClover) { println("Running tests " + (useClover?"WITH":"WITHOUT") + " clover") } } + + /* don't fail on no matching tests (so --tests will run across all testTasks) */ + testTask.filter.setFailOnNoMatchingTests(false) + + /* ensure the "test" task dependsOn all the testTasks */ + test.dependsOn testTask } gradle.buildFinished { @@ -1849,49 +1907,73 @@ gradle.buildFinished { printResults allResults allResults.each {r -> if (r[2].resultType == TestResult.ResultType.FAILURE) - throw GradleException("Failed tests (buildFinished)!") + throw new GradleException("Failed tests!") } } } +private static String colString(styler, col, colour, text) { + return col?"${styler[colour](text)}":text +} + +private static String getSummaryLine(s, pn, tn, rt, rc, rs, rf, rsk, t, col) { + def colour = 'black' + def text = rt + def nocol = false + if (rc == 0) { + text = "-----" + nocol = true + } else { + switch(rt) { + case TestResult.ResultType.SUCCESS: + colour = 'green' + break; + case TestResult.ResultType.FAILURE: + colour = 'red' + break; + default: + nocol = true + break; + } + } + StringBuilder sb = new StringBuilder() + sb.append("${pn}") + if (tn != null) + sb.append(":${tn}") + sb.append(" results: ") + sb.append(colString(s, col && !nocol, colour, text)) + sb.append(" (") + sb.append("${rc} tests, ") + sb.append(colString(s, col && rs > 0, 'green', rs)) + sb.append(" successes, ") + sb.append(colString(s, col && rf > 0, 'red', rf)) + sb.append(" failures, ") + sb.append("${rsk} skipped) in ${t}") + return sb.toString() +} + private static void printResults(allResults) { + // styler from https://stackoverflow.com/a/56139852 def styler = 'black red green yellow blue magenta cyan white'.split().toList().withIndex(30).collectEntries { key, val -> [(key) : { "\033[${val}m${it}\033[0m" }] } def maxLength = 0 def failedTests = false def summaryLines = [] - allResults.each { + def totalcount = 0 + def totalsuccess = 0 + def totalfail = 0 + def totalskip = 0 + def totaltime = TimeCategory.getSeconds(0) + // sort on project name then task name + allResults.sort {a, b -> a[0] == b[0]? a[1]<=>b[1]:a[0] <=> b[0]}.each { def projectName = it[0] def taskName = it[1] def result = it[2] def time = it[3] def report = it[4] - def colour = 'black' - switch(result.resultType) { - case TestResult.ResultType.SUCCESS: - colour = 'green' - break; - case TestResult.ResultType.FAILURE: - colour = 'red' - failedTests = true - break; - default: - colour = 'yellow' - break; - } - def summaryCol = "${projectName}:${taskName} results: ${styler[colour](result.resultType)} (" + - "${result.testCount} tests, " + - (result.successfulTestCount > 0 ? "${styler['green'](result.successfulTestCount)} successes" : "${result.successfulTestCount} successes") + ", " + - (result.failedTestCount > 0 ? "${styler['red'](result.failedTestCount)} failures" : "${result.failedTestCount} failures") + ", " + - "${result.skippedTestCount} skipped" + - ") " + "in ${time}" - def summaryPlain = "${projectName}:${taskName} results: ${result.resultType} (" + - "${result.testCount} tests, " + - "${result.successfulTestCount} successes, " + - "${result.failedTestCount} failures, " + - "${result.skippedTestCount} skipped" + - ") " + "in ${time}" + def summaryCol = getSummaryLine(styler, projectName, taskName, result.resultType, result.testCount, result.successfulTestCount, result.failedTestCount, result.skippedTestCount, time, true) + def summaryPlain = getSummaryLine(styler, projectName, taskName, result.resultType, result.testCount, result.successfulTestCount, result.failedTestCount, result.skippedTestCount, time, false) def reportLine = "Report file: ${report}" def ls = summaryPlain.length() def lr = reportLine.length() @@ -1900,50 +1982,43 @@ private static void printResults(allResults) { maxLength = m def info = [ls, summaryCol, reportLine] summaryLines.add(info) + failedTests |= result.resultType == TestResult.ResultType.FAILURE + totalcount += result.testCount + totalsuccess += result.successfulTestCount + totalfail += result.failedTestCount + totalskip += result.skippedTestCount + totaltime += time + } + def totalSummaryCol = getSummaryLine(styler, "OVERALL", "", failedTests?TestResult.ResultType.FAILURE:TestResult.ResultType.SUCCESS, totalcount, totalsuccess, totalfail, totalskip, totaltime, true) + def totalSummaryPlain = getSummaryLine(styler, "OVERALL", "", failedTests?TestResult.ResultType.FAILURE:TestResult.ResultType.SUCCESS, totalcount, totalsuccess, totalfail, totalskip, totaltime, false) + def tls = totalSummaryPlain.length() + if (tls > maxLength) + maxLength = tls + def info = [tls, totalSummaryCol, null] + summaryLines.add(info) + + def allSummaries = [] + for(sInfo : summaryLines) { + def ls = sInfo[0] + def summary = sInfo[1] + def report = sInfo[2] + + StringBuilder sb = new StringBuilder() + sb.append("│" + summary + " " * (maxLength - ls) + "│") + if (report != null) { + sb.append("\n│" + report + " " * (maxLength - report.length()) + "│") + } + allSummaries += sb.toString() } println "┌${"${"─" * maxLength}"}┐" - - println summaryLines.collect {info -> - def ls = info[0] - def summary = info[1] - def report = info[2] - - return "│" + summary + " " * (maxLength - ls) + "│" + "\n" + - "│" + report + " " * (maxLength - report.length()) + "│" - - }.join("\n├${"${"─" * maxLength}"}┤\n") - + println allSummaries.join("\n├${"${"─" * maxLength}"}┤\n") println "└${"${"─" * maxLength}"}┘" } /* END of test tasks results summary */ -task verifyTestStatus { - group = "Verification" - description = "Task that FAILs the build if any tests failed" - doLast { - if (testsFailed) { - throw new GradleException("There were failing tests!") - } - } -} - -test { - // from original test task - if (useClover) { - dependsOn cloverClasses - } else { //? - dependsOn testClasses - } - dependsOn tasks.withType(Test).matching {t -> t.getName().startsWith("testTask")} - finalizedBy verifyTestStatus - - - // not running tests in this task - exclude "**/*" -} - +/* task compileLinkCheck(type: JavaCompile) { options.fork = true classpath = files("${jalviewDir}/${utils_dir}") @@ -1978,6 +2053,7 @@ task linkCheck(type: JavaExec) { inputs.dir(helpBuildDir) outputs.file(helpLinksCheckerOutFile) } +*/ // import the pubhtmlhelp target @@ -1992,10 +2068,14 @@ task cleanPackageDir(type: Delete) { } } +// block of dependencies +//compileTestJava.dependsOn compileLinkCheck // +//copyChannelResources.dependsOn compileLinkCheck // +//convertMdFiles.dependsOn compileLinkCheck // jar { dependsOn prepare - dependsOn linkCheck + dependsOn //linkCheck manifest { attributes "Main-Class": main_class, @@ -2006,7 +2086,7 @@ jar { } def outputDir = "${jalviewDir}/${package_dir}" - destinationDirectory = file(outputDir) + outputDir = file(outputDir) archiveFileName = rootProject.name+".jar" duplicatesStrategy "EXCLUDE" @@ -2080,7 +2160,7 @@ shadowJar { mainClassName = shadow_jar_main_class mergeServiceFiles() - classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION + archiveClassifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION minimize() } @@ -2185,8 +2265,8 @@ task getdownWebsite() { props.put("getdown_txt_ui.instant_background_image", "${getdownImagesBuildDir}/${getdown_instant_background_image}") props.put("getdown_txt_ui.error_background", "${getdownImagesBuildDir}/${getdown_error_background}") props.put("getdown_txt_ui.progress_image", "${getdownImagesBuildDir}/${getdown_progress_image}") - props.put("getdown_txt_ui.icon", "${getdownImagesBuildDir}/${getdown_icon}") - props.put("getdown_txt_ui.mac_dock_icon", "${getdownImagesBuildDir}/${getdown_mac_dock_icon}") + props.put("getdown_txt_ui.icon", "${getdownImagesDir}/${getdown_icon}") + props.put("getdown_txt_ui.mac_dock_icon", "${getdownImagesDir}/${getdown_mac_dock_icon}") } props.put("getdown_txt_title", jalview_name) @@ -2735,6 +2815,7 @@ task installerFiles(type: com.install4j.gradle.Install4jTask) { 'WRAPPER_LINK': getdownWrapperLink, 'BASH_WRAPPER_SCRIPT': getdown_bash_wrapper_script, 'POWERSHELL_WRAPPER_SCRIPT': getdown_powershell_wrapper_script, + 'BATCH_WRAPPER_SCRIPT': getdown_batch_wrapper_script, 'WRAPPER_SCRIPT_BIN_DIR': getdown_wrapper_script_dir, 'INSTALLER_NAME': install4jInstallerName, 'INSTALL4J_UTILS_DIR': install4j_utils_dir, @@ -4148,9 +4229,157 @@ task eclipseAutoBuildTask { } +task jalviewjsCopyStderrLaunchFile(type: Copy) { + from file(jalviewjs_stderr_launch) + into jalviewjsSiteDir + + inputs.file jalviewjs_stderr_launch + outputs.file jalviewjsStderrLaunchFilename +} + +task cleanJalviewjsChromiumUserDir { + doFirst { + delete jalviewjsChromiumUserDir + } + outputs.dir jalviewjsChromiumUserDir + // always run when depended on + outputs.upToDateWhen { !file(jalviewjsChromiumUserDir).exists() } +} + +task jalviewjsChromiumProfile { + dependsOn cleanJalviewjsChromiumUserDir + mustRunAfter cleanJalviewjsChromiumUserDir + + def firstRun = file("${jalviewjsChromiumUserDir}/First Run") + + doFirst { + mkdir jalviewjsChromiumProfileDir + firstRun.text = "" + } + outputs.file firstRun +} + +task jalviewjsLaunchTest { + group "Test" + description "Check JalviewJS opens in a browser" + dependsOn jalviewjsBuildSite + dependsOn jalviewjsCopyStderrLaunchFile + dependsOn jalviewjsChromiumProfile + + def macOS = OperatingSystem.current().isMacOsX() + def chromiumBinary = macOS ? jalviewjs_macos_chromium_binary : jalviewjs_chromium_binary + if (chromiumBinary.startsWith("~/")) { + chromiumBinary = System.getProperty("user.home") + chromiumBinary.substring(1) + } + + def stdout + def stderr + doFirst { + def timeoutms = Integer.valueOf(jalviewjs_chromium_overall_timeout) * 1000 + + def binary = file(chromiumBinary) + if (!binary.exists()) { + throw new StopExecutionException("Could not find chromium binary '${chromiumBinary}'. Cannot run task ${name}.") + } + stdout = new ByteArrayOutputStream() + stderr = new ByteArrayOutputStream() + def execStdout + def execStderr + if (jalviewjs_j2s_to_console.equals("true")) { + execStdout = new org.apache.tools.ant.util.TeeOutputStream( + stdout, + System.out) + execStderr = new org.apache.tools.ant.util.TeeOutputStream( + stderr, + System.err) + } else { + execStdout = stdout + execStderr = stderr + } + def execArgs = [ + "--no-sandbox", // --no-sandbox IS USED BY THE THORIUM APPIMAGE ON THE BUILDSERVER + "--headless=new", + "--disable-gpu", + "--timeout=${timeoutms}", + "--virtual-time-budget=${timeoutms}", + "--user-data-dir=${jalviewDirAbsolutePath}/${jalviewjsBuildDir}/${jalviewjs_chromium_user_dir}", + "--profile-directory=${jalviewjs_chromium_profile_name}", + "--allow-file-access-from-files", + "--enable-logging=stderr", + "file://${jalviewDirAbsolutePath}/${jalviewjsStderrLaunchFilename}" + ] + + if (true || macOS) { + ScheduledExecutorService executor = Executors.newScheduledThreadPool(3); + Future f1 = executor.submit( + () -> { + exec { + standardOutput = execStdout + errorOutput = execStderr + executable(chromiumBinary) + args(execArgs) + println "COMMAND: '"+commandLine.join(" ")+"'" + } + executor.shutdownNow() + } + ) + + def noChangeBytes = 0 + def noChangeIterations = 0 + executor.scheduleAtFixedRate( + () -> { + String stderrString = stderr.toString() + // shutdown the task if we have a success string + if (stderrString.contains(jalviewjs_desktop_init_string)) { + f1.cancel() + Thread.sleep(1000) + executor.shutdownNow() + } + // if no change in stderr for 10s then also end + if (noChangeIterations >= jalviewjs_chromium_idle_timeout) { + executor.shutdownNow() + } + if (stderrString.length() == noChangeBytes) { + noChangeIterations++ + } else { + noChangeBytes = stderrString.length() + noChangeIterations = 0 + } + }, + 1, 1, TimeUnit.SECONDS) + + executor.schedule(new Runnable(){ + public void run(){ + f1.cancel() + executor.shutdownNow() + } + }, timeoutms, TimeUnit.MILLISECONDS) + + executor.awaitTermination(timeoutms+10000, TimeUnit.MILLISECONDS) + executor.shutdownNow() + } + + } + + doLast { + def found = false + stderr.toString().eachLine { line -> + if (line.contains(jalviewjs_desktop_init_string)) { + println("Found line '"+line+"'") + found = true + return + } + } + if (!found) { + throw new GradleException("Could not find evidence of Desktop launch in JalviewJS.") + } + } +} + + task jalviewjs { group "JalviewJS" - description "Build the site" + description "Build the JalviewJS site and run the launch test" dependsOn jalviewjsBuildSite + dependsOn jalviewjsLaunchTest } -