mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-22 04:05:39 +00:00
1194 lines
39 KiB
Groovy
1194 lines
39 KiB
Groovy
import org.apache.tools.ant.filters.*
|
|
|
|
/*********************************************************************************
|
|
* distribution.gradle
|
|
*
|
|
* This contains gradle tasks for packaging Ghidra artifacts for distribution. To
|
|
* run a full distribution, execute either the "createLocalInstallation" or
|
|
* "createMultiPlatformInstallation" tasks. The former will create a distribution
|
|
* for the local platform only; the latter will create it for all available platforms.
|
|
*
|
|
* Note: This is included from the main build.gradle file.
|
|
*
|
|
*********************************************************************************/
|
|
|
|
/*********************************************************************************
|
|
* NOTE: Throughout this file, many of the "into" clauses of zip tasks are
|
|
* enclosed within a closure. This is to delay the evaluation of the zip
|
|
* destination path, so that subprojects have the opportunity to change that
|
|
* path. Specifically, subprojects can specify an "extendsFromProject" property
|
|
* which will cause the content from that project to be "overlayed" onto the
|
|
* base project within the zip and consequently, the extracted distribution.
|
|
* For example, if a project named "Bar" extended a project named "Foo" such
|
|
* that in the distribution, you wanted all the files from "Foo" and "Bar" to
|
|
* exist in a module named "Foo", you would include the following line in the
|
|
* build.gradle file for "Bar"
|
|
*
|
|
* project.ext.extendsFromProject = project(':Foo')
|
|
*
|
|
*********************************************************************************/
|
|
|
|
/********************************************************************************
|
|
* Local Vars
|
|
*********************************************************************************/
|
|
def currentPlatform = getCurrentPlatformName()
|
|
def PROJECT_DIR = file (rootProject.projectDir.absolutePath)
|
|
def DISTRIBUTION_DIR = file("$buildDir/dist")
|
|
def ZIP_NAME_PREFIX = "${rootProject.DISTRO_PREFIX}_${rootProject.RELEASE_NAME}_${rootProject.BUILD_DATE_SHORT}"
|
|
ext.ZIP_DIR_PREFIX = rootProject.DISTRO_PREFIX
|
|
|
|
|
|
FileTree javadocFiles = fileTree (rootProject.projectDir.toString()) {
|
|
include '**/Framework/**/*.java'
|
|
include '**/Features/Base/src/main/java/**/*.java'
|
|
exclude '**/Features/Base/src/main/java/ghidra/app/plugin/**/*.java';
|
|
include '**/Features/Decompiler/src/main/java/ghidra/app/decompiler/**/*.java'
|
|
include '**/Features/Python/**/*.java'
|
|
exclude '**/GhidraBuild/**/*.java';
|
|
exclude '**/src/test/**'
|
|
exclude '**/src/test.slow/**'
|
|
exclude '**/pcodeCPort/**' // not intended for general consumption
|
|
}
|
|
|
|
/*********************************************************************************
|
|
* JAVADOCS - RAW
|
|
*
|
|
* Creates javadocs for all source defined in the above 'javadocFiles' file tree.
|
|
*
|
|
* Note: Artifacts are placed in a temporary folder that is deleted upon
|
|
* completion of the build.
|
|
*
|
|
*********************************************************************************/
|
|
task createJavadocs(type: Javadoc, description: 'Generate javadocs for all projects', group: 'Documentation') {
|
|
|
|
destinationDir = file(rootProject.projectDir.toString() + "/build/tmp/javadoc")
|
|
|
|
failOnError false
|
|
|
|
// Here for reference. If we want to turn on javadoc for all source files, uncomment
|
|
// the following line (and comment out the next one):
|
|
// source subprojects.collect { it.sourceSets.main.allJava }
|
|
source javadocFiles
|
|
|
|
// Must add classpath for main and test source sets. Javadoc will fail if it cannot
|
|
// find referenced classes.
|
|
classpath = files(subprojects.findAll {
|
|
!it.name.equals("JsonDoclet") // Need to exclude this until we upgrade json-simple (hamcrest dependency issue)
|
|
}.collect {
|
|
it.sourceSets.main.compileClasspath
|
|
it.sourceSets.test.compileClasspath
|
|
})
|
|
|
|
// If we don't exclude module directories, the javascript search feature doesn't work
|
|
options.addBooleanOption("-no-module-directories", true)
|
|
|
|
// Some internal packages are not public and need to be exported.
|
|
options.addMultilineStringsOption("-add-exports").setValue(["java.desktop/sun.awt.image=ALL-UNNAMED",
|
|
"java.desktop/sun.awt=ALL-UNNAMED",
|
|
"java.base/sun.security.x509=ALL-UNNAMED",
|
|
"java.base/sun.security.provider=ALL-UNNAMED",
|
|
"java.base/sun.security.util=ALL-UNNAMED"])
|
|
}
|
|
|
|
/*********************************************************************************
|
|
* JSONDOCS - RAW
|
|
*
|
|
* Creates JSON docs for all source defined in the above 'javadocFiles' file tree.
|
|
* These documents are used by Python to show system documentation (whereas Java will
|
|
* use Javadoc files).
|
|
*
|
|
* Note: Artifacts are placed in a temporary folder that is deleted upon
|
|
* completion of the build.
|
|
*
|
|
*********************************************************************************/
|
|
|
|
configurations {
|
|
jsondoc
|
|
}
|
|
|
|
dependencies {
|
|
jsondoc project('JsonDoclet')
|
|
}
|
|
|
|
task createJsondocs(type: Javadoc, description: 'Generate JSON docs for all projects', group: 'Documentation') {
|
|
|
|
group 'private'
|
|
|
|
String ROOT_PROJECT_DIR = rootProject.projectDir.toString()
|
|
|
|
destinationDir = file(ROOT_PROJECT_DIR + "/build/tmp/jsondoc")
|
|
|
|
failOnError false
|
|
|
|
// Here for reference. If we want to turn on javadoc for all source files, uncomment
|
|
// the following line (and comment out the next one):
|
|
// source subprojects.collect { it.sourceSets.main.allJava }
|
|
source javadocFiles
|
|
|
|
// Must add classpath for main and test source sets. Javadoc will fail if it cannot
|
|
// find referenced classes.
|
|
classpath = files(subprojects.findAll {
|
|
!it.name.equals("JsonDoclet") // Need to exclude this until we upgrade json-simple (hamcrest dependency issue)
|
|
}.collect {
|
|
it.sourceSets.main.compileClasspath
|
|
it.sourceSets.test.compileClasspath
|
|
})
|
|
|
|
// Generate at package level because user may try to get help directly on an object they have
|
|
// rather than its public interface.
|
|
options.addBooleanOption("package", true)
|
|
|
|
// Some internal packages are not public and need to be exported.
|
|
options.addMultilineStringsOption("-add-exports").setValue(["java.desktop/sun.awt.image=ALL-UNNAMED",
|
|
"java.desktop/sun.awt=ALL-UNNAMED",
|
|
"java.base/sun.security.x509=ALL-UNNAMED",
|
|
"java.base/sun.security.provider=ALL-UNNAMED",
|
|
"java.base/sun.security.util=ALL-UNNAMED"])
|
|
|
|
options.doclet = "JsonDoclet"
|
|
doFirst {
|
|
options.docletpath = new ArrayList(configurations.jsondoc.files)
|
|
}
|
|
dependsOn project('JsonDoclet').jar // Wish I didn't have to reach like this
|
|
}
|
|
|
|
/*********************************************************************************
|
|
* JAVADOCS - ZIP
|
|
*
|
|
* Creates a zip file of all javadocs to be put in the release.
|
|
*
|
|
* Note: Artifacts are placed in a temporary folder, deleted at build completion
|
|
*
|
|
*********************************************************************************/
|
|
task zipJavadocs(type: Zip) {
|
|
group 'private'
|
|
archiveName 'GhidraAPI_javadoc.zip'
|
|
destinationDir file(rootProject.projectDir.toString() + "/build/tmp")
|
|
|
|
from createJavadocs {
|
|
into "api"
|
|
}
|
|
|
|
from createJsondocs {
|
|
into "api"
|
|
}
|
|
|
|
description "Zips javadocs for Ghidra API. [gradleScripts/distribution.gradle]"
|
|
}
|
|
|
|
/*********************************************************************************
|
|
* COMMON
|
|
*
|
|
* Copies all common jars and associated resources. This is basically
|
|
* everything that is NOT an extension.
|
|
*
|
|
*********************************************************************************/
|
|
task assembleCommon (type: Copy) {
|
|
|
|
group 'private'
|
|
description "Copies the common files/folders to the distribution location."
|
|
destinationDir file(DISTRIBUTION_DIR.getPath() + "/" + ZIP_DIR_PREFIX)
|
|
dependsOn {subprojects.ip}
|
|
dependsOn {subprojects.assemble}
|
|
|
|
|
|
// Make sure that we don't try to copy the same file with the same path.
|
|
duplicatesStrategy 'exclude'
|
|
|
|
exclude "**/certification.manifest"
|
|
exclude "**/certification.local.manifest"
|
|
exclude "**/.project"
|
|
exclude "**/.classpath"
|
|
exclude "**/delete.me"
|
|
exclude "**/.vs/**"
|
|
exclude "**/*.vcxproj.user"
|
|
|
|
/////////////////
|
|
// GHIDRA PROJECTS
|
|
/////////////////
|
|
subprojects.each { p ->
|
|
p.afterEvaluate {
|
|
|
|
boolean excludeFromBuild = p.findProperty("excludeFromBuild") ?: false;
|
|
|
|
if (!excludeFromBuild && isGhidra(p) && !isExtension(p)) {
|
|
|
|
if (!isEmptyProject(p)) {
|
|
from (p.jar) {
|
|
// use closures for getting zip path to delay evaluation. See note at top of
|
|
//file.
|
|
into { getZipPath(p) + "/lib" }
|
|
}
|
|
}
|
|
|
|
from (p.projectDir.toString() + "/Module.manifest") {
|
|
into { getZipPath(p) }
|
|
}
|
|
|
|
from (p.projectDir.toString() + "/data") {
|
|
into { getZipPath(p) + "/data" }
|
|
exclude 'build.xml' // associated with language modules (dev use only)
|
|
}
|
|
|
|
from (BIN_REPO + '/' + getZipPath(p) + "/data") {
|
|
into { getZipPath(p) + "/data" }
|
|
}
|
|
|
|
from (p.projectDir.toString() + "/ghidra_scripts") {
|
|
exclude 'bin/'
|
|
into { getZipPath(p) + "/ghidra_scripts" }
|
|
}
|
|
|
|
from (p.projectDir.toString() + "/build/LICENSE.txt") {
|
|
into { getZipPath(p) }
|
|
}
|
|
|
|
// handle special case where modules build data artifacts into the build dir
|
|
from (p.projectDir.toString() + "/build/data") {
|
|
into {getZipPath(p) + "/data" }
|
|
}
|
|
|
|
// External Libraries
|
|
gradle.taskGraph.whenReady { taskGraph ->
|
|
List<String> externalPaths = getExternalDependencies(p)
|
|
externalPaths.each { path ->
|
|
from (path) {
|
|
into { getZipPath(p) + "/lib" }
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////
|
|
// SOURCE FOR BUILD
|
|
//
|
|
// Some projects require that we provide source that can be built (makefiles,
|
|
// gradle build files, c-code, etc...). If a project has a task for that
|
|
// purpose, execute it here.
|
|
//
|
|
// Note the 'afterEvaluate' call - this must be done so the zip task in the
|
|
// subproject is added to the task graph before the 'dependsOn' clause is
|
|
// executed. If we don't do this, that dependsOn relationship won't be respected
|
|
// and the subproject zip task won't be executed.
|
|
/////////////////
|
|
p.tasks.each { t ->
|
|
if (t.name == "zipBuildableSource") {
|
|
assembleCommon.dependsOn {t}
|
|
from (t) {
|
|
into getZipPath(p)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////
|
|
// COPY all GPL code
|
|
/////////////////////////////
|
|
from (ROOT_PROJECT_DIR + "/GPL") {
|
|
exclude "*/bin"
|
|
exclude "*/build"
|
|
into "GPL"
|
|
}
|
|
|
|
//////////////////////////////
|
|
// LGPL SOURCE INCLUSION
|
|
//////////////////////////////
|
|
from ("${rootProject.ext.BIN_REPO}/ExternalLibraries/libsSrc/jcalendar-1.3.3.zip" ) {
|
|
into "GPL/librarySourceForLGPL"
|
|
}
|
|
|
|
//////////////////////////////
|
|
// LICENSE SUPPORT
|
|
//////////////////////////////
|
|
with getMultiRepoCopySpec( "licenses", "licenses" )
|
|
from (ROOT_PROJECT_DIR) {
|
|
include "LICENSE.txt"
|
|
}
|
|
|
|
|
|
//////////////////////////////
|
|
// LAUNCH SUPPORT
|
|
//////////////////////////////
|
|
from (project("LaunchSupport").jar) {
|
|
into "support"
|
|
}
|
|
|
|
//////////////////////////////
|
|
// SKELETON PROJECT
|
|
//////////////////////////////
|
|
from (ROOT_PROJECT_DIR + "/GhidraBuild/Skeleton") {
|
|
exclude 'bin'
|
|
exclude 'build'
|
|
exclude 'ghidra_scripts/bin/'
|
|
exclude '.classpath'
|
|
exclude '.project'
|
|
rename "buildTemplate.gradle", "build.gradle"
|
|
into "Extensions/Ghidra/Skeleton"
|
|
}
|
|
|
|
//////////////////////////////
|
|
// ECLIPSE SUPPORT
|
|
//////////////////////////////
|
|
from (BIN_REPO + "/GhidraBuild/EclipsePlugins/GhidraDev") {
|
|
include 'GhidraDev*.zip'
|
|
into "Extensions/Eclipse/GhidraDev/"
|
|
}
|
|
from (ROOT_PROJECT_DIR + "/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin") {
|
|
include 'GhidraDev_README.html'
|
|
into "Extensions/Eclipse/GhidraDev/"
|
|
}
|
|
|
|
/////////////////
|
|
// DB DIR LOCK FILE
|
|
//
|
|
// This lock file must be created to prevent users from modifying script files. We
|
|
// create it here, copy it to the archive, then delete it.
|
|
/////////////////
|
|
File dbLockFile = file('Ghidra/.dbDirLock')
|
|
|
|
from ('Ghidra') {
|
|
include '.dbDirLock'
|
|
into 'Ghidra'
|
|
|
|
doFirst {
|
|
dbLockFile.withWriter { out ->
|
|
out.writeLine("lock file to prevent modification of core ghidra scripts")
|
|
}
|
|
}
|
|
doLast {
|
|
dbLockFile.delete()
|
|
}
|
|
}
|
|
|
|
/////////////////
|
|
// SUPPORT SCRIPTS
|
|
/////////////////
|
|
from (ROOT_PROJECT_DIR + "/Ghidra/RuntimeScripts/Common/support") {
|
|
into "support"
|
|
}
|
|
|
|
/////////////////
|
|
// SERVER SCRIPTS
|
|
/////////////////
|
|
from (ROOT_PROJECT_DIR + "/Ghidra/RuntimeScripts/Common/server") {
|
|
into "server"
|
|
}
|
|
|
|
/////////////////
|
|
// APPLICATION PROPERTIES
|
|
/////////////////
|
|
from (ROOT_PROJECT_DIR + "/Ghidra/application.properties") {
|
|
def buildDateFile = file("$buildDir/build_date.properties")
|
|
doFirst {
|
|
file("$buildDir").mkdirs()
|
|
buildDateFile.text = ""
|
|
if (rootProject.BUILD_DATES_NEEDED) {
|
|
buildDateFile.text += "application.build.date=" + rootProject.BUILD_DATE + "\n"
|
|
buildDateFile.text += "application.build.date.short=" + rootProject.BUILD_DATE_SHORT + "\n"
|
|
}
|
|
}
|
|
doLast {
|
|
delete buildDateFile
|
|
}
|
|
into "Ghidra"
|
|
filter (ConcatFilter, prepend: buildDateFile)
|
|
}
|
|
|
|
/////////////////
|
|
// GLOBALS
|
|
/////////////////
|
|
subprojects { p ->
|
|
|
|
if (!isExtension(p)) {
|
|
|
|
// First get a list of all files that are under 'src/global'.
|
|
FileTree fileTree = getGlobalFiles(p)
|
|
|
|
// Now loop over each one, copying it into the zip we're creating. Each will be placed
|
|
// at the root level, starting with the first folder AFTER 'src/global/'.
|
|
//
|
|
// eg: If the file is '/Ghidra/Configurations/Common/src/global/docs/hello.html', then
|
|
// the file in the zip will be at /docs/hello.html
|
|
//
|
|
fileTree.each { File file ->
|
|
String filePath = getGlobalFilePathSubDirName(file)
|
|
from (file) {
|
|
into filePath
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////
|
|
// GHIDRA DOCS
|
|
/////////////////
|
|
with configure(getMultiRepoCopySpec( "GhidraDocs", "docs" )) {
|
|
exclude "**/build.gradle"
|
|
exclude "**/build/**"
|
|
}
|
|
|
|
/////////////////
|
|
// IDA PRO
|
|
/////////////////
|
|
from (ROOT_PROJECT_DIR + "/GhidraBuild/IDAPro") {
|
|
into "Extensions/IDAPro"
|
|
exclude "certification.manifest"
|
|
exclude ".classpath"
|
|
exclude ".project"
|
|
}
|
|
|
|
// Special Case: The xmlldr.py file needs to be in two places in the distribution, so
|
|
// after copying over the full IDA directory structure above, do an additional copy
|
|
// of this one specific file to the 'plugins' folder.
|
|
from (ROOT_PROJECT_DIR + "/GhidraBuild/IDAPro/Python/6xx/loaders/xmlldr.py") {
|
|
into "Extensions/IDAPro/Python/6xx/plugins"
|
|
}
|
|
|
|
/////////////////
|
|
// JAVADOCS
|
|
/////////////////
|
|
from (zipJavadocs) {
|
|
into 'docs'
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************
|
|
* COMMON - NATIVES
|
|
*
|
|
* Creates copy tasks for each platform, to move native files to the
|
|
* distribution folder.
|
|
*
|
|
* Input: Native executables created during the build phase. It is assumed that
|
|
* these have already been built and are located in the proper location.
|
|
*
|
|
*********************************************************************************/
|
|
project.OS_NAMES.each { platform ->
|
|
task ("assemble$platform", type: Copy ) {
|
|
|
|
// Running the 'assemble' gradle task will run all compilation tasks for all languages. So
|
|
// this must be run before executing this task.
|
|
dependsOn {subprojects.assemble}
|
|
|
|
// delete the gradle ziptree temp directory because of gradle bug not cleaning up its temp files.
|
|
delete rootProject.file("build/tmp/expandedArchives")
|
|
|
|
// get the natives for the specified platform
|
|
subprojects { sub ->
|
|
dependsOn { sub.tasks.findAll { it.name == "buildNatives_$platform" } }
|
|
}
|
|
|
|
group 'private'
|
|
description "Copies the platform-dependent files/folders to the distribution location."
|
|
destinationDir file(DISTRIBUTION_DIR.getPath() + "/" + ZIP_DIR_PREFIX)
|
|
|
|
// Make sure that we don't try to copy the same file with the same path into the
|
|
// zip (this can happen!)
|
|
duplicatesStrategy 'exclude'
|
|
|
|
String ROOT_PROJECT_DIR = rootProject.projectDir.toString()
|
|
|
|
gradle.taskGraph.whenReady {
|
|
|
|
/////////////////
|
|
// GHIDRA PROJECTS
|
|
/////////////////
|
|
subprojects { p ->
|
|
if (isGhidra(p)) {
|
|
|
|
// the getZipPath calls here are not in closures because we are already in a taskGraph.whenReady closure
|
|
from (p.projectDir.toString() + "/build/os/$platform") {
|
|
exclude '*.lib'
|
|
exclude '*.exp'
|
|
into getZipPath(p) + "/os/$platform"
|
|
}
|
|
from (p.projectDir.toString() + "/os/$platform") {
|
|
into getZipPath(p) + "/os/$platform"
|
|
}
|
|
|
|
// Special case for Win64 build as we have to also include the Win32 binaries
|
|
// in the final zip.
|
|
if (platform == "win64") {
|
|
from (p.projectDir.toString() + "/build/os/win32") {
|
|
into getZipPath(p) + "/os/win32"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////
|
|
// SUPPORT SCRIPTS
|
|
/////////////////
|
|
if( isLinux(platform) || isMac(platform) ) {
|
|
with getMultiRepoCopySpec( "Ghidra/RuntimeScripts/Linux/support", "support" )
|
|
}
|
|
if( isWindows(platform) ) {
|
|
with getMultiRepoCopySpec( "Ghidra/RuntimeScripts/Windows/support", "support" )
|
|
}
|
|
|
|
/////////////////
|
|
// SERVER SCRIPTS
|
|
/////////////////
|
|
if( isLinux(platform) || isMac(platform) ) {
|
|
from (ROOT_PROJECT_DIR + "/Ghidra/RuntimeScripts/Linux/server") {
|
|
into "server"
|
|
}
|
|
}
|
|
if( isWindows(platform) ) {
|
|
from (ROOT_PROJECT_DIR + "/Ghidra/RuntimeScripts/Windows/server") {
|
|
into "server"
|
|
}
|
|
}
|
|
|
|
/////////////////
|
|
// GHIDRA RUN SCRIPT
|
|
/////////////////
|
|
if( isLinux(platform) || isMac(platform) ) {
|
|
from (ROOT_PROJECT_DIR + "/Ghidra/RuntimeScripts/Linux") {
|
|
include "ghidraRun"
|
|
}
|
|
}
|
|
if( isWindows(platform) ) {
|
|
from (ROOT_PROJECT_DIR + "/Ghidra/RuntimeScripts/Windows") {
|
|
include "ghidraRun.bat"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************
|
|
* SOURCE - SUBPROJECTS
|
|
*
|
|
* Creates tasks for each project that will generate a zip file containing
|
|
* all source files in that project.
|
|
*
|
|
* Input: Everything under the 'src/' folder.
|
|
*
|
|
* Output: A zip file of the form "source-<project name>.zip".
|
|
*
|
|
* --------------------------------------------------------------------------------
|
|
*
|
|
* Note: These tasks are not intended to be called directly (though they may).
|
|
* They are utilized by the 'zipAllSource' task (defined below).
|
|
*
|
|
* Note2: This task MUST be placed above the zipAllSource task.
|
|
*********************************************************************************/
|
|
task zipSourceSubprojects {
|
|
|
|
subprojects { p ->
|
|
|
|
task zipSourceSubproject (type: Zip) { t ->
|
|
|
|
// Define some metadata about the zip (name, location, version, etc....)
|
|
t.group 'private'
|
|
t.description "Creates the source zips for each module [gradleScripts/distribution.gradle]"
|
|
t.archiveName p.name + "-src.zip"
|
|
t.destinationDir file(p.projectDir.path + "/build/tmp/src")
|
|
|
|
// Without this we get duplicate files but it's unclear why. It doesn't seem that this
|
|
// task is being executed multiple times, and sourceSets.main.java contains the
|
|
// correct elements. Whatever the cause, this fixes the problem.
|
|
duplicatesStrategy 'exclude'
|
|
|
|
from sourceSets.main.java
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*********************************************************************************
|
|
* SOURCE - COMMON
|
|
*
|
|
* Copies source zips for all common projects to the distribution folder.
|
|
*
|
|
**********************************************************************************/
|
|
task assembleSourceCommon (type: Copy) {
|
|
|
|
group 'private'
|
|
description "Copies source zips for all common projects to the distribution folder"
|
|
destinationDir DISTRIBUTION_DIR
|
|
|
|
// Loop over all projects and all tasks. We need to find all the dynamically-created tasks
|
|
// that were generated by the createSourceZipTasksForProjects task. Each of these is called:
|
|
//
|
|
// "zipSource"
|
|
//
|
|
// The individual zip tasks are unique because the task name is relative to the project it
|
|
// is a member of.
|
|
allprojects.tasks.each { tasks ->
|
|
|
|
tasks.each { task ->
|
|
|
|
if (task.name.contains("zipSourceSubproject")) {
|
|
|
|
if (isGhidra(task.project) && !isExtension(task.project)) {
|
|
|
|
// Ensure that this task won't run until the task which generates the project
|
|
// zip file is run.
|
|
assembleSourceCommon.dependsOn(task)
|
|
|
|
// Set the copy task info to grab all output from the given task, and place it at
|
|
// the location just determined.
|
|
// compute into clause in closure for delayed evaluation. See note at top of file
|
|
assembleSourceCommon.from (task) {
|
|
into { ZIP_DIR_PREFIX + "/" + getZipPath(task.project) + "/lib" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This forces the task that creates the project zip files to be evaluated first.
|
|
dependsOn (zipSourceSubprojects)
|
|
}
|
|
|
|
|
|
/*********************************************************************************
|
|
* CONTRIBS
|
|
*
|
|
* Zips up contribs and places them in the distribution folder.
|
|
*
|
|
* --------------------------------------------------------------------------------
|
|
* Note: Each individual contrib should extend these tasks in their own build
|
|
* scripts to provide additional functionality.
|
|
*
|
|
**********************************************************************************/
|
|
subprojects { p ->
|
|
|
|
if (isExtension(p)) {
|
|
|
|
task zipExtensions (type: Zip) {
|
|
|
|
it.group 'private'
|
|
it.description "Creates a zip file for an extension module. [gradleScripts/distribution.gradle]"
|
|
it.archiveName "${ZIP_NAME_PREFIX}_${p.name}.zip"
|
|
it.destinationDir DISTRIBUTION_DIR
|
|
|
|
// Make sure that we don't try to copy the same file with the same path into the
|
|
// zip (this can happen!)
|
|
duplicatesStrategy 'exclude'
|
|
|
|
// Exclude any files that contain "delete.me" in the path; this is a convention we used
|
|
// at one time that should be removed.
|
|
exclude "**/delete.me"
|
|
|
|
// This filtered property file copy must appear before the general
|
|
// copy to ensure that it is prefered over the unmodified file
|
|
File propFile = new File(p.projectDir, "extension.properties")
|
|
from (propFile) {
|
|
String version = "${rootProject.RELEASE_VERSION}"
|
|
filter (ReplaceTokens, tokens: [extversion: version])
|
|
into { getBaseProjectName(p) }
|
|
}
|
|
|
|
if (!isEmptyProject(p)) {
|
|
from (p.jar) {
|
|
// use closures for getting zip path to delay evaluation. See note at top of
|
|
//file.
|
|
into { getBaseProjectName(p) + "/lib" }
|
|
}
|
|
}
|
|
|
|
from (p.projectDir) { f ->
|
|
exclude 'build/**'
|
|
exclude 'build.gradle'
|
|
exclude 'certification.manifest'
|
|
exclude "*.project"
|
|
exclude "*.classpath"
|
|
exclude 'dist/**'
|
|
exclude '.gradle/**/*'
|
|
exclude 'ghidra_scripts/bin/'
|
|
exclude 'bin/**'
|
|
exclude 'src/**'
|
|
exclude 'test/**'
|
|
exclude 'data/build.xml'
|
|
exclude 'developer_scripts'
|
|
|
|
// general place where extension modules can put files that won't get
|
|
// included in standard zip
|
|
exclude 'contribZipExclude/**'
|
|
|
|
into { getBaseProjectName(p) }
|
|
}
|
|
|
|
/////////////////
|
|
// SOURCE
|
|
/////////////////
|
|
from (tasks["zipSourceSubproject"]) {
|
|
into { getBaseProjectName(p) + "/lib" }
|
|
}.dependsOn(zipSourceSubprojects)
|
|
|
|
|
|
/////////////////
|
|
// EXTERNAL LIBS
|
|
/////////////////
|
|
gradle.taskGraph.whenReady { taskGraph ->
|
|
List<String> externalPaths = getExternalDependencies(p)
|
|
externalPaths.each { path ->
|
|
from (path) {
|
|
into { getBaseProjectName(p) + "/lib" }
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////
|
|
// GLOBALS
|
|
/////////////////
|
|
if (isExtension(p)) {
|
|
|
|
// First get a list of all files that are under 'src/global'.
|
|
FileTree fileTree = getGlobalFiles(p)
|
|
|
|
// Now loop over each one, copying it into the zip we're creating. Each will be placed
|
|
// at the root level, starting with the first folder AFTER 'src/global/'.
|
|
//
|
|
// eg: If the file is '/Ghidra/Configurations/Common/src/global/docs/hello.html', then
|
|
// the file in the zip will be at /docs/hello.html
|
|
//
|
|
fileTree.each { File file ->
|
|
String filePath = getGlobalFilePathSubDirName(file)
|
|
from (file) {
|
|
into filePath
|
|
}
|
|
}
|
|
}
|
|
|
|
// handle special case where modules build data artifacts into the build dir
|
|
from (p.projectDir.toString() + "/build/data") {
|
|
into { getBaseProjectName(p) + "/data" }
|
|
}
|
|
|
|
/////////////////
|
|
// NATIVES
|
|
/////////////////
|
|
project.OS_NAMES.each { platform ->
|
|
|
|
from (p.projectDir.toString() + "/os/$platform") {
|
|
into { getBaseProjectName(p) + "/os/$platform" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************
|
|
*
|
|
* Creates a directory of extensions that are excluded from the installation zip.
|
|
*
|
|
**********************************************************************************/
|
|
task createInstallationExcludes(type: Copy) {
|
|
|
|
group 'private'
|
|
description "Creates directory of extensions that are excluded from the installation zip (does not clean up artifacts) [gradleScripts/distribution.gradle]"
|
|
|
|
destinationDir new File(DISTRIBUTION_DIR.getPath(), "excluded_extensions")
|
|
|
|
// Make sure that we don't try to copy the same file with the same path.
|
|
duplicatesStrategy 'exclude'
|
|
|
|
subprojects { sub ->
|
|
afterEvaluate {
|
|
boolean includeExtension = sub.findProperty("includeExtensionInInstallation") ?: false;
|
|
boolean extendsFromProject = sub.hasProperty("extendsFromProject");
|
|
if (isExtension(sub) && !includeExtension && !extendsFromProject) {
|
|
from (sub.zipExtensions)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
import groovy.io.FileType
|
|
import java.nio.file.Path
|
|
import java.nio.file.Files
|
|
import java.nio.file.attribute.FileTime
|
|
import java.time.OffsetDateTime
|
|
import java.util.concurrent.TimeUnit
|
|
import java.time.ZoneId
|
|
|
|
/*********************************************************************************
|
|
* Update sla file timestamps to current time plus timeOffsetMinutes value.
|
|
*
|
|
* distributionDirectoryPath - Contains files/folders used by gradle zip task.
|
|
* timeOffsetMinutes - Number of minutes to increase sla file timestamp.
|
|
*
|
|
**********************************************************************************/
|
|
def updateSlaFilesTimestamp(String distributionDirectoryPath, int timeOffsetMinutes) {
|
|
logger.debug("updateSlaFilesTimestamp: distributionDirectoryPath = '$distributionDirectoryPath' and timeOffsetMinutes = '$timeOffsetMinutes',")
|
|
|
|
if (timeOffsetMinutes <= 0) {
|
|
throw new GradleException("updateSlaFilesTimestamp: timeOffsetMinutes value of '$timeOffsetMinutes' is invalid.")
|
|
}
|
|
|
|
// path to sla files in distribution directory
|
|
def directory = new File(distributionDirectoryPath)
|
|
|
|
if (!directory.exists()) {
|
|
throw new GradleException("updateSlaFilesTimestamp: path to sla files '$directory' does not exist.")
|
|
}
|
|
|
|
OffsetDateTime dt = OffsetDateTime.now(ZoneId.of("UTC")).plusMinutes(timeOffsetMinutes);
|
|
|
|
int numFilesAdded = 0;
|
|
|
|
// For each .sla file, update timestamp attributes.
|
|
directory.eachFileRecurse(FileType.FILES) { file ->
|
|
if(file.name.endsWith('sla')) {
|
|
Files.setAttribute(file.toPath(), "creationTime", FileTime.from(dt.toEpochSecond(), TimeUnit.SECONDS ));
|
|
Files.setAttribute(file.toPath(), "lastModifiedTime", FileTime.from(dt.toEpochSecond(), TimeUnit.SECONDS ));
|
|
Files.setAttribute(file.toPath(), "lastAccessTime", FileTime.from(dt.toEpochSecond(), TimeUnit.SECONDS ));
|
|
|
|
logger.debug("updateSlaFilesTimestamp: Updating $file.name with timestamp attributes of " + new Date(file.lastModified()))
|
|
|
|
numFilesAdded++
|
|
}
|
|
}
|
|
|
|
println "updateSlaFilesTimestamp: Updated timestamps to $numFilesAdded .sla files."
|
|
}
|
|
|
|
/*********************************************************************************
|
|
*
|
|
* Adds decompiler pdf documentation to zip. First, the task Decompiler:buildDecompilerDocumentationPdfs
|
|
* creates the pdfs. Then, the pdfs to be added are specified. If the pdf file does does not exist,
|
|
* (ex: there was an error or wrong platform) the zip task continues.
|
|
*
|
|
**********************************************************************************/
|
|
def addDecompilerPdfsToZip (Task task) {
|
|
|
|
task.dependsOn ':Decompiler:buildDecompilerDocumentationPdfs' // creates decompiler pdf files
|
|
|
|
def decompilerPdfZipPath = ZIP_DIR_PREFIX + "/docs/languages/"
|
|
def appProject = subprojects.find { project -> 'Decompiler' == project.name }
|
|
|
|
// Add decompiler pdf files to zip. If the pdf files do not exist during execution time
|
|
// (if there was an error or wrong platform), the zip task will move on.
|
|
appProject.getTasksByName('buildDecompilerDocumentationPdfs', true).outputs.each { output ->
|
|
output.files.each { file ->
|
|
if (file.name.endsWith("pdf")) {
|
|
logger.debug("$task.name: Adding Decompiler documentation (if it exists) $file.name to $decompilerPdfZipPath")
|
|
task.from (file) {
|
|
into {
|
|
decompilerPdfZipPath
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************
|
|
*
|
|
* Creates the local installation zip.
|
|
*
|
|
**********************************************************************************/
|
|
task createLocalInstallationZip(type: Zip) { t ->
|
|
|
|
group 'private'
|
|
description "Creates local installation zip (does not clean up artifacts) [gradleScripts/distribution.gradle]"
|
|
|
|
dependsOn assembleCommon
|
|
dependsOn assembleSourceCommon
|
|
dependsOn "assemble$currentPlatform"
|
|
|
|
addDecompilerPdfsToZip(t)
|
|
|
|
archiveName "${ZIP_NAME_PREFIX}_${currentPlatform}.zip"
|
|
destinationDir DISTRIBUTION_DIR
|
|
|
|
// Make sure that we don't try to copy the same file with the same path.
|
|
duplicatesStrategy 'exclude'
|
|
|
|
from (DISTRIBUTION_DIR.getPath() + "/" + ZIP_DIR_PREFIX) {
|
|
into ZIP_DIR_PREFIX
|
|
}
|
|
|
|
subprojects { sub ->
|
|
afterEvaluate {
|
|
boolean includeExtension = sub.findProperty("includeExtensionInInstallation") ?: false;
|
|
if (isExtension(sub) && includeExtension) {
|
|
from (sub.zipExtensions) {
|
|
into {
|
|
ZIP_DIR_PREFIX + "/Extensions/Ghidra"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
doFirst {
|
|
// We always want the extensions directory to exist in the zip, even if there's nothing
|
|
// installed there.
|
|
new File( DISTRIBUTION_DIR.getPath() + "/" + ZIP_DIR_PREFIX + "/Ghidra/Extensions").mkdirs()
|
|
|
|
// The dependent tasks copy the sla and slaspec files into "extractTo/<release name>/ghidra/"
|
|
// and then later to "extractTo/<release name>/dist/", which this zip task compresses. The copy
|
|
// tasks do not preserve the file modification times. If slaspec timestamp > sla timestamp,
|
|
// a sleigh compile is triggered on Ghidra app startup. Calling this method before files are zipped
|
|
// will ensure the zip archive has sla files newer than slaspec. Give new timestamp of now plus
|
|
// two minutes.
|
|
updateSlaFilesTimestamp(DISTRIBUTION_DIR.getPath(), 2)
|
|
}
|
|
|
|
doLast {
|
|
delete file(DISTRIBUTION_DIR.getPath() + "/" + ZIP_DIR_PREFIX)
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************
|
|
*
|
|
* Creates everything needed for the final distribution, for the local platform
|
|
* only.
|
|
*
|
|
**********************************************************************************/
|
|
task createLocalInstallation() {
|
|
|
|
group 'private'
|
|
description "Creates local installation (no extraction) [gradleScripts/distribution.gradle]"
|
|
|
|
dependsOn createInstallationExcludes
|
|
dependsOn createLocalInstallationZip
|
|
|
|
doLast {
|
|
|
|
// Delete any unnecessary artifacts used to create the final zip.
|
|
subprojects { sub ->
|
|
if (isExtension(sub)) {
|
|
delete zipExtensions
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************
|
|
*
|
|
* Creates the multi-platform installation zip.
|
|
*
|
|
**********************************************************************************/
|
|
task createMultiPlatformInstallationZip(type: Zip) { t ->
|
|
|
|
group 'private'
|
|
description "Creates multi-platform installation zip (does not clean up artifacts) [gradleScripts/distribution.gradle]"
|
|
|
|
dependsOn ":assembleCommon"
|
|
dependsOn ":assemblewin32"
|
|
dependsOn ":assemblewin64"
|
|
dependsOn ":assemblelinux64"
|
|
dependsOn ":assembleosx64"
|
|
dependsOn ":assembleSourceCommon"
|
|
|
|
addDecompilerPdfsToZip(t)
|
|
|
|
archiveName "${ZIP_NAME_PREFIX}.zip"
|
|
destinationDir DISTRIBUTION_DIR
|
|
|
|
// Make sure that we don't try to copy the same file with the same path.
|
|
duplicatesStrategy 'exclude'
|
|
|
|
from (DISTRIBUTION_DIR.getPath() + "/" + ZIP_DIR_PREFIX) {
|
|
into ZIP_DIR_PREFIX
|
|
}
|
|
|
|
subprojects { sub ->
|
|
afterEvaluate {
|
|
boolean includeExtension = sub.findProperty("includeExtensionInInstallation") ?: false;
|
|
if (isExtension(sub) && includeExtension) {
|
|
from (sub.zipExtensions) {
|
|
into {
|
|
ZIP_DIR_PREFIX + "/Extensions/Ghidra"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
doFirst {
|
|
// We always want the extensions directory to exist in the zip, even if there's nothing
|
|
// installed there.
|
|
new File( DISTRIBUTION_DIR.getPath() + "/" + ZIP_DIR_PREFIX + "/Ghidra/Extensions").mkdirs()
|
|
|
|
// The dependent tasks copy the sla and slaspec files into "extractTo/<release name>/ghidra/"
|
|
// and then later to "extractTo/<release name>/dist/", which this zip task compresses. The copy
|
|
// tasks do not preserve the file modification times. If slaspec timestamp > sla timestamp,
|
|
// a sleigh compile is triggered on Ghidra app startup. Calling this method before files are zipped
|
|
// will ensure the zip archive has sla files newer than slaspec. Give new timestamp of now plus
|
|
// two minutes.
|
|
updateSlaFilesTimestamp(DISTRIBUTION_DIR.getPath(), 2)
|
|
}
|
|
|
|
doLast {
|
|
delete file(DISTRIBUTION_DIR.getPath() + "/" + ZIP_DIR_PREFIX)
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************
|
|
*
|
|
* Creates everything needed for the final distribution, for all platforms.
|
|
*
|
|
**********************************************************************************/
|
|
task createMultiPlatformInstallation() {
|
|
|
|
group 'private'
|
|
description "Creates multi-platform installation (no extraction) [gradleScripts/distribution.gradle]"
|
|
|
|
dependsOn createInstallationExcludes
|
|
dependsOn createMultiPlatformInstallationZip
|
|
|
|
doLast {
|
|
|
|
// Delete any unnecessary artifacts used to create the final zip.
|
|
subprojects { sub ->
|
|
if (isExtension(sub)) {
|
|
delete zipExtensions
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************
|
|
*
|
|
* Builds the Ghidra installation zip file for the local platform
|
|
*
|
|
**********************************************************************************/
|
|
task buildGhidra() {
|
|
dependsOn createLocalInstallationZip
|
|
|
|
doLast {
|
|
|
|
// Delete any unnecessary artifacts used to create the final zip.
|
|
subprojects { sub ->
|
|
if (isExtension(sub)) {
|
|
delete zipExtensions
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************************
|
|
* Returns true if the given project is an extension.
|
|
*********************************************************************************/
|
|
def isExtension(Project p) {
|
|
return p.projectDir.toString().contains(File.separator + "Extensions" + File.separator)
|
|
}
|
|
|
|
/*********************************************************************************
|
|
* Returns true if the given project is in Ghidra.
|
|
*********************************************************************************/
|
|
def isGhidra(Project p) {
|
|
return p.projectDir.toString().contains(File.separator + "Ghidra" + File.separator) ||
|
|
p.projectDir.toString().contains(File.separator + "GPL" + File.separator)
|
|
}
|
|
|
|
/*********************************************************************************
|
|
* Returns a FileTree of all files in the given project that are under the
|
|
* '/src/global' folder, if there is one.
|
|
*********************************************************************************/
|
|
FileTree getGlobalFiles(Project project) {
|
|
|
|
FileTree fileTree = project.fileTree('src/global') {
|
|
include '**/*'
|
|
}
|
|
|
|
return fileTree
|
|
}
|
|
|
|
/*********************************************************************************
|
|
* Takes the given file and returns a string representing the file path with everything
|
|
* up-to and including 'src/global' removed, as well as the filename.
|
|
*
|
|
* eg: If the file path is '/Ghidra/Configurations/Common/src/global/docs/hello.html',
|
|
* the returned string will be at /docs
|
|
*
|
|
* Note: We have to use 'File.separator' instead of a slash ('/') because of how
|
|
* windows/unix handle slashes ('/' vs. '\'). We only need to do this in cases where we're
|
|
* using java string manipulation libraries (eg String.replace); Gradle already
|
|
* understands how to use the proper slash.
|
|
*********************************************************************************/
|
|
String getGlobalFilePathSubDirName(File file) {
|
|
|
|
// First strip off everything before 'src/global/ in the file path.
|
|
def slashIndex = file.path.indexOf('src' + File.separator + 'global')
|
|
String filePath = file.path.substring(slashIndex);
|
|
|
|
// Now remove 'src/global/' from the string.
|
|
filePath = filePath.replace('src' + File.separator + 'global' + File.separator, "");
|
|
|
|
// Now we need to strip off the filename itself, which we do by finding the last
|
|
// instance of a slash ('/') in the string.
|
|
//
|
|
// Note that it's possible there is no slash (all we have is a filename), meaning
|
|
// this file will be placed at the root level.
|
|
//
|
|
slashIndex = filePath.lastIndexOf(File.separator)
|
|
if (slashIndex != -1) {
|
|
filePath = filePath.substring(0, slashIndex+1) // +1 for the slash
|
|
}
|
|
else {
|
|
filePath = ""
|
|
}
|
|
|
|
return filePath
|
|
}
|
|
|
|
/*********************************************************************************
|
|
* Checks the project source sets to see if there are any source files and/or
|
|
* resources.
|
|
*********************************************************************************/
|
|
def isEmptyProject (Project project) {
|
|
def empty = true
|
|
project.sourceSets.each { ss ->
|
|
if (ss.allSource.isEmpty() == false) {
|
|
empty = false
|
|
}
|
|
}
|
|
|
|
return empty
|
|
}
|
|
|
|
/*********************************************************************************
|
|
* Produces a CopySpec on the rootProject which facilitates the copying of all
|
|
* srcFolder contents to a specified destFolder within the distribution.
|
|
* All folder names are relative to the project root (e.g., GhidraBuild/BuildFiles).
|
|
* The resulting CopySpec should be specified using the "with" declaration
|
|
* for a Copy task or other similar tasks.
|
|
*********************************************************************************/
|
|
def getMultiRepoCopySpec(String srcFolder, String destFolder) {
|
|
return rootProject.copySpec {
|
|
from rootProject.projectDir.path + "/.."
|
|
include "*/$srcFolder/**"
|
|
exclude "**/certification.manifest"
|
|
exclude "**/certification.local.manifest"
|
|
exclude "**/.project"
|
|
exclude "**/.classpath"
|
|
exclude "**/build"
|
|
|
|
eachFile {
|
|
path = path.replaceAll(".*/$srcFolder", destFolder)
|
|
}
|
|
includeEmptyDirs = false
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* The following block of code changes the output directory for native builds
|
|
* to <projectDir>/build/os/<platform>/<executable>
|
|
****************************************************************************/
|
|
gradle.taskGraph.whenReady {
|
|
subprojects { p->
|
|
tasks.withType(LinkExecutable).each { t ->
|
|
File f = t.linkedFile.getAsFile().get()
|
|
String filename = f.getName()
|
|
NativePlatform platform = t.targetPlatform.get()
|
|
String osName = platform.getName()
|
|
t.linkedFile = p.file("build/os/${osName}/$filename")
|
|
}
|
|
tasks.withType(LinkSharedLibrary).each { t ->
|
|
File f = t.linkedFile.getAsFile().get()
|
|
String filename = f.getName()
|
|
NativePlatform platform = t.targetPlatform.get()
|
|
String osName = platform.getName()
|
|
t.linkedFile = p.file("build/os/${osName}/$filename")
|
|
}
|
|
}
|
|
}
|