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