mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-08-25 03:56:41 +00:00
445 lines
16 KiB
Groovy
445 lines
16 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.
|
|
*/
|
|
/*****************************************************************************************
|
|
This file is a "mix-in" gradle script that individual gradle projects should include if they
|
|
have content for the Ghidra help system. A gradle project can include help support by adding
|
|
the following to its build.gradle file.
|
|
|
|
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
|
|
|
|
|
|
Note: This code is copied into buildExtension.gradle. All changes to this file should
|
|
be made to that file.
|
|
|
|
|
|
Help Build System Notes
|
|
This file contains custom glue coded needed to adapt the structure of Ghidra's help to the
|
|
Gradle build system. 'Building help' is defined as validating help content, generating
|
|
all necessary help files used by the Java Help system, and then placing the help and
|
|
generated content in a place to be consumed by Ghidra, which differs for development mode
|
|
and production mode. Validating the help content consists of ensuring: hyperlinks point
|
|
to valid destinations and image references point to existing images. (This is done to find
|
|
broken help links at build time.)
|
|
|
|
|
|
This file supports building help to work in development mode using
|
|
Eclipse as well as when performing a build of Ghidra. Generated help content is written to:
|
|
'build/help/main/help/'
|
|
|
|
Developent Mode
|
|
The Eclipse projects are setup so that 'build/help/main' is part of the classpath. This
|
|
triggers Eclipse to copy help resources to the respective project's 'bin' directory,
|
|
which makes the help content available at runtime in Eclipse. In this setup no jar
|
|
files are used at runtime.
|
|
|
|
Production Mode
|
|
In production mode the contents of 'build/help/main' are added to the final output of
|
|
the Gradle 'jar' task, which will be <Module>.jar.
|
|
|
|
Gradle Building
|
|
During the help build process we place the contents of 'build/help/main' inside of an
|
|
artifact named <Module>-help.jar. This allows us to depend on these artifacts from the
|
|
projects we depend upon. Specifically, the 'buildHelpFiles' task depends upon the
|
|
<Module>-help.jar artifact from all dependent Modules.
|
|
|
|
To get Gradle's incremental building to work correctly, the following list of inputs
|
|
is declared for validating and building the help content:
|
|
1) Java files in the Help module used to build help - the help building code
|
|
2) all dependency data/*.theme.properties - for images used by help via theme IDs
|
|
3) all dependency src/main/help folder - for help content
|
|
4) all dependency src/main/resources - for images used by help
|
|
5) This module's equivalent inputs for 2-3
|
|
|
|
In order to correctly find these inputs, we use the main runtime classpath to find
|
|
Ghidra Modules. These modules are then scanned to find these inputs for each module.
|
|
The final collection of all these items for all dependent Modules is added to the set
|
|
of task inputs for building help. Thus, when any of the input above change, the help is
|
|
considered out-of-date and will be rebuilt.
|
|
|
|
The help build code is called via a JavaExec call. This call will pass arguments to the
|
|
process for any dependent Module's help. This dependent help will be inside of the
|
|
<Module>-help.jar artifact described above. This file contains code to locate those
|
|
artifacts so they can be passed into the help build java process.
|
|
|
|
*****************************************************************************************/
|
|
|
|
// The help modules must be configured first so that we can reference its runtime classpath
|
|
configurations {
|
|
|
|
// This represents the Help module jar file. This is required for building help.
|
|
helpModule
|
|
|
|
// This is used by the indexHelp task to configure the jar file dependency
|
|
helpIndex
|
|
}
|
|
|
|
dependencies {
|
|
|
|
helpIndex "javax.help:javahelp:2.0.05"
|
|
|
|
// signal that we depend on the Help.jar for our Java build files
|
|
helpModule project(':Help')
|
|
}
|
|
|
|
sourceSets {
|
|
|
|
// register help resources to be considered inputs to this project; when these resources change,
|
|
// this project will be considered out-of-date
|
|
main {
|
|
resources {
|
|
srcDir 'src/main/help' // help .html files to be copied to the jar
|
|
srcDir 'build/help/main' // generated help items (from the indexer); copied to the jar
|
|
}
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************************
|
|
Utility Methods
|
|
*****************************************************************************************/
|
|
|
|
// Turns the given file into a 'normalized' path using the Java Path API
|
|
def normalize(File file) {
|
|
def path = null;
|
|
try {
|
|
path = java.nio.file.Paths.get(file.getAbsolutePath());
|
|
}
|
|
catch (Exception e) { // InvalidPathException
|
|
// we have seen odd strings being placed into the classpath--ignore them
|
|
return cpPath;
|
|
}
|
|
|
|
def normalizedPath = path.normalize();
|
|
def absolutePath = normalizedPath.toAbsolutePath();
|
|
return absolutePath.toString();
|
|
}
|
|
|
|
// Returns the Ghidra module directory for the given file if it is a Ghidra jar file
|
|
def getModulePathFromJar(File file) {
|
|
|
|
String path = normalize(file)
|
|
String forwardSlashedPath = path.replaceAll("\\\\", "/")
|
|
def jarPattern = ~'.*/(.*)/(?:lib|build/libs)/(.+).jar'
|
|
def matcher = jarPattern.matcher(forwardSlashedPath)
|
|
if (!matcher.matches()) {
|
|
return null
|
|
}
|
|
|
|
def moduleName = matcher.group(1);
|
|
def index = forwardSlashedPath.indexOf(moduleName) + moduleName.length()
|
|
return forwardSlashedPath.substring(0, index)
|
|
}
|
|
|
|
// Parses the classpath looking for all Module jar file paths, using those to locate the module
|
|
// that contains that jar file.
|
|
// Note: In development mode, the <Module>.jar file on the classpath may not actually yet be built.
|
|
// In that case, we can still use that path to locate the module.
|
|
def getMyModules(Collection<File> fullClasspath) {
|
|
return fullClasspath.collect(file -> getModulePathFromJar(file))
|
|
.findAll(path -> path != null)
|
|
.collect(path -> new File(path))
|
|
}
|
|
|
|
// This method contains logic for calculating help inputs based on the classpath of the project
|
|
// The work is cached, as the inputs may be requested multiple times during a build
|
|
ext.helpInputsCache = null
|
|
def getHelpInputs(Collection<File> fullClasspath) {
|
|
|
|
if (ext.helpInputsCache != null) {
|
|
return ext.helpInputsCache
|
|
}
|
|
|
|
def results = new HashSet<File>()
|
|
Collection<File> modules = getMyModules(fullClasspath)
|
|
modules.each { m ->
|
|
getHelpInputsFromModule(m.getAbsolutePath(), results)
|
|
}
|
|
|
|
// the classpath above does not include my module's contents, so add that manually
|
|
def modulePath = file('.').getAbsolutePath()
|
|
getHelpInputsFromModule(modulePath, results)
|
|
|
|
ext.helpInputsCache = results.findAll(File::exists)
|
|
return ext.helpInputsCache
|
|
}
|
|
|
|
def getHelpInputsFromModule(String moduleDirPath, Set<File> results) {
|
|
|
|
// add all desired directories now and filter later those that do not exist
|
|
File moduleDir = new File(moduleDirPath)
|
|
results.add(new File(moduleDir, 'src/main/resources')) // images
|
|
results.add(new File(moduleDir, 'src/main/help')) // html files
|
|
|
|
File dataDir = new File(moduleDir, 'data') // theme properties files
|
|
if (dataDir.exists()) {
|
|
FileCollection themeFiles = fileTree(dataDir) {
|
|
include '**/*.theme.properties'
|
|
}
|
|
results.addAll(themeFiles.getFiles())
|
|
}
|
|
}
|
|
|
|
def getModuleResourcesDirs(Collection<File> fullClasspath) {
|
|
def modules = getMyModules(fullClasspath)
|
|
return modules.collect(m -> new File(m, 'src/main/resources'))
|
|
.findAll(dir -> dir.exists())
|
|
}
|
|
|
|
// Locatates 'buildHelp' tasks in projects that this project depends on. The output of the tasks
|
|
// is the module's help jar, which is only used to build help and not in the final release. The
|
|
// jar file names follow this format: <Module>-help.jar.
|
|
def getDependentProjectHelpTasks(Collection<File> fullClasspath) {
|
|
|
|
def myModules = getMyModules(fullClasspath)
|
|
def myProjects = filterProjectsBy(myModules)
|
|
return myProjects.collect(p -> p.tasks.findByPath('buildHelp'))
|
|
.findAll(t -> t != null)
|
|
}
|
|
|
|
// Only projects matching the given collection of modules are returned
|
|
def filterProjectsBy(Collection<File> modules) {
|
|
return modules.collect(m -> m.getName())
|
|
.collect(name -> rootProject.findProject(name))
|
|
.findAll(p -> p != null)
|
|
}
|
|
|
|
/*****************************************************************************************
|
|
Tasks
|
|
*****************************************************************************************/
|
|
|
|
tasks.register('cleanHelp') {
|
|
File helpOutput = file('build/help/main/help')
|
|
doFirst {
|
|
delete helpOutput
|
|
}
|
|
}
|
|
|
|
// Task for calling the java help indexer, which creates a searchable index of the help contents
|
|
tasks.register('indexHelp', JavaExec) {
|
|
|
|
group "private"
|
|
description "indexes the helps files for this module. [gradle/helpProject.gradle]"
|
|
|
|
File helpRootDir = file('src/main/help/help')
|
|
File outputFile = file("build/help/main/help/${project.name}_JavaHelpSearch")
|
|
|
|
inputs.dir helpRootDir
|
|
outputs.dir outputFile
|
|
|
|
classpath = configurations.helpIndex
|
|
|
|
mainClass = 'com.sun.java.help.search.Indexer'
|
|
|
|
doFirst {
|
|
|
|
// gather up all the help files into a file collection
|
|
FileTree helpFiles = fileTree('src/main/help') {
|
|
include '**/*.htm'
|
|
include '**/*.html'
|
|
}
|
|
|
|
if (helpFiles.isEmpty()) {
|
|
// must have help to index
|
|
throw new GradleException("No help files found")
|
|
}
|
|
|
|
// The index tool has a config file parameter, which allows you to pass arguments via a file
|
|
// instead of the command line. This is useful when dealing with file paths. The only
|
|
// thing we use in the config file is a root directory path that should be stripped off all
|
|
// the help references to make them relative instead of absolute. We generate this config
|
|
// file below.
|
|
File configFile = file('build/helpconfig')
|
|
|
|
// create the config file when the task runs and not during configuration.
|
|
configFile.parentFile.mkdirs();
|
|
configFile.write "IndexRemove ${helpRootDir.absolutePath}" + File.separator + "\n"
|
|
|
|
// pass the config file we created as an argument to the indexer
|
|
args '-c',"$configFile"
|
|
|
|
// tell the indexer where send its output
|
|
args '-db', outputFile.absolutePath
|
|
|
|
// for each help file that was found, add it as an argument to the indexer
|
|
helpFiles.each { File file ->
|
|
args "${file.absolutePath}"
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Task for building Ghidra help files
|
|
// - depends on the output from the help indexer
|
|
// - validates help
|
|
// - the files generated will be placed in a diretory usable during development mode and will
|
|
// eventually be placed in:
|
|
// - the <Module>.jar file in production mode, or
|
|
// - the <Module>-help.jar file in development mode
|
|
tasks.register('buildHelpFiles', JavaExec) {
|
|
|
|
group "private"
|
|
|
|
dependsOn 'indexHelp'
|
|
|
|
// Depend on all <Module>-help.jar files for our dependency modules. These jar files must be
|
|
// built before we run, since the files will be passed to the help builder. Use a closure to
|
|
// ensure that the classpath is ready when needed.
|
|
dependsOn({
|
|
getDependentProjectHelpTasks(sourceSets.main.runtimeClasspath.files)
|
|
})
|
|
|
|
File helpRootDir = file('src/main/help/help')
|
|
File outputDir = file('build/help/main/help')
|
|
|
|
//
|
|
// Inputs (used for incremental building):
|
|
// 1) Java files in the Help module used to build help
|
|
// 2) all dependency data/**/*.theme.properties - for images used by help via theme IDs
|
|
// 3) all dependency src/main/help folder - for help content
|
|
// 4) all dependency src/main/resources - for images used by help
|
|
// 5) This module's equivalent inputs for 2-3
|
|
//
|
|
|
|
// 1) Java files in the Help module used to build help
|
|
inputs.files(configurations.helpModule)
|
|
|
|
// 2-5) from above
|
|
inputs.files({
|
|
// Note: this must be done lazily in a closure since the classpath is not ready at
|
|
// configuration time.
|
|
return getHelpInputs(sourceSets.main.runtimeClasspath.files)
|
|
})
|
|
|
|
outputs.dir outputDir
|
|
|
|
mainClass = 'help.GHelpBuilder'
|
|
|
|
args '-n', "${project.name}" // use the module's name for the help file name
|
|
|
|
args '-o', "${outputDir.absolutePath}" // set the output directory arg
|
|
|
|
|
|
// args '-debug' // print debug info
|
|
|
|
doFirst {
|
|
|
|
//
|
|
// The classpath needs to include items used by internal Java code to validate help
|
|
// resources:
|
|
// 1) The jar path of each dependent Module. The jar file will be on the 'main' runtime
|
|
// classpath, but may not yet exist. Regardless, the Java code will use the path to
|
|
// locate the module for that path.
|
|
// 2) Each module's 'src/main/resources' dir (this is needed when the jar files from 1
|
|
// above have not been built)
|
|
// 3) This module's 'src/main/resources' dir
|
|
//
|
|
|
|
// Each java project and its dependencies are needed to locate each Ghidra module. Each
|
|
// module is scanned to find the theme properties files in the 'data' directories.
|
|
classpath += sourceSets.main.runtimeClasspath
|
|
|
|
classpath += files(getModuleResourcesDirs(sourceSets.main.runtimeClasspath.files))
|
|
|
|
classpath += files('src/main/resources')
|
|
|
|
|
|
// To build help, the validator needs any other help content that this module may reference.
|
|
// Add each of these dependencies as an argument to the validator.
|
|
// The dependency file is the <Module>-help.jar file from the 'buildHelp' tasks upon which
|
|
// we depend.
|
|
def buildHelpTasks = getDependentProjectHelpTasks(sourceSets.main.runtimeClasspath.files)
|
|
buildHelpTasks.each {
|
|
|
|
def jarFiles = it.outputs.files
|
|
jarFiles.each { helpJar ->
|
|
args "-hp"
|
|
args "${helpJar.absolutePath}"
|
|
}
|
|
}
|
|
|
|
// The help dir to process. This needs to be the last argument to the process,
|
|
// thus, this is why it is inside of this block
|
|
args "${helpRootDir.absolutePath}"
|
|
|
|
// Sigal that any System.out messages from this Java process should be logged at INFO level.
|
|
// To see this output, run gradle with the '-i' option to show INFO messages.
|
|
logging.captureStandardOutput LogLevel.INFO
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* This task creates a jar file that is used by dependent modules only at build time. The name of
|
|
* this jar is <Module>-help.jar. This is in contrast to each module's jar which itself contains
|
|
* all help needed in production. The module's jar filename is <Module>.jar.
|
|
*/
|
|
tasks.register('buildHelp', Jar) {
|
|
|
|
group rootProject.GHIDRA_GROUP
|
|
description " Builds the help for this module. [gradle/helpProject.gradle]\n"
|
|
|
|
dependsOn tasks.named('buildHelpFiles')
|
|
duplicatesStrategy 'exclude'
|
|
|
|
from "build/help/main" // include the generated help and index files from
|
|
from "src/main/help" // include the help source files
|
|
|
|
destinationDirectory = file("build/libs")
|
|
archiveBaseName = project.name + '-help'
|
|
}
|
|
|
|
|
|
// Task for finding unused images that are not referenced from Ghidra help files
|
|
tasks.register('findUnusedHelp', JavaExec) {
|
|
|
|
group "private"
|
|
description " Finds unused help images for this module. [gradle/helpProject.gradle]\n"
|
|
|
|
File helpRootDir = file('src/main/help/help')
|
|
inputs.dir helpRootDir
|
|
inputs.files(configurations.helpModule)
|
|
|
|
mainClass = 'help.validator.UnusedHelpImageFileFinder'
|
|
|
|
// args '-debug' // print debug info
|
|
|
|
doFirst {
|
|
classpath sourceSets.main.runtimeClasspath
|
|
|
|
// the current help dir to process
|
|
args "-hp"
|
|
args "${helpRootDir.absolutePath}"
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// include the help into the module's jar
|
|
jar {
|
|
duplicatesStrategy 'exclude'
|
|
from "build/help/main" // include the generated help and index files
|
|
from "src/main/help" // include the help source files
|
|
}
|
|
|
|
// build the help whenever this module's jar file is built
|
|
processResources.dependsOn buildHelp
|
|
jar.dependsOn buildHelp
|
|
|
|
|
|
// make sure generated help directories exist during prepdev so that the directories are created and
|
|
// eclipse doesn't complain about missing src directories.
|
|
rootProject.prepDev.dependsOn buildHelp
|
|
|