opi-3/backend/build.gradle

480 lines
15 KiB
Groovy

plugins {
id 'java'
id 'war'
id 'jacoco'
id 'org.sonarqube' version '3.5.0.2730'
id 'com.github.node-gradle.node' version '3.5.1'
}
group = 'ru.akarpov.web4'
version = '1.0-SNAPSHOT'
sourceCompatibility = '17'
// Load all properties from gradle.properties
def props = new Properties()
file("${projectDir}/gradle.properties").withInputStream { props.load(it) }
repositories {
mavenCentral()
}
dependencies {
// Jakarta EE
providedCompile "jakarta.platform:jakarta.jakartaee-api:${props.jakartaEeVersion}"
// Hibernate
implementation "org.hibernate.orm:hibernate-core:${props.hibernateVersion}"
// PostgreSQL
implementation "org.postgresql:postgresql:${props.postgresqlVersion}"
// JWT for authentication
implementation "com.auth0:java-jwt:${props.jwtVersion}"
// JSON-B
implementation "jakarta.json.bind:jakarta.json.bind-api:${props.jsonbVersion}"
// Swagger/OpenAPI
providedCompile "org.eclipse.microprofile.openapi:microprofile-openapi-api:${props.openapiVersion}"
implementation "io.swagger.core.v3:swagger-annotations:${props.swaggerVersion}"
implementation "io.swagger.core.v3:swagger-jaxrs2-jakarta:${props.swaggerVersion}"
implementation "org.webjars:swagger-ui:${props.swaggeruiVersion}"
// Testing
testImplementation "org.junit.jupiter:junit-jupiter-api:${props.junitVersion}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${props.junitVersion}"
testImplementation "org.mockito:mockito-core:${props.mockitoVersion}"
testImplementation "org.mockito:mockito-junit-jupiter:${props.mockitoVersion}"
// Selenium for functional testing
testImplementation "org.seleniumhq.selenium:selenium-java:${props.seleniumVersion}"
testImplementation "io.github.bonigarcia:webdrivermanager:${props.webdriverManagerVersion}"
}
// Define the main Java source directory
sourceSets {
main {
java {
srcDirs = ['src/main/java']
}
resources {
srcDirs = ['src/main/resources']
}
}
test {
java {
srcDirs = ['src/test/java']
}
resources {
srcDirs = ['src/test/resources']
}
}
}
// Configure Node plugin for frontend build
node {
version = "${props.nodeVersion}"
npmVersion = "${props.npmVersion}"
download = true
nodeProjectDir = file("${projectDir}/frontend")
}
// Task 1: compile - Compile Java sources
task compile(type: JavaCompile) {
source = sourceSets.main.java.srcDirs
classpath = sourceSets.main.compileClasspath
destinationDir = sourceSets.main.java.outputDir
options.encoding = 'UTF-8'
}
// Task 2: build - Build the entire project
task buildProject(dependsOn: [compile, war, npmBuild]) {
description = 'Builds the entire project (backend and frontend)'
group = 'build'
}
// NPM tasks for frontend
task npmInstall(type: NpmTask) {
description = 'Installs all dependencies from package.json'
workingDir = file("${projectDir}/frontend")
args = ['install']
}
task npmBuild(type: NpmTask, dependsOn: npmInstall) {
description = 'Builds the frontend'
workingDir = file("${projectDir}/frontend")
args = ['run', 'build']
}
// Task to copy frontend build to backend webapp directory
task copyFrontendToBackend(type: Copy, dependsOn: npmBuild) {
from "${projectDir}/frontend/build"
into "${buildDir}/webapp"
// Preserve swagger-ui.html
doFirst {
mkdir "${buildDir}/temp"
if (file("${projectDir}/src/main/webapp/swagger-ui.html").exists()) {
copy {
from "${projectDir}/src/main/webapp/swagger-ui.html"
into "${buildDir}/temp"
}
}
}
doLast {
if (file("${buildDir}/temp/swagger-ui.html").exists()) {
copy {
from "${buildDir}/temp/swagger-ui.html"
into "${buildDir}/webapp"
}
}
}
}
// Task 3: clean - Clean build directories
clean {
description = 'Cleans all build directories and temporary files'
delete "${buildDir}"
delete "${projectDir}/frontend/build"
delete "${projectDir}/frontend/node_modules"
delete fileTree(dir: "${projectDir}/src/main/webapp", excludes: ["WEB-INF/**"])
}
// Configure war task to include frontend
war {
dependsOn copyFrontendToBackend
webAppDirectory = file("${buildDir}/webapp")
archiveName = "web-lab4.war"
manifest {
attributes(
'Implementation-Title': project.name,
'Implementation-Version': project.version,
'Main-Class': "${props.mainClass}"
)
}
}
// Task 4: test - Run JUnit tests
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
// Exclude functional tests from regular test task
exclude '**/functional/**'
}
// Task 5: scp - Transfer built project to a server
task scp(dependsOn: buildProject) {
description = 'Transfers the built project to a remote server'
doLast {
exec {
commandLine 'scp', "${buildDir}/libs/web-lab4.war", "${props.remoteUser}@${props.remoteHost}:${props.remotePath}"
}
}
}
// Task 6: xml - Validate all XML files
task xml {
description = 'Validates all XML files in the project'
doLast {
fileTree(dir: projectDir, include: '**/*.xml').each { file ->
javaexec {
classpath = configurations.runtimeClasspath
main = 'javax.xml.validation.SchemaFactory'
args = [file.absolutePath]
}
}
}
}
// Task 7: doc - Add MD5 and SHA-1 to MANIFEST and generate JavaDoc
task doc(dependsOn: war) {
description = 'Adds MD5 and SHA-1 checksums to MANIFEST and generates JavaDoc'
doLast {
// Generate JavaDoc
exec {
workingDir projectDir
commandLine 'javadoc', '-d', "${buildDir}/docs/javadoc",
'-sourcepath', "${projectDir}/src/main/java",
'-subpackages', 'ru.akarpov.web4'
}
// Calculate checksums and add to MANIFEST
def warFile = file("${buildDir}/libs/web-lab4.war")
def md5 = warFile.digest('MD5')
def sha1 = warFile.digest('SHA-1')
war.manifest {
attributes(
'MD5-Checksum': md5,
'SHA1-Checksum': sha1
)
}
// Rebuild the WAR file with updated MANIFEST
war.execute()
// Add JavaDoc to the WAR file
ant.zip(destfile: warFile, update: true) {
zipfileset(dir: "${buildDir}/docs/javadoc", prefix: 'javadoc')
}
}
}
// Task 8: native2ascii - Convert localization files
task native2ascii {
description = 'Converts localization files using native2ascii'
doLast {
fileTree(dir: "${projectDir}/src/main/resources", include: '**/*.properties').each { file ->
def outputFile = new File(file.absolutePath + '.ascii')
exec {
commandLine 'native2ascii', '-encoding', 'UTF-8', file.absolutePath, outputFile.absolutePath
}
outputFile.renameTo(file)
}
}
}
// Task 9: music - Play music after build
task music(dependsOn: buildProject) {
description = 'Plays music after successful build'
doLast {
if (System.properties['os.name'].toLowerCase().contains('windows')) {
exec {
commandLine 'cmd', '/c', "start ${props.musicFile}"
}
} else if (System.properties['os.name'].toLowerCase().contains('mac')) {
exec {
commandLine 'afplay', "${props.musicFile}"
}
} else {
exec {
commandLine 'xdg-open', "${props.musicFile}"
}
}
}
}
// Task 10: report - Save JUnit report to XML and commit to Git
task report(dependsOn: test) {
description = 'Saves JUnit report to XML and commits to Git'
doLast {
if (!project.gradle.taskGraph.hasTask(':test') || tasks.test.state.failure == null) {
def reportFile = file("${buildDir}/test-results/test/TEST-junit-report.xml")
exec {
commandLine 'git', 'add', reportFile.absolutePath
}
exec {
commandLine 'git', 'commit', '-m', 'Add JUnit test report'
}
println "Test report committed to Git: ${reportFile.absolutePath}"
} else {
println "Tests failed, report not committed"
}
}
}
// Task 11: diff - Check working copy and commit if specified classes are not changed
task diff {
description = 'Checks working copy and commits if specified classes are not changed'
doLast {
def changedFiles = ""
exec {
commandLine 'git', 'status', '--porcelain'
standardOutput = new ByteArrayOutputStream()
changedFiles = standardOutput.toString().trim()
}
def excludedClasses = props.excludedClasses.split(',')
boolean canCommit = true
for (String changedFile : changedFiles.split('\n')) {
def fileName = changedFile.substring(3)
for (String excludedClass : excludedClasses) {
if (fileName.contains(excludedClass)) {
canCommit = false
break
}
}
}
if (canCommit && !changedFiles.isEmpty()) {
exec {
commandLine 'git', 'add', '.'
}
exec {
commandLine 'git', 'commit', '-m', 'Auto-commit: Changes not affecting excluded classes'
}
println "Changes committed to Git"
} else {
println "Changes affect excluded classes or no changes found, not committing"
}
}
}
// Task 12: team - Get previous revisions, build them and package as zip
task team {
description = 'Gets previous revisions, builds them and packages as zip'
doLast {
def currentDir = file("${buildDir}/team")
currentDir.mkdirs()
// Get the last 2 revisions
for (int i = 1; i <= 2; i++) {
def revisionDir = new File(currentDir, "revision-${i}")
revisionDir.mkdirs()
exec {
commandLine 'git', 'archive', "HEAD~${i}", '--format=tar',
'--output', "${revisionDir}/archive.tar"
}
exec {
workingDir revisionDir
commandLine 'tar', '-xf', 'archive.tar'
}
// Build the revision
exec {
workingDir revisionDir
commandLine './gradlew', 'build'
}
}
// Package the builds into a zip file
ant.zip(destfile: "${buildDir}/team-builds.zip") {
fileset(dir: "${buildDir}/team")
}
}
}
// Task 13: alt - Create alternative version with renamed variables and classes
task alt {
description = 'Creates alternative version with renamed variables and classes'
doLast {
def altDir = file("${buildDir}/alt")
copy {
from "${projectDir}/src"
into "${altDir}/src"
}
def replacements = [
'User': 'AppUser',
'Point': 'Coordinate',
'PointService': 'CoordinateService',
'UserService': 'AppUserService'
]
fileTree(dir: altDir, include: '**/*.java').each { file ->
def content = file.text
replacements.each { key, value ->
content = content.replaceAll(key, value)
}
file.text = content
}
// Build the alternative version
exec {
workingDir altDir
commandLine './gradlew', 'build'
}
}
}
// Task 14: history - Try to compile, if fails, get previous version from Git
task history {
description = 'If compilation fails, gets previous version from Git until a working one is found'
doLast {
boolean compileSuccess = false
int revision = 0
while (!compileSuccess && revision < 10) { // Limit to last 10 revisions
try {
if (revision > 0) {
// Checkout previous revision
exec {
commandLine 'git', 'checkout', "HEAD~${revision}"
}
}
// Try to compile
tasks.compile.execute()
compileSuccess = true
if (revision > 0) {
// Create diff file with the next revision (first broken one)
exec {
standardOutput = new FileOutputStream("${buildDir}/first-broken-diff.patch")
commandLine 'git', 'diff', "HEAD..HEAD~${revision-1}"
}
println "Found working revision at HEAD~${revision}, diff saved to first-broken-diff.patch"
}
} catch (Exception e) {
println "Compilation failed for revision HEAD~${revision}, trying older version"
revision++
}
}
// Restore to original revision
if (revision > 0) {
exec {
commandLine 'git', 'checkout', 'HEAD'
}
}
if (!compileSuccess) {
println "Could not find a working revision in the last 10 commits"
}
}
}
// Task 15: env - Build and run in alternative environments
task env {
description = 'Builds and runs the program in alternative environments'
doLast {
def environments = props.environments.split(';')
environments.each { env ->
def (javaVersion, vmArgs) = env.split(':')
println "Building and running with Java ${javaVersion} and VM args ${vmArgs}"
exec {
environment 'JAVA_HOME', "${props.javaHome}${javaVersion}"
commandLine './gradlew', 'build'
}
exec {
environment 'JAVA_HOME', "${props.javaHome}${javaVersion}"
commandLine "${props.javaHome}${javaVersion}/bin/java",
vmArgs.split(',') as List,
'-jar',
"${buildDir}/libs/web-lab4.war"
}
}
}
}
// Task for functional testing
task functionalTest(type: Test) {
description = 'Runs functional tests'
group = 'verification'
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath
// Only run tests in the functional package
include '**/functional/**'
// Set system property to identify functional tests
systemProperty 'test.type', 'functional'
// Report results
reports {
html.enabled = true
junitXml.enabled = true
}
}
// Integrate functional testing into the build process
build.dependsOn functionalTest