mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-27 21:15:32 +08:00
Major refactoring of the gradle build system.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
apply from: "$rootProject.projectDir/gradle/support/ip.gradle"
|
||||
|
||||
rootProject.assembleDistribution.dependsOn ip
|
||||
|
||||
|
||||
// all application tests depend on all the sleigh processors to be built
|
||||
tasks.withType(Test).all {
|
||||
it.dependsOn ":allSleighCompile"
|
||||
}
|
||||
|
||||
rootProject.OS_NAMES.each { platform ->
|
||||
rootProject.tasks.findAll {it.name == "assembleDistribution_$platform"}.each { t ->
|
||||
|
||||
def p = this.project
|
||||
|
||||
// the getZipPath calls here are not in closures because we are already in a taskGraph.whenReady closure
|
||||
t.from (p.projectDir.toString() + "/build/os/$platform") {
|
||||
exclude '*.lib'
|
||||
exclude '*.exp'
|
||||
into getZipPath(p) + "/os/$platform"
|
||||
}
|
||||
t.from (p.projectDir.toString() + "/os/$platform") {
|
||||
into getZipPath(p) + "/os/$platform"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For Win64 build, we have to also include any Win32 binaries in the final zip.
|
||||
rootProject.assembleDistribution_win64 {
|
||||
from (this.project.projectDir.toString() + "/build/os/win32") {
|
||||
into getZipPath(this.project) + "/os/win32"
|
||||
}
|
||||
}
|
||||
|
||||
plugins.withType(JavaPlugin) {
|
||||
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 java modules"
|
||||
t.archiveName project.name + "-src.zip"
|
||||
t.destinationDir file(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
|
||||
}
|
||||
}
|
||||
/*********************************************************************************
|
||||
* 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
|
||||
}
|
||||
ext.getGlobalFilePathSubDirName = this.&getGlobalFilePathSubDirName
|
||||
@@ -0,0 +1,149 @@
|
||||
import org.gradle.api.*;
|
||||
import org.gradle.artifacts.*;
|
||||
import org.gradle.process.JavaExecSpec;
|
||||
import org.gradle.api.file.*;
|
||||
import org.gradle.api.tasks.*;
|
||||
import org.gradle.api.internal.file.UnionFileCollection;
|
||||
|
||||
import groovy.xml.MarkupBuilder;
|
||||
|
||||
public class WriteEclipseLauncher extends JavaExec {
|
||||
@OutputFile
|
||||
File dest;
|
||||
|
||||
@Input
|
||||
boolean isRunFave = false;
|
||||
|
||||
@Input
|
||||
boolean isDbgFave = false;
|
||||
|
||||
@Input
|
||||
boolean useEclipseDefaultClasspath = false
|
||||
|
||||
public <E> E one(Collection<E> col) {
|
||||
assert col.size() == 1;
|
||||
return col.iterator().next();
|
||||
}
|
||||
|
||||
void walkFileCollection(UnionFileCollection col, Set gathered) {
|
||||
col.sources.each {
|
||||
walkFileCollection(it, gathered);
|
||||
}
|
||||
}
|
||||
|
||||
void walkFileCollection(ConfigurableFileCollection col, Set gathered) {
|
||||
col.from.each {
|
||||
walkFileCollection(it, gathered);
|
||||
}
|
||||
}
|
||||
|
||||
void walkFileCollection(SourceSetOutput col, Set gathered) {
|
||||
gathered.add(col);
|
||||
}
|
||||
|
||||
void walkFileCollection(Configuration col, Set gathered) {
|
||||
col.allDependencies.each {
|
||||
walkDependency(it, gathered)
|
||||
}
|
||||
}
|
||||
|
||||
void walkDependency(ExternalModuleDependency dep, Set gathered) {
|
||||
gathered.add(dep)
|
||||
}
|
||||
|
||||
void walkDependency(ProjectDependency dep, Set gathered) {
|
||||
gathered.add(dep)
|
||||
Project project = dep.dependencyProject
|
||||
String confName = dep.targetConfiguration ?: 'default'
|
||||
Configuration configuration = project.configurations."$confName"
|
||||
walkFileCollection(configuration, gathered)
|
||||
}
|
||||
|
||||
String makeEntryXml(ExternalModuleDependency dep, FileCollection cp) {
|
||||
def writer = new StringWriter();
|
||||
def xml = new MarkupBuilder(writer);
|
||||
def file = cp.find { it.name.contains(dep.name) }
|
||||
xml.mkp.xmlDeclaration(version: '1.0', encoding: 'UTF-8', standalone: 'no')
|
||||
xml.runtimeClasspathEntry(
|
||||
externalArchive: file,
|
||||
path: 5,
|
||||
// TODO: Figure out source jar
|
||||
type: 2
|
||||
);
|
||||
return writer
|
||||
}
|
||||
|
||||
String makeEntryXml(Project proj) {
|
||||
def writer = new StringWriter();
|
||||
def xml = new MarkupBuilder(writer);
|
||||
xml.mkp.xmlDeclaration(version: '1.0', encoding: 'UTF-8', standalone: 'no')
|
||||
xml.runtimeClasspathEntry(
|
||||
path: 5,
|
||||
projectName: proj.eclipse.project.name,
|
||||
type: 1
|
||||
)
|
||||
return writer
|
||||
}
|
||||
|
||||
String makeEntryXml(ProjectDependency dep, FileCollection cp) {
|
||||
return makeEntryXml(dep.dependencyProject);
|
||||
}
|
||||
|
||||
String makeEntryXml(SourceSetOutput out, FileCollection cp) {
|
||||
Task task = one(out.buildDependencies.getDependencies(null))
|
||||
return makeEntryXml(task.project)
|
||||
}
|
||||
|
||||
public File forName(String name) {
|
||||
return project.file(".launch/${name}.launch")
|
||||
}
|
||||
|
||||
List<String> getJvmArgumentsForEclipse() {
|
||||
List<String> all = allJvmArgs;
|
||||
int index = all.indexOf('-cp');
|
||||
if (index == -1) {
|
||||
return all;
|
||||
}
|
||||
all.remove(index);
|
||||
all.remove(index);
|
||||
return all;
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void exec() { // Override exec. Instead write launcher
|
||||
dest.parentFile.mkdirs();
|
||||
def launcher = new MarkupBuilder(new FileWriter(dest));
|
||||
Set gathered = new LinkedHashSet();
|
||||
if (!useEclipseDefaultClasspath) {
|
||||
walkFileCollection(classpath, gathered);
|
||||
}
|
||||
launcher.mkp.xmlDeclaration(version: '1.0', encoding: 'UTF-8', standalone: 'no')
|
||||
launcher.launchConfiguration(type: 'org.eclipse.jdt.launching.localJavaApplication') {
|
||||
listAttribute(key: 'org.eclipse.debug.ui.favoriteGroups') {
|
||||
if (isRunFave) {
|
||||
listEntry(value: 'org.eclipse.debug.ui.launchGroup.run');
|
||||
}
|
||||
if (isDbgFave) {
|
||||
listEntry(value: 'org.eclipse.debug.ui.launchGroup.debug');
|
||||
}
|
||||
}
|
||||
if (!useEclipseDefaultClasspath) {
|
||||
listAttribute(key: 'org.eclipse.jdt.launching.CLASSPATH') {
|
||||
gathered.each {
|
||||
listEntry(value: makeEntryXml(it, classpath))
|
||||
}
|
||||
}
|
||||
}
|
||||
booleanAttribute(key: 'org.eclipse.jdt.launching.DEFAULT_CLASSPATH', value: useEclipseDefaultClasspath);
|
||||
stringAttribute(key: 'org.eclipse.jdt.launching.MAIN_TYPE', value: main);
|
||||
// TODO: Proper escaping of program and JVM arguments.
|
||||
stringAttribute(key: 'org.eclipse.jdt.launching.PROGRAM_ARGUMENTS', value: args.join(' '));
|
||||
stringAttribute(key: 'org.eclipse.jdt.launching.PROJECT_ATTR', value: project.eclipse.project.name);
|
||||
stringAttribute(key: 'org.eclipse.jdt.launching.VM_ARGUMENTS', value: jvmArgumentsForEclipse.join(' '));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
ext.WriteEclipseLauncher = WriteEclipseLauncher
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import org.apache.tools.ant.filters.*
|
||||
|
||||
task zipExtensions (type: Zip) {
|
||||
def p = this.project
|
||||
|
||||
it.group 'private'
|
||||
it.description "Creates a zip file for an extension module. [gradle/support/distribution.gradle]"
|
||||
it.archiveName "${rootProject.ext.ZIP_NAME_PREFIX}_${p.name}.zip"
|
||||
it.destinationDir rootProject.ext.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) }
|
||||
}
|
||||
|
||||
|
||||
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'
|
||||
exclude 'data/build.xml'
|
||||
exclude '**/.settings/**'
|
||||
|
||||
|
||||
// general place where extension modules can put files that won't get
|
||||
// included in standard zip
|
||||
exclude 'contribZipExclude/**'
|
||||
|
||||
into { getBaseProjectName(p) }
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// EXTERNAL LIBS
|
||||
/////////////////
|
||||
gradle.taskGraph.whenReady { taskGraph ->
|
||||
List<String> externalPaths = getExternalDependencies(p)
|
||||
externalPaths.each { path ->
|
||||
from (path) {
|
||||
into { getBaseProjectName(p) + "/lib" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// GLOBALS
|
||||
/////////////////
|
||||
|
||||
// First get a list of all files that are under 'src/global'.
|
||||
FileTree fileTree = project.fileTree('src/global') {
|
||||
include '**/*'
|
||||
}
|
||||
// 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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugins.withType(JavaPlugin) {
|
||||
zipExtensions {
|
||||
def p = this.project
|
||||
from (p.jar) {
|
||||
// use closures for getting zip path to delay evaluation. See note at top of file.
|
||||
into { getBaseProjectName(p) + "/lib" }
|
||||
}
|
||||
from (p.tasks["zipSourceSubproject"]) {
|
||||
into { getBaseProjectName(p) + "/lib" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
|
||||
/*********************************************************************************
|
||||
* ip.gradle
|
||||
*
|
||||
* This file defines the gradle tasks for generating the LICENSE.txt in each module
|
||||
* which lists all the 3rd party files in this module and their licenses.
|
||||
*
|
||||
* The task also verifies that the license for each of the 3rd party files is allowed
|
||||
* based on what license files exist in the root/licenses directory.
|
||||
*
|
||||
* It also reads the Module.manifest file where license information is recorded for
|
||||
* jar dependencies and build artifacts. These are added to the LICENSE.txt file. Also,
|
||||
* the jar dependencies (as defined in the build.gradle file) are examined to make
|
||||
* sure they are defined in the Module.manifest file.
|
||||
*********************************************************************************/
|
||||
|
||||
/*********************************************************************************
|
||||
* Defines the main ip task for each subproject
|
||||
*********************************************************************************/
|
||||
task ip {
|
||||
doLast {
|
||||
|
||||
// scans all the files in the module, reads ip from header, verifies ip, and creates mapping
|
||||
def ipToFileMap = getIpForModule(project)
|
||||
|
||||
// reads the ip info from the Module.manifest file and verifies each ip
|
||||
def moduleManifestIpMap = getModuleManifestIp(project)
|
||||
|
||||
// gets the external libs from gradle and verifies they are accounted for in the Module.manifest file
|
||||
checkExternalLibsInMap(moduleManifestIpMap, project)
|
||||
|
||||
// adds the ip info from the Module.manifest file to the map generated from scanning the module files.
|
||||
addModuleManifestIp(ipToFileMap, moduleManifestIpMap)
|
||||
|
||||
// writes the LICENSE.txt file for the module
|
||||
writeLicenseInfo(project, ipToFileMap)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********************************************************************************
|
||||
* Addes the ip information from the Module.manifest file into the ipToFileMap
|
||||
*********************************************************************************/
|
||||
def addModuleManifestIp(Map<String, List<String>> ipToFileMap, Map<String, String> moduleManifestIpMap) {
|
||||
for (path in moduleManifestIpMap.keySet()) {
|
||||
String ip = moduleManifestIpMap.get(path)
|
||||
addToMap(ipToFileMap, ip, path)
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Reads the ip info in the Module.manifest file and creates a mapping of path to ip
|
||||
*********************************************************************************/
|
||||
def Map<String, String> getModuleManifestIp(Project project) {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
File moduleManifest = new File(project.projectDir, "Module.manifest")
|
||||
if (!moduleManifest.exists()) {
|
||||
return map
|
||||
}
|
||||
def allowedIP = getAllowedIP(project)
|
||||
|
||||
def lines = moduleManifest.readLines();
|
||||
String key = "MODULE FILE LICENSE:"
|
||||
for(line in lines) {
|
||||
if (line.startsWith(key)) {
|
||||
String s = line.substring(key.length()).trim()
|
||||
int index = s.indexOf(' ')
|
||||
String path = s.substring(0, index).trim()
|
||||
String ip = s.substring(index+1).trim()
|
||||
def ipString = ip.replace(' ','_')
|
||||
assert allowedIP.contains(ipString) : "Encountered Non-allowed IP: "+ip+ " for Module.manifest entry: "+path
|
||||
map.put(path, ip)
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/**********************************************************************************
|
||||
* Gets the gradle dependences and makes sure each external lib is accounted for in
|
||||
* the map from the Module.manifest file
|
||||
*********************************************************************************/
|
||||
def checkExternalLibsInMap(Map<String, String> map, Project project) {
|
||||
if (project.plugins.withType(JavaPlugin)) {
|
||||
List<String> libs = getExternalDependencies(project)
|
||||
libs.each { lib ->
|
||||
String libName = new File(lib).getName() // get just the filename without the path
|
||||
String relativePath = "lib/"+libName;
|
||||
assert map.containsKey(relativePath) : "No License specified for external library: "+relativePath+ " in module "+project.projectDir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Examines all the files in the module, reads their ip from the header, verifies
|
||||
* that the ip is allowed, and adds an entry to a mapping of the ip to a list of
|
||||
* files with that ip
|
||||
*********************************************************************************/
|
||||
def Map<String, List<String>> getIpForModule(Project p) {
|
||||
Map<String, List<String>> map = new HashMap<String, List<String>>()
|
||||
File certificationFile = new File(p.projectDir, "certification.manifest")
|
||||
if (!certificationFile.exists()) {
|
||||
return map;
|
||||
}
|
||||
def allowedIP = getAllowedIP(p)
|
||||
FileTree tree = p.fileTree(".") {
|
||||
exclude "bin/**";
|
||||
exclude "**/build/**";
|
||||
exclude "certification.manifest"
|
||||
exclude "certification.local.manifest"
|
||||
exclude ".project"
|
||||
exclude ".classpath"
|
||||
exclude "Module.manifest"
|
||||
exclude "build.gradle"
|
||||
exclude "**/Misc/Tips.htm"
|
||||
exclude "**/*.sla"
|
||||
exclude "**/.gradle/**"
|
||||
exclude "**/.settings/**"
|
||||
exclude "**/data/build.xml" // language build file (generated for dev only)
|
||||
exclude "**/.vs/**"
|
||||
exclude "**/*.vcxproj.user"
|
||||
}
|
||||
tree.each { file ->
|
||||
String ip = getIp(p.projectDir, file)
|
||||
assert ip != null : "No IP found for "+file.path+ " in module: "+p.projectDir
|
||||
String ipString = ip.replace(' ','_')
|
||||
assert allowedIP.contains(ipString) : "Found non-allowed IP: "+ip+" for file "+file.path+" in module: "+p.projectDir
|
||||
addToMap(map, ip, getRelativePath(p.projectDir, file))
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Returns the relative path of a file in the module
|
||||
*********************************************************************************/
|
||||
def String getRelativePath(File projectDir, File file) {
|
||||
return file.getPath().substring(projectDir.getPath().length()+1)
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* adds a path and its ip to the mapping of ip to list of files
|
||||
*********************************************************************************/
|
||||
def addToMap(Map<String, List<String>> map, String ip, String path) {
|
||||
List<String> list = map.get(ip);
|
||||
if (list == null) {
|
||||
list = new ArrayList<String>();
|
||||
map.put(ip, list);
|
||||
}
|
||||
list.add(path)
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* checks if a file supports a C style header based on its extension.
|
||||
*********************************************************************************/
|
||||
def isSourceFile(File file) {
|
||||
|
||||
String filename = file.getName().toLowerCase();
|
||||
|
||||
return filename.endsWith(".java") ||
|
||||
filename.endsWith(".c") ||
|
||||
filename.endsWith(".groovy") ||
|
||||
filename.endsWith(".cpp") ||
|
||||
filename.endsWith(".cc") ||
|
||||
filename.endsWith(".h") ||
|
||||
filename.endsWith(".y") ||
|
||||
filename.endsWith(".l") ||
|
||||
filename.endsWith(".hh") ||
|
||||
filename.endsWith(".css") ||
|
||||
filename.endsWith(".jj");
|
||||
}
|
||||
|
||||
|
||||
/*********************************************************************************
|
||||
* Gets the ip for a file in the module from its header (or certification.manifest
|
||||
*********************************************************************************/
|
||||
def getIp(File projectDir, File file) {
|
||||
if (isSourceFile(file)) {
|
||||
return getIpForSourceFile(file);
|
||||
}
|
||||
return getIpForNonSourceFile(projectDir, file);
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Gets the ip from a file that has a certification header
|
||||
*********************************************************************************/
|
||||
def getIpForSourceFile(File file) {
|
||||
String ip =null
|
||||
String line;
|
||||
file.withReader { reader ->
|
||||
while((line = reader.readLine()) != null) {
|
||||
if (line.startsWith(" * IP:")) {
|
||||
ip = line.substring(7).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Gets the ip for a file that does not have a header, but has an entry in the
|
||||
* certication.manifest
|
||||
*********************************************************************************/
|
||||
def getIpForNonSourceFile(File projectDir, File file) {
|
||||
String ip = null
|
||||
File manifest = new File(projectDir, "certification.manifest");
|
||||
def lines = manifest.readLines()
|
||||
lines.each {line ->
|
||||
line = line.trim();
|
||||
def parts = line.split("\\|");
|
||||
if (parts.length > 2 && file.toString().replace(File.separator, "/").endsWith(parts[0])) {
|
||||
ip = parts[2];
|
||||
}
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Writes the license information to the LICENSE.txt file for the module
|
||||
*********************************************************************************/
|
||||
def writeLicenseInfo(Project project, Map<String, List<String>> map) {
|
||||
if (map.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
File buildDir = new File(project.projectDir, "build")
|
||||
buildDir.mkdir();
|
||||
|
||||
File licenseFile = new File(buildDir,"LICENSE.txt");
|
||||
|
||||
def buf = new StringBuffer();
|
||||
addLicenseProlog(project, buf)
|
||||
|
||||
map.keySet().each { ip ->
|
||||
reportLicenseFiles(buf, ip, map.get(ip))
|
||||
}
|
||||
licenseFile.text = buf.toString()
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Writes the files for a single ip
|
||||
*********************************************************************************/
|
||||
def reportLicenseFiles(StringBuffer buf, String ip, List<String> filepaths) {
|
||||
if (ip.equals("GHIDRA") || ip.equals("LICENSE")) {
|
||||
return;
|
||||
}
|
||||
buf.append(ip+":\n\n")
|
||||
filepaths.each { path ->
|
||||
buf.append("\t")
|
||||
buf.append(path)
|
||||
buf.append("\n")
|
||||
}
|
||||
buf.append("\n")
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Generates the text for the prolog (non-changing) part of the LICENSE.txt file
|
||||
*********************************************************************************/
|
||||
def addLicenseProlog(Project project, StringBuffer buf) {
|
||||
if (project.projectDir.toString().contains(File.separator + "GPL" + File.separator)) {
|
||||
buf.append("The program in this module is released under the GPL 3 license. \n")
|
||||
buf.append("The files used to create this program include both public domain\n")
|
||||
buf.append("files created by the Ghidra team and 3rd party files with \n")
|
||||
buf.append("the GPL 3 or GPL 3 compatible license. ")
|
||||
buf.append("The license files for each of license used can be found in the\n")
|
||||
buf.append("<installation root>/GPL/licenses.\n\n")
|
||||
buf.append("\nThe 3rd party files in this module are as follows:\n\n\n")
|
||||
}
|
||||
else {
|
||||
buf.append("Ghidra software is released under the Apache 2.0 license. In addition, \n")
|
||||
buf.append("there are numerous 3rd party components that each have their \n")
|
||||
buf.append("own license. The license file for each of these licenses can be found\n")
|
||||
buf.append("in the licenses directory in the installation root directory.\n")
|
||||
buf.append("\nThe 3rd party files in this module are as follows:\n\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********************************************************************************
|
||||
* Examines the <root>/licenses directory to discover what licenses are allowed
|
||||
*********************************************************************************/
|
||||
def Set<String> getAllowedIP(Project p) {
|
||||
Set<String> set = new HashSet<String>()
|
||||
def projectPath = p.projectDir.path.replace(File.separator, "/");
|
||||
if (projectPath.contains("/GPL/")) {
|
||||
set.add("GPL_3")
|
||||
set.add("GPL_3_Linking_Permitted")
|
||||
set.add("GPL_2_With_Classpath_Exception")
|
||||
set.add("Public_Domain")
|
||||
set.add("LGPL_3.0")
|
||||
set.add("LGPL_2.1")
|
||||
}
|
||||
else {
|
||||
File root = p.rootProject.file("..")
|
||||
root.listFiles().each { f ->
|
||||
File licenseDir = new File(f, "licenses")
|
||||
File[] files = licenseDir.listFiles()
|
||||
files.each { file ->
|
||||
set.add(getIpName(file.getName()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set.add("GHIDRA")
|
||||
set.add("LICENSE")
|
||||
set.add("Copyright_Distribution_Permitted")
|
||||
return set;
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* converts a file name to an ip name that can be compared to info from headers.
|
||||
*********************************************************************************/
|
||||
def String getIpName(String filename) {
|
||||
if (filename.endsWith(".txt")) filename = filename.substring(0, filename.length()-4)
|
||||
if (filename.endsWith(".htm")) filename = filename.substring(0, filename.length()-4)
|
||||
if (filename.endsWith(".html")) filename = filename.substring(0, filename.length()-5)
|
||||
return filename
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Don't profile tests
|
||||
**/*Test*
|
||||
generic/test/**
|
||||
|
||||
// Ignore exception classes, as usually do not contain logic
|
||||
**/*Exception*
|
||||
|
||||
|
||||
|
||||
// Utility classes not used by Ghidra
|
||||
**/certify/**
|
||||
**/tracker/**
|
||||
**/review/**
|
||||
extract/**
|
||||
generic/profile/**
|
||||
ghidra/app/help/**
|
||||
ghidra/cpp/**
|
||||
ghidra/feature/fid/debug/**
|
||||
ghidra/util/profile/**
|
||||
**/stl/**
|
||||
ghidra/pcodeCPort/**
|
||||
ghidra/program/database/data/DataTypeArchiveTransformer*
|
||||
ghidra/sleigh/grammar/**
|
||||
ghidra/util/GhidraJarBuilder*
|
||||
generic/jar/**
|
||||
ghidra/util/JavaSourceFile*
|
||||
// this should probably be repackaged as 'help/build'
|
||||
help/**
|
||||
help/screenshot/**
|
||||
help/validator/**
|
||||
util/DebugThreadDumper**
|
||||
JsonDoclet*
|
||||
|
||||
|
||||
|
||||
// Auto-generated code
|
||||
ghidra/app/util/cparser/C/**
|
||||
ghidra/app/util/cparser/CPP/**
|
||||
ghidra/app/util/cparser/cplusplus/**
|
||||
|
||||
|
||||
// Classes not used during testing
|
||||
db/GhidraDBBufferFileAnalyzer*
|
||||
db/DbViewer*
|
||||
ghidra/DatabaseBenchMarks*
|
||||
ghidra/GhidraLauncher*
|
||||
ghidra/launch/**
|
||||
LaunchSupport*
|
||||
ghidra/GhidraThreadGroup*
|
||||
ghidra/HelpAdapter*
|
||||
ghidra/ClassSearcherStatusReportingTaskMonitor*
|
||||
ghidra/app/plugin/debug/**
|
||||
|
||||
|
||||
// Old/deprecated APIs
|
||||
ghidra/app/program/database/oldfunction/**
|
||||
ghidra/feature/vt/api/stringable/deprecated/**
|
||||
**/BookmarkDBAdapterV0/**
|
||||
**/BookmarkDBAdapterV1/**
|
||||
**/BookmarkTypeDBAdapterNoTable/**
|
||||
**/OldBookmark/**
|
||||
|
||||
|
||||
|
||||
|
||||
// Interface/constant classes
|
||||
ghidra/app/plugin/GenericPluginCategoryNames*
|
||||
|
||||
|
||||
|
||||
// Language code - currently untested
|
||||
ghidra/app/plugin/processors/generic/**
|
||||
ghidra/app/util/disassemble/**
|
||||
generic/lsh/vector/**
|
||||
ghidra/pcode/**
|
||||
ghidra/program/emulation/**
|
||||
ghidra/program/model/pcode/**
|
||||
ghidra/util/state/**
|
||||
|
||||
// (we currently do not test analyzers)
|
||||
ghidra/app/plugin/core/analysis/**
|
||||
ghidra/javaclass/**
|
||||
ghidra/util/state/analysis/**
|
||||
|
||||
|
||||
|
||||
|
||||
// Hard to test headlessly
|
||||
ghidra/app/plugin/core/printing/**
|
||||
|
||||
|
||||
// File formats -- these should be tested!!!!
|
||||
ghidra/file/formats/**
|
||||
ghidra/file/jad/**
|
||||
ghidra/app/cmd/formats/**
|
||||
mobiledevices/**
|
||||
|
||||
|
||||
// Items we should probably figure out how to test
|
||||
# ghidra/util/bean/dnd/**
|
||||
# ghidra/app/plugin/core/renoir/**
|
||||
# ghidra/app/util/demangler/gnu/**
|
||||
# ghidra/util/demangler/**
|
||||
# ghidra/server/**
|
||||
# ghidra/remote/security/**
|
||||
|
||||
// Packages that use reflection, which can be broken by Jacoco
|
||||
# ghidra/python/**
|
||||
# ghidra/app/util/bin/**
|
||||
|
||||
// Contribs
|
||||
ghidra/app/plugin/prototype/**
|
||||
ghidra/idapro/**
|
||||
ghidra/machinelearning/**
|
||||
DelphiAnalyzer*
|
||||
SortedInstructionMerger*
|
||||
DecodeBitMasks*
|
||||
ollydbg/**
|
||||
dbg/**
|
||||
// this is only used by dbg
|
||||
ghidra/io/connection/**
|
||||
|
||||
// Old stuff??
|
||||
# org/crosswire/**
|
||||
# ghidra/comm/**
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
/*****************************************************************************************
|
||||
*
|
||||
* Reads the Ghidra/application.properties file and sets properties for the version,
|
||||
* release name, and distro prefix (ghidira_<version>)
|
||||
*
|
||||
*****************************************************************************************/
|
||||
def ghidraProps = new Properties()
|
||||
file("Ghidra/application.properties").withReader { reader ->
|
||||
ghidraProps.load(reader)
|
||||
version = ghidraProps.getProperty('application.version')
|
||||
project.ext.RELEASE_VERSION = version
|
||||
project.ext.RELEASE_NAME = ghidraProps.getProperty('application.release.name')
|
||||
project.ext.DISTRO_PREFIX = "ghidra_${version}"
|
||||
project.ext.JAVA_COMPILER = ghidraProps.getProperty('application.java.compiler')
|
||||
|
||||
// Build dates may or may not be already present in the application.properties file.
|
||||
// If they are not present, we will set the dates so Gradle can use them, and we will
|
||||
// indicate that the build dates need to be injected into the build's final
|
||||
// application.properties file when it is copied.
|
||||
project.ext.BUILD_DATE = ghidraProps.getProperty('application.build.date')
|
||||
project.ext.BUILD_DATE_SHORT = ghidraProps.getProperty('application.build.date.short')
|
||||
project.ext.BUILD_DATES_NEEDED = false
|
||||
if (BUILD_DATE == null) {
|
||||
project.ext.BUILD_DATE = getCurrentDateTimeLong()
|
||||
project.ext.BUILD_DATE_SHORT = getCurrentDate()
|
||||
project.ext.BUILD_DATES_NEEDED = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
/**************************************************************************************
|
||||
* Method to add a single project to this gradle build.
|
||||
*
|
||||
* Param name: The name of the project.
|
||||
* Param path: The path relative to the root project directory
|
||||
* Param mustExist: True if the project directory must exist for the project to be included
|
||||
* (ex: devtools exists pre-extraction but not post-extraction but still
|
||||
* must be created for gradle to compile)
|
||||
*
|
||||
*
|
||||
* Example: if name is 'Utility' and path is "Ghidra/Framework', then this is equal to
|
||||
*
|
||||
* include 'Utility'
|
||||
* project(":Utility").projectDir = new File(rootProject.projectDir, "Ghidra/Framework/Utility")
|
||||
*
|
||||
* NOTE: if the project name is in the excludeProjects set, then that project will be skipped.
|
||||
*
|
||||
**************************************************************************************/
|
||||
ext.includeProject = { name, path, mustExist ->
|
||||
includeProjectNamed(name, name, path, mustExist);
|
||||
}
|
||||
|
||||
ext.includeProjectNamed = { name, dirName, path, mustExist ->
|
||||
File projectDir = new File(rootProject.projectDir, "$path/$dirName")
|
||||
if (projectDir.exists() || mustExist) {
|
||||
include name;
|
||||
project(":$name").projectDir = projectDir;
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************************
|
||||
* Method to add all projects in a single directory to this gradle build. It looks
|
||||
* for all the directories (one leve down only) under the given path that contain a build.gradle file. Then
|
||||
* for each of those, it call includeProject() to include that project.
|
||||
*
|
||||
* Param path: The path relative to the root project directory
|
||||
*
|
||||
* Example: if path is 'Ghidra/Framework', it will create projects for Utility, Generic, DB, etc.
|
||||
*
|
||||
**************************************************************************************/
|
||||
ext.includeProjects = { path ->
|
||||
FileTree fileTree = fileTree(rootProject.projectDir.absolutePath + "/"+path) {
|
||||
include '*/build.gradle'
|
||||
}
|
||||
fileTree.each { gradleBuildFile ->
|
||||
String projectName = gradleBuildFile.parentFile.name
|
||||
includeProject(projectName, path, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
import java.util.regex.*;
|
||||
import groovy.io.FileType;
|
||||
|
||||
ext.testReport = null; // contains <classname, duration> from JUnit test report
|
||||
|
||||
/*
|
||||
* Checks if html test report for an individual test class has a valid name.
|
||||
*/
|
||||
boolean hasValidTestReportClassName(String name) {
|
||||
return name.endsWith("Test.html") && !name.contains("Suite")
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns duration for a test class report.
|
||||
*/
|
||||
long getDurationFromTestReportClass(String fileContents, String fileName) {
|
||||
/* The duration for the entire test class appears in the test report as (multiline):
|
||||
* <div class="infoBox" id="duration">
|
||||
* <div class="counter">0s</div>
|
||||
* The duration value may appear in the format of: 1m2s, 1m2.3s, 3.4s
|
||||
*/
|
||||
Pattern p = Pattern.compile("(?<=id=\"duration\">[\r\n]<div\\sclass=\"counter\">)(.*?)(?=</div)",
|
||||
Pattern.MULTILINE);
|
||||
Matcher m = p.matcher(fileContents);
|
||||
assert m.find() == true
|
||||
String duration = m.group()
|
||||
assert duration != null && duration.trim().length() > 0
|
||||
|
||||
long durationInMillis
|
||||
|
||||
// Parse out the duration
|
||||
if (duration.contains("m") && duration.contains("s")) { // has minute and seconds
|
||||
int minutes = Integer.parseInt(duration.substring(0, duration.indexOf("m")))
|
||||
double seconds = Double.parseDouble(duration.substring(duration.indexOf("m") + 1
|
||||
, duration.length()-1))
|
||||
durationInMillis = (minutes * 60 * 1000) + (seconds * 1000)
|
||||
} else if (!duration.contains("m") && duration.contains("s")) { // has only seconds
|
||||
double seconds = Double.parseDouble(duration.substring(0, duration.length()-1))
|
||||
durationInMillis = (seconds * 1000)
|
||||
} else { // unknown format
|
||||
assert false : "getDurationFromTestReportClass: Unknown duration format in $fileName. 'duration' value is $duration"
|
||||
}
|
||||
logger.debug("getDurationFromTestReportClass: durationInMillis = '"+ durationInMillis
|
||||
+"' parsed from duration = '" + duration + "' in $fileName")
|
||||
return durationInMillis
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates <fully qualified classname, duration> from JUnit test report
|
||||
*/
|
||||
def HashMap<String, Long> getTestReport() {
|
||||
// populate testReport only once per gradle configuration phase
|
||||
if (project.testReport == null) {
|
||||
logger.debug("getTestReport: Populating 'testReport' using '$testTimeParserInputDir'")
|
||||
|
||||
testReport = new HashMap<String, Long>();
|
||||
|
||||
File classesReportDir = new File(testTimeParserInputDir)
|
||||
if(!classesReportDir.exists()) {
|
||||
logger.info("getTestReport: The path '$testTimeParserInputDir' does not exist on the file system." +
|
||||
" Returning empty testReport map.")
|
||||
return project.testReport
|
||||
}
|
||||
|
||||
int excludedHtmlFiles = 0 // counter
|
||||
int totalHtmlFiles = 0
|
||||
String excludedHtmlFileNames = "" // for log.info summary message
|
||||
|
||||
classesReportDir.eachFileRecurse (FileType.FILES) { file ->
|
||||
totalHtmlFiles++
|
||||
// Only read html file for a Test and not a test Suite
|
||||
if(hasValidTestReportClassName(file.name)) {
|
||||
|
||||
String fileContents = file.text
|
||||
/* The fully qualified class name appears in the test report as:
|
||||
* <h1>Class ghidra.app.plugin.assembler.sleigh.BuilderTest</h1>
|
||||
*/
|
||||
String fqNameFromTestReport = fileContents.find("(?<=<h1>Class\\s).*?(?=</h1>)")
|
||||
|
||||
long durationInMillis = getDurationFromTestReportClass(fileContents, file.name)
|
||||
|
||||
testReport.put(fqNameFromTestReport, durationInMillis)
|
||||
logger.debug("getTestReport: Added to testReport: class name = '"
|
||||
+ fqNameFromTestReport + "' and durationInMillis = '"+ durationInMillis
|
||||
+"' from " + file.name)
|
||||
} else {
|
||||
logger.debug("getTestReport: Excluding " + file.name + " from test report parsing.")
|
||||
excludedHtmlFileNames += file.name + ", "
|
||||
excludedHtmlFiles++
|
||||
}
|
||||
}
|
||||
|
||||
assert totalHtmlFiles != 0 : "getTestReport: Did not parse any valid html files in $testTimeParserInputDir. Directory might be empty"
|
||||
assert totalHtmlFiles == (testReport.size() + excludedHtmlFiles) : "Not all html files processed."
|
||||
|
||||
logger.info("getTestReport:\n" +
|
||||
"\tIncluded " + testReport.size() + " and excluded " + excludedHtmlFiles
|
||||
+ " html files out of " + totalHtmlFiles + " in Junit test report.\n"
|
||||
+ "\tExcluded html file names are: " + excludedHtmlFileNames + "\n"
|
||||
+ "\tParsed test report located at " + testTimeParserInputDir)
|
||||
}
|
||||
|
||||
return project.testReport
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if Java test class has a valid name.
|
||||
*/
|
||||
boolean hasValidTestClassName(String name) {
|
||||
return name != null && name.endsWith("Test.java") &&
|
||||
!(name.contains("Abstract") || name.contains("Suite"))
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if Java test class is excluded via 'org.junit.experimental.categories.Category'
|
||||
*/
|
||||
boolean hasCategoryExcludes(String fileContents) {
|
||||
|
||||
String annotation1 = "@Category\\(PortSensitiveCategory.class\\)" // evaluated as regex
|
||||
String annotation2 = "@Category\\(NightlyCategory.class\\)"
|
||||
|
||||
return fileContents.find(annotation1) || fileContents.find(annotation2)
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a fully qualified class name from a java class.
|
||||
*/
|
||||
String constructFullyQualifiedClassName(String fileContents, String fileName) {
|
||||
String packageName = fileContents.find("(?<=package\\s).*?(?=;)")
|
||||
logger.debug("constructFullyQualifiedClassName: Found '" + packageName + "' in " + fileName)
|
||||
|
||||
assert packageName != null : "constructFullyQualifiedClassName: Null packageName found in $fileName"
|
||||
assert !packageName.startsWith("package")
|
||||
assert !packageName.endsWith(";")
|
||||
|
||||
return packageName + "." + fileName.replace(".java","")
|
||||
}
|
||||
|
||||
/* Creates a list of test classes, sorted by duration, for a subproject.
|
||||
* First parses JUnit test report located at 'testTimeParserInputDir' for <fully qualified class name, duration in milliseconds> .
|
||||
* Then traverses a test sourceSet for a subproject for a test to include and assigns a duration value.
|
||||
* Returns a sorted list of test classes for the sourceSet parameter.
|
||||
*/
|
||||
def ArrayList getTestsForSubProject(SourceDirectorySet sourceDirectorySet) {
|
||||
|
||||
assert (getTestReport() != null) : "getTestsForSubProject: testReport should not be null"
|
||||
|
||||
def testsForSubProject = new LinkedHashMap<>();
|
||||
|
||||
int includedClassFilesNotInTestReport = 0 // class in sourceSet but not in test report, 'bumped' to first task
|
||||
int includedClassFilesInTestReport = 0 // class in sourceSet and in test report
|
||||
int excludedClassFilesBadName = 0 // excluded class in sourceSet with invalid name
|
||||
int excludedClassFilesCategory = 0 // excluded class in sourceSet with @Category annotation
|
||||
int excludedClassAllTestsIgnored = 0 // excluded class in sourceSet with test report duration of 0
|
||||
|
||||
logger.debug("getTestsForSubProject: Found " + sourceDirectorySet.files.size()
|
||||
+ " file(s) in source set to process.")
|
||||
|
||||
for (File file : sourceDirectorySet.getFiles()) {
|
||||
logger.debug("getTestsForSubProject: Found file in sourceSet = " + file.name)
|
||||
|
||||
// Must have a valid class name
|
||||
if(!hasValidTestClassName(file.name)) {
|
||||
logger.debug("getTestsForSubProject: Excluding file '" + file.name + "' based on name.")
|
||||
excludedClassFilesBadName++
|
||||
continue
|
||||
}
|
||||
|
||||
String fileContents = file.text
|
||||
|
||||
// Must not have a Category annotation
|
||||
if (hasCategoryExcludes(fileContents)) {
|
||||
logger.debug("getTestsForSubProject: Found category exclude for '"
|
||||
+ file.name + "'. Excluding this class from running.")
|
||||
excludedClassFilesCategory++
|
||||
continue
|
||||
}
|
||||
|
||||
String fqName = constructFullyQualifiedClassName( fileContents, file.name)
|
||||
// Lookup the test duration
|
||||
if (getTestReport().containsKey(fqName)) {
|
||||
long duration = getTestReport().get(fqName)
|
||||
|
||||
// Some classes from test report have duration value of 0. Exclude these from running.
|
||||
if (duration > 0) {
|
||||
testsForSubProject.put(fqName, duration)
|
||||
logger.debug("getTestsForSubProject: Adding '" + fqName + "'")
|
||||
includedClassFilesInTestReport++
|
||||
} else {
|
||||
logger.debug("getTestsForSubProject: Excluding '" + fqName
|
||||
+ "' because duration from test report is " + duration
|
||||
+ "ms. Probably because all test methods are @Ignore'd." )
|
||||
excludedClassAllTestsIgnored++
|
||||
}
|
||||
} else {
|
||||
logger.debug("getTestsForSubProject: Found test class not in test report."
|
||||
+ " Bumping to front of tasks '" + fqName + "'")
|
||||
testsForSubProject.put(fqName, 3600000) // cheap way to bump to front of (eventually) sorted list
|
||||
includedClassFilesNotInTestReport++
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by duration
|
||||
def sorted = testsForSubProject.sort { a, b -> b.value <=> a.value }
|
||||
|
||||
logger.info ("getTestsForSubProject:\n"
|
||||
+ "\tIncluding " + includedClassFilesInTestReport + " test classes for this sourceSet because they are in the test report.\n"
|
||||
+ "\tIncluding/bumping " + includedClassFilesNotInTestReport + " not in test report.\n"
|
||||
+ "\tExcluding "+ excludedClassFilesBadName +" based on name not ending in 'Test' or contains 'Abstract' or 'Suite', " + excludedClassFilesCategory
|
||||
+ " based on '@Category, " + excludedClassAllTestsIgnored + " because duration = 0ms.\n"
|
||||
+ "\tReturning sorted list of size "+ sorted.size() + " out of " + sourceDirectorySet.files.size()
|
||||
+ " total files found in sourceSet.")
|
||||
|
||||
int filesProcessed = includedClassFilesNotInTestReport + includedClassFilesInTestReport +
|
||||
excludedClassFilesBadName + excludedClassFilesCategory + excludedClassAllTestsIgnored
|
||||
|
||||
assert sourceDirectorySet.files.size() == filesProcessed : "getTestsForSubProject did not process every file in sourceSet"
|
||||
|
||||
return new ArrayList(sorted.keySet())
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Determines if test task creation should be skipped for parallelCombinedTestReport task.
|
||||
*********************************************************************************/
|
||||
def boolean shouldSkipTestTaskCreation(Project subproject) {
|
||||
if (!parallelMode) {
|
||||
logger.debug("shouldSkipTestTaskCreation: Skip task creation for $subproject.name. Not in parallel mode.")
|
||||
return true
|
||||
}
|
||||
if (!subproject.hasProperty("sourceSets")) {
|
||||
logger.debug("shouldSkipTestTaskCreation: subproject $subproject.name has no sourceSet property.")
|
||||
return true
|
||||
}
|
||||
if (subproject.sourceSets.findByName("test") == null ||
|
||||
subproject.sourceSets.test.java.files.isEmpty()) {
|
||||
logger.debug("shouldSkipTestTaskCreation: Skip task creation for $subproject.name. No test sources.")
|
||||
return true
|
||||
}
|
||||
if (subproject.findProperty("excludeFromParallelTests") ?: false) {
|
||||
logger.debug("shouldSkipTestTaskCreation: Skip task creation for $subproject.name."
|
||||
+ " 'excludeFromParallelTests' found.")
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Determines if integrationTest task creation should be skipped for parallelCombinedTestReport task.
|
||||
*********************************************************************************/
|
||||
def boolean shouldSkipIntegrationTestTaskCreation(Project subproject) {
|
||||
if (!parallelMode) {
|
||||
logger.debug("shouldSkipIntegrationTestTaskCreation: Skip task creation for $subproject.name."
|
||||
+ " Not in parallel mode.")
|
||||
return true
|
||||
}
|
||||
if (!subproject.hasProperty("sourceSets")) {
|
||||
logger.debug("shouldSkipIntegrationTestTaskCreation: subproject $subproject.name has no sourceSet property.")
|
||||
return true
|
||||
}
|
||||
if (subproject.sourceSets.findByName("integrationTest") == null ||
|
||||
subproject.sourceSets.integrationTest.java.files.isEmpty()) {
|
||||
logger.debug("shouldSkipIntegrationTestTaskCreation: Skip task creation for $subproject.name."
|
||||
+ " No integrationTest sources.")
|
||||
return true
|
||||
}
|
||||
if (subproject.findProperty("excludeFromParallelIntegrationTests") ?: false) {
|
||||
logger.debug("shouldSkipIntegrationTestTaskCreation: Skip task creation for $subproject.name."
|
||||
+ "'excludeFromParallelIntegrationTests' found.")
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Gets the path to the last archived test report. This is used by the
|
||||
* 'parallelCombinedTestReport' task when no -PtestTimeParserInputDir is supplied
|
||||
* via cmd line.
|
||||
*********************************************************************************/
|
||||
def String getLastArchivedReport(String reportArchivesPath) {
|
||||
|
||||
// skip configuration for this property if not in parallelMode
|
||||
if (!parallelMode) {
|
||||
logger.debug("getLastArchivedReport: not in 'parallelMode'. Skipping.")
|
||||
return ""
|
||||
}
|
||||
|
||||
File reportArchiveDir = new File(reportArchivesPath);
|
||||
logger.info("getLastArchivedReport: searching for test report to parse in " + reportArchivesPath)
|
||||
|
||||
if(!reportArchiveDir.exists()) {
|
||||
logger.info("getLastArchivedReport: '$reportArchiveDir' does not exist.")
|
||||
return ""
|
||||
}
|
||||
|
||||
// filter for report archive directories.
|
||||
File[] files = reportArchiveDir.listFiles(new FilenameFilter() {
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.startsWith("reports_");
|
||||
}
|
||||
});
|
||||
|
||||
assert (files != null && files.size() > 0) :
|
||||
"""Could not find test report archives in '$reportArchiveDir'.
|
||||
because no -PtestTimeParserInputDir=<path/to/report> supplied via cmd line"""
|
||||
|
||||
logger.debug("getLastArchivedReport: found " + files.size() + " archived report directories in '"
|
||||
+ reportArchiveDir.getPath() + "'.")
|
||||
// Sort by lastModified date. The last modified directory will be first.
|
||||
files = files.sort{-it.lastModified()}
|
||||
logger.debug("getLastArchivedReport: selecting report archive to parse: " + files[0].getAbsolutePath())
|
||||
return files[0].getAbsolutePath()
|
||||
}
|
||||
|
||||
ext {
|
||||
getTestsForSubProject = this.&getTestsForSubProject // export this utility method to project
|
||||
shouldSkipTestTaskCreation = this.&shouldSkipTestTaskCreation
|
||||
shouldSkipIntegrationTestTaskCreation = this.&shouldSkipIntegrationTestTaskCreation
|
||||
getLastArchivedReport = this.&getLastArchivedReport
|
||||
}
|
||||
Reference in New Issue
Block a user