diff --git a/.github/workflows/README.adoc b/.github/workflows/README.adoc new file mode 100644 index 000000000..4fed2413a --- /dev/null +++ b/.github/workflows/README.adoc @@ -0,0 +1,41 @@ +== The YAML workflow files vs. the `*.main.kts` files + +The YAML workflow files are generated from the `*.main.kts` files. + +These use the https://github.com/typesafegithub/github-workflows-kt[github-workflows-kt] +Kotlin DSL library to conveniently and type-safely write GitHub Action workflow files. + +As there is no official built-in support in GitHub Actions yet until +https://github.com/orgs/community/discussions/15904 is considered, the YAML files +need to be generated manually. + +There is a safeguard check in all the generated files that this is not forgotten. +Running a workflow where the according `*.main.kts` produces a different output will +fail the execution. Additionally, a workflow that runs for pushes and pull requests +checks the consistency of all the YAML files as not all might be run. + + + +== Ways to generate the YAML workflow files + +There are multiple ways to generate the YAML files and all of them are fine, +but be aware of the last one of the caveats below if you are not using the Gradle method: + +* If you are in a `sh` derivate like e.g. `bash` and Kotlin is installed and + available in the `PATH`, you can just call the `*.main.kts` script like any + other shell script: ++ +[source,bash] +---- +$ ./release.main.kts +---- + +* If Kotlin is installed somewhere you can call it with the `*.main.kts` script + as argument: ++ +[source,bash] +---- +$ path/to/kotlin release.main.kts +---- + +* From the IDE you can create a run configuration that executes the `*.main.kts` script. diff --git a/.github/workflows/build.main.kts b/.github/workflows/build.main.kts new file mode 100755 index 000000000..0a5e74b31 --- /dev/null +++ b/.github/workflows/build.main.kts @@ -0,0 +1,461 @@ +#!/usr/bin/env kotlin + +@file:Repository("https://repo.maven.apache.org/maven2/") +@file:DependsOn("io.github.typesafegithub:github-workflows-kt:3.0.1") +//@file:DependsOn("io.github.typesafegithub:github-workflows-kt:3.0.2-SNAPSHOT") + +@file:Repository("https://bindings.krzeminski.it/") +//@file:Repository("http://localhost:8080/") +//@file:Repository("http://localhost:8080/v2/") +//@file:Repository("http://localhost:8080/refresh/") + +//@file:DependsOn("tecolicom:actions-use-homebrew-tools___major:[v1,v2)") +//@file:DependsOn("Wandalen:wretry.action__main___major:[v3,v4)") +//@file:DependsOn("actions:checkout___major:[v4,v5)") +//@file:DependsOn("actions:setup-java___major:[v4,v5)") +//@file:DependsOn("actions:setup-java:[v4,v5)") +//@file:DependsOn("DamianReeves:write-file-action___minor:[v1,v2)") +//@file:DependsOn("actions__types__66e2cf81-db71-4622-a4fb-3f611e2c9c8f:cache:v4") // boolean +//@file:DependsOn("actions__types__b694d967-f59a-41f9-b151-823585b51f72:cache:v4") // string +//@file:DependsOn("actions__types__599dcd1f-0f3f-4ea2-80af-ae0737b0a5d6:cache:v4") // untyped +//@file:DependsOn("actions:cache___major:[v4,v5)") +//@file:DependsOn("actions:cache__restore___major:[v4,v5)") +//@file:DependsOn("actions:cache__save___major:[v4,v5)") +//@file:DependsOn("actions:upload-artifact___major:[v4,v5)") + +@file:DependsOn("tecolicom:actions-use-homebrew-tools:v1") +@file:DependsOn("Wandalen:wretry.action__main:v3") +@file:DependsOn("actions:checkout:v4") +@file:DependsOn("actions:setup-java:v4") +@file:DependsOn("DamianReeves:write-file-action:v1.3") +@file:DependsOn("actions:cache:v4") +@file:DependsOn("actions:cache__restore:v4") +@file:DependsOn("actions:cache__save:v4") +@file:DependsOn("actions:upload-artifact:v4") +//@file:DependsOn("mxschmitt:action-tmate:v3") + +import io.github.typesafegithub.workflows.actions.actions.Cache +import io.github.typesafegithub.workflows.actions.actions.CacheRestore +import io.github.typesafegithub.workflows.actions.actions.CacheSave +import io.github.typesafegithub.workflows.actions.actions.Checkout +import io.github.typesafegithub.workflows.actions.actions.SetupJava +import io.github.typesafegithub.workflows.actions.actions.SetupJava.Distribution.Temurin +import io.github.typesafegithub.workflows.actions.actions.UploadArtifact +import io.github.typesafegithub.workflows.actions.actions.UploadArtifact.BehaviorIfNoFilesFound.Error +import io.github.typesafegithub.workflows.actions.actions.UploadArtifact.BehaviorIfNoFilesFound.Ignore +import io.github.typesafegithub.workflows.actions.actions.UploadArtifact.CompressionLevel.NoCompression +import io.github.typesafegithub.workflows.actions.damianreeves.WriteFileAction +//import io.github.typesafegithub.workflows.actions.mxschmitt.ActionTmate +import io.github.typesafegithub.workflows.actions.tecolicom.ActionsUseHomebrewTools +import io.github.typesafegithub.workflows.actions.wandalen.WretryActionMain +import io.github.typesafegithub.workflows.domain.AbstractResult.Status.Skipped +import io.github.typesafegithub.workflows.domain.AbstractResult.Status.Success +//import io.github.typesafegithub.workflows.domain.Expression +//import io.github.typesafegithub.workflows.domain.JobOutputs +import io.github.typesafegithub.workflows.domain.RunnerType +import io.github.typesafegithub.workflows.domain.RunnerType.UbuntuLatest +import io.github.typesafegithub.workflows.domain.Shell.Bash +import io.github.typesafegithub.workflows.domain.triggers.PullRequest +import io.github.typesafegithub.workflows.domain.triggers.Push +import io.github.typesafegithub.workflows.dsl.expressions.Contexts.always +import io.github.typesafegithub.workflows.dsl.expressions.Contexts.runner +import io.github.typesafegithub.workflows.dsl.expressions.expr +import io.github.typesafegithub.workflows.dsl.workflow + +workflow( + name = "Build", + on = listOf( + Push(), + PullRequest() + ), + sourceFile = __FILE__ +) { + val allPossibleArtifacts = listOf( + Artifact( + name = "Manual in A4 Paper size", + path = "dist/jedit*manual-a4.pdf" + ), + Artifact( + name = "Manual in Letter Paper size", + path = "dist/jedit*manual-letter.pdf" + ), + Artifact( + name = "Source Package", + path = "dist/jedit*source.tar.bz2" + ), + Artifact( + name = "Java based Installer", + path = "dist/jedit*install.jar" + ), + Artifact( + name = "Slackware Installer", + path = "dist/jedit-*-noarch-1sao.tgz" + ), + Artifact( + name = "Debian Installer", + path = "dist/jedit_*_all.deb" + ), + Artifact( + name = "Windows Installer", + path = "dist/jedit*install.exe" + ), + Artifact( + name = "macOS Installer", + path = "dist/jedit*install.dmg", + condition = "${runner.os} == 'macOS'" + ), + Artifact( + name = "macOS Intermediate Result", + path = "dist/jedit*-dist-mac-finish.tar.bz2", + condition = "${runner.os} != 'macOS'" + ), + Artifact( + name = "Debian Repository Packages File", + path = "dist/Packages", + ignoreIfMissing = true + ), + Artifact( + name = "Debian Repository Packages File (gz)", + path = "dist/Packages.gz", + ignoreIfMissing = true + ), + Artifact( + name = "Debian Repository Packages File (bz2)", + path = "dist/Packages.bz2", + ignoreIfMissing = true + ), + Artifact( + name = "Debian Repository Release File", + path = "dist/Release", + ignoreIfMissing = true + ), + Artifact( + name = "Debian Repository Release File Signature", + path = "dist/Release.gpg", + ignoreIfMissing = true + ) + ) + + val build = job( + id = "build", + name = "Build on ${expr("matrix.os")}", + runsOn = RunnerType.Custom(expr("matrix.os")), +// outputs = object : JobOutputs() { +// var booleanOutput by output() +// var booleanOutput2 by output() +// var stringOutput by output() +// var stringOutput2 by output() +// var anyOutput by output() +// var anyOutput2 by output() +// }, + _customArguments = mapOf( + "strategy" to mapOf( + "fail-fast" to false, + "matrix" to mapOf( + "os" to listOf( + "ubuntu-latest", + "windows-latest", + "macos-latest" + ) + ) + ) + ) + ) { +// uses(action = ActionTmate()) + + run( + name = "Install Wine on Linux", + command = """ + sudo dpkg --add-architecture i386 + sudo wget -nc -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key + sudo wget -NP /etc/apt/sources.list.d/ "https://dl.winehq.org/wine-builds/ubuntu/dists/${'$'}(lsb_release -c | grep -o '\w*${'$'}')/winehq-${'$'}(lsb_release -c | grep -o '\w*${'$'}').sources" + sudo apt update + sudo apt install --yes --no-install-recommends winehq-stable + winecfg /v win10 + """.trimIndent(), + condition = "${runner.os} == 'Linux'" + ) + + uses( + name = "Install Wine on macOS", + action = ActionsUseHomebrewTools( + tools = listOf("wine-stable"), + path = listOf( + "/Applications", + "/Library" + ) + ), + condition = "${runner.os} == 'macOS'" + ) + + val innoSetupInstaller = "${expr { runner.temp }}/innosetup-6.3.3.exe" + + val cacheInnoSetupInstaller = uses( + name = "Cache InnoSetup installer on Linux and macOS", + action = Cache( + path = listOf(innoSetupInstaller), + key = innoSetupInstaller.substringAfterLast('/') + ), + condition = "(${runner.os} == 'Linux') || (${runner.os} == 'macOS')" + ) + +// // cacheHit boolean typed +// jobOutputs.booleanOutput = cache.outputs.cacheHit +// jobOutputs.booleanOutput2 = cache.outputs.cacheHit_Untyped +// //jobOutputs.stringOutput = cache.outputs.cacheHit // does not work +// jobOutputs.stringOutput = cache.outputs.cacheHit_Untyped // drop-in +// jobOutputs.stringOutput2 = cache.outputs.cacheHit_Untyped +// //jobOutputs.anyOutput = cache.outputs.cacheHit // does not work +// jobOutputs.anyOutput = Expression(cache.outputs.cacheHit.expression) // drop-in +// jobOutputs.anyOutput2 = cache.outputs.cacheHit_Untyped + +// // cacheHit string typed +// //jobOutputs.booleanOutput = cache.outputs.cacheHit // does not work +// jobOutputs.booleanOutput = cache.outputs.cacheHit_Untyped // drop-in +// jobOutputs.booleanOutput2 = cache.outputs.cacheHit_Untyped +// jobOutputs.stringOutput = cache.outputs.cacheHit +// jobOutputs.stringOutput2 = cache.outputs.cacheHit_Untyped +// //jobOutputs.anyOutput = cache.outputs.cacheHit // does not work +// jobOutputs.anyOutput = Expression(cache.outputs.cacheHit.expression) // drop-in +// jobOutputs.anyOutput2 = cache.outputs.cacheHit_Untyped + +// // cacheHit untyped +// jobOutputs.booleanOutput = cache.outputs.cacheHit_Untyped +// jobOutputs.booleanOutput2 = cache.outputs.cacheHit_Untyped +// jobOutputs.stringOutput = cache.outputs.cacheHit_Untyped +// jobOutputs.stringOutput2 = cache.outputs.cacheHit_Untyped +// jobOutputs.anyOutput = cache.outputs.cacheHit_Untyped +// jobOutputs.anyOutput2 = cache.outputs.cacheHit_Untyped + +// println("cache.outputs.cacheHit.expression = ${cache.outputs.cacheHit.expression}") +// println("cache.outputs.cacheHit.expressionString = ${cache.outputs.cacheHit.expressionString}") +// println("cache.outputs.cacheHit.expression = ${cache.outputs.cacheHit_Untyped.expression}") +// println("cache.outputs.cacheHit.expressionString = ${cache.outputs.cacheHit_Untyped.expressionString}") +// println("cache.outputs[\"cache-hit\"].expression = ${cache.outputs["cache-hit"].expression}") +// println("cache.outputs[\"cache-hit\"].expressionString = ${cache.outputs["cache-hit"].expressionString}") +// +// uses( +// name = "Cache InnoSetup installer on Linux and macOS", +// action = Cache( +// path = listOf("${expr { runner.tool_cache }}/innosetup.exe/6.3.3"), +// key = "innosetup.exe_6.3.3", +//// lookupOnlyExpression = cache.outputs.cacheHit // good type +//// lookupOnlyExpression = Expression(cache.outputs.cacheHit.expression) // bad type +//// lookupOnly_Untyped = cache.outputs.cacheHit.expressionString // bad type +// lookupOnlyExpression = cache.outputs.cacheHit_Untyped // untyped +//// lookupOnlyExpression = cache.outputs["cache-hit"] // ad-hoc-output +// ), +// condition = "(${runner.os} == 'Linux') || (${runner.os} == 'macOS')" +// ) + + run( + name = "Provision InnoSetup on Linux and macOS", + command = "wget -O $innoSetupInstaller https://files.jrsoftware.org/is/6/innosetup-6.3.3.exe", + condition = """ + ((${runner.os} == 'Linux') || (${runner.os} == 'macOS')) + && (${cacheInnoSetupInstaller.outputs.cacheHit} == false) + """.trimIndent() + ) + + run( + name = "Start Xvfb on Linux", + command = "Xvfb :0 -screen 0 1024x768x16 &", + condition = "${runner.os} == 'Linux'" + ) + + uses( + name = "Install InnoSetup on Linux", + env = mapOf( + "DISPLAY" to ":0.0" + ), + // part of work-around for Xvfb sometimes not starting fast enough + action = WretryActionMain( +// command = "wine $innoSetupInstaller /SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART && false", + command = "echo Executing $innoSetupInstaller && false", + attemptLimit = 3, + attemptDelay = 1000, + preRetryCommand = "echo ::warning::Reexecuting $innoSetupInstaller" + ), + condition = "${runner.os} == 'Linux'" + ) + + run( + name = "Install InnoSetup on macOS", + command = "wine $innoSetupInstaller /SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART", + condition = "${runner.os} == 'macOS'" + ) + + run( + name = "Configure Git", + command = "git config --global core.autocrlf input" + ) + + uses( + name = "Checkout", + action = Checkout() + ) + + uses( + name = "Setup Java 11", + action = SetupJava( + javaVersion = "11", + distribution = Temurin + ) + ) + + uses( + name = "Configure Build Properties for Linux", + action = WriteFileAction( + path = "build.properties", + contents = """ + wine.executable = wine + winepath.executable = winepath + innosetup.compiler.executable = /home/runner/.wine/drive_c/Program Files (x86)/Inno Setup 6/ISCC.exe + innosetup.via.wine = true + """.trimIndent() + ), + condition = "${runner.os} == 'Linux'" + ) + + uses( + name = "Configure Build Properties for Windows", + action = WriteFileAction( + path = "build.properties", + contents = """ + innosetup.compiler.executable = C:/Program Files (x86)/Inno Setup 6/ISCC.exe + """.trimIndent() + ), + condition = "${runner.os} == 'Windows'" + ) + + uses( + name = "Configure Build Properties for macOS", + action = WriteFileAction( + path = "build.properties", + contents = """ + wine.executable = wine + winepath.executable = winepath + innosetup.compiler.executable = /Users/runner/.wine/drive_c/Program Files (x86)/Inno Setup 6/ISCC.exe + innosetup.via.wine = true + """.trimIndent() + ), + condition = "${runner.os} == 'macOS'" + ) + + val build = run( + name = "Build", + command = "ant -keep-going dist" + ) + + val buildSuccessful = """ + (${always()}) + && (${build.outcome eq Success}) + """.trimIndent() + + // Upload all result files from macOS build that can build all artifacts as one archive + uses( + name = "Upload All Result Files", + action = UploadArtifact( + name = "All Artifacts", + path = allPossibleArtifacts.map { it.path }, + ifNoFilesFound = Error, + compressionLevel = NoCompression + ), + condition = "($buildSuccessful) && (${runner.os} == 'macOS')" + ) + + allPossibleArtifacts.forEach { (name, path, ignoreIfMissing, condition) -> + val verifyArtifactWasBuilt = run( + name = "Verify $name was Built", + command = if (ignoreIfMissing) { + """ + [ -f $path ] && echo 'found=true' || true >>"${'$'}GITHUB_OUTPUT" + """.trimIndent() + } else { + """ + [ -f $path ] && echo 'found=true' >>"${'$'}GITHUB_OUTPUT" + """.trimIndent() + }, + condition = condition?.let { "($buildSuccessful) && ($it)" } ?: buildSuccessful, + shell = Bash + ) + + uses( + name = "Save $name to Cache", + action = CacheSave( + path = listOf(path), + key = "${expr { github.run_id }} - $name - ${expr { runner.os }}", + enableCrossOsArchive = true + ), + // work-around for https://github.com/typesafegithub/github-workflows-kt/issues/1587 + // && (${verifyArtifactWasBuilt.outputs["found"]} == 'true') + condition = """ + ($buildSuccessful) + && (${verifyArtifactWasBuilt.outcome eq Success}) + && (steps.${verifyArtifactWasBuilt.id}.outputs.found == 'true') + """.trimIndent() + ) + } + + val uploadAllUnexpectedResultFiles = uses( + name = "Upload All Unexpected Result Files", + action = UploadArtifact( + name = "Unexpected Artifacts (${expr { runner.os }})", + path = listOf("dist") + allPossibleArtifacts.map { "!${it.path}" }, + ifNoFilesFound = Ignore, + compressionLevel = NoCompression + ), + condition = buildSuccessful + ) + + run( + name = "Verify No Unexpected Result Files", + command = "[ '${expr { uploadAllUnexpectedResultFiles.outputs.artifactId }}' == '' ]", + condition = """ + (${always()}) + && (${uploadAllUnexpectedResultFiles.outcome eq Success}) + """.trimIndent(), + shell = Bash + ) + } + + job( + id = "upload-artifacts", + name = "Upload Individual Artifacts from one of the Jobs", + runsOn = UbuntuLatest, + needs = listOf(build), + condition = """ + (${always()} + && (${build.result neq Skipped})) + """.trimIndent() + ) { + allPossibleArtifacts.forEach { (name, path) -> + uses( + name = "Restore $name from Cache", + action = CacheRestore( + path = listOf(path), +// key = "${build.outputs.stringOutput.expressionString} - ${build.outputs.stringOutput2.expressionString} - ${build.outputs.booleanOutput.expressionString} - ${build.outputs.booleanOutput2.expressionString} - ${build.outputs.anyOutput.expressionString} - ${build.outputs.anyOutput2.expressionString} - ${expr { github.run_id }} - $name - macOS", + key = "${expr { github.run_id }} - $name - macOS", + restoreKeys = listOf("${expr { github.run_id }} - $name - ") + ) + ) + + uses( + name = "Upload $name", + action = UploadArtifact( + name = name, + path = listOf(path), + ifNoFilesFound = Ignore, + compressionLevel = NoCompression + ) + ) + } + } +} + +data class Artifact( + val name: String, + val path: String, + val ignoreIfMissing: Boolean = false, + val condition: String? = null +) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..88e7184d0 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,672 @@ +# This file was generated using Kotlin DSL (.github/workflows/build.main.kts). +# If you want to modify the workflow, please change the Kotlin file and regenerate this YAML file. +# Generated with https://github.com/typesafegithub/github-workflows-kt + +name: 'Build' +on: + push: {} + pull_request: {} +jobs: + check_yaml_consistency: + name: 'Check YAML consistency' + runs-on: 'ubuntu-latest' + steps: + - id: 'step-0' + name: 'Check out' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Execute script' + run: 'rm ''.github/workflows/build.yaml'' && ''.github/workflows/build.main.kts''' + - id: 'step-2' + name: 'Consistency check' + run: 'git diff --exit-code ''.github/workflows/build.yaml''' + build: + name: 'Build on ${{ matrix.os }}' + runs-on: '${{ matrix.os }}' + needs: + - 'check_yaml_consistency' + strategy: + fail-fast: false + matrix: + os: + - 'ubuntu-latest' + - 'windows-latest' + - 'macos-latest' + steps: + - id: 'step-0' + name: 'Install Wine on Linux' + run: |- + sudo dpkg --add-architecture i386 + sudo wget -nc -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key + sudo wget -NP /etc/apt/sources.list.d/ "https://dl.winehq.org/wine-builds/ubuntu/dists/$(lsb_release -c | grep -o '\w*$')/winehq-$(lsb_release -c | grep -o '\w*$').sources" + sudo apt update + sudo apt install --yes --no-install-recommends winehq-stable + winecfg /v win10 + if: 'runner.os == ''Linux''' + - id: 'step-1' + name: 'Install Wine on macOS' + uses: 'tecolicom/actions-use-homebrew-tools@v1' + with: + tools: 'wine-stable' + path: '/Applications /Library' + if: 'runner.os == ''macOS''' + - id: 'step-2' + name: 'Cache InnoSetup installer on Linux and macOS' + uses: 'actions/cache@v4' + with: + path: '${{ runner.temp }}/innosetup-6.3.3.exe' + key: 'innosetup-6.3.3.exe' + if: '(runner.os == ''Linux'') || (runner.os == ''macOS'')' + - id: 'step-3' + name: 'Provision InnoSetup on Linux and macOS' + run: 'wget -O ${{ runner.temp }}/innosetup-6.3.3.exe https://files.jrsoftware.org/is/6/innosetup-6.3.3.exe' + if: |- + ((runner.os == 'Linux') || (runner.os == 'macOS')) + && (steps.step-2.outputs.cache-hit == false) + - id: 'step-4' + name: 'Start Xvfb on Linux' + run: 'Xvfb :0 -screen 0 1024x768x16 &' + if: 'runner.os == ''Linux''' + - id: 'step-5' + name: 'Install InnoSetup on Linux' + uses: 'Wandalen/wretry.action/main@v3' + with: + command: 'echo Executing ${{ runner.temp }}/innosetup-6.3.3.exe && false' + pre_retry_command: 'echo ::warning::Reexecuting ${{ runner.temp }}/innosetup-6.3.3.exe' + attempt_limit: '3' + attempt_delay: '1000' + env: + DISPLAY: ':0.0' + if: 'runner.os == ''Linux''' + - id: 'step-6' + name: 'Install InnoSetup on macOS' + run: 'wine ${{ runner.temp }}/innosetup-6.3.3.exe /SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART' + if: 'runner.os == ''macOS''' + - id: 'step-7' + name: 'Configure Git' + run: 'git config --global core.autocrlf input' + - id: 'step-8' + name: 'Checkout' + uses: 'actions/checkout@v4' + - id: 'step-9' + name: 'Setup Java 11' + uses: 'actions/setup-java@v4' + with: + java-version: '11' + distribution: 'temurin' + - id: 'step-10' + name: 'Configure Build Properties for Linux' + uses: 'DamianReeves/write-file-action@v1.3' + with: + path: 'build.properties' + contents: |- + wine.executable = wine + winepath.executable = winepath + innosetup.compiler.executable = /home/runner/.wine/drive_c/Program Files (x86)/Inno Setup 6/ISCC.exe + innosetup.via.wine = true + if: 'runner.os == ''Linux''' + - id: 'step-11' + name: 'Configure Build Properties for Windows' + uses: 'DamianReeves/write-file-action@v1.3' + with: + path: 'build.properties' + contents: 'innosetup.compiler.executable = C:/Program Files (x86)/Inno Setup 6/ISCC.exe' + if: 'runner.os == ''Windows''' + - id: 'step-12' + name: 'Configure Build Properties for macOS' + uses: 'DamianReeves/write-file-action@v1.3' + with: + path: 'build.properties' + contents: |- + wine.executable = wine + winepath.executable = winepath + innosetup.compiler.executable = /Users/runner/.wine/drive_c/Program Files (x86)/Inno Setup 6/ISCC.exe + innosetup.via.wine = true + if: 'runner.os == ''macOS''' + - id: 'step-13' + name: 'Build' + run: 'ant -keep-going dist' + - id: 'step-14' + name: 'Upload All Result Files' + uses: 'actions/upload-artifact@v4' + with: + name: 'All Artifacts' + path: |- + dist/jedit*manual-a4.pdf + dist/jedit*manual-letter.pdf + dist/jedit*source.tar.bz2 + dist/jedit*install.jar + dist/jedit-*-noarch-1sao.tgz + dist/jedit_*_all.deb + dist/jedit*install.exe + dist/jedit*install.dmg + dist/jedit*-dist-mac-finish.tar.bz2 + dist/Packages + dist/Packages.gz + dist/Packages.bz2 + dist/Release + dist/Release.gpg + if-no-files-found: 'error' + compression-level: '0' + if: |- + ((always()) + && (steps.step-13.outcome == 'success')) && (runner.os == 'macOS') + - id: 'step-15' + name: 'Verify Manual in A4 Paper size was Built' + shell: 'bash' + run: '[ -f dist/jedit*manual-a4.pdf ] && echo ''found=true'' >>"$GITHUB_OUTPUT"' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-16' + name: 'Save Manual in A4 Paper size to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/jedit*manual-a4.pdf' + key: '${{ github.run_id }} - Manual in A4 Paper size - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-15.outcome == 'success') + && (steps.step-15.outputs.found == 'true') + - id: 'step-17' + name: 'Verify Manual in Letter Paper size was Built' + shell: 'bash' + run: '[ -f dist/jedit*manual-letter.pdf ] && echo ''found=true'' >>"$GITHUB_OUTPUT"' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-18' + name: 'Save Manual in Letter Paper size to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/jedit*manual-letter.pdf' + key: '${{ github.run_id }} - Manual in Letter Paper size - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-17.outcome == 'success') + && (steps.step-17.outputs.found == 'true') + - id: 'step-19' + name: 'Verify Source Package was Built' + shell: 'bash' + run: '[ -f dist/jedit*source.tar.bz2 ] && echo ''found=true'' >>"$GITHUB_OUTPUT"' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-20' + name: 'Save Source Package to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/jedit*source.tar.bz2' + key: '${{ github.run_id }} - Source Package - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-19.outcome == 'success') + && (steps.step-19.outputs.found == 'true') + - id: 'step-21' + name: 'Verify Java based Installer was Built' + shell: 'bash' + run: '[ -f dist/jedit*install.jar ] && echo ''found=true'' >>"$GITHUB_OUTPUT"' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-22' + name: 'Save Java based Installer to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/jedit*install.jar' + key: '${{ github.run_id }} - Java based Installer - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-21.outcome == 'success') + && (steps.step-21.outputs.found == 'true') + - id: 'step-23' + name: 'Verify Slackware Installer was Built' + shell: 'bash' + run: '[ -f dist/jedit-*-noarch-1sao.tgz ] && echo ''found=true'' >>"$GITHUB_OUTPUT"' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-24' + name: 'Save Slackware Installer to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/jedit-*-noarch-1sao.tgz' + key: '${{ github.run_id }} - Slackware Installer - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-23.outcome == 'success') + && (steps.step-23.outputs.found == 'true') + - id: 'step-25' + name: 'Verify Debian Installer was Built' + shell: 'bash' + run: '[ -f dist/jedit_*_all.deb ] && echo ''found=true'' >>"$GITHUB_OUTPUT"' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-26' + name: 'Save Debian Installer to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/jedit_*_all.deb' + key: '${{ github.run_id }} - Debian Installer - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-25.outcome == 'success') + && (steps.step-25.outputs.found == 'true') + - id: 'step-27' + name: 'Verify Windows Installer was Built' + shell: 'bash' + run: '[ -f dist/jedit*install.exe ] && echo ''found=true'' >>"$GITHUB_OUTPUT"' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-28' + name: 'Save Windows Installer to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/jedit*install.exe' + key: '${{ github.run_id }} - Windows Installer - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-27.outcome == 'success') + && (steps.step-27.outputs.found == 'true') + - id: 'step-29' + name: 'Verify macOS Installer was Built' + shell: 'bash' + run: '[ -f dist/jedit*install.dmg ] && echo ''found=true'' >>"$GITHUB_OUTPUT"' + if: |- + ((always()) + && (steps.step-13.outcome == 'success')) && (runner.os == 'macOS') + - id: 'step-30' + name: 'Save macOS Installer to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/jedit*install.dmg' + key: '${{ github.run_id }} - macOS Installer - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-29.outcome == 'success') + && (steps.step-29.outputs.found == 'true') + - id: 'step-31' + name: 'Verify macOS Intermediate Result was Built' + shell: 'bash' + run: '[ -f dist/jedit*-dist-mac-finish.tar.bz2 ] && echo ''found=true'' >>"$GITHUB_OUTPUT"' + if: |- + ((always()) + && (steps.step-13.outcome == 'success')) && (runner.os != 'macOS') + - id: 'step-32' + name: 'Save macOS Intermediate Result to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/jedit*-dist-mac-finish.tar.bz2' + key: '${{ github.run_id }} - macOS Intermediate Result - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-31.outcome == 'success') + && (steps.step-31.outputs.found == 'true') + - id: 'step-33' + name: 'Verify Debian Repository Packages File was Built' + shell: 'bash' + run: '[ -f dist/Packages ] && echo ''found=true'' || true >>"$GITHUB_OUTPUT"' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-34' + name: 'Save Debian Repository Packages File to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/Packages' + key: '${{ github.run_id }} - Debian Repository Packages File - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-33.outcome == 'success') + && (steps.step-33.outputs.found == 'true') + - id: 'step-35' + name: 'Verify Debian Repository Packages File (gz) was Built' + shell: 'bash' + run: '[ -f dist/Packages.gz ] && echo ''found=true'' || true >>"$GITHUB_OUTPUT"' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-36' + name: 'Save Debian Repository Packages File (gz) to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/Packages.gz' + key: '${{ github.run_id }} - Debian Repository Packages File (gz) - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-35.outcome == 'success') + && (steps.step-35.outputs.found == 'true') + - id: 'step-37' + name: 'Verify Debian Repository Packages File (bz2) was Built' + shell: 'bash' + run: '[ -f dist/Packages.bz2 ] && echo ''found=true'' || true >>"$GITHUB_OUTPUT"' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-38' + name: 'Save Debian Repository Packages File (bz2) to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/Packages.bz2' + key: '${{ github.run_id }} - Debian Repository Packages File (bz2) - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-37.outcome == 'success') + && (steps.step-37.outputs.found == 'true') + - id: 'step-39' + name: 'Verify Debian Repository Release File was Built' + shell: 'bash' + run: '[ -f dist/Release ] && echo ''found=true'' || true >>"$GITHUB_OUTPUT"' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-40' + name: 'Save Debian Repository Release File to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/Release' + key: '${{ github.run_id }} - Debian Repository Release File - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-39.outcome == 'success') + && (steps.step-39.outputs.found == 'true') + - id: 'step-41' + name: 'Verify Debian Repository Release File Signature was Built' + shell: 'bash' + run: '[ -f dist/Release.gpg ] && echo ''found=true'' || true >>"$GITHUB_OUTPUT"' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-42' + name: 'Save Debian Repository Release File Signature to Cache' + uses: 'actions/cache/save@v4' + with: + path: 'dist/Release.gpg' + key: '${{ github.run_id }} - Debian Repository Release File Signature - ${{ runner.os }}' + enableCrossOsArchive: 'true' + if: |2- + ((always()) + && (steps.step-13.outcome == 'success')) + && (steps.step-41.outcome == 'success') + && (steps.step-41.outputs.found == 'true') + - id: 'step-43' + name: 'Upload All Unexpected Result Files' + uses: 'actions/upload-artifact@v4' + with: + name: 'Unexpected Artifacts (${{ runner.os }})' + path: |- + dist + !dist/jedit*manual-a4.pdf + !dist/jedit*manual-letter.pdf + !dist/jedit*source.tar.bz2 + !dist/jedit*install.jar + !dist/jedit-*-noarch-1sao.tgz + !dist/jedit_*_all.deb + !dist/jedit*install.exe + !dist/jedit*install.dmg + !dist/jedit*-dist-mac-finish.tar.bz2 + !dist/Packages + !dist/Packages.gz + !dist/Packages.bz2 + !dist/Release + !dist/Release.gpg + if-no-files-found: 'ignore' + compression-level: '0' + if: |- + (always()) + && (steps.step-13.outcome == 'success') + - id: 'step-44' + name: 'Verify No Unexpected Result Files' + shell: 'bash' + run: '[ ''${{ steps.step-43.outputs.artifact-id }}'' == '''' ]' + if: |- + (always()) + && (steps.step-43.outcome == 'success') + upload-artifacts: + name: 'Upload Individual Artifacts from one of the Jobs' + runs-on: 'ubuntu-latest' + needs: + - 'build' + - 'check_yaml_consistency' + if: |- + (always() + && (needs.build.result != 'skipped')) + steps: + - id: 'step-0' + name: 'Restore Manual in A4 Paper size from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/jedit*manual-a4.pdf' + key: '${{ github.run_id }} - Manual in A4 Paper size - macOS' + restore-keys: '${{ github.run_id }} - Manual in A4 Paper size - ' + - id: 'step-1' + name: 'Upload Manual in A4 Paper size' + uses: 'actions/upload-artifact@v4' + with: + name: 'Manual in A4 Paper size' + path: 'dist/jedit*manual-a4.pdf' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-2' + name: 'Restore Manual in Letter Paper size from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/jedit*manual-letter.pdf' + key: '${{ github.run_id }} - Manual in Letter Paper size - macOS' + restore-keys: '${{ github.run_id }} - Manual in Letter Paper size - ' + - id: 'step-3' + name: 'Upload Manual in Letter Paper size' + uses: 'actions/upload-artifact@v4' + with: + name: 'Manual in Letter Paper size' + path: 'dist/jedit*manual-letter.pdf' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-4' + name: 'Restore Source Package from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/jedit*source.tar.bz2' + key: '${{ github.run_id }} - Source Package - macOS' + restore-keys: '${{ github.run_id }} - Source Package - ' + - id: 'step-5' + name: 'Upload Source Package' + uses: 'actions/upload-artifact@v4' + with: + name: 'Source Package' + path: 'dist/jedit*source.tar.bz2' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-6' + name: 'Restore Java based Installer from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/jedit*install.jar' + key: '${{ github.run_id }} - Java based Installer - macOS' + restore-keys: '${{ github.run_id }} - Java based Installer - ' + - id: 'step-7' + name: 'Upload Java based Installer' + uses: 'actions/upload-artifact@v4' + with: + name: 'Java based Installer' + path: 'dist/jedit*install.jar' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-8' + name: 'Restore Slackware Installer from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/jedit-*-noarch-1sao.tgz' + key: '${{ github.run_id }} - Slackware Installer - macOS' + restore-keys: '${{ github.run_id }} - Slackware Installer - ' + - id: 'step-9' + name: 'Upload Slackware Installer' + uses: 'actions/upload-artifact@v4' + with: + name: 'Slackware Installer' + path: 'dist/jedit-*-noarch-1sao.tgz' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-10' + name: 'Restore Debian Installer from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/jedit_*_all.deb' + key: '${{ github.run_id }} - Debian Installer - macOS' + restore-keys: '${{ github.run_id }} - Debian Installer - ' + - id: 'step-11' + name: 'Upload Debian Installer' + uses: 'actions/upload-artifact@v4' + with: + name: 'Debian Installer' + path: 'dist/jedit_*_all.deb' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-12' + name: 'Restore Windows Installer from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/jedit*install.exe' + key: '${{ github.run_id }} - Windows Installer - macOS' + restore-keys: '${{ github.run_id }} - Windows Installer - ' + - id: 'step-13' + name: 'Upload Windows Installer' + uses: 'actions/upload-artifact@v4' + with: + name: 'Windows Installer' + path: 'dist/jedit*install.exe' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-14' + name: 'Restore macOS Installer from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/jedit*install.dmg' + key: '${{ github.run_id }} - macOS Installer - macOS' + restore-keys: '${{ github.run_id }} - macOS Installer - ' + - id: 'step-15' + name: 'Upload macOS Installer' + uses: 'actions/upload-artifact@v4' + with: + name: 'macOS Installer' + path: 'dist/jedit*install.dmg' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-16' + name: 'Restore macOS Intermediate Result from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/jedit*-dist-mac-finish.tar.bz2' + key: '${{ github.run_id }} - macOS Intermediate Result - macOS' + restore-keys: '${{ github.run_id }} - macOS Intermediate Result - ' + - id: 'step-17' + name: 'Upload macOS Intermediate Result' + uses: 'actions/upload-artifact@v4' + with: + name: 'macOS Intermediate Result' + path: 'dist/jedit*-dist-mac-finish.tar.bz2' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-18' + name: 'Restore Debian Repository Packages File from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/Packages' + key: '${{ github.run_id }} - Debian Repository Packages File - macOS' + restore-keys: '${{ github.run_id }} - Debian Repository Packages File - ' + - id: 'step-19' + name: 'Upload Debian Repository Packages File' + uses: 'actions/upload-artifact@v4' + with: + name: 'Debian Repository Packages File' + path: 'dist/Packages' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-20' + name: 'Restore Debian Repository Packages File (gz) from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/Packages.gz' + key: '${{ github.run_id }} - Debian Repository Packages File (gz) - macOS' + restore-keys: '${{ github.run_id }} - Debian Repository Packages File (gz) - ' + - id: 'step-21' + name: 'Upload Debian Repository Packages File (gz)' + uses: 'actions/upload-artifact@v4' + with: + name: 'Debian Repository Packages File (gz)' + path: 'dist/Packages.gz' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-22' + name: 'Restore Debian Repository Packages File (bz2) from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/Packages.bz2' + key: '${{ github.run_id }} - Debian Repository Packages File (bz2) - macOS' + restore-keys: '${{ github.run_id }} - Debian Repository Packages File (bz2) - ' + - id: 'step-23' + name: 'Upload Debian Repository Packages File (bz2)' + uses: 'actions/upload-artifact@v4' + with: + name: 'Debian Repository Packages File (bz2)' + path: 'dist/Packages.bz2' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-24' + name: 'Restore Debian Repository Release File from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/Release' + key: '${{ github.run_id }} - Debian Repository Release File - macOS' + restore-keys: '${{ github.run_id }} - Debian Repository Release File - ' + - id: 'step-25' + name: 'Upload Debian Repository Release File' + uses: 'actions/upload-artifact@v4' + with: + name: 'Debian Repository Release File' + path: 'dist/Release' + if-no-files-found: 'ignore' + compression-level: '0' + - id: 'step-26' + name: 'Restore Debian Repository Release File Signature from Cache' + uses: 'actions/cache/restore@v4' + with: + path: 'dist/Release.gpg' + key: '${{ github.run_id }} - Debian Repository Release File Signature - macOS' + restore-keys: '${{ github.run_id }} - Debian Repository Release File Signature - ' + - id: 'step-27' + name: 'Upload Debian Repository Release File Signature' + uses: 'actions/upload-artifact@v4' + with: + name: 'Debian Repository Release File Signature' + path: 'dist/Release.gpg' + if-no-files-found: 'ignore' + compression-level: '0' diff --git a/.github/workflows/check-all-workflow-yaml-consistency.main.kts b/.github/workflows/check-all-workflow-yaml-consistency.main.kts new file mode 100755 index 000000000..50a41ecfa --- /dev/null +++ b/.github/workflows/check-all-workflow-yaml-consistency.main.kts @@ -0,0 +1,46 @@ +#!/usr/bin/env kotlin + +@file:Repository("https://repo.maven.apache.org/maven2/") +@file:DependsOn("io.github.typesafegithub:github-workflows-kt:2.3.0") + +@file:Repository("https://bindings.krzeminski.it/") +@file:DependsOn("actions:checkout:v4") + +import io.github.typesafegithub.workflows.actions.actions.Checkout +import io.github.typesafegithub.workflows.domain.RunnerType.UbuntuLatest +import io.github.typesafegithub.workflows.domain.triggers.PullRequest +import io.github.typesafegithub.workflows.domain.triggers.Push +import io.github.typesafegithub.workflows.dsl.workflow + +workflow( + name = "Check all Workflow YAML Consistency", + on = listOf( + Push(), + PullRequest() + ), + sourceFile = __FILE__ +) { + job( + id = "check_all_workflow_yaml_consistency", + name = "Check all Workflow YAML Consistency", + runsOn = UbuntuLatest + ) { + uses( + name = "Checkout", + action = Checkout() + ) + + run( + name = "Regenerate all Workflow YAMLs", + command = """find .github/workflows -mindepth 1 -maxdepth 1 -name '*.main.kts' -exec {} \;""" + ) + + run( + name = "Check for Modifications", + command = """ + git add --intent-to-add . + git diff --exit-code + """.trimIndent() + ) + } +} diff --git a/.github/workflows/mirror-canonical-repository.main.kts b/.github/workflows/mirror-canonical-repository.main.kts index 1eb84e6fc..2648c39bb 100755 --- a/.github/workflows/mirror-canonical-repository.main.kts +++ b/.github/workflows/mirror-canonical-repository.main.kts @@ -11,20 +11,16 @@ import io.github.typesafegithub.workflows.domain.triggers.WorkflowDispatch import io.github.typesafegithub.workflows.dsl.expressions.Contexts.secrets import io.github.typesafegithub.workflows.dsl.expressions.expr import io.github.typesafegithub.workflows.dsl.workflow -import io.github.typesafegithub.workflows.yaml.ConsistencyCheckJobConfig.Disabled workflow( name = "Mirror Canonical Repository", on = listOf( Push(), RepositoryDispatch(), - //TODO - //Schedule(triggers = listOf(Cron(minute = "44"))), + Schedule(triggers = listOf(Cron(minute = "44"))), WorkflowDispatch() ), - sourceFile = __FILE__, - //TODO - consistencyCheckJobConfig = Disabled + sourceFile = __FILE__ ) { job( id = "mirror_repository", @@ -35,6 +31,7 @@ workflow( name = "Clone Canonical Repository", command = "git clone --bare git://git.code.sf.net/p/jedit/jEdit.bak ." ) + val MIRROR_TOKEN by secrets run( name = "Push To Mirror Repository", diff --git a/build.xml b/build.xml index d4ea77222..9928ed7fc 100644 --- a/build.xml +++ b/build.xml @@ -167,6 +167,7 @@ + (beta != 99 ? "pre" + beta : "." + micro); project.setUserProperty("jedit.version", version); project.setUserProperty("jedit.build.number", build); + project.setUserProperty("jedit.build.number.no.leading.zeros", major + "." + minor + "." + beta + "." + micro); project.setUserProperty("jedit.version.final", Boolean.toString(beta == 99)); - - - - - - - - + + + + + + + + + + + @@ -1416,6 +1420,8 @@ value="${jar.filename}"/> + @target.java.version@ - @jedit.build.number@ + @jedit.build.number.no.leading.zeros@ @jedit.version@ jEdit - Programmer's Text Editor Copyright © 1998-@current.year@ Contributors - @jedit.build.number@ + @jedit.build.number.no.leading.zeros@ @jedit.version@ jEdit Contributors