ghidra/gradle/init.gradle
Ryan Kurtz 9e209d90cf GT-2897: User/script now puts yajsw.zip directly into the ghidra source
repo eliminating the need for a ghidra.bin directory to be created.
2019-06-26 12:56:42 -04:00

348 lines
13 KiB
Groovy

/*******************************************************************************
* init.gradle *
* *
* Sets up the gradle configuration for external users and downloads *
* any required dependencies that aren't available in the *
* other online repositories (eg: maven). This should be run *
* immediately after cloning the Ghidra repository before any other gradle *
* tasks are run. *
* *
* Specifically, this task: *
* *
* 1. Downloads various dependencies required by the ghidra build and *
* puts them in <USER_HOME>/flatRepo. The files to be *
* downloaded: *
* - dex-tools-2.0.zip *
* - AXMLPrinter2.jar *
* - hfsexplorer-0_21-bin.zip *
* - yajsw-stable-12.12.zip *
* - cdt-8.6.0.zip *
* - PyDev 6.3.1.zip *
* *
* 2. Creates a gradle configuration file (repos.config) in *
* <USER_HOME>/.gradle/init.d/. This contains repository *
* information used by gradle to find dependencies (it points *
* gradle to the flatRepo location created above). *
* *
* usage: from the command line in the main ghidra repository *
* directory, run the following: *
* *
* gradle --init-script gradle/init.gradle <any_task> *
* *
* Note: Running this script multiple times will cause the config *
* file to be recreated and all dependencies re-downloaded. *
* *
* Note: All files are downloaded to a the standard java temporary folder *
* location, in a sub-folder called 'ghidra. This is cleaned up and *
* removed when the script completes. *
* TODO: make sure this folder is cleaned up in EVERY case, especially *
* if the script fails at some point *
* *
*******************************************************************************/
import java.util.zip.*;
import java.nio.file.*;
import java.security.MessageDigest;
import org.apache.commons.io.*;
import org.apache.commons.io.filefilter.*;
ext.HOME_DIR = System.getProperty('user.home')
ext.REPO_DIR = ((Script)this).buildscript.getSourceFile().getParentFile().getParentFile()
ext.FLAT_REPO_DIR = new File(HOME_DIR, "flatRepo")
File TMP_DIR = new File(System.getProperty('java.io.tmpdir'))
ext.DOWNLOADS_DIR = new File(TMP_DIR, "ghidra")
ext.FILE_SIZE = 0;
// The URLs for each of the archives to be downloaded
ext.DEX_ZIP = 'https://github.com/pxb1988/dex2jar/releases/download/2.0/dex-tools-2.0.zip'
ext.AXML_ZIP = 'https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/android4me/AXMLPrinter2.jar'
ext.HFS_ZIP = 'https://sourceforge.net/projects/catacombae/files/HFSExplorer/0.21/hfsexplorer-0_21-bin.zip'
ext.YAJSW_ZIP = 'https://sourceforge.net/projects/yajsw/files/yajsw/yajsw-stable-12.12/yajsw-stable-12.12.zip'
ext.PYDEV_ZIP = 'https://sourceforge.net/projects/pydev/files/pydev/PyDev%206.3.1/PyDev%206.3.1.zip'
ext.CDT_ZIP = 'http://www.eclipse.org/downloads/download.php?r=1&protocol=https&file=/tools/cdt/releases/8.6/cdt-8.6.0.zip'
// Store the MD5s for each of the downloads so we can verify that we retrieved them
// all successfully
ext.DEX_MD5 = '032456b9db9e6059376611553aecf31f'
ext.AXML_MD5 = '55d70be9862c2b456cc91a933c197934'
ext.HFS_MD5 = 'cc1713d634d2cd1fd7f21e18ae4d5d5c'
ext.YAJSW_MD5 = 'e490ea92554f0238d74d4ef6161cb2c7'
ext.PYDEV_MD5 = '06263bdef4917c49d8d977d12c2d5073'
ext.CDT_MD5 = 'd41d8cd98f00b204e9800998ecf8427e'
// Number of times to try and establish a connection when downloading files before
// failing
ext.NUM_RETRIES = 2
// Set up a maven repository configuration so we can get access to Apache FileUtils for
// copying/deleting files.
initscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'commons-io:commons-io:2.5'
}
}
// This is where the real flow of the script starts...
try {
createDirs()
createConfigFile()
populateFlatRepo()
}
finally {
cleanup()
}
/**
* Creates the directories where the dependencies will be downloaded and stored
*/
def createDirs() {
if (!DOWNLOADS_DIR.exists()) {
DOWNLOADS_DIR.mkdirs()
}
if (!FLAT_REPO_DIR.exists()) {
FLAT_REPO_DIR.mkdirs()
}
}
/**
* Creates the repos.gradle configuration file that tells Gradle
* where to look for dependencies. This ensures that Gradle will
* find the jars we store in the local flat repo.
*/
def createConfigFile() {
ext.repoConfigDir = new File(HOME_DIR + "/.gradle/init.d")
ext.repoConfigFile = new File(repoConfigDir, "repos.gradle")
if (!repoConfigDir.exists()) {
repoConfigDir.mkdirs()
}
repoConfigFile.write("ext.HOME = System.getProperty('user.home')")
repoConfigFile.append("\nallprojects {")
repoConfigFile.append("\nrepositories {")
repoConfigFile.append("\nmavenCentral()")
repoConfigFile.append("\njcenter()")
repoConfigFile.append('\nflatDir name: "flat", dirs:["$HOME/flatRepo"]')
repoConfigFile.append("\n}")
repoConfigFile.append("\n}")
}
/**
* Downloads a file from a URL. If there is a problem connecting to the given
* URL the attempt will be retried NUM_RETRIES times before failing.
*
* Progress is shown on the command line in the form of the number of bytes
* downloaded and a percentage of the total.
*
* Note: We do not validate that the number of bytes downloaded matches the
* expected total here; any discrepencies will be caught when checking
* the MD5s later on.
*
* @param url the file to download
* @param filename the local file to create for the download
*/
def download(url, filename) {
println("File: " + url)
BufferedInputStream istream = establishConnection(url, NUM_RETRIES);
assert istream != null : " ***CONNECTION FAILURE***\n max attempts exceeded; exiting\n"
FileOutputStream ostream = new FileOutputStream(filename);
def dataBuffer = new byte[1024];
int bytesRead;
int totalRead;
while ((bytesRead = istream.read(dataBuffer, 0, 1024)) != -1) {
ostream.write(dataBuffer, 0, bytesRead);
totalRead += bytesRead
// print progress on the same line in the console...
int pctComplete = (totalRead / FILE_SIZE) * 100
print("\r")
print(" Downloading: " + totalRead + " of " + FILE_SIZE + " (" + pctComplete + "%)")
System.out.flush()
}
println("")
istream.close();
ostream.close();
}
/**
* Attemps to establish a connection to the given URL.
*
* @param url the site to connect to
* @param retries the number of times to attempt to reconnect if there is a failure
* @return the InputStream for the URL
*/
def establishConnection(url, retries) {
for (int i=0; i<retries; i++) {
try {
println(" Connect attempt " + (i+1) + " of " + retries)
URLConnection conn = new URL(url).openConnection();
FILE_SIZE = conn.getContentLength();
return new BufferedInputStream(new URL(url).openStream());
}
catch (Exception e) {
println(" Connection error! " + e)
}
}
}
/**
* Unzips a file to a directory
*
* @param sourceDir the directory where the zip file resides
* @param targetDir the directory where the unzipped files should be placed
* @param zipFileName the name of the file to unpack
*/
def unzip(sourceDir, targetDir, zipFileName) {
def zip = new ZipFile(new File(sourceDir, zipFileName))
zip.entries().findAll { !it.directory }.each { e ->
(e.name as File).with { f ->
if (f.parentFile != null) {
File destPath = new File(targetDir.path, f.parentFile.path)
destPath.mkdirs()
File targetFile = new File(destPath.path, f.name)
targetFile.withOutputStream { w ->
w << zip.getInputStream(e)
}
}
}
}
}
/**
* Downloads and stores the necessary dependencies in the local
* flat repository.
*/
def populateFlatRepo() {
// 1. Download all the dependencies.
download (DEX_ZIP, DOWNLOADS_DIR.path + '/dex-tools-2.0.zip')
validateChecksum(DOWNLOADS_DIR.path + '/dex-tools-2.0.zip', DEX_MD5);
download (AXML_ZIP, FLAT_REPO_DIR.path + '/AXMLPrinter2.jar')
validateChecksum(FLAT_REPO_DIR.path + '/AXMLPrinter2.jar', AXML_MD5);
download (HFS_ZIP, DOWNLOADS_DIR.path + '/hfsexplorer-0_21-bin.zip')
validateChecksum(DOWNLOADS_DIR.path + '/hfsexplorer-0_21-bin.zip', HFS_MD5);
download (YAJSW_ZIP, DOWNLOADS_DIR.path + '/yajsw-stable-12.12.zip')
validateChecksum(DOWNLOADS_DIR.path + '/yajsw-stable-12.12.zip', YAJSW_MD5);
download (PYDEV_ZIP, DOWNLOADS_DIR.path + '/PyDev 6.3.1.zip')
validateChecksum(DOWNLOADS_DIR.path + '/PyDev 6.3.1.zip', PYDEV_MD5);
download (CDT_ZIP, DOWNLOADS_DIR.path + '/cdt-8.6.0.zip')
validateChecksum(DOWNLOADS_DIR.path + '/cdt-8.6.0.zip', CDT_ZIP);
// 2. Unzip the dependencies
unzip(DOWNLOADS_DIR, DOWNLOADS_DIR, "dex-tools-2.0.zip")
unzipHfsx()
// 3. Copy the necessary jars to the flatRepo directory. Yajsw, CDT, and PyDev go directly into
// the source repository.
copyDexTools()
copyHfsx()
copyYajsw()
copyPyDev()
copyCdt()
}
/**
* Generates the md5 for the given file and compares it against the
* expected result. If there is no match an assert exception will be
* generated.
*
* @param filename the fully-qualified file path+name
* @param expectedMd5 the expected md5 for the file
*/
def validateChecksum(filename, expectedMd5) {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(Files.readAllBytes(Paths.get(filename)));
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
assert(sb.toString().equals(expectedMd5));
}
/**
* Unzips the hfsx zip file
*/
def unzipHfsx() {
def hfsxdir = getOrCreateTempHfsxDir()
unzip (DOWNLOADS_DIR, hfsxdir, "hfsexplorer-0_21-bin.zip")
}
/**
* Copies the dex-tools jars to the flat repository
*
* Note: This will only copy files beginning with "dex-"
*/
def copyDexTools() {
FileUtils.copyDirectory(new File(DOWNLOADS_DIR, 'dex2jar-2.0/lib/'), FLAT_REPO_DIR, new WildcardFileFilter("dex-*"));
}
/**
* Copies the necessary hfsx jars to the flat repository
*/
def copyHfsx() {
FileUtils.copyFile(new File(DOWNLOADS_DIR, "hfsx/lib/csframework.jar"), new File(FLAT_REPO_DIR, "csframework.jar"));
FileUtils.copyFile(new File(DOWNLOADS_DIR, "hfsx/lib/hfsx_dmglib.jar"), new File(FLAT_REPO_DIR, "hfsx_dmglib.jar"));
FileUtils.copyFile(new File(DOWNLOADS_DIR, "hfsx/lib/hfsx.jar"), new File(FLAT_REPO_DIR, "hfsx.jar"));
FileUtils.copyFile(new File(DOWNLOADS_DIR, "hfsx/lib/iharder-base64.jar"), new File(FLAT_REPO_DIR, "iharder-base64.jar"));
}
/**
* Copies the yajswdir zip to its location in the GhidraServer project.
*/
def copyYajsw() {
FileUtils.copyFile(new File(DOWNLOADS_DIR, "yajsw-stable-12.12.zip"), new File(REPO_DIR, "Ghidra/Features/GhidraServer/build/data/yajsw-stable-12.12.zip"));
}
/**
* Copies the pydev zip to its bin repository location
*/
def copyPyDev() {
FileUtils.copyFile(new File(DOWNLOADS_DIR, 'PyDev 6.3.1.zip'), new File(REPO_DIR, "GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/data/buildDependencies/PyDev 6.3.1.zip"));
}
/**
* Copies the cdt zip to its bin repository location
*/
def copyCdt() {
FileUtils.copyFile(new File(DOWNLOADS_DIR, 'cdt-8.6.0.zip'), new File(REPO_DIR, "GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/data/buildDependencies/cdt-8.6.0.zip"));
}
/**
* Creates a temporary folder to house the hfsx zip contents
*
* @return the newly-created hfsx directory object
*/
def getOrCreateTempHfsxDir() {
def hfsxdir = new File (DOWNLOADS_DIR, "hfsx")
if (!hfsxdir.exists()) {
hfsxdir.mkdir()
}
return hfsxdir;
}
/**
* Performs any cleanup operations that need to be performed after the flat repo has
* been populated.
*/
def cleanup() {
if (DOWNLOADS_DIR.exists()) {
FileUtils.deleteDirectory(DOWNLOADS_DIR)
}
}