Commit of cross-compile logic ported from java builder

master
nathan 2018-06-30 15:20:54 +02:00
commit b318c44c2e
2 changed files with 547 additions and 0 deletions

124
.gitignore vendored Normal file
View File

@ -0,0 +1,124 @@
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
.idea/**/codeStyles/
.idea/**/codeStyleSettings.xml
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/shelf/
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-debug/
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
######################
# End JetBrains IDEs #
######################
# From https://github.com/github/gitignore/blob/master/Gradle.gitignore
.gradle
/build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# Work around https://youtrack.jetbrains.com/issue/IDEA-116898
gradle/wrapper/gradle-wrapper.properties
# From https://github.com/github/gitignore/blob/master/Java.gitignore
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
##########################################################
# Specific to this module
.iml

View File

@ -0,0 +1,423 @@
/*
* Copyright 2012 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.crossCompile
import de.undercouch.gradle.tasks.download.Download
import de.undercouch.gradle.tasks.download.Verify
import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream
import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream
import org.apache.commons.compress.compressors.pack200.Pack200CompressorInputStream
import org.apache.commons.compress.compressors.pack200.Pack200CompressorOutputStream
import org.gradle.api.*
import org.gradle.api.invocation.Gradle
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.GroovyCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.util.GradleVersion
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.*
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
/**
* Downloads JDKs and configures the bootstrap classpath when cross-compiling java projects
*/
class PrepareJdk : Plugin<Project> {
companion object {
data class JDK(val file: File, val checksum: String)
private val versionInfo = HashMap<JavaVersion, JDK>()
private const val compressSuffix = ".pack.lzma"
private const val jarSuffix = ".jar"
val logger: Logger = LoggerFactory.getLogger(PrepareJdk::class.java)
}
override fun apply(project: Project) {
project.plugins.apply(JavaBasePlugin::class.java)
project.afterEvaluate {
// don't waste time if this is not a java project
val convention: JavaPluginConvention? = project.convention.plugins["java"] as? JavaPluginConvention
var needsJdk = false
if (convention != null) {
// check if we need to extract anything..
if (convention.targetCompatibility != JavaVersion.current()) {
needsJdk = true
}
project.tasks.forEach {
if (!needsJdk && it is JavaCompile) {
if (JavaVersion.toVersion(it.targetCompatibility) != JavaVersion.current()) {
needsJdk = true
}
}
}
if (needsJdk) {
// if there is a clean task (usually the first thing to run, if run), run after the clean task, otherwise run first.
val hasClean = project.gradle.startParameter.taskNames.filter { it.toLowerCase().contains("clean") }
if (hasClean.isNotEmpty()) {
val task = project.tasks.last { it.name == hasClean.last() }
task.doLast {
setupDownload(project)
}
}
else {
setupDownload(project)
}
}
}
}
}
private fun setupDownload(project: Project) {
downloadAndExtractJdk(project)
project.tasks.forEach {
if (it is AbstractCompile) {
if (JavaVersion.toVersion(it.targetCompatibility) != JavaVersion.current()) {
// only supports JAVA and GROOVY
configureTaskBootstrapClassPath(project, it, JavaVersion.toVersion(it.targetCompatibility))
}
}
}
}
private fun downloadAndExtractJdk(project: Project) {
val outputDir = File(project.buildDir, "jdkRuntimes")
if (!outputDir.exists()) outputDir.mkdirs()
logger.info("Preparing cross-compile environment")
// download the JDK runtimes from github
val prefix = "https://github.com/dorkbox/JavaBuilder/raw/master/jdkRuntimes"
versionInfo[JavaVersion.VERSION_1_6] = JDK(File(outputDir, "openJdk6_rt.jar.pack.lzma").absoluteFile, "313a8b3fe4736520f7a4b6de37f1c80698502ee7")
versionInfo[JavaVersion.VERSION_1_7] = JDK(File(outputDir, "openJdk7_rt.jar.pack.lzma").absoluteFile, "b42aa62d1772d1f2e8f93664c1e8cb866374e511")
versionInfo[JavaVersion.VERSION_1_8] = JDK(File(outputDir, "openJdk8_rt.jar.pack.lzma").absoluteFile, "98616c3fc020750dce84f02bc65e5f66b839b29d")
// if we are offline, DO NOT try to download anything
if (!project.gradle.startParameter.isOffline) {
// download JDKS we don't know about
val downloadJdkList = getDownloadJdkList(prefix)
val elements = versionInfo.map { it.value.file.name }
downloadJdkList.removeAll(elements)
for (jdk in downloadJdkList) {
try {
val version = JavaVersion.toVersion(jdk.substring(7, jdk.indexOf('_')))
versionInfo[version] = JDK(File(outputDir, jdk).absoluteFile, "")
} catch (e: Exception) {
logger.error("Unable to parse/download $jdk")
}
}
// download all JDKS ...
for (jdk in versionInfo.values) {
downloadJDK(project, prefix, jdk.file, jdk.checksum)
}
}
val jarFiles = getFiles(outputDir, jarSuffix)
val compressedFiles = getFiles(outputDir, compressSuffix)
var hasFiles = false
// discover which files need compressing
val iterator = jarFiles.iterator()
while (iterator.hasNext()) {
val jarFile = iterator.next()
logger.debug("JarFile $jarFile")
hasFiles = true
val file = getCompressedFile(jarFile)
// Don't always need to compress the jdk files. This checks if the compressed version exists
if (file.canRead() && file.length() > 0) {
iterator.remove()
}
}
// discover which files need un-compressing
val iterator2 = compressedFiles.iterator()
while (iterator2.hasNext()) {
val compressedFile = iterator2.next()
logger.debug("CompressedFile $compressedFile")
hasFiles = true
val file = getUncompressedFile(compressedFile)
// Don't always need to decompress the jdk files. This checks if the extracted version exists
if (file.canRead() && file.length() > 0) {
iterator2.remove()
}
}
if (!hasFiles) {
throw GradleException("Unable to find or extract jar files, none were found in $outputDir")
}
if (!compressedFiles.isEmpty() || !jarFiles.isEmpty()) {
logger.info("Preparing cross compile environment")
}
// NOTE: we also compress files so we can automatically create NEW files if we need to
for (inputFile in jarFiles) {
// pack200 + LZMA
logger.debug("Compressing $inputFile")
var inputStream: InputStream? = null
var outputStream: OutputStream? = null
try {
inputStream = FileInputStream(inputFile)
inputStream = BufferedInputStream(inputStream)
outputStream = FileOutputStream(getCompressedFile(inputFile))
outputStream = BufferedOutputStream(outputStream)
// now pack and compress
outputStream = LZMACompressorOutputStream(outputStream)
outputStream = Pack200CompressorOutputStream(outputStream)
inputStream.copyTo(outputStream)
} catch (e: Exception) {
logger.error("Error compressing files", e)
} finally {
close(outputStream)
close(inputStream)
}
}
for (inputFile in compressedFiles) {
// unLZMA + unpack200
logger.debug("Extracting $inputFile")
var inputStream: InputStream? = null
var outputStream: OutputStream? = null
try {
inputStream = FileInputStream(inputFile)
inputStream = BufferedInputStream(inputStream)
outputStream = FileOutputStream(getUncompressedFile(inputFile))
outputStream = BufferedOutputStream(outputStream)
// now uncompress and unpack
inputStream = LZMACompressorInputStream(inputStream)
inputStream = Pack200CompressorInputStream(inputStream)
inputStream.copyTo(outputStream)
} catch (e: Exception) {
logger.error("Error extracting files", e)
} finally {
close(outputStream)
close(inputStream)
}
}
logger.info("Done preparing cross-compile environment")
}
private fun getDownloadJdkList(prefix: String): ArrayList<String> {
val jdkRuntimes = ArrayList<String>()
with(URL(prefix).openConnection() as HttpURLConnection) {
// optional default is GET
requestMethod = "GET"
logger.debug("Sending 'GET' request to URL : $url")
logger.debug("Response Code : $responseCode")
BufferedReader(InputStreamReader(inputStream)).use {
val jdkDirName = "jdkRuntimes"
val lzmaExtension = ".jar.pack.lzma"
var inputLine = it.readLine()
while (inputLine != null) {
val indexJdk = inputLine.lastIndexOf(jdkDirName)
if (indexJdk > -1) {
val startIndex = indexJdk + jdkDirName.length + 1
val indexLzma = inputLine.indexOf(lzmaExtension, startIndex, false)
if (indexLzma > -1) {
val message = inputLine.substring(startIndex, indexLzma + lzmaExtension.length)
jdkRuntimes.add(message)
logger.debug(message)
}
}
inputLine = it.readLine()
}
}
}
return jdkRuntimes
}
@Suppress("DEPRECATION")
private fun configureTaskBootstrapClassPath(project: Project, task: Task, targetVersion: JavaVersion) {
val location = versionInfo[targetVersion]?.file
if (location != null) {
val file = getUncompressedFile(location)
val bootstrapClasspath = project.files(file)
val bootClasspath = bootstrapClasspath.joinToString(File.pathSeparator)
if (task is JavaCompile) {
logger.debug("Configuring task ${task.name} with $bootClasspath")
if (project.gradle.versionGreaterThan("4.2.1")) {
task.options.bootstrapClasspath = bootstrapClasspath
}
else {
task.options.bootClasspath = bootClasspath
}
}
else if (task is GroovyCompile) {
logger.debug("Configuring task ${task.name} with $bootClasspath")
if (project.gradle.versionGreaterThan("4.2.1")) {
task.options.bootstrapClasspath = bootstrapClasspath
}
else {
task.options.bootClasspath = bootClasspath
}
}
// project.plugins.withId("kotlin") {
// logger.debug("Configuring task ${task.name} with $bootClasspath ?????")
//
// withType(KotlinCompile::class.java) {
// it.kotlinOptions.jdkHome = file.jdkHome
// }
// }
}
else {
logger.error("Unable to determine bootstrap path $targetVersion for ${task.name}")
}
}
private fun getFiles(directory: File, suffix: String): MutableList<File> {
val outputFiles = ArrayList<File>()
if (directory.isDirectory) {
val files = directory.listFiles()
for (file in files) {
val name = file.name
if (name.endsWith(suffix)) {
outputFiles.add(file)
}
}
}
return outputFiles
}
private fun close(inputStream: InputStream?) {
try {
inputStream?.close()
} catch (ignored: Exception) {
}
}
private fun close(outputStream: OutputStream?) {
try {
outputStream?.close()
} catch (ignored: Exception) {
}
}
private fun getUncompressedFile(compressedFile: File): File {
val nameLength = compressedFile.name.length
val fixedName = compressedFile.name.substring(0, nameLength - compressSuffix.length)
return File(compressedFile.parentFile, fixedName)
}
private fun getCompressedFile(jarFile: File): File {
return File(jarFile.parentFile, jarFile.name + compressSuffix)
}
private fun Gradle.versionGreaterThan(version: String): Boolean = versionCompareTo(version) > 0
private fun Gradle.versionCompareTo(version: String): Int {
return GradleVersion.version(gradleVersion).compareTo(GradleVersion.version(version))
}
/**
* checksum can be empty string to do a basic "does this file exist" check to determine if the file needs downloading
* Download, if necessary, the specified JDK
*/
private fun downloadJDK(project: Project, url: String, file: File, sha1Checksum: String) {
var valid = false
val fileName = file.name
if (!sha1Checksum.isEmpty()) {
val verify = project.tasks.create("verify$fileName", Verify::class.java)
verify.src(file)
verify.algorithm("SHA1")
verify.checksum(sha1Checksum)
try {
verify.verify()
valid = true
} catch (ignored: Exception) {
// verify throws an exception if it cannot verify the file.
}
}
else {
// lame way to verify, but tnable to find or extract jar fihere is no checksum...
valid = file.canRead() && file.length() > 0
}
if (!valid) {
val download = project.tasks.create("download$fileName", Download::class.java)
download.src("$url/$fileName")
download.dest(file)
download.quiet(true)
download.overwrite(false)
try {
download.download()
} catch (e: Exception) {
logger.error("Unable to download $url", e)
}
}
}
}