blob: 7242dc27f5002ccdb035ddf7b74d139f78ae7c94 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import com.github.vlsi.gradle.crlf.CrLfSpec
import com.github.vlsi.gradle.crlf.LineEndings
import com.github.vlsi.gradle.git.FindGitAttributes
import com.github.vlsi.gradle.git.dsl.gitignore
import org.gradle.api.internal.TaskOutputsInternal
plugins {
id("com.github.vlsi.crlf")
id("com.github.vlsi.stage-vote-release")
signing
}
var jars = arrayOf(
":src:bshclient",
":src:launcher",
":src:components",
":src:core",
// ":src:examples",
":src:functions",
":src:jorphan",
":src:protocol:bolt",
":src:protocol:ftp",
":src:protocol:http",
":src:protocol:java",
":src:protocol:jdbc",
":src:protocol:jms",
":src:protocol:junit",
":src:protocol:ldap",
":src:protocol:mail",
":src:protocol:mongodb",
":src:protocol:native",
":src:protocol:tcp")
// isCanBeConsumed = false ==> other modules must not use the configuration as a dependency
val buildDocs by configurations.creating {
isCanBeConsumed = false
}
val binLicense by configurations.creating {
isCanBeConsumed = false
}
val srcLicense by configurations.creating {
isCanBeConsumed = false
}
// Note: you can inspect final classpath (list of jars in the binary distribution) via
// gw dependencies --configuration runtimeClasspath
dependencies {
for (p in jars) {
api(project(p))
testCompile(project(p, "testClasses"))
}
runtimeOnly("com.github.bulenkov.darcula:darcula") {
because("""
It just looks good, however Darcula is not used explicitly,
so the dependency is added for distribution only""".trimIndent())
}
binLicense(project(":src:licenses", "binLicense"))
srcLicense(project(":src:licenses", "srcLicense"))
buildDocs(platform(project(":src:bom")))
buildDocs("org.apache.velocity:velocity")
buildDocs("commons-lang:commons-lang")
buildDocs("commons-collections:commons-collections")
buildDocs("org.jdom:jdom")
}
tasks.named(BasePlugin.CLEAN_TASK_NAME).configure {
doLast {
// createDist can't yet remove outdated jars (e.g. when dependency is updated to a newer version)
// so we enhance "clean" task to kill the jars
delete(fileTree("$rootDir/bin") { include("ApacheJMeter.jar") })
delete(fileTree("$rootDir/lib") { include("*.jar") })
delete(fileTree("$rootDir/lib/ext") { include("ApacheJMeter*.jar") })
}
}
// Libs are populated dynamically since we can't get the full set of dependencies
// before we execute all the build scripts
val libs = copySpec {
// Third-party dependencies + jorphan.jar
}
val libsExt = copySpec {
// Apache JMeter jars
}
val binLibs = copySpec {
// ApacheJMeter.jar launcher
}
// Splits jar dependencies to "lib", "lib/ext", and "bin" folders
val populateLibs by tasks.registering {
dependsOn(configurations.runtimeClasspath)
doLast {
val deps = configurations.runtimeClasspath.get().resolvedConfiguration.resolvedArtifacts
// This ensures project exists, if project is renamed, names should be corrected here as wells
val launcherProject = project(":src:launcher").path
val bshclientProject = project(":src:bshclient").path
val jorphanProject = project(":src:jorphan").path
listOf(libs, libsExt, binLibs).forEach {
it.fileMode = "644".toInt(8)
it.dirMode = "755".toInt(8)
}
for (dep in deps) {
val compId = dep.id.componentIdentifier
// The path is "relative" to rootDir/lib
when (compId) {
is ProjectComponentIdentifier ->
(when (compId.projectPath) {
launcherProject -> binLibs
jorphanProject, bshclientProject -> libs
else -> libsExt
}).from(dep.file) {
// Technically speaking, current JMeter artifacts do not have version in the name
// however rename is here just in case
rename { dep.name + "." + dep.extension }
}
else -> libs.from(dep.file)
}
}
}
}
// This adds dependency on "populateLibs" task
// This makes uses of these copySpecs transparently depend on the builder task
libs.from(populateLibs)
libsExt.from(populateLibs)
binLibs.from(populateLibs)
val copyLibs by tasks.registering(Sync::class) {
val junitSampleJar = project(":src:protocol:junit-sample").tasks.named(JavaPlugin.JAR_TASK_NAME)
dependsOn(junitSampleJar)
val generatorJar = project(":src:generator").tasks.named(JavaPlugin.JAR_TASK_NAME)
// Can't use $rootDir since Gradle somehow reports .gradle/caches/ as "always modified"
rootSpec.into("$rootDir/lib")
with(libs)
preserve {
// Sync does not really know which files it copied during previous times, so
// it just removes everything it sees.
// We configure it to keep txt files that should be present there (the files come from Git source tree)
include("**/*.txt")
// Keep jars in lib/ext so developers don't have to re-install the plugsin again and again
include("ext/*.jar")
}
into("ext") {
with(libsExt)
from(generatorJar)
}
into("junit") {
from(junitSampleJar) {
rename { "test.jar" }
}
}
}
val copyBinLibs by tasks.registering(Copy::class) {
// Can't use $rootDir since Gradle somehow reports .gradle/caches/ as "always modified"
rootSpec.into("$rootDir/bin")
with(binLibs)
}
val createDist by tasks.registering {
group = LifecycleBasePlugin.BUILD_GROUP
description = "Copies JMeter jars and dependencies to projectRoot/lib/ folder"
dependsOn(copyLibs)
dependsOn(copyBinLibs)
}
// This task scans the project for gitignore / gitattributes, and that is reused for building
// source/binary artifacts with the appropriate eol/executable file flags
val gitProps by rootProject.tasks.existing(FindGitAttributes::class)
fun createAnakiaTask(
taskName: String,
baseDir: String,
extension: String = ".html",
style: String,
velocityProperties: String,
projectFile: String,
excludes: Array<String>,
includes: Array<String>
): TaskProvider<Task> {
val outputDir = "$buildDir/docs/$taskName"
val prepareProps = tasks.register("prepareProperties$taskName") {
// AnakiaTask can't use relative paths, and it forbids ../, so we create a dedicated
// velocity.properties file that contains absolute path
inputs.file(velocityProperties)
val outputProps = "$buildDir/docProps/$taskName/velocity.properties"
outputs.file(outputProps)
doLast {
// Unfortunately, Velocity does not use Java properties format.
// For instance, Properties escape : as \:, however Velocity does not understand that.
// Thus it tries to use c\:\path\to\workspace which does not work
val p = `java.util`.Properties()
file(velocityProperties).reader().use {
p.load(it)
}
p["resource.loader"] = "file"
p["file.resource.loader.path"] = baseDir
p["file.resource.loader.class"] = "org.apache.velocity.runtime.resource.loader.FileResourceLoader"
val specials = Regex("""([,\\])""")
val lines = p.entries
.map { (it.key as String) + "=" + ((it.value as String).replace(specials, """\\$1""")) }
.sorted()
file(outputProps).apply {
parentFile.run { isDirectory || mkdirs() } || throw IllegalStateException("Unable to create directory $parentFile")
writer().use {
it.appendln("# Auto-generated from $velocityProperties to pass absolute path to Velocity")
for (line in lines) {
it.appendln(line)
}
}
}
}
}
return tasks.register(taskName) {
inputs.file("$baseDir/$style")
inputs.file("$baseDir/$projectFile")
inputs.files(fileTree(baseDir) {
include(*includes)
exclude(*excludes)
})
inputs.property("extension", extension)
outputs.dir(outputDir)
dependsOn(prepareProps)
doLast {
ant.withGroovyBuilder {
"taskdef"("name" to "anakia",
"classname" to "org.apache.velocity.anakia.AnakiaTask",
"classpath" to buildDocs.asPath)
"anakia"("basedir" to baseDir,
"destdir" to outputDir,
"extension" to extension,
"style" to style,
"projectFile" to projectFile,
"excludes" to excludes.joinToString(" "),
"includes" to includes.joinToString(" "),
"lastModifiedCheck" to "true",
"velocityPropertiesFile" to prepareProps.get().outputs.files.singleFile)
}
}
}
}
val xdocs = "$rootDir/xdocs"
fun CopySpec.docCssAndImages() {
from(xdocs) {
include("css/**")
include("images/**")
}
}
fun CopySpec.manuals() {
from(xdocs) {
include("demos/**")
include("extending/jmeter_tutorial.pdf")
include("usermanual/**/*.pdf")
}
}
fun CopySpec.printableDocumentation() {
into("docs") {
docCssAndImages()
}
into("printable_docs") {
from(buildPrintableDoc)
manuals()
}
}
val buildPrintableDoc = createAnakiaTask("buildPrintableDoc", baseDir = xdocs,
style = "stylesheets/site_printable.vsl",
velocityProperties = "$xdocs/velocity.properties",
projectFile = "stylesheets/printable_project.xml",
excludes = arrayOf("**/stylesheets/**", "extending.xml", "extending/*.xml"),
includes = arrayOf("**/*.xml"))
val previewPrintableDocs by tasks.registering(Copy::class) {
group = JavaBasePlugin.DOCUMENTATION_GROUP
description = "Creates preview of a printable documentation to build/docs/printable_preview"
into("$buildDir/docs/printable_preview")
CrLfSpec().run {
gitattributes(gitProps)
printableDocumentation()
}
}
val lastEditYear: String by rootProject.extra
fun xslt(
subdir: String,
outputDir: String,
includes: Array<String> = arrayOf("*.xml"),
excludes: Array<String> = arrayOf("extending.xml")
) {
val relativePath = if (subdir.isEmpty()) "." else ".."
ant.withGroovyBuilder {
"xslt"("style" to "$xdocs/stylesheets/website-style.xsl",
"basedir" to "$xdocs/$subdir",
"destdir" to "$outputDir/$subdir",
"excludes" to excludes.joinToString(" "),
"includes" to includes.joinToString(" ")
) {
"param"("name" to "relative-path", "expression" to relativePath)
"param"("name" to "subdir", "expression" to subdir)
"param"("name" to "year", "expression" to lastEditYear)
}
}
}
val processSiteXslt by tasks.registering {
val outputDir = "$buildDir/siteXslt"
inputs.files(xdocs)
inputs.property("year", lastEditYear)
outputs.dir(outputDir)
doLast {
for (f in (outputs as TaskOutputsInternal).previousOutputFiles) {
f.delete()
}
for (i in arrayOf("", "usermanual", "localising")) {
xslt(i, outputDir)
}
}
}
fun CopySpec.siteLayout() {
// TODO: generate doap_JMeter.rdf
from("$xdocs/download_jmeter.cgi")
into("api") {
javadocs()
}
from(processSiteXslt)
docCssAndImages()
manuals()
}
// See https://github.com/gradle/gradle/issues/10960
val previewSiteDir = buildDir.resolve("site")
val previewSite by tasks.registering(Sync::class) {
group = JavaBasePlugin.DOCUMENTATION_GROUP
description = "Creates preview of a site to build/docs/site"
into(previewSiteDir)
CrLfSpec().run {
gitattributes(gitProps)
siteLayout()
}
}
val distributionGroup = "distribution"
val baseFolder = "apache-jmeter-${rootProject.version}"
fun CopySpec.javadocs() = from(javadocAggregate)
fun CopySpec.excludeLicenseFromSourceRelease() {
// Source release has "/licenses" folder with licenses for third-party dependencies
// It is populated by "dependencyLicenses" above,
// so we ignore the folder when building source releases
exclude("licenses/**")
exclude("LICENSE")
}
fun CrLfSpec.binaryLayout() = copySpec {
gitattributes(gitProps)
into(baseFolder) {
// Note: license content is taken from "/build/..", so gitignore should not be used
// Note: this is a "license + third-party licenses", not just Apache-2.0
// Note: files(...) adds both "files" and "dependency"
from(files(binLicense))
from(rootDir) {
gitignore(gitProps)
exclude("bin/testfiles")
include("bin/**")
include("lib/ext/**")
include("lib/junit/**")
include("extras/**")
include("README.md")
excludeLicenseFromSourceRelease()
}
into("bin") {
with(binLibs)
}
into("lib") {
with(libs)
into("ext") {
with(libsExt)
}
}
printableDocumentation()
into("docs/api") {
javadocs()
}
}
}
fun CrLfSpec.sourceLayout() = copySpec {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
gitattributes(gitProps)
into(baseFolder) {
// Note: license content is taken from "/build/..", so gitignore should not be used
// Note: this is a "license + third-party licenses", not just Apache-2.0
// Note: files(...) adds both "files" and "dependency"
from(files(srcLicense))
// Include all the source files
from(rootDir) {
gitignore(gitProps)
excludeLicenseFromSourceRelease()
}
}
}
val javadocAggregate by tasks.registering(Javadoc::class) {
group = JavaBasePlugin.DOCUMENTATION_GROUP
description = "Generates aggregate javadoc for all the artifacts"
val sourceSets = jars.map { project(it).sourceSets.main }
classpath = files(sourceSets.map { set -> set.map { it.output + it.compileClasspath } })
// Aggregate javadoc needs to include generated JMeterVersion class
// So we use delay computation of source files
setSource(sourceSets.map { set -> set.map { it.allJava } })
setDestinationDir(file("$buildDir/docs/javadocAggregate"))
}
val skipDist: Boolean by rootProject.extra
// Generates distZip, distTar, distZipSource, and distTarSource tasks
// The archives and checksums are put to build/distributions
for (type in listOf("binary", "source")) {
if (skipDist) {
break
}
for (archive in listOf(Zip::class, Tar::class)) {
val taskName = "dist${archive.simpleName}${type.replace("binary", "").capitalize()}"
val archiveTask = tasks.register(taskName, archive) {
val eol = if (archive == Tar::class) LineEndings.LF else LineEndings.CRLF
group = distributionGroup
description = "Creates $type distribution with $eol line endings for text files"
if (this is Tar) {
compression = Compression.GZIP
}
// Gradle does not track "filters" as archive/copy task dependencies,
// So a mere change of a file attribute won't trigger re-execution of a task
// So we add a custom property to re-execute the task in case attributes change
inputs.property("gitproperties", gitProps.map { it.props.attrs.toString() })
// Gradle defaults to the following pattern, and JMeter was using apache-jmeter-5.1_src.zip
// [baseName]-[appendix]-[version]-[classifier].[extension]
archiveBaseName.set("apache-jmeter-${rootProject.version}${if (type == "source") "_src" else ""}")
CrLfSpec(eol).run {
wa1191SetInputs(gitProps)
with(if (type == "source") sourceLayout() else binaryLayout())
}
}
releaseArtifacts {
artifact(archiveTask)
}
}
}
releaseArtifacts {
previewSite(previewSite) {
into("site") {
from(previewSiteDir)
}
}
}
val runGui by tasks.registering() {
group = "Development"
description = "Builds and starts JMeter GUI"
dependsOn(createDist)
doLast {
javaexec {
workingDir = File(project.rootDir, "bin")
main = "org.apache.jmeter.NewDriver"
classpath("$rootDir/bin/ApacheJMeter.jar")
jvmArgs("-Xss256k")
jvmArgs("-XX:MaxMetaspaceSize=256m")
val osName = System.getProperty("os.name")
if (osName.contains(Regex("mac os x|darwin|osx", RegexOption.IGNORE_CASE))) {
jvmArgs("-Xdock:name=JMeter")
jvmArgs("-Xdock:icon=$rootDir/xdocs/images/jmeter_square.png")
jvmArgs("-Dapple.laf.useScreenMenuBar=true")
jvmArgs("-Dapple.eawt.quitStrategy=CLOSE_ALL_WINDOWS")
}
}
}
}