Skip to content

Instantly share code, notes, and snippets.

@aev-mambro2
Last active May 3, 2021 12:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aev-mambro2/2c39b30ee1b326325c8c665bde2a7d39 to your computer and use it in GitHub Desktop.
Save aev-mambro2/2c39b30ee1b326325c8c665bde2a7d39 to your computer and use it in GitHub Desktop.
build.gradle
/*
* Gradle user guide: https://docs.gradle.org/
* Gradle install: https://gradle.org/install/
* Based on Gradle version 5.4.1
* @author A.E.Veltstra
* @since 2.20.0121.1100
* @version 2.21.302.1025
*/
import io.franzbecker.gradle.lombok.task.DelombokTask
plugins {
/** Reduces rebuild time, by keeping track of what changed. */
id 'com.gradle.build-scan' version '2.0.2'
/** Enable publishing to the local Ivy repository. */
id 'ivy-publish'
/** Add delombok abilities to the Gradle tasks. Delombok causes the lombok
project to generate java code based on lombok annotations found in java
classes. Clashes with Freefair Lombok plug-in.
*/
id 'io.franzbecker.gradle-lombok' version '2.0'
id 'net.ltgt.apt' version '0.19'
id 'java'
}
/** Bootstrap all projects and this build script for use with the Eclipse IDE. */
apply plugin: 'eclipse'
apply plugin: 'build-dashboard'
apply plugin: 'project-report'
/** This is added by the "build-scan" plug-in. We are required to agree to its license. */
buildScan {
termsOfServiceUrl = "https://gradle.com/terms-of-service"
termsOfServiceAgree = "yes"
}
/** Set a specific version of Gradle, rather than depend on a local configuration.
* It became necessary to specify, after some versions introduced breaking changes.
*/
task updateGradleWrapper(type: Wrapper) {
group 'wrapper'
gradleVersion = '5.4.1'
}
/** Information references declared and initialized here will become available through
* rootProject.ext.
*/
project.ext {
company = "com.company"
companyProject = "${company}.Project"
app_title = ""
sourceRepoFolder = "\\\\shared-storage-unit\\sharedlibraries\\"
targetRepoFolder = "\\\\application-server\\c\$\\Shared\\"
targetSuiteFolder = "\\\\application-server\\c\$\\Project\\"
targetJavaVersion = "jdk-11.0.8+10-jre"
targetJavaRuntimeHome = "C:\\Program Files\\Java\\oj9\\${targetJavaVersion}"
targetJavaImplementation = "OJ9 OpenJDK ${targetJavaVersion}"
SharedCompileTimeFolder = "T:\\Shared\\"
SharedRuntimeFolder = "C:\\Shared\\"
companyNetworkSID = "{???????-??????-??????-?????}"
/** copy this from Base/build.gradle/app_version */
baseLibRevision = "2.21.304.1115"
baseLibName = "${companyProject}.Base-${baseLibRevision}.jar"
baseLib = SharedCompileTimeFolder + baseLibName
baseLibRuntime = SharedRuntimeFolder + baseLibName
runBaseFolder = "C:\\Project\\"
logBaseFolder = "\\\\shared-storage-unit\\Users\\ProjectRunner\\logs\\Project\\"
manifestVendor = "Company, Inc."
versionedSourceCompatibility = JavaVersion.VERSION_11
primo = new Random()
now = java.time.Instant.now()
ldt = now.atZone(java.time.ZoneId.systemDefault()).toLocalDateTime()
nowForFilenames = ldt.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd't'HHmm"))
nowForScheduledTasks = ldt.format(java.time.format.DateTimeFormatter.ofPattern("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'S"))
nowForHumans = ldt.format(java.time.format.DateTimeFormatter.ofPattern("MMM' 'dd', 'yyyy', 'HH':'mm'hr'"))
datedVersion = "2.21." + ldt.format(java.time.format.DateTimeFormatter.ofPattern("Mdd'.'Hmm"))
}
allprojects {
group 'com.company.Project'
eclipse {
/** Refreshing the Gradle project from within Eclipse would cause the removal of the Gradle nature,
* effectively taking away the ability to use Gradle on the project. This makes sure it's added back in.
*/
project.natures = ['org.eclipse.buildship.core.gradleprojectnature']
}
/** The operating system writes information in cp1252. Gradle inherits that property. But our projects and
* just about all of the files it generates are encoded in UTF-8. We need to let Gradle know. */
tasks.withType(JavaCompile) { options.encoding = 'UTF-8' }
tasks.withType(Test) { systemProperty "file.encoding", "UTF-8" }
/** Where Gradle needs to search for dependencies. */
repositories {
/** Read pre-cached files from a local folder (old) */
flatDir {
name "SharedCompileTimeRepo"
dirs rootProject.SharedCompileTimeFolder
}
/** Read pre-published files published to a local Ivy repository (new) */
ivy {
url "file://E:/shared-ivy-repo"
name "ivyLocal"
}
/** Anything Gradle couldn't load can come from Maven Central */
mavenCentral()
/** Anything Gradle couldn't load from Maven can come from BinTray, a trusted, central, online repository */
jcenter()
}
/** Lombok plug-in sets compileOnly dependencies and annotationOnly.
It MUST be applied prior to the java plug-in.
*/
apply plugin: 'io.franzbecker.gradle-lombok'
lombok {
version = "1.18.4"
sha256 = "39f3922deb679b1852af519eb227157ef2dd0a21eec3542c8ce1b45f2df39742"
}
task delombok(type: DelombokTask, dependsOn: compileJava) {
group 'delombok'
afterEvaluate { Project pr ->
ext.outputDir = file("$buildDir/delombok")
outputs.dir(outputDir)
sourceSets.main.java.srcDirs.each {
inputs.dir(it)
args(it, "-d", outputDir)
}
doFirst {
outputDir.deleteDir()
}
}
}
task delombokHelp(type: DelombokTask) {
group 'delombok'
args "--help"
}
/** Bootstrap the project and this build script for building Java projects. */
apply plugin: 'java'
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
subprojects {
/** Add a separate location for integration tests so we can run them separately from unit tests. */
sourceSets {
integrationTest {
java.srcDir 'src/integrationTest/java'
resources.srcDir 'src/integrationTest/resources'
}
}
dependencies {
/* Reduces java boilerplate coding. */
implementation 'org.projectlombok:lombok:1.18.4'
annotationProcessor 'org.projectlombok:lombok:1.18.4'
/** The "testImplementation" configuration is added by the plug-in "java". */
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0',
//this allows junit to run with parameters:
'org.junit.jupiter:junit-jupiter-params:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
integrationTestCompile sourceSets.main.output,
sourceSets.test.output,
configurations.compile,
configurations.testCompile,
'org.junit.jupiter:junit-jupiter-api:5.7.0',
'org.junit.jupiter:junit-jupiter-params:5.7.0'
integrationTestRuntime configurations.runtime,
configurations.testRuntime,
'org.junit.jupiter:junit-jupiter-engine:5.7.0'
}
/** Add the lombok configuration to all of the compile classpaths */
sourceSets.each{ sourceSet ->
sourceSet.ext.delombok = new File(buildDir, "generated-src/delombok/" + sourceSet.name);
}
/** The javadoc task is added by the "java" and "java-library" plug-ins.
* It generates HTML-based documentation about the application, for other developers.
*/
javadoc {
title project.app_title
/** Gradle uses Ant to produce javadocs. But by default, it doesn't know the @version and @default annotations. */
options.setTags([
'default:a:Default Value:',
'version:a:Version:'
])
options.use(true)
/** If we want the javadoc to link through to dependencies, we need to specify a classpath. */
classpath = sourceSets.main.output + sourceSets.main.compileClasspath
dependsOn delombok
source = "build/delombok"
failOnError = false
}
/** The jar task is added by the "java" and "java-library" plug-ins.
* It generates a jar file, which is a java executable zip file.
* Here, we specify the information that the jar file needs to place into its manifest file.
* A manifest tells other developers as well as java all kinds of details about the application.
* Most importantly, we need to specify the main class name, which is the default entry point
* that starts the application.
*/
jar {
manifest {
afterEvaluate { Project project ->
attributes(
"Application-Name": project.app_title,
"Implementation-Title": project.name,
"Implementation-Vendor": rootProject.manifestVendor,
"Implementation-Version": rootProject.datedVersion,
"Main-Class": project.mainClassName,
"Manifest-Version": "1.0",
"Name": project.name.toLowerCase().replace('.', '/'),
"Specification-Title": project.name,
"Specification-Vendor": rootProject.manifestVendor,
"Specification-Version": "2.20.0121.1100"
)
}
}
}
/** To generate a jar file containing the javadocs for the application. The jar file can then be bundled using the
* artifacts task, after which they can be uploaded to a shared server location or repository.
*/
task javadocJar(type: Jar) {
group 'documentation'
classifier 'javadoc'
from javadoc.destinationDir
}
/** To generate a jar file containing the source code for the application. The jar file can then be bundled using the
* "artifacts" task, after which they can be uploaded to a shared server location or repository.
*/
task sourcesJar(type: Jar) {
group 'build'
classifier 'sources'
from sourceSets.main.allSource
}
/** To determine how the integration tests should run.
* Copied from https://www.michael-bull.com/blog/2016/06/04/separating-integration-and-unit-tests-with-gradle
*/
task integrationTest(type: Test) {
afterEvaluate { Project project ->
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = 'Runs the integration tests.'
/* Integration tests are often memory intensive and could use an increase in the JVM's maximum heap size. */
maxHeapSize = '1024m'
//testClassesDir = sourceSets.integrationTest.output.classesDirs
/* The task requires us to define where both our classes and classpath reside, which we can find from the SourceSet we defined previously. */
classpath = sourceSets.integrationTest.runtimeClasspath
/* Where our test reports are written for various formats, e.g. binary, HTML, and XML.*/
binResultsDir = file("$buildDir/integration-test-results/binary/integrationTest")
mustRunAfter tasks.test
}
}
/** We want Gradle to test our projects with JUnit, and apply the same restrictions we intend to apply to the final
* application when it's running on the target application server.
*/
test {
afterEvaluate { Project project ->
jvmArgs project.ext.jvmArgs
useJUnitPlatform()
failFast = false
testLogging.showStandardStreams = true
}
}
/** Tell Gradle which tasks to execute, to include their output into distribution tasks like "uploadArchives". */
artifacts {
archives sourcesJar
archives javadocJar
}
}
configure(subprojects.findAll{it.name != 'com.company.Project.Base'}) {
/**
* Do not apply the application plugin.
* We generate distributions ourselves using the task generateDistribution().
*/
/**
* We have our own ideas about how to format a date. We want it to be used in all locations where the "java" task
* deems necessary.
*/
version = rootProject.ext.datedVersion
dependencies {
compile project(':com.company.Project.Base')
}
/**
* These applications will script their run invocations into versioned run scripts, which output their logging to
* files dated by the moment of the run. The run scripts will be executed automatically on schedule. Since the OS of
* the target servers is Microsoft Windows, the run scripts are batch files, and the schedules are encoded as Windows
* Scheduled Tasks. Each application gets a run script and a scheduled task for each of the account configurations,
* making a run process only that account. The result is placed in a new dated folder inside build/distributions.
*/
task generateDistribution(type: Copy, dependsOn: assemble) {
group 'distribution'
description 'Generates a new application jar, and account-and-mode-specific batch files and scheduled tasks.'
afterEvaluate { Project pr ->
def outputFolder = "$distsDir/${rootProject.datedVersion}"
def projectNameParts = pr.name.split('\\.')
def projectBaseName = projectNameParts[projectNameParts.length - 1]
def workDir = rootProject.ext.runBaseFolder + projectBaseName + "\\${rootProject.datedVersion}"
def logDir = rootProject.ext.logBaseFolder + projectBaseName
def cp = sourceSets.main.runtimeClasspath.findAll{
it.getName().endsWith(".jar") && !it.getName().contains("sources") && !it.getName().contains("javadoc")
}
.sort().collect{rootProject.ext.SharedRuntimeFolder + it.getName()}.join(";")
from ('src/templates/deployment/readme.md') {
expand([
appJarName: jar.archiveName,
baseLib: rootProject.ext.baseLibName,
classPath: jar.archiveName + ';' + cp,
errorLog: logDir + "\\Peek\\Production\\scheduled-task-err.log",
jvmArgs: pr.jvmArgs.join(' '),
mainClassName: pr.mainClassName,
Shared: rootProject.ext.SharedRuntimeFolder,
nowForHumans: rootProject.ext.nowForHumans,
sourceCompatibility: rootProject.ext.versionedSourceCompatibility,
sourceRepoFolder: rootProject.ext.sourceRepoFolder,
stdLog: logDir + "\\Peek\\Production\\scheduled-task-run.log",
workDir: workDir
])
}
['Prod': 'Production', 'Test': 'Test'].each { modeKey, modeName ->
[
'accountKey': [abbr: 'account', logFolder: 'account', ediFolder: 'account']
].each{ accountKey, accountProps ->
from (rootProject.file('templates/deployment')) {
include 'Account-Mode-Task.bat'
filteringCharset = 'UTF-8'
expand([
appJarName: jar.archiveName,
baseLib: rootProject.ext.baseLibName,
classPath: jar.archiveName + ';' + cp,
isDebuggingBatch: 'Test' == modeKey ? 1 : 0,
javaHome: rootProject.ext.targetJavaRuntimeHome,
jvmArgs: pr.jvmArgs.join(' '),
logFolder: logDir + "\\${accountProps.logFolder}\\$modeName",
mainClassName: pr.mainClassName,
nowForHumans: rootProject.ext.nowForHumans,
accountName: accountKey,
sourceCompatibility: rootProject.ext.versionedSourceCompatibility,
testArgument: 'Test' == modeKey ? '/test /nodbwrites /thirdparty Project ' : '/thirdparty Project ',
verbosity: 'Test' == modeKey ? '-verbose' : '',
accountId: accountKey
])
rename 'Account', accountProps.abbr
rename 'Mode', modeKey
rename 'Task', projectBaseName
}
from (rootProject.file('templates/deployment')) {
include 'Account-Mode-Task.xml'
filteringCharset = 'UTF-16'
expand([
errorLog: logDir + "\\${accountProps.logFolder}\\$modeName\\scheduled-task-err.log",
executable: workDir + "\\${accountProps.abbr}-${modeKey}-${projectBaseName}.bat",
companyNetworkSID: rootProject.ext.companyNetworkSID,
nowForScheduledTasks: rootProject.ext.nowForScheduledTasks,
startWhenAvailable: 'false',
stdLog: logDir + "\\${accountProps.logFolder}\\$modeName\\scheduled-task-run.log",
wakeToRun: 'false',
workDir: workDir,
'triggers': 'Test' == modeKey ? '' : '''<Triggers>
<CalendarTrigger>
<StartBoundary>2020-01-22T09:'''
+ ((rootProject.ext.primo.nextInt() % 7) + 26)
+ ':'
+ String.valueOf(Math.abs(rootProject.ext.primo.nextInt() % 59) + 1).padLeft(2, '0')
+ '''</StartBoundary>
<ExecutionTimeLimit>P1D</ExecutionTimeLimit>
<Enabled>true</Enabled>
<RandomDelay>PT3M</RandomDelay>
<ScheduleByWeek>
<WeeksInterval>1</WeeksInterval>
<DaysOfWeek>
<Monday/>
<Wednesday/>
<Friday/>
</DaysOfWeek>
</ScheduleByWeek>
</CalendarTrigger>
</Triggers>'''])
rename 'Account', accountProps.abbr
rename 'Mode', modeKey
rename 'Task', projectBaseName
}
}
}
from (jar)
into outputFolder
}
}
/**
* The package-info file is included in an application's developer documentation (javadocs). It provides an intro to
* the package: what it does, needs, etc. This task regenerates one based on a template, adding in a version and
* the latest dependencies.
* The task assumes that the project name is a fully-qualified java name, and that its lower-casing equals the
* package.
*/
task regeneratePackageInfo (type: Copy) {
group 'documentation'
description 'Generates a new package-info.java for the project'
afterEvaluate { Project project ->
def projectNameParts = project.name.split('/.')
def projectBaseName = projectNameParts[projectNameParts.length - 1]
def cp = sourceSets.main.runtimeClasspath.findAll{
it.getName().endsWith(".jar") && !it.getName().contains("sources") && !it.getName().contains("javadoc")
}.sort().collect{rootProject.ext.SharedRuntimeFolder + it.getName()}.join(";\n * ")
def runtimeLibs = sourceSets.main.runtimeClasspath.findAll{
it.getName().endsWith(".jar") && !it.getName().contains("sources") && !it.getName().contains("javadoc")
}.sort().collect{it.getName()}.join("\n * <li>")
def compileLibs = sourceSets.main.compileClasspath.findAll{
it.getName().endsWith(".jar")
}.sort().collect{it.getName()}.join("\n * <li>")
from ('src/templates/java/' + project.name.replace('.', '/') + '/package.info')
expand([
app_title: project.app_title,
appJarName: project.name + '-' + rootProject.ext.datedVersion + '.jar',
cp: cp,
errorLog: rootProject.logBaseFolder + projectBaseName
+ "\\Account\\Production\\scheduled-task-err.log",
jvmArgs: project.jvmArgs.join(' '),
mainClassName: project.mainClassName,
Shared: rootProject.ext.SharedRuntimeFolder,
runtimeLibs: runtimeLibs,
requiredLibs: compileLibs,
sourceRepo: rootProject.sourceRepoFolder,
version: rootProject.datedVersion
])
into 'src/main/java/' + project.name.replace('.', '/')
rename 'package.info', 'package-info.java'
}
}
javadoc.dependsOn regeneratePackageInfo
/**
* We would like to copy the generated distrution straight to the target application server.
* However, Gradle fails to let us specify authentication credentials, so we'd only be able to run
* this task if the credentials used to run it also allow us writing privileges on the target
* server. If that isn't the case, disable this task.
*/
task uploadDistribution(type:Copy, dependsOn:generateDistribution) {
enabled true
group 'distribution'
description 'Copies the generated distribution into the designated project folder on the target app server.'
afterEvaluate { Project project ->
def projectNameParts = project.name.split('\\.')
def projectBaseName = projectNameParts[projectNameParts.length - 1]
from "$distsDir/${rootProject.datedVersion}"
into "${rootProject.targetSuiteFolder}/$projectBaseName/${rootProject.datedVersion}"
}
}
}
@aev-mambro2
Copy link
Author

A build gradle like the above is suited for creating reproducible deployments and test runs of software. The above specifically is built for hierarchical project suites, where some task-based modules depend on other modules. In suite architecture, a set of tasks depend on a single shared base module. We encounter this architecture a lot when performing relatively basic CRUD operations across many different remote REST API servers: each of those remote hosts has their own requirements and processes that differ from the others, but all of them have the same basic underlying principles and data structures. Their shared processes and data structures go into the suite's base module, and all other tasks for that same remote host get to depend on it. But each of those tasks can be developed, tested, and deployed independently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment