ghidra/gradleScripts/distribution.gradle
2019-03-26 13:46:51 -04:00

1186 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
})
// 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"
/////////////////
// 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" }
}
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 '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")
}
}
}