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" 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 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-.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 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//ghidra/" // and then later to "extractTo//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//ghidra/" // and then later to "extractTo//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 /build/os// ****************************************************************************/ 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") } } }