ghidra/build.gradle
2022-03-22 01:36:29 -04:00

369 lines
13 KiB
Groovy

/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply plugin: 'eclipse'
apply from: 'gradle/root/eclipse.gradle'
apply from: "gradle/support/eclipseLauncher.gradle"
apply from: "gradle/support/loadApplicationProperties.gradle"
/***************************************************************************************
* Make sure the correct version of gradle is being used
***************************************************************************************/
import org.gradle.util.GradleVersion;
final GradleVersion minimum_version = GradleVersion.version("${rootProject.GRADLE_MINIMUM_VERSION}")
if (GradleVersion.current() < minimum_version) {
throw new GradleException("Requires at least $minimum_version, but was run with $gradle.gradleVersion")
}
/***************************************************************************************
* Define the location of JAVA_HOME
***************************************************************************************/
if (System.env.JAVA_HOME) {
// Allow Gradle's default JAVA_HOME to be overridden by an environment variable we set
project.ext.JAVA_HOME = System.env.JAVA_HOME;
}
else {
project.ext.JAVA_HOME = "${System.properties.'java.home'}"
}
/***************************************************************************************
* Make sure Gradle wasn't launched with a 32-bit Java...it can cause confusing errors
***************************************************************************************/
if ("32".equals(System.getProperty("sun.arch.data.model"))) {
throw new GradleException("\n\n\t32-bit Java detected! Please use 64-bit Java.\n\n");
}
/*********************************************************************************
* Define the location of bin repo
*********************************************************************************/
project.ext.GHIDRA_GROUP = "Z Ghidra"
project.ext.ROOT_PROJECT_DIR = projectDir.absolutePath
project.ext.BIN_REPO = file("${projectDir}/../ghidra.bin").absolutePath
project.ext.DEPS_DIR = file("${projectDir}/dependencies")
/*********************************************************************************
* Prevent forked Java processes from stealing focus
*********************************************************************************/
allprojects {
tasks.withType(JavaForkOptions) {
jvmArgs '-Djava.awt.headless=true'
}
}
/*********************************************************************************
* Use flat directory-style repository if flatRepo directory is present.
*********************************************************************************/
def flatRepo = file("${DEPS_DIR}/flatRepo")
if (flatRepo.isDirectory()) {
allprojects {
repositories {
mavenLocal()
mavenCentral()
jcenter()
flatDir name: "flat", dirs:["$flatRepo"]
}
}
}
else {
File f = file("ghidra.repos.config")
if (!f.exists()) {
throw new GradleException("\n\n\n\tUnable to find the local maven repo." +
" Ensure you have created the ${flatRepo.getName()} directory or ${f.getName()} file.\n\n\n");
}
}
/*********************************************************************************
* Imports
* For these tasks to be available on all subprojects, this MUST be placed
* after the "setupJava" configuration.
*
* Note: Distribution.gradle and distributionNew.gradle must not be applied at the
* same time; they have tasks with the same name. The former is the 'old' way
* of building (produces several zips) while the former produces only one.
* Eventually distribution.gradle will be removed entirely, but it is included
* here for the time being for those who need it.
*********************************************************************************/
apply from: "GPL/utils.gradle" // adds utilities used in both Ghidra and GPL projects
apply from: "GPL/nativePlatforms.gradle" // adds native platform support
apply from: "gradle/root/test.gradle" // adds tasks for running tests
apply from: "gradle/root/prepDev.gradle" // adds prepDev task for each subproject
apply from: 'gradle/root/distribution.gradle' // adds zip tasks
apply from: 'gradle/root/usage.gradle' // adds task documentation
apply from: "gradle/root/svg.gradle" // adds task to process svg files
apply from: "gradle/root/jacoco.gradle" // adds tasks for java code coverage
apply plugin: 'base'
clean {
delete "$buildDir"
}
/******************************************************************************************
*
* Utility methods used by multiple build.gradle files
*
*****************************************************************************************/
/*********************************************************************************
* Returns a relative path from the root (ghidra) to the project's directory.
* This is used to determine where inside a zip file to place a particular artifact
* (we want them to all start at "Ghidra/...."
*
* ie: If we have the following:
* project dir = /Users/<blah>/git/ghidra.master/ghidra/Ghidra/Features/Base
* root project = /Users/<blah>/git/ghidra.master/ghidra/Ghidra
*
* Then the returned value will be:
* Ghidra/Features/Base
*
* There are two special cases - Projects that live outside ghidra and projects
* that are extension projects. Projects that live outside ghidra will
* have zip paths that make the project appear as if it did live in ghidra.
* Projects that extend other projects will appear as though they live in the project
* that they extend. See the note at the top of the distribution.gradle file for more details.
*********************************************************************************/
def getZipPath(Project p) {
String path = getGhidraRelativePath(p)
// if the project has been defined as an "extension" to another project, change its
// zip path to the path of its "base" project. A project is an extension if it has
// defined an "extendsFromProject" property.
if (p.hasProperty('extendsFromProject')) {
Project baseProject = p.extendsFromProject
path = getGhidraRelativePath(baseProject);
}
if (p.hasProperty("pathExtension")) {
path = path + "/" + p.pathExtension
}
return path
}
def getBaseProjectName(Project p) {
if (p.hasProperty('extendsFromProject')) {
Project baseProject = p.extendsFromProject
return baseProject.name
}
return p.name
}
/*********************************************************************************
* Returns the current date formatted as yyyyMMdd.
*********************************************************************************/
def getCurrentDate() {
def date = new Date()
def formattedDate = date.format('yyyyMMdd')
return formattedDate
}
/*********************************************************************************
* Returns the current date/time formatted as yyyyMMdd-HHmm.
*********************************************************************************/
def getCurrentDateTime() {
def date = new Date()
def formattedDate = date.format('yyyyMMdd-HHmm')
return formattedDate
}
/*********************************************************************************
* Returns the current date/time formatted as yyyy-MMM-dd HHmm z.
*********************************************************************************/
def getCurrentDateTimeLong() {
def date = new Date()
def formattedDate = date.format('yyyy-MMM-dd HHmm z')
return formattedDate
}
/*********************************************************************************
* Returns a map of all the external library paths declared as dependencies for the
* given project, mapped to their respective ExternalDependency objects.
*
*********************************************************************************/
Map<String, ExternalDependency> getExternalRuntimeDependencies(Project project) {
def map = [:]
if (project.configurations.find { it.name == 'api' }) {
map.putAll(getExternalRuntimeDependencies(project, project.configurations.api));
}
if (project.configurations.find { it.name == 'implementation' }) {
map.putAll(getExternalRuntimeDependencies(project, project.configurations.implementation));
}
if (project.configurations.find { it.name == 'runtimeOnly' }) {
map.putAll(getExternalRuntimeDependencies(project, project.configurations.runtimeOnly));
}
return map
}
Map<String, ExternalDependency> getExternalRuntimeDependencies(Project project, Configuration configuration) {
def map = [:]
configuration.dependencies.each { dep ->
// if the dependency is an external jar
if (dep instanceof ExternalDependency) {
String name = dep.getName()
Set<String> classifiers = dep.artifacts.classifier
String group = dep.getGroup()
String version = dep.getVersion()
String searchString = name
if (version != null) {
searchString = name+"-"+version;
}
if (!classifiers.empty) {
String cls = classifiers.first()
if (cls != null) {
searchString+= "-$cls"
}
}
// search for the dependency in the runtime class path
String depPath = project.configurations.runtimeClasspath.find {
it.name.contains(searchString)
}
if (depPath == null) {
println("****************DID NOT FIND DEPENDENCY: name = "+name+" version = "+version)
}
// if we found the path, then add it to the list
if (depPath) {
map.put(depPath, dep)
}
}
}
return map;
}
/******************************************************************************************
*
* Creates a file that lists the libraries used by each module.
*
******************************************************************************************/
String generateLibraryDependencyMapping() {
File libsFile = file("$buildDir/libraryDependencies.txt")
// Check to make sure the build folder exists - if it doesn't, the 'libsFile.withWriter'
// call (below) will fail miserably.
def buildFolder = file ("$buildDir")
if (!buildFolder.exists()) {
buildFolder.mkdirs()
}
libsFile.withWriter { out ->
subprojects { p ->
p.plugins.withType(JavaPlugin) {
def libs = getExternalRuntimeDependencies(p);
if (libs != null) {
out.println "Module: $p.name"
libs.each { path, dep ->
out.println "\t$path"
}
}
}
}
}
return libsFile.absolutePath
}
/******************************************************************************************
*
* Generates a hash of the given file with the given hash algorithm.
*
******************************************************************************************/
import java.security.DigestInputStream
import java.security.MessageDigest
String generateHash(File file, String alg) {
file.withInputStream {
new DigestInputStream(it, MessageDigest.getInstance(alg)).withStream {
it.eachByte {}
it.messageDigest.digest().encodeHex() as String
}
}
}
/******************************************************************************************
*
* Creates a CycloneDX Software Bill of Materials (SBOM) for the given project and
* returns it as a map.
*
******************************************************************************************/
def generateSoftwareBillOfMaterials(Project p) {
// Get license info from the Module.manifest file (if it exists)
def licenses = [:]
def manifestFile = file("${p.projectDir}/Module.manifest")
if (manifestFile.exists()) {
manifestFile.readLines().each { line ->
line = line.trim()
if (line.startsWith("MODULE FILE LICENSE:")) {
// Expected line: "MODULE FILE LICENSE: relative_path/to/jar License Type"
def value = line.substring("MODULE FILE LICENSE:".length()).trim()
def libAndLicense = value.split(" ", 2)
if (libAndLicense.size() != 2) {
throw new GradleException("Error parsing " + manifestFile + ":\n\t" + line)
}
def libPath = libAndLicense[0].trim()
def libName = libPath.substring(libPath.lastIndexOf("/") + 1)
def license = libAndLicense[1].trim()
licenses[libName] = license
}
}
}
// SBOM header
def sbom = ["bomFormat" : "CycloneDX", "specVersion" : "1.4", "version" : 1]
// SBOM components
sbom.components = []
getExternalRuntimeDependencies(p).each { path, dep ->
def f = file(path)
def component = [:]
component.type = "library"
component.group = dep.group ?: ""
component.name = dep.name
component.version = dep.version ?: ""
component.properties = [["ghidra-module" : p.name]]
if (dep.group && dep.version) {
component.purl = "pkg:maven/${dep.group}/${dep.name}@${dep.version}"
}
component.hashes = []
["MD5", "SHA-1"].each { alg ->
component.hashes << ["alg" : alg, "content" : generateHash(f, alg)]
}
def license = licenses[f.name]
if (license) {
component.licenses = [["license" : ["name" : license]]]
}
sbom.components << component
}
return sbom
}
task allSleighCompile {
}