/* * Build logic for Progressia * build.gradle * * Please refer to * * docs/building/BuildScriptReference.md * * for user reference. */ plugins { id 'java' // GrGit // A JGit wrapper for Groovy used to access git commit info id 'org.ajoberstar.grgit' version '4.1.1' } /* * Configure Java version */ java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } compileJava { /* * We want to compile for Java 8. * If we are using JDK 8, no further action is required. * However, on JDK 9 and later versions, '--release' option is required, * which is missing on JDK 8. */ if (JavaVersion.current() != JavaVersion.VERSION_1_8) { options.compilerArgs.addAll(['--release', '8']) } } /* * Dependencies */ repositories { mavenCentral() /* * Windcorp Maven repository * Currently used by: * - ru.windcorp.fork.io.github.java-graphics:glm:1.0.1 */ maven { url 'https://windcorp.ru/./maven' } } dependencies { // Google Guava // A generic utilities library implementation 'com.google.guava:guava:31.0.1-jre' // Trove4j // Provides optimized Collections for primitive types implementation 'net.sf.trove4j:trove4j:3.0.3' // java-graphics // A GLM (OpenGL Mathematics) port to Java // Unfortunately, Maven Central Repository provides an outdated version of this library, which contains several critical bugs implementation 'ru.windcorp.fork.io.github.java-graphics:glm:1.0.1' // Log4j // A logging library implementation 'org.apache.logging.log4j:log4j-api:2.17.1' implementation 'org.apache.logging.log4j:log4j-core:2.17.1' // JUnit // A unit-testing library testImplementation 'junit:junit:4.13.2' // Also see LWJGL dependencies in build_logic/lwjgl.gradle } /* * Version resolution */ import org.ajoberstar.grgit.* // Pattern: vMAJOR.MINOR.PATCH[-SUFFIX] project.ext.tagFormat = /^v(\d+)\.(\d+)\.(\d+)(-[\w\-]*)?$/ String version_parseVersion(String tag, boolean increment) { try { def data = (tag =~ tagFormat)[0] def major = data[1] def minor = data[2] def patch = data[3] as int def suffix = data[4] ?: '' if (increment) { def oldVersion = "$major.$minor.$patch$suffix" patch++ def newVersion = "$major.$minor.$patch$suffix" logger.info "Version parsed from Git: $oldVersion, incremented to $newVersion" return newVersion } else { def newVersion = "$major.$minor.$patch$suffix" logger.info "Version parsed from Git: $newVersion" return newVersion } } catch (any) { logger.warn "Could not parse version from tag \"$tag\"" return tag } } Tag version_findRelevantTag(Grgit git) { def tags = git.tag.list() def commits = [ git.head() ] def visited = new HashSet<>() while (true) { if (commits.isEmpty()) return null def nextCommits = new HashSet<>() def formatSpecificationPrinted = false for (def commit : commits) { def tag = tags.findAll { it.commit == commit } ?.max { it.dateTime } if (tag != null) { if (tag.name ==~ tagFormat) { return tag } else { if (!formatSpecificationPrinted) { formatSpecificationPrinted = true logger.info 'Expecting tag format: vMAJOR.MINOR.PATCH[-SUFFIX]' } logger.info 'Ignoring tag due to invalid format: {}', tag.name } } nextCommits.addAll commit.parentIds.collect(git.resolve.&toCommit) } visited.addAll commits nextCommits.removeAll visited commits = nextCommits } } task resolveVersion { description 'Resolves version information from Git repository or project properties.' doFirst { project.ext.commit = System.env.GIT_COMMIT project.ext.branch = System.env.GIT_BRANCH try { def git = Grgit.open(dir: project.projectDir) project.ext.commit = commit ?: git.head().id project.ext.branch = branch ?: git.branch.current().name if (project.version != 'unspecified') { // Leave version as-is } else { // Resolve version from Git def tag = version_findRelevantTag(git) if (tag == null) { String suffix if (project.hasProperty('buildId')) { suffix = project.buildId; } else { suffix = java.time.ZonedDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern('yyyy_MM_dd')) } project.version = "999.0.0-$suffix" logger.warn 'Git repository does not contain an applicable tag, using dummy version {}\nSpecify version with -Pversion=1.2.3 or create a Git tag named v1.2.3', project.version } else { project.version = version_parseVersion(tag.name, tag.commit != git.head()) } } } catch (org.eclipse.jgit.errors.RepositoryNotFoundException e) { if (project.version == 'unspecified') project.version = 'dev' project.ext.commit = commit ?: '-' project.ext.branch = branch ?: '-' logger.warn 'No Git repository found in project root, using dummy version {}\nSpecify version with -Pversion=1.2.3 or create a Git tag named v1.2.3', project.version } if (branch.contains '/') { // Strip remote - no one wants that project.ext.branch = branch.takeAfter '/' } if (!project.hasProperty('buildId')) { project.ext.buildId = '-' } } doLast { } } /* * Apply LWJGL logic */ apply from: 'build_logic/lwjgl.gradle' /* * Copy libraries into build/libs/lib directory, next to Progressia.jar */ task exportLibs(type: Sync) { description 'Copies runtime libraries into a subdirectory next to the output JAR.' jar.dependsOn exportLibs dependsOn lwjgl_addNativesToRuntimeOnly // from defined in configureManifest into 'build/libs/lib' } /* * Configure JAR manifest */ task configureManifest { description 'Populates JAR manifest with Main-Class, Class-Path and version metadata.' jar.dependsOn configureManifest dependsOn resolveVersion dependsOn lwjgl_addNativesToRuntimeOnly doFirst { def classPath = project.lwjgl.replaceNativesIn(configurations.runtimeClasspath) exportLibs.from classPath jar.manifest.attributes( 'Main-Class': 'ru.windcorp.progressia.client.ProgressiaClientMain', 'Class-Path': classPath.collect { "lib/${java.net.URLEncoder.encode it.name}" } .join(' '), 'Specification-Title': 'Progressia', 'Implementation-Title': 'Progressia', 'Implementation-Version': project.version, 'Implementation-Version-Git-Commit': project.commit, 'Implementation-Version-Git-Branch': project.branch, 'Implementation-Version-BuildId': project.buildId, ) } } /* * Packaging working directory configuration */ import java.nio.file.* import java.nio.file.attribute.* task deletePackagingDirs(type: Delete) { description 'Deletes build/tmp/packaging directory.' followSymlinks = false delete 'build/tmp/packaging' } task linkBuildOutputForPackaging { description 'Symlinks the contents of build/libs into packaging working directory.' dependsOn build mustRunAfter deletePackagingDirs onlyIf { preparePackaging.ext.mode == 'symlink' } ext.from = 'build/libs' ext.into = "build/tmp/packaging/workingDir/${ -> preparePackaging.ext.buildDest}" inputs.dir from outputs.dir into doLast { def fromPath = Paths.get from def intoPath = Paths.get into Files.createDirectories intoPath Files.list(fromPath).each { def fileName = it.fileName.toString() // Exclude all JARs except the current one if (fileName ==~ "${project.name}.*\\.jar" && fileName != tasks.jar.archiveFileName.get()) return Files.createSymbolicLink intoPath.resolve(it.fileName), intoPath.relativize(it) } } } task copyBuildOutputForPackaging(type: Copy) { description 'Copies the contents of build/libs into packaging working directory.' dependsOn build mustRunAfter deletePackagingDirs onlyIf { preparePackaging.ext.mode == 'copy' } from 'build/libs' filesMatching("${project.name}*.jar") { include tasks.jar.archiveFileName.get() } into "build/tmp/packaging/workingDir/${ -> preparePackaging.ext.buildDest}" } task preparePackaging { preparePackaging.ext.buildDest = '' preparePackaging.ext.mode = 'symlink' dependsOn deletePackagingDirs dependsOn linkBuildOutputForPackaging dependsOn copyBuildOutputForPackaging } /* * Apply all packaging scripts */ new File(projectDir, 'build_logic/packaging').list().each { apply from: "build_logic/packaging/$it/script.gradle" } /* * Ensure no more than one packaging task is scheduled */ gradle.taskGraph.whenReady { graph -> if (graph.allTasks.count { it.name ==~ /package[^_]*/ } > 1) { def offenders = graph.allTasks.findAll { it.name ==~ /package[^_]*/ } throw new GradleException("Cannot execute multiple package tasks within a single build\n" + "\tOffending tasks: $offenders") } } /* * Convenience build tasks */ task buildCrossPlatform { description 'Builds the project including native libraries for all available platforms.' group 'Progressia' dependsOn requestCrossPlatformDependencies dependsOn build doLast { logger.info 'Native libraries for all platforms have been added' } } task buildLocal { description "Builds the project including only native libraries for current platform (${lwjgl.localArch})." group 'Progressia' dependsOn build doLast { logger.info "Native libraries only for platform ${lwjgl.localArch} have been added" } }