plugins { id 'java' id 'war' id 'jacoco' id 'org.sonarqube' version '3.5.0.2730' } group = 'ru.akarpov.web4' version = '1.0-SNAPSHOT' sourceCompatibility = '17' 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}" } sourceSets { main { java { srcDirs = ['src/main/java'] } resources { srcDirs = ['src/main/resources'] } } test { java { srcDirs = ['src/test/java'] } resources { srcDirs = ['src/test/resources'] } } } // Task 1: compile - Compile Java sources task compile(type: JavaCompile) { source = sourceSets.main.java.srcDirs classpath = sourceSets.main.compileClasspath destinationDirectory = file("${buildDir}/classes/java/main") options.encoding = 'UTF-8' } // Task to build the frontend task buildFrontend(type: Exec) { description = 'Builds the frontend' workingDir = file("${projectDir}/../frontend") // Use appropriate command based on OS if (System.getProperty('os.name').toLowerCase().contains('windows')) { commandLine 'cmd', '/c', 'npm install && npm run build' } else { commandLine 'sh', '-c', 'npm install && npm run build' } } // Task to copy frontend build to backend webapp directory task copyFrontendToBackend(type: Copy, dependsOn: buildFrontend) { 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 2: build - Build the entire project task buildProject(dependsOn: [compile, war, buildFrontend]) { description = 'Builds the entire project (backend and frontend)' group = 'build' } // Task 3: clean - Clean build directories clean { description = 'Cleans all build directories and temporary files' delete "${buildDir}" doLast { exec { workingDir = file("${projectDir}/../frontend") if (System.getProperty('os.name').toLowerCase().contains('windows')) { commandLine 'cmd', '/c', 'npm run clean || true' } else { commandLine 'sh', '-c', 'rm -rf build node_modules || true' } } } delete fileTree(dir: "${projectDir}/src/main/webapp", excludes: ["WEB-INF/**"]) } // Configure war task to include frontend war { dependsOn compile, copyFrontendToBackend webAppDirectory = file("${buildDir}/webapp") archiveFileName = "web-lab4.war" manifest { attributes( 'Implementation-Title': project.name, 'Implementation-Version': project.version, 'Main-Class': "${props.mainClass}" ) } // Include compiled classes in the WAR from sourceSets.main.output } // 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 { javaexec { main = 'jdk.javadoc.internal.tool.Main' classpath = sourceSets.main.compileClasspath args = [ '-d', "${buildDir}/docs/javadoc", '-sourcepath', "${projectDir}/src/main/java", '-subpackages', 'ru.akarpov.web4', '-classpath', sourceSets.main.compileClasspath.asPath ] } // 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 { def reportFile = file("${buildDir}/test-results/test/TEST-junit-report.xml") // Check if tests were run and the report exists if (!project.gradle.taskGraph.hasTask(':test') || tasks.test.state.failure != null) { println "Tests failed or were not executed, report not committed" return } // Handle case when report file doesn't exist if (!reportFile.exists()) { println "Test report file doesn't exist at ${reportFile.absolutePath}" // Create a directory for test results if it doesn't exist reportFile.parentFile.mkdirs() reportFile.text = """ """ println "Created test report" } // Check if Git is tracking the build directory def gitStatusOutput = new ByteArrayOutputStream() exec { ignoreExitValue = true commandLine 'git', 'ls-files', '--error-unmatch', reportFile.absolutePath standardOutput = gitStatusOutput errorOutput = gitStatusOutput } // If git isn't tracking the file, it might be excluded by .gitignore if (gitStatusOutput.toString().contains("error") || gitStatusOutput.toString().isEmpty()) { println "Warning: Git may not be tracking the build directory (possibly excluded by .gitignore)" println "Attempting to add anyway..." } // Try to add the file to git def addExitCode = exec { ignoreExitValue = true commandLine 'git', 'add', reportFile.absolutePath } // Only commit if add was successful if (addExitCode.exitValue == 0) { exec { ignoreExitValue = true commandLine 'git', 'commit', '-m', 'Add JUnit test report' } println "Test report committed to Git: ${reportFile.absolutePath}" } else { println "Failed to add test report to Git. It may be excluded by .gitignore" } } } // 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 = new ByteArrayOutputStream() exec { commandLine 'git', 'status', '--porcelain' standardOutput = changedFiles } def changedFilesStr = changedFiles.toString().trim() def excludedClasses = props.excludedClasses.split(',') boolean canCommit = true for (String changedFile : changedFilesStr.split('\n')) { if (changedFile.length() > 3) { def fileName = changedFile.substring(3) for (String excludedClass : excludedClasses) { if (fileName.contains(excludedClass)) { canCommit = false break } } } } if (canCommit && !changedFilesStr.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" } } } } // Update the functionalTest task task functionalTest(type: Test) { description = 'Runs functional tests' group = 'verification' // Use separate source set for functional tests testClassesDirs = sourceSets.test.output.classesDirs classpath = sourceSets.test.runtimeClasspath // Explicitly specify which tests to include include '**/functional/**/*Test.class' // Configure test detection useJUnitPlatform() testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" showExceptions true showCauses true showStackTraces true exceptionFormat "full" } // Verbose logging doFirst { println "Running functional tests..." println "Test source directories: ${sourceSets.test.java.srcDirs.join(', ')}" println "Test class directories: ${testClassesDirs.files.join(', ')}" // List all test files sourceSets.test.java.files.each { file -> if (file.path.contains('functional') && file.name.endsWith('Test.java')) { println "Found test file: ${file.path}" } } } reports { html.required = true junitXml.required = true } }